lleto
lleto

Reputation: 684

MySQL get linked results from the same table

I can't wrap my head around a small (hopefully) MySQL question. I have a table called links. It contains a customer_id field and a linked_id field and basically links customer accounts to each other where customer_id is in the lead. The newly created accounts can spawn accounts themselves and I would like to see all accounts that were created by the logged on user + all the accounts created by subaccounts.

Table looks like this:

+----+-------------+-----------+
| id | customer_id | linked_id |
+----+-------------+-----------+
|  1 |           1 |         5 |
|  2 |           1 |         2 |
|  3 |           1 |        11 |
|  4 |           1 |        13 |
|  5 |          13 |        14 |
|  6 |           3 |         4 |
|  7 |           7 |         8 |
+----+-------------+-----------+

So if I am logged in as user with customer_id 1 then I would like to get the userlist with linked_id 5,2,11,13 (because they are a direct connection) and linked_id 14 (because this user was created by a user who is directly connected to 1).

The query needs to be a subquery to get all user details. I currently have:

SELECT username, firstname, lastname, email, active, level FROM customers WHERE id
IN (SELECT linked_id FROM links WHERE customer_id=1) or id=1;

This obviously only returns the direct connections and the user with id=1 directly.

Upvotes: 2

Views: 99

Answers (1)

lleto
lleto

Reputation: 684

Thanks to eggyal for putting me on the right track. Seeing the relative complexity I do not feel so ashamed anymore that I could not crack it in the first go.

I ended up doing some research and found some nice setups to used closure tables in mysql. I ended up creating a stored procedure to populate my closure table and of course the new table cust_closure. I renamed by links table to cust_links.

cust_links:

+-------------+---------+------+-----+---------+----------------+
| Field       | Type    | Null | Key | Default | Extra          |
+-------------+---------+------+-----+---------+----------------+
| id          | int(11) | NO   | PRI | NULL    | auto_increment |
| customer_id | int(11) | YES  |     | NULL    |                |
| linked_id   | int(11) | YES  |     | NULL    |                |
+-------------+---------+------+-----+---------+----------------+

cust_closure:

+-------------+---------+------+-----+---------+-------+
| Field       | Type    | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+-------+
| customer_id | int(11) | YES  |     | NULL    |       |
| linked_id   | int(11) | YES  |     | NULL    |       |
| distance    | int(11) | YES  |     | NULL    |       |
+-------------+---------+------+-----+---------+-------+

And then added the stored procedure:

CREATE PROCEDURE populate_cust_closure()
BEGIN
  DECLARE distance int;
  TRUNCATE TABLE cust_closure;
  SET distance = 0;
  -- seed closure with self-pairs (distance 0)
  INSERT INTO cust_closure (customer_id, linked_id, distance)
    SELECT customer_id, customer_id, distance
      FROM cust_links GROUP BY customer_id;

  -- for each pair (root, leaf) in the closure,
  -- add (root, leaf->child) from the base table
  REPEAT
    SET distance = distance + 1;
    INSERT INTO cust_closure (customer_id, linked_id, distance)
      SELECT cust_closure.customer_id, cust_links.linked_id, distance
        FROM cust_closure, cust_links
          WHERE cust_closure.linked_id = cust_links.customer_id
          AND cust_closure.distance = distance - 1;
  UNTIL ROW_COUNT()=0
  END REPEAT;
END // 

When I then called the stored procedure it produced:

mysql> select * from cust_closure;
+-------------+-----------+----------+
| customer_id | linked_id | distance |
+-------------+-----------+----------+
|           1 |         1 |        0 |
|           3 |         3 |        0 | 
|           7 |         7 |        0 | 
|          13 |        13 |        0 | 
|           1 |         5 |        0 |
|           1 |         2 |        0 |
|           1 |        11 |        0 |
|           1 |        13 |        0 |
|          13 |        14 |        0 |
|           1 |        14 |        1 |
|           3 |         4 |        0 |
|           7 |         8 |        0 |
+-------------+-----------+----------+

So now my original query becomes:

SELECT username, firstname, lastname, email, active, level FROM customers WHERE id
IN (SELECT linked_id FROM cust_closure WHERE customer_id=1);

Thanks again for eggyal and hope this helps someone in the future.

Upvotes: 1

Related Questions