MySql group by and order by different column - mysql

I have a table and I wanted to group by one column and get all values with order by date and time column.
**My table**
-------------------------------
id | name | created_at
===+======+===========
1 | a | 2020-11-18 04:33:55
2 | b | 2020-11-14 10:17:28
3 | c | 2020-11-12 20:26:00
4 | a | 2020-11-11 18:35:24
5 | c | 2020-11-10 10:55:04
**Result**
-------------------------------
id | name | created_at
===+======+===========
1 | a | 2020-11-18 04:33:55
2 | b | 2020-11-14 10:17:28
3 | c | 2020-11-12 20:26:00
In the older version of Mysql(V. 5.7.32) the below query is working fine.
SELECT * FROM `my_table` GROUP BY name ORDER by created_at DESC
But in the new version of mysql(V. 8.0.22) the below code is not working.
Please anyone have a solution for it.

WITH
cte AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY name ORDER BY created_at DESC) rn
FROM my_table )
SELECT *
FROM cte
WHERE rn = 1;

Related

How to fetch the information from latest record in MySQL (MardiaDB) dependent table?

I have a MySQL database (also working as MariaDB on some servers) with 2 main tables. One is the tickets and the another is ticket_contents as below:
Tickets
+----+---------+-------------+
|id + subject | Create date |
+----+---------+-------------+
|1 | Ticket1 | 2020-05-24 |
+----+---------+-------------+
|2 | Ticket2 | 2020-05-25 |
+----+---------+-------------+
Ticket Contents
+----+-----------+---------------------+-------------+
|id + ticket_id | update date_time | content |
+----+-----------+---------------------+-------------+
|1 | 1 | 2020-05-24 08:30:15 | msg1 |
+----+-----------+---------------------+-------------+
|1 | 2 | 2020-05-25 10:05:15 | msg2 |
+----+-----------+---------------------+-------------+
|1 | 1 | 2020-05-25 12:15:00 | msg3 |
+----+-----------+---------------------+-------------+
What I need is to have the list of tickets with their latest content record time as this and with the latest update order for whole tickets:
+----------+----------+---------------------+
|ticket_id |subject |last_update |
+----------+---_------+---------------------+
|1 | Ticket1 | 2020-05-25 12:15:00 |
+----------+----------+---------------------+
|2 | Ticket2 | 2020-05-25 10:05:15 |
+----------+----------+---------------------+
This is the code I have written, but it does not provide the correct latest child record information.
SELECT t.id, c.date_time FROM tickets t JOIN
(SELECT ticket_id, date_time FROM tickets_contents GROUP BY ticket_id) c on t.id = c.ticket_id
group by t.id order by date_time desc
Can you help me to correct my SQL code please?
You had it almost right, you must use MAX to get the highest date of a GROUP BY
SELECT t.id,t.subject,t2.maxtime
FROM Tickets t
INNER JOIN (SELECT MAX(`update date_time`) maxtime,ticket_id FROM `Ticket Contents` GROUP BY ticket_id) t2
ON t.id = t2.ticket_id
id | subject | maxtime
-: | :------ | :------------------
1 | Ticket1 | 2020-05-25 12:15:00
2 | Ticket2 | 2020-05-25 10:05:15
db<>fiddle here
Provided that your DBMS version is 10.2+, then window functions such as RANK() might be used to determine the tickets with latest update :
SELECT tt.id AS ticket_id, tt.subject, tt.date_time AS last_update
FROM
(
SELECT t.*, tc.date_time,
RANK() OVER (PARTITION BY tc.ticket_id ORDER BY tc.date_time DESC ) AS rnk
FROM tickets t
JOIN tickets_contents tc
ON tc.ticket_id = t.id
) tt
WHERE rnk = 1
ORDER BY ticket_id;
Demo
where PARTITION BY stands for grouping for each column concerned (in this case : ticket_id), and ORDER BY ... DESC is used to filter the last record (in this case : latest).

How to calculate max values of groups?

I have a table like so (I'm not sure how to format tables)
Category / Products / Purchases
1 | A | 12
1 | B | 13
1 | C | 11
2 | A | 1
2 | B | 2
2 | C | 3
Expected output:
1 | B | 13
2 | C | 3
However I keep on getting
1 | A | 13
2 | A | 3
ie. It just selects the first occurrence of the second column.
Here is my code:
SELECT Category, Products, MAX(Purchases) FROM myTable GROUP BY Category;
Use filtering in the where clause:
select t.*
from t
where t.purchases = (select max(t2.purchases) from t t2 where t2.category = t.category);
With NOT EXISTS:
select m.* from myTable m
where not exists (
select 1 from myTable
where category = m.category and purchases > m.purchases
)
See the demo.
Results:
| Category | Products | Purchases |
| -------- | -------- | --------- |
| 1 | B | 13 |
| 2 | C | 3 |
You can use row_number() to identify max purchase for each group or replace rownumber() to rank() if there are ties of max purchases for each group
Select Category, Products,
Purchases from (Select Category,
Products,
Purchases,
row_number() over (partition by
category, products order by
purchases desc) rn from table) t
where t.rn=1
)

How to track previous row status count

I want to calculate count of order status changes within different states.
My Orderstatus table:
| id |ordr_id| status |
|----|-------|------------|
| 1 | 1 | pending |
| 2 | 1 | processing |
| 3 | 1 | complete |
| 4 | 2 | pending |
| 5 | 2 | cancelled |
| 6 | 3 | processing |
| 7 | 3 | complete |
| 8 | 4 | pending |
| 9 | 4 | processing |
Output I want:
| state | count |
|----------------------|-------|
| pending->processing | 2 |
| processing->complete | 2 |
| pending->cancelled | 1 |
Currently I'm fetching the results by SELECT order_id,GROUP_CONCAT(status) as track FROM table group by order_id and then process the data in php to get the output. But is that possible in query itself ?
Use lag():
select prev_status, status, count(*)
from (select t.*,
lag(status) over (partition by order_id order by status) as prev_status
from t
) t
group by prev_status, status;
LAG() is available in MySQL starting with version 8.
Note that you can filter out the first status for each order by putting where prev_status is not null in the outer query.
Your version is not quite correct, because it does not enforce the ordering. It should be:
SELECT order_id,
GROUP_CONCAT(status ORDER BY id) as track
EDIT:
In earlier versions of MySQL, you can use a correlated subquery:
select prev_status, status, count(*)
from (select t.*,
(select t2.status
from t t2
where t2.order_id = t.order_id and t2.id < t.id
order by t2.id desc
limit 1
) as prev_status
from t
) t
group by prev_status, status;
If id column ensure the sequence of records, you can use self join to achieve your requirement as below-
SELECT A.Status +'>'+ B.Status, COUNT(*)
FROM OrderStatus A
INNER JOIN OrderStatus B
ON A.id = B.id -1
WHERE B.Status IS NOT NULL
GROUP BY A.Status +'>'+ B.Status
With a join of the 3 status change types to the grouping of the table that you already did:
select c.changetype, count(*) counter
from (
select 'pending->processing' changetype union all
select 'processing->complete' union all
select 'pending->cancelled'
) c inner join (
select
group_concat(status order by id separator '->') changestatus
from tablename
group by ordr_id
) t on concat('->', t.changestatus, '->') like concat('%->', changetype, '->%')
group by c.changetype
See the demo.
Results:
> changetype | counter
> :------------------- | ------:
> pending->cancelled | 1
> pending->processing | 2
> processing->complete | 2
...or just a simple join...
SELECT CONCAT(a.status,'->',b.status) action
, COUNT(*) total
FROM my_table a
JOIN my_table b
ON b.ordr_id = a.ordr_id
AND b.id = a.id + 1
GROUP
BY action;
+----------------------+-------+
| action | total |
+----------------------+-------+
| pending->cancelled | 1 |
| pending->processing | 2 |
| processing->complete | 2 |
+----------------------+-------+
Note that this relies on the fact that ids are contiguous.

Select top most non-duplicated entry after ordering by other columns [duplicate]

This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 5 years ago.
I would like to select the "top most" entry for each row with a duplicated column value.
Performing the following query -
SELECT *
FROM shop
ORDER BY shop.start_date DESC, shop.created_date DESC;
I get the result set -
+--------+---------+------------+--------------+
| row_id | shop_id | start_date | created_date |
+--------+---------+------------+--------------+
| 1 | 1 | 2017-02-01 | 2017-01-01 |
| 2 | 1 | 2017-01-01 | 2017-02-01 |
| 3 | 2 | 2017-01-01 | 2017-07-01 |
| 4 | 2 | 2017-01-01 | 2017-01-01 |
+--------+---------+------------+--------------+
Can I modify the SELECT so that I only get back the "top rows" for each unique shop_id -- in this case, row_ids 1 and 3. There can be 1..n number of rows with the same shop_id.
Similarly, if my query above returned the following order, I'd want to only SELECT row_ids 1 and 4 since those would be the "top most" entries each shop_id.
+--------+---------+------------+--------------+
| row_id | shop_id | start_date | created_date |
+--------+---------+------------+--------------+
| 1 | 1 | 2017-02-01 | 2017-01-01 |
| 2 | 1 | 2017-01-01 | 2017-02-01 |
| 4 | 2 | 2017-01-01 | 2017-07-01 |
| 3 | 2 | 2017-01-01 | 2017-01-01 |
+--------+---------+------------+--------------+
You can do this by using a subquery:
select s.*
from shop s
where s.row_id = (
select row_id
from shop
where shop_id = s.shop_id
order by start_date desc, created_date desc
limit 1
)
Mind the assumption of row_id being uniq for each shop_id in this query example.
Demonstration
Or like this:
select t.*
from shop t
join (
select t2.shop_id, t2.start_date, max(t2.created_date) as created_date
from shop t2
join (
select max(start_date) as start_date, shop_id
from shop
group by shop_id
) t3 on t3.shop_id = t2.shop_id and t3.start_date = t2.start_date
group by t2.shop_id, t2.start_date
) t1 on t1.shop_id = t.shop_id and t.start_date = t1.start_date and t.created_date = t1.created_date
Mind that in case there can be records with the same start_date and created_date for the same shop_id you would need to use another group by s.shop_id, s.start_date, s.created_date in the outer query (adding min(row_id) with other columns listed in the group by in select)
Demonstration
Try joining to a subquery which finds the "top" rows for each shop_id:
SELECT t1.*
FROM shop t1
INNER JOIN
(
SELECT shop_id, MIN(row_id) AS min_id
FROM shop
GROUP BY shop_id
) t2
ON t1.shop_id = t2.shop_id AND
t1.row_id = t2.min_id
ORDER BY
t1.start_date DESC,
t1.created_date DESC;
Demo

Sql Multi-selecting one line from subgroups inside the same table

Given the following table:
id | group_s | name
_____________________
1 | 1 | pollo
2 | 1 | cordero
3 | 1 | cerdo
4 | 2 | tomates
5 | 2 | naranjas
6 | 2 | manzanas
I would like to randomly select one line from every group.
Example of possible outputs (since it is random):
id | group_s | name
_____________________
3 | 1 | cerdo
5 | 2 | naranjas
or
id | group_s | name
_____________________
1 | 1 | pollo
6 | 2 | manzanas
and so on..
I don't have a clue how to do it. I suppose I should multiselect the table.
I did try the following without success:
SELECT T2.* FROM (
SELECT group_s
FROM mytable
GROUP BY group_s ORDER BY RAND() LIMIT 1) AS T1
JOIN mytable AS T2
ON T1.group_s = T2.group_s;
Use the window function ROW_NUMBER() OVER(PARTITION BY group_s) with ORDER BY NEWID() to randomly get the ordering, something like this:
WITH CTE
AS
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY group_s
ORDER BY newid()) AS RN
FROM yourTable
)
SELECT id , group_s , name
FROM CTE
WHERE RN = 1;
See it in action here:
SQL Fiddle Demo