Recursive query with loop counter - mysql

In MySQL I have the following object table:
| id | parent_id | name |
|---:|----------:|:-----|
| 1 | NULL | root |
| 2 | 9 | obj1 |
| 3 | 10 | obj2 |
| 4 | 7 | obj3 |
| 5 | 8 | obj4 |
| 6 | 4 | obj5 |
| 7 | 2 | obj6 |
| 8 | 3 | obj7 |
| 9 | 1 | obj8 |
| 10 | 1 | obj9 |
And the following Query to get a path from my element (id 6) to root:
SELECT #id :=
(
SELECT parent_id
FROM object
WHERE id = #id
) AS tree
FROM (
SELECT #id := 6
) a
STRAIGHT_JOIN object
WHERE #id IS NOT NULL
The result is:
| tree |
|-----:|
| 4 |
| 7 |
| 2 |
| 9 |
| 1 |
| NULL |
But I need a counter in the result, that represents the travertion through the path (like a counter in a for-loop):
| tree | ctr |
|-----:|----:|
| 4 | 1 |
| 7 | 2 |
| 2 | 3 |
| 9 | 4 |
| 1 | 5 |
| NULL | 6 |
Is it possible to add the counter and how?

I'm not sure if your query is guaranteed to work, but it might (between the straight join and subquery, the order of execution might be guaranteed). You can get a counter by using another variable:
SELECT #id := (SELECT parent_id
FROM object
WHERE id = #id
) AS tree,
(#rn := #rn + 1) as ctr
FROM (SELECT #id := 6, #rn := 0) a STRAIGHT_JOIN
object
WHERE #id IS NOT NULL;

Related

How to query MIN value of MAX subquery with two distinct columns?

I have a table like this:
+---------------+--------------+------+-----+----------+
| Field | Type | Null | Key | Default |
+---------------+--------------+------+-----+----------+
| id | smallint(6) | NO | PRI | NULL |
| Book | tinyint(4) | NO | | NULL |
| Chapter | smallint(6) | NO | | NULL |
| Paragraph | smallint(6) | NO | | NULL |
| Text | text | YES | | NULL |
| RevisionNum | mediumint(9) | NO | PRI | NULL |
+---------------+--------------+------+-----+----------+
mysql> select id,Book,Chapter,Paragraph,RevisionNum FROM MyTable ORDER BY id LIMIT 11;
+-----+------+---------+-----------+-------------+
| id | Book | Chapter | Paragraph | RevisionNum |
+-----+------+---------+-----------+-------------+
| 1 | 1 | 1 | 1 | 0 |
| 1 | 1 | 1 | 1 | 1 |
| 1 | 1 | 1 | 1 | 2 |
| 2 | 1 | 2 | 2 | 0 |
| 2 | 1 | 2 | 2 | 1 |
| 2 | 1 | 2 | 2 | 2 |
| 2 | 1 | 2 | 2 | 3 |
| 3 | 1 | 2 | 3 | 0 |
| 4 | 1 | 2 | 4 | 0 |
| 4 | 1 | 2 | 4 | 1 |
| 5 | 1 | 3 | 5 | 0 |
+-----+------+---------+-----------+-------------+
To find a book or chapter which has no unrevised paragraph,
I wish to query either the minimum value of the maximums of
all the distinct id's for that chapter or book, or else in
some fashion determine that no id remains unedited (with a
MAX(RevisionNum) of zero).
Most of my attempts to date have ended in errors like this one:
SELECT DISTINCT Book,RecordNum FROM MyTable
-> WHERE 0 < ALL (SELECT DISTINCT RecordNum,MAX(RevisionNum)
FROM MyTable
WHERE MAX(RevisionNum) > 0);
ERROR 1111 (HY000): Invalid use of group function
...And I wasn't using the "GROUP BY" function at all!
The following query produces results, but simply
gives ALL id's, and does not actually show a unique
set of Book records, as requested. How could this happen?
SELECT DISTINCT Book,id,MAX(RevisionNum) FROM MyTable GROUP BY id LIMIT 5;
+------+----+------------------+
| Book | id | MAX(RevisionNum) |
+------+----+------------------+
| 1 | 1 | 30 |
| 1 | 2 | 16 |
| 1 | 3 | 15 |
| 1 | 4 | 10 |
| 1 | 5 | 9 |
+------+----+------------------+
What would the correct query be to give results more like this:
+------+-----+-----------------------+
| Book | id | MIN(MAX(RevisionNum)) |
+------+-----+-----------------------+
| 1 | 5 | 3 |
| 2 | 17 | 1 |
| 3 | 33 | 2 |
| 4 | 147 | 0 |
| 5 | 225 | 2 |
+------+-----+-----------------------+
Are you looking for two levels of aggregation?
select id, book, min(max_revisionnum)
from (select id, book, chapter, paragraph, max(revisionnum) as max_revisionnum
from mytable
group by id, book, chapter, paragraph
) t
group by id, book;
EDIT:
Based on your comment, you can use:
select *
from (select id, book, chapter, paragraph, max(revisionnum) as max_revisionnum,
row_number() over (partition by book order by max(revisionnum) desc) as seqnum
from mytable
group by id, book, chapter, paragraph
) t
where seqnum = 1;
Here is a db<>fiddle.
In older versions of MariaDB, you can use a correlated subquery:
select t.*
from mytable t
where (id, book, chapter, paragraph, revisionnum) = (select t2.id, t2.book, t2.chapter, t2.paragraph, t2.revisionnum
from mytable t2
where t2.book = t.book
order by t2.revisionnum desc
limit 1
);
For this query, try adding an index on (book, revisionnum desc).

mysql sequence number by value coloumn (query UPDATE)

example:
I have a table with the columns
______________________
|field_id|Code|seq_num|
| 1 | a | 1 |
| 1 | a | 2 |
| 1 | a | 3 |
| 2 | a | 4 |
| 2 | a | 5 |
| 3 | a | 6 |
| 3 | a | 7 |
| 3 | a | 8 |
how to query it, so sequence number look like this
_____________________
|field_id|Code|seq_num|
| 1 | a | 1 |
| 1 | a | 2 |
| 1 | a | 3 |
| 2 | a | 1 |
| 2 | a | 2 |
| 3 | a | 1 |
| 3 | a | 2 |
| 3 | a | 3 |
please help!!
One method is to get the minimum sequence for the field:
select t.field_id, t.code,
(seq_num - min_seqnum + 1) as seqnum
from t join
(select field_id, min(seq_num) as min_seq_num
from t
group by field_id
) f
on t.field_id = f.field_id;
You can also do this using variables, if you don't trust the current sequence numbers to have no gaps:
select . . .,
(#rn := if(#f = field_id, #rn + 1,
if(#f := field_id, 1, 1)
)
) as seq_no
from (select t.*
from t
order by field_id, seq_no
) t cross join
(select #f := '', #rn := 0) params;

How to get top n records group by wildcards

Here is my table example:
+--------+-------------+
| id | city_detail |
+--------+-------------+
| 1 | 12_hyd_test |
| 2 | 13_blr_test |
| 3 | 15_blr_test |
| 4 | 18_hyd_test |
| 5 | 17_coi_test |
| 6 | 22_coi_test |
| 7 | 62_hyd_test |
| 8 | 72_blr_test |
| 9 | 92_blr_test |
| 10 | 42_hyd_test |
| 11 | 21_coi_test |
| 12 | 82_coi_test |
+--------+-------+-----+
From this table, how to use like condition with group by to select like this
+--------+-------------+
| id | city_detail |
+--------+-------------+
| 12 | 82_coi_test |
| 11 | 21_coi_test |
| 10 | 42_hyd_test |
| 7 | 62_hyd_test |
| 9 | 92_blr_test |
| 8 | 72_blr_test |
+--------+-------+-----+
In each city show only two result (%coi% or %hyd% or '%blr%') order by id DESC
Probably the simplest method is to use variables:
select e.*
from (select e.*,
(#rn := if(#c = substr(city_detail, 4), #rn + 1,
if(#c := substr(city_detail, 4), 1, 1
)
) as seqnum
from example e cross join
(select #c := '', #rn := 0) params
order by substr(city_detail, 4)
) e
where rn <= 2;

SQL Query Conditional accumulation

it is possible to display accumulated data, resetting the count based on a condition?
I would like to create a script to accumulate if there is value 1 in cell number, but if another value the count should be restarted. Something like what is displayed in the column cumulative_with_condition.
+----+------------+--------+
| id | release | number |
+----+------------+--------+
| 1 | 2016-07-08 | 4 |
| 2 | 2016-07-09 | 1 |
| 3 | 2016-07-10 | 1 |
| 4 | 2016-07-12 | 2 |
| 5 | 2016-07-13 | 1 |
| 6 | 2016-07-14 | 1 |
| 7 | 2016-07-15 | 1 |
| 8 | 2016-07-16 | 2-3 |
| 9 | 2016-07-17 | 3 |
| 10 | 2016-07-18 | 1 |
+----+------------+--------+
select * from version where id > 1 and id < 9;
+----+------------+--------+---------------------------+
| id | release | number | cumulative_with_condition |
+----+------------+--------+---------------------------+
| 2 | 2016-07-09 | 1 | 1 |
| 3 | 2016-07-10 | 1 | 2 |
| 4 | 2016-07-12 | 2 | 0 |
| 5 | 2016-07-13 | 1 | 1 |
| 6 | 2016-07-14 | 1 | 2 |
| 7 | 2016-07-15 | 1 | 3 |
| 8 | 2016-07-16 | 2-3 | 0 |
+----+------------+--------+---------------------------+
You want something like row_number() (not exactly, but like that). You can do that using variables:
select t.*,
(#rn := if(number = 1, #rn + 1,
if(#n := number, 0, 0)
)
) as cumulative_with_condition
from t cross join
(select #n := '', #rn := 0) params
order by t.id;
As an alternative to using user variables, as demonstrated by Gordon Linoff, in this case it's also possible to self-join, group and count:
SELECT t.id, t.release, t.number, COUNT(version.id) AS cumulative_with_condition
FROM version RIGHT JOIN (
SELECT highs.*, MAX(lows.id) min
FROM version lows RIGHT JOIN version highs ON lows.id <= highs.id
WHERE lows.number <> '1'
GROUP BY highs.id
) t ON version.id > t.min AND version.id <= t.id
WHERE t.id > 1 AND t.id < 9
GROUP BY t.id
See it on sqlfiddle.
But, frankly, neither approach is particularly elegant—as I commented previously, you're probably best off implementing this within your application code.

Is there a way in MySQL to do a “round robin” ORDER BY on a particular field in variable universe?

My list is a infinite universe, as an example, I would like to take a table such as this one:
+-------+------+
| group | name |
+-------+------+
| 1 | A |
| 1 | B |
| 1 | C |
| 1 | D |
| 1 | E |
| 1 | F |
| 1 | G |
| 1 | H |
| 1 | I |
| 2 | J |
| 2 | L |
| 2 | M |
| 3 | N |
| 4 | O |
| 4 | P |
| 4 | Q |
| 4 | R |
| 4 | S |
| 5 | U |
| 6 | V |
+-------+------+
And run a query that produces results in this order:
+-------+------+
| group | name |
+-------+------+
| 1 | A |
| 2 | J |
| 3 | N |
| 4 | O |
| 5 | U |
| 6 | V |
| 1 | B |
| 2 | L |
| 4 | P |
| 1 | C |
| 2 | M |
| 4 | Q |
| 1 | D |
| 4 | R |
| 1 | E |
| 4 | S |
| 1 | F |
| 1 | G |
| 1 | H |
| 1 | I |
+-------+------+
select `group`, name from (
select
t.*,
#rn := if(`group` != #ng, 1, #rn + 1) as ng,
#ng := `group`
from t
, (select #rn:=0, #ng:=null) v
order by `group`, name
) sq
order by ng, `group`, name
see it working live in an sqlfiddle
To explain it a little...
, (select #rn:=0, #ng:=null) v
This line is just a fancy way to initialize the variables on the fly. It's the same as omitting this line but having SET #rn := 0; SET #ng := NULL; before the SELECT.
Then the ORDER BY in the subquery is very important. In a relational database there is no order unless you specify it.
Here
#rn := if(`group` != #ng, 1, #rn + 1) as ng,
#ng := `group`
the first line is a simple check, if the value of group in the current row is different from the value of #ng. If yes, assign 1 to #rn, if not, increment #rn.
The order of the columns in the SELECT clause is very important. MySQL processes them one by one. In the second line, we assign the value of group of the current row to #ng. When the next row of the table is processed by the query, in the first line of the two above, #ng will therefore hold the value of the previous row.
The outer select is just cosmetics, to hide unnecessary columns. Feel free to ask if anything is still unclear. Oh, and here you can read more about user defined variables in MySQL.
Note though, that needing variables is rather the exception. They often result in full table scans. Whatever you want to achieve with variables in select statements is often better done on application level, rather than database level.