how to retrieve hierarchical data retrieved as a collection - mysql

I have implemented the following hierarchical data using MySQL
1
|----- 2
|----- 3
4
|----- 5
|----- 6
|----- 7
id | path | level | parent_id | content |
-------------------------------------------------------------------
1 1 1 NULL xxx
2 1:2 2 1 yyy
3 1:3 2 1 abc
4 4 1 NULL zzz
5 4:5 2 4 yyy
6 4:6 2 4 abc
7 4:6:7 3 6 abc
Assuming I have only these records,
how do I retrieve them in a tree structure and yet within a single collection starting with the last tree?
What I expected from the query or stored procedure is to return me the following in exactly this order
id
-----
4
5
6
7
1
2
3
How do I do the same but starting with the first tree?
id
-----
1
2
3
4
5
6
7

try including order by desc
ORDER by path desc,id asc

To cope with parents / children possibly something like this:-
SELECT PathTable.*, SUM(OrderVal) AS OrderCalc
FROM
(
SELECT id,
POW(100, MaxDepth-i-1) * SUBSTRING_INDEX(SUBSTRING_INDEX(path, ':', (i+1)), ':', -1) AS OrderVal
FROM PathTable
CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) Sub1
CROSS JOIN (SELECT MAX(LENGTH(path) - LENGTH(REPLACE(path, ':', ''))) + 1 AS MaxDepth FROM PathTable) Sub2
WHERE i <= (LENGTH(path) - LENGTH(REPLACE(path, ':', '')))
) Sub1
INNER JOIN PathTable
ON Sub1.id = PathTable.id
GROUP BY PathTable.id
ORDER BY OrderCalc
This is splitting up the path field and calculating an order value based on 100 to the power of the level of the bit of path, taking into account the max number of bits of path, times that bit of path (so 4:6:7 lands up as 7 + 6 * 100^1 + 4 * 100^2), then ordering by that.
100 chosen just as it is bigger than the largest single value in the path.

Related

Need help to group the available order details table to arrive at the distribution of count(order_line_item) , Qty and count(Qty)

I have a order table with the following information
Order ID, Product ID, Quantity ordered
OID PID Qty
1 10 1
1 20 2
2 10 2
2 40 4
3 50 1
3 20 3
4 30 1
4 90 2
4 90 5
5 10 2
5 20 2
5 70 5
5 60 1
6 80 2
If I run the following query
select `Qty`, count(`Qty`)
from `table`
group by `Qty`
I get the distribution of quantities in the table, which is
Qty count(`Qty`)
1 4
2 6
3 1
4 1
5 2
I want to find the distribution of quantity at order_line_item level too
That is how many orders which have one line item, had items with 1 quantity, 2 quantity and so one, something like
Count(Order_line_item) Qty Count(Qty)
1 2 1
2 1 2
2 2 2
2 3 1
2 4 1
3 1 1
3 2 1
3 5 1
4 1 1
4 2 2
4 5 1
What modification should i make in the above query to achieve this
Try this query
SELECT count_order_line_items, `Qty`, count(*)
FROM (
SELECT count(*) over (partition by `OID`) as count_order_line_items,
`Qty`
FROM Table1
) x
GROUP BY count_order_line_items, `Qty`
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=07dfb27a7d434eca1f0b9641aadd53c8
If your mysql version is less than 8 then try this one
SELECT count_order_line_items, `Qty`, count(*)
FROM Table1 t1
JOIN (
SELECT `OID`, count(*) as count_order_line_items
FROM Table1
GROUP BY `OID`
) t2 ON t1.`OID` = t2.`OID`
GROUP BY count_order_line_items, `Qty`
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=28c291a4693f31029a103f5c41a97d77

Mysql selecting rows

I have a database scheme with a table like this:
id --- username --- sex
1 A 1
2 D 2
3 F 1
4 G 2
5 H 1
6 x 1
7 r 1
I want to select only 2 males and lets say 4 females, male is 1 and female is 2. How would we achieve that in one mysql query and if I have more var's to select by ?
(select * from your_table where sex = 1 limit 2)
union all
(select * from your_table where sex = 2 limit 4)

Getting row number based on cate (MySql)

I have the following table and data using a query
SELECT b.*, (#rownum :=#rownum +1) AS row_number
FROM notices b, (SELECT #rownum := 0 ) row
Result:
id cate rownumber
-------------------
1 5 1
2 5 2
3 6 3
4 5 4
5 5 5
6 5 6
7 5 7
I want to output the result below without using row_number() or other function since my rdbms cannot support these.
id cate rownumber
-------------------
1 5 1
2 5 2
3 6 1
4 5 3
5 5 4
6 5 5
7 5 6
To simulate ROW_NUMBER with a partition in MySQL you can simply introduce another session variable to keep track of the cate group value as you iterate over the records in notices.
SET #rn = NULL;
SET #cate = NULL;
SELECT id, cate, rn
FROM
(
SELECT
#rn:=CASE WHEN #cate = cate THEN #rn + 1 ELSE 1 END AS rn,
#cate:=cate,
id,
cate
FROM notices
ORDER BY
cate, id
) t
ORDER BY cate, rn;
Demo here:
Rextester

MySQL COUNT values fo each column

I’m trying to figure out how to count the values in more than one column.
It seem the first COUNT I do gives me the correct results but everything I’ve tried to get the second column count gives the wrong result.
For example, with the following two columns,
Q2 Q3
1 1
1 1
2 2
1 1
1 1
5 5
3 5
5 3
4 1
2 2
3 3
3 3
5 5
3 3
2 1
2 1
3 2
4 1
1 1
1 1
2 2
5 5
3 3
2 1
3 3
1 1
2 1
SELECT COUNT(Q2) AS QU2 FROM mytable GROUP BY Q2
QU2 = 7 7 7 2 4
gives me the count for Q2. 7 one’s, 7 two’s and so on...
However, the following gives me an unexpected result.
SELECT COUNT(Q2) AS QU2, COUNT(Q3) AS QU3 FROM mytable GROUP BY Q2, Q3
7 4 3 1 5 1 2 1 3
I think its something with the GROUP BY but I don’t know how to get around it to get the needed result.
So I'm tying to get the result of
QU2 = 7 7 7 2 4
QU3 = 13 4 6 4
Or
QU2 QU3
7 13
7 4
7 6
2 4
4
and so on for QU4 QU5 ... I would appreciate any help.
Thank you
I think that this will get you closest to what you want. You can replace the numbers table with any method that generates the numbers 1 to whatever the max value is in Q2 or Q3.
CREATE TABLE dbo.Numbers (num INT)
INSERT INTO dbo.Numbers (num) VALUES (1), (2), (3), (4), (5)
SELECT
N.num,
SUM(CASE WHEN MT.Q2 = N.num THEN 1 ELSE 0 END) AS QU2,
SUM(CASE WHEN MT.Q3 = N.num THEN 1 ELSE 0 END) AS QU3
FROM
dbo.Numbers N
CROSS JOIN dbo.My_Table MT
GROUP BY
N.num
Adding additional columns (for Q4, etc.) just means adding another SUM(CASE...)
How GROUP BY works?
Let's talk about your first query (I added the column Q2 in the SELECT clause to make its output more clear):
SELECT Q2, COUNT(*) AS QU2
FROM mytable
GROUP BY Q2
First, it gets all the rows matching the WHERE criteria, if a WHERE clause exists. Because your query doesn't have a WHERE clause, all the rows from the table are read.
On the next step the rows read on the previous step are grouped by the expression specified in the GROUP BY clause (let's assume it contains only one expression, as the query above does). Internally, grouping the rows requires sorting them first.
This is how the data is organized on this step. I added horizontal separators between the rows that go in each group to make everything clear:
Q2 Q3
-------
1 1
1 1
1 1
1 1
1 1
1 1
1 1
-------
2 1
2 1
2 1
2 1
2 2
2 2
2 2
-------
3 2
3 3
3 3
3 3
3 3
3 3
3 5
-------
4 1
4 1
-------
5 3
5 5
5 5
5 5
-------
On the next step, from each group it creates a single row that goes to the generated result set.
The query above clearly returns:
Q2 QU2
--------
1 7
2 7
3 7
4 2
5 4
What happens when the GROUP BY clause contains more than one expression?
Let's take your second query (again, I added some columns to show its behaviour):
SELECT Q2, Q3, COUNT(*) AS cnt
FROM mytable
GROUP BY Q2, Q3
It works similar with the previous query but, because the GROUP BY clause contains two expression, each group created for the values of Q2 is split in sub-groups based on the value of Q3. Assuming there is another expression (let' say, Q4) in the GROUP BY clause, each sub-group created for a pair (Q2, Q3) is further divided into sub-groups for all the values of Q4 and so on.
For your table, the groups and sub-groups are as follows:
Q2 Q3
=======
1 1
1 1
1 1
1 1
1 1
1 1
1 1
=======
2 1
2 1
2 1
2 1
---
2 2
2 2
2 2
=======
3 2
---
3 3
3 3
3 3
3 3
3 3
---
3 5
=======
4 1
4 1
=======
5 3
---
5 5
5 5
5 5
=======
I used double lines to separate the groups and smaller single lines to separate the subgroups inside each group.
The output of this query is:
Q2 Q3 cnt
------------
1 1 7
2 1 4
2 2 3
3 2 1
3 3 5
3 5 1
4 1 2
5 3 1
5 5 3
How to get the desired result?
It is not possible to get the result you want using a single query. Even more, the result sets you suggest doesn't make much sense.
You can combine two queries using UNION in order to get the data you need and additional information that helps you know where those numbers come from:
SELECT 'Q2' AS source, Q2 AS q, COUNT(Q2) AS cnt FROM mytable GROUP BY Q2
UNION
SELECT 'Q3' AS source, Q3 AS q, COUNT(Q3) AS cnt FROM mytable GROUP BY Q3
The output is:
source q cnt
----------------
Q2 1 7
Q2 2 7
Q2 3 7
Q2 4 2
Q2 5 4
Q3 1 13
Q3 2 4
Q3 3 6
Q3 5 4
Pretty clear, isn't it? The first 5 rows come from the query GROUP BY Q2 and their value in the column q tells what was the value of Q2 for each group (there are 7 occurrences of 1 in column Q2, 7 of 2, 7 of 3, 2 of 4 and so on). The last 4 rows tell the similar story about Q3 (13 rows have 1 in column Q3 and so on).
Remark
There is a difference between COUNT(*) and COUNT(Q2): COUNT(*) counts the rows from the group, COUNT(Q2) counts the not-NULL values in the column Q2. It doesn't care about duplicate, it only ignore the NULL values. If you want to count the distinct values then you have to add the DISTINCT keyword: COUNT(DISTINCT Q2).
I think you need to unpivot the data. In this case, that just means multiple group by connected by union all:
select 'q2' as which, q2, count(*) as cnt
from mytable
group by q2
union all
select 'q3' as which, q3, count(*) as cnt
from mytable
group by q3;
You can add as many more subqueries as you like.
Note: this puts the values in separate rows, rather than in separate columns.
I reckon Asaph is on the right track , however I would alter this slightly try select distinct count(Q2) as QU2, count(Q3) as QU3 from myTable;

mysql select most recent, limit by source

I have a table with the following fields:
id
source_id
title
date
I want to select the 25 most recent items, so SELECT * FROM table ORDER BY date DESC LIMIT 50
The extra requirement is to select only the 3 most recent from every source_id.
So if the records look something like that,
id | source_id | title | date
----+-----------+-------+---------
1 2 aaa 2012-1-1
2 2 aaa 2012-1-2
3 2 aaa 2012-1-3
4 2 aaa 2012-1-4
5 3 aaa 2012-1-5
6 4 aaa 2012-1-6
I want my query to return items 4,3,2,5,6
So just the 3 most recent of every source with an over all limit of 25.
I'm not sure it's clear enough so please ask if you need more details.
Here you go:
SELECT *
FROM your_table t1
WHERE
(
SELECT COUNT(*)
FROM your_table t2
WHERE
t1.source_id = t2.source_id
AND t1.date < t2.date
) < 3
ORDER BY source_id, date DESC
Result:
4 2 aaa 2012-01-04
3 2 aaa 2012-01-03
2 2 aaa 2012-01-02
5 3 aaa 2012-01-05
6 4 aaa 2012-01-06
In plain English: take only rows that have less than 3 newer rows with the same source_id.
NOTE: This could select more than 3 rows per source_id if the third newest date (for the same source_id) happens to be shared by more than one row. Let me know what "3 newest" means in this context if that's a problem...
select * from table where source_id in
(select distinct source_id from table order by date limit 3)
LIMIT 25