0

Things to know:

  • I am inserting this statement inside of our Reporting software, so I don't have the luxury of using PHP to do the looping for me.
  • I am not familiar with the LOOP clause inside MySQL, I did a little research before, but could use some help with the syntax.

Question:

How would I loop through this MySQL statement? (Statement given below)

The following statement works right now, I have further CASE WHEN's to add it, but for now I'm trying to solve for the most confusing part of the whole thing.

I need to loop through the MySQL statement ( or find another way to write the same thing) until I reach the bottom of the tree. To illustrate what I am trying to do:

Let's assume I need to get the Downline of this entire tree starting from the top. Which means I have to run this MySQL statement for each person below.

enter image description here

In our current set-up, I store the ID of the person above them in the record like so, and to follow the record DOWN, I have to give it the Upline ID and return all the records where it has that Upline ID, which returns all the IMMMEDIATE downlines.

+---------+-------------+------+
| ID      | Upline ID   | Name |
+---------+-------------+------+
| 745753  | 654-64645-3 | John |
| 098678  | 916-59172-1 | Jill |
| 543272  | 866-99573-8 | Fred |
| 634543  | 126-97939-3 | Dean |
| 923461  | 734-84628-5 | Bill |
| 861345  | 643-01957-0 | Cris |
+---------+-------------+------+

This means for each row returned in the set, I have to follow the MySQL statement down each record tree individually, and do the same for each row returned, causing the MySQL statements to multiply, UNTIL, when I run the statement for that record, nothing is returned, so I reached the end of the downline.

Without further waiting, here is statement (I give it the first person at the top's ID in the WHERE clause to get the statement started):

    SELECT wn_writing_number_cstm.title_c, 
    wn_writing_number.`name`, 
    preps_contracted_reps.first_name, 
    preps_contracted_reps.last_name, 
    cac_customize_agent_comp_cstm.commission_percentage_c, 
    wn_writing_number_cstm.id_c
FROM wn_writing_number 
     LEFT OUTER JOIN wn_writing_number_cac_customize_agent_comp_1_c ON wn_writing_number_cac_customize_agent_comp_1_c.wn_writing946b_number_ida = wn_writing_number.id
     LEFT OUTER JOIN cac_customize_agent_comp ON wn_writing_number_cac_customize_agent_comp_1_c.wn_writing3148nt_comp_idb = cac_customize_agent_comp.id
     LEFT OUTER JOIN cac_customize_agent_comp_cstm ON cac_customize_agent_comp.id = cac_customize_agent_comp_cstm.id_c
     LEFT OUTER JOIN aos_products_cac_customize_agent_comp_1_c ON cac_customize_agent_comp_cstm.id_c = aos_products_cac_customize_agent_comp_1_c.aos_produca2b8nt_comp_idb
     LEFT OUTER JOIN preps_contracted_reps_wn_writing_number_1_c ON preps_contracted_reps_wn_writing_number_1_c.preps_contracted_reps_wn_writing_number_1wn_writing_number_idb = wn_writing_number.id
     LEFT OUTER JOIN preps_contracted_reps ON preps_contracted_reps_wn_writing_number_1_c.preps_cont9effed_reps_ida = preps_contracted_reps.id
     LEFT OUTER JOIN wn_writing_number_cstm ON wn_writing_number_cstm.id_c = wn_writing_number.id
WHERE wn_writing_number_cstm.wn_writing_number_id_c = '53506bbe-008f-577c-2114-576b32e0ad11'

Here is a Query Builder Diagram to help illustrate this model:

enter image description here

And finally, here is what this statement actually returns, I'd need to run this statement twice again, one for each row with the ID of each of those records in my WHERE clause:

+---------+-------------+------------+-------------------------------------+
| title_c | name        | first_name | last_name | percent_c | id_c        |
+---------+-------------+------------+-------------------------------------+
| A       | MP-AB0682-16| Andrea     | Donald    | 10        | 823462345   |
| GA      | RO-RM4619-16| Ronald     | Yeller    | 12        | 632811634   |
+---------+-------------+------------+-----------+-----------+-------------+

Please comment for any further clarification needed. Thanks!

Josh Whitlow
  • 481
  • 6
  • 25
  • Are you sure you want 2 or 3 actual resultsets coming back and that your programming interface can process them, as such? – Drew Jul 20 '16 at 20:39
  • If you "cache" previous results and their level/iteration to a "temp" table, you can use that temp table to query for each level rather than every individual parent. – Uueerdo Jul 20 '16 at 20:41
  • I would certainly do ^ because a loop/iterate or a cursor gets ugly – Drew Jul 20 '16 at 20:44
  • Or make a stored proc, which is always a great home for a self-join on hierarchies – Drew Jul 20 '16 at 20:45
  • @Drew when I've need to do this, a loop was still needed, but no cursor; you just loop until no more records are added and then do the final/results selection from the "temp" table. – Uueerdo Jul 20 '16 at 20:53
  • A loop is a loop. I was focusing on my first comment though. I has to do with the consumer of it. – Drew Jul 20 '16 at 20:54
  • The report I'm building is essentially a contact list, where I pull in everyone in their downline's contact information, so a single temp table I could do one select on would be perfect. As for the software, I'm pretty confident it can process the result sets, but I won't know for sure until I test it both in NaviCat, then in the software itself. But so far it has handled fairly complex statements up to this point. @Uueerdo, I'm unfamiliar with how to do what you are suggesting, can you give me some starting help at least, or where I could find to learn how to do this? – Josh Whitlow Jul 20 '16 at 22:08
  • @Drew, well, I finally got everything working in NaviCat (also tested in phpMyAdmin), and the I got the query over there and it's kicking it back with a syntax error near `DELIMITER`. Our Reporting engine uses a JDBC driver to connect to the DB, looks like this might be a needle in a haystack issue because I looked online and it looks like there is a load of different options to skim through. – Josh Whitlow Jul 21 '16 at 20:02
  • Well a delimiter is often used just to create the stored proc. Some environments need it, some don't (allegedly phpmyadmin). It is a chunk demarcation thing. I've never used a delimiter statement beyond its use for the creation of sp's, fcns, events, triggers. Just for the create part (once and done) like in my last answer [here](http://stackoverflow.com/a/38512724) – Drew Jul 21 '16 at 20:07

1 Answers1

2

A semi-pseudo code answer to clarify my method (sorry, don't have time for a more precise solution, and don't use the syntax frequently enough to remember it exactly without looking it up):

DECLARE lastInsertCount INT;
DECLARE lastLayer INT;
CREATE TABLE `temp`
(
   `layer` int,
   [fields you want and parent's identifier]
);

INSERT INTO `temp`(`layer`, fields you want...)
SELECT 0, fields you want...
FROM theTable
WHERE [is "root" result]
;
SET lastLayer := 0;

SET lastInsertCount := 1;
LayerLoop: WHILE lastInsertCount > 0

  INSERT INTO `temp`(`layer`, fields you want...)
  SELECT lastLayer + 1, fields you want...
  FROM theTable
  WHERE parent_id IN (SELECT id FROM `temp` WHERE layer = lastLayer)
  ;

  SET lastInsertCount := ROW_COUNT();
  SET lastLayer := lastLayer + 1;

END WHILE LayerLoop;

SELECT fields you want...
FROM `temp`
;

DROP TABLE `temp`;

Edit: If you allow "cycles" in the tree, which would make it not a tree (but I've had to worry about it in bad data), you can avoid those causing you to loop infinitely by adding

AND id NOT IN (SELECT id FROM `temp`)

to the where clause.

Uueerdo
  • 15,723
  • 1
  • 16
  • 21
  • I will be marking this as the correct answer, but just one last thing I can't figure out, I'm getting errors in the SQL syntax around the `DO WHILE`, instead of `rows_affected`, I'm tried `mysql_affected_rows()`, I also tried `WHILE mysql_affected_rows() > 0`, then `END WHILE` at the end instead of the `DO` then `WHILE`, but none of that works? Am I missing something? – Josh Whitlow Jul 21 '16 at 15:28
  • Like I said, it is pseudocode, so the exact syntax may vary; I'm actually surprised it worked with such little modification... I'll see if I can make it closer to precise syntax. – Uueerdo Jul 21 '16 at 16:47
  • @JoshWhitlow Updated answer to use more actual syntax; and replaced use of session variables (the `@` variables) with declared local variables. – Uueerdo Jul 21 '16 at 16:58
  • I apologize for my lack of knowledge, I assumed I could copy paste this, and modify the `INSERT INTO` and `SELECT`, but after doing a lot of research I was finding things like the fact that `DECLARE` has to be used inside `BEGIN` and `END` statements, Which means I need a procedure, but supposedly the Procedure has to be stored with `DELIMITER`, and then I have to invoke it with `CALL`. But I'm doing something wrong because none of that is working. [Here](http://imgur.com/a/pM6rc) is a link to what I have so far using the most recent syntax you suggested. Where am I wrong? – Josh Whitlow Jul 21 '16 at 17:57
  • 1
    On a quick glance: [DECLARE is permitted only inside a BEGIN ... END compound statement and must be at its start, before any other statements](http://dev.mysql.com/doc/refman/5.7/en/declare.html); and it also looks like you missed changing an `i` on line 43. Also, you don't need the `;` on that last `END` (line 46). _Sidenote: you may not want to use the table name `temp`, but something more unique. With a true TEMPORARY table you wouldn't need to worry about this, but those have limitations that prevent them from being viable for this purpose._ – Uueerdo Jul 21 '16 at 18:06
  • That worked! Thanks for working through this with me! This is been really helpful and has also helped expand my knowledge of MySQL. I also had to put 'id_c' and not 'id' in the sub-select on line 58. – Josh Whitlow Jul 21 '16 at 18:14