I have Price_trend table in that m_id and c_id are foreign keys. I need m_id column 3,7 top two records.
Her is my db-fiddle
table: price_trend
id
c_id
m_id
date
1
1
3
2022-12-08
2
1
3
2022-12-06
3
1
3
2022-12-05
4
1
7
2022-12-03
5
1
7
2022-12-02
6
1
7
2022-12-01
My Attempt:
I have written the query up to this point, but am stuck on what to do next
select * from price_trend where c_id=1 and m_id in(3,7) limit 4;
I want result:
id
c_id
m_id
date
1
1
3
2022-12-08
2
1
3
2022-12-06
4
1
7
2022-12-03
5
1
7
2022-12-02
On MySQL 8+ we can use the ROW_NUMBER() window function:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY m_id ORDER BY date DESC) rn
FROM price_trend
WHERE c_id = 1 AND m_id IN (3, 7)
)
SELECT id, c_id, m_id, date
FROM cte
WHERE rn <= 2
ORDER BY m_id, date DESC;
ROW_NUMBER() assigns a sequence 1, 2, 3 to each record in a given m_id group, with the most recent record being assigned 1, the next most recent 2, and so on. In the outer query we then restrict to only records having the first two row numbers.
In MySQL 5.X, you can use variables to generate the ranking index. Once you get it, you can select ranking values less or equal to 2.
SET #rownum = 0;
SET #prevmid = NULL;
SELECT id, c_id, m_id, date_
FROM (SELECT id, c_id, date_,
IF(m_id = #prevmid, #rownum := #rownum + 1, #rownum := 1) AS rn,
IF(m_id = #prevmid, m_id, #prevmid := m_id) AS m_id
FROM price_trend
WHERE c_id = 1 AND m_id IN (3, 7)
ORDER BY id) cte
WHERE rn <= 2
Check the demo here.
Related
Table: signin
username
class
aaa
1
aaa
1
bbb
1
bbb
1
ccc
1
ddd
2
ddd
2
eee
2
There are two classes. How do I get first TWO username who has the most total count for each class(class-1 & class-2) like below ↓.
username
class
total
aaa
1
2
bbb
1
2
ddd
2
2
eee
2
1
EDIT: Is there any way not using over partition by?
In older version of MySQL you need to generate the ranking manually without any ranking function. Below code will work.
Schema and insert statements:
create table signin(username varchar(50),class int);
insert into signin values('aaa', 1);
insert into signin values('aaa', 1);
insert into signin values('bbb', 1);
insert into signin values('bbb', 1);
insert into signin values('ccc', 1);
insert into signin values('ddd', 2);
insert into signin values('ddd', 2);
insert into signin values('eee', 2);
Query:
select username,class,total
from
(
select username,class,count(*) total ,
#rn := IF(#prev = class, #rn + 1, 1) AS rn,
#prev := class
from signin
JOIN (SELECT #prev := NULL, #rn := 0) AS vars
group by class,username
order by class,username,count(*) desc
)t
where rn<=2
Output:
username
class
total
aaa
1
2
bbb
1
2
ddd
2
2
eee
2
1
db<fiddle here
Rank them by count in descending order and display only the first two. Sample data in lines #1 - 10; query begins at line #11.
SQL> with signin (username, class) as
2 (select 'aaa', 1 from dual union all
3 select 'aaa', 1 from dual union all
4 select 'bbb', 1 from dual union all
5 select 'bbb', 1 from dual union all
6 select 'ccc', 1 from dual union all
7 select 'ddd', 2 from dual union all
8 select 'ddd', 2 from dual union all
9 select 'eee', 2 from dual
10 )
11 select username, class, total
12 from (select username, class, count(*) total,
13 rank() over (partition by class order by count(*) desc) rnk
14 from signin
15 group by username, class
16 )
17 where rnk <= 2
18 order by class, username;
USE CLASS TOTAL
--- ---------- ----------
aaa 1 2
bbb 1 2
ddd 2 2
eee 2 1
SQL>
With rank() ranking window function and common table expression you can easily achieve that.
To get the usernames with most count in a class I have used rank()over (partition by class order by count(*) desc) along with group by class,username
Schema and insert statements:
create table signin(username varchar(50),class int);
insert into signin values('aaa', 1);
insert into signin values('aaa', 1);
insert into signin values('bbb', 1);
insert into signin values('bbb', 1);
insert into signin values('ccc', 1);
insert into signin values('ddd', 2);
insert into signin values('ddd', 2);
insert into signin values('eee', 2);
Query:
with cte as
(
select username,class,count(*) total,
rank()over (partition by class order by count(*) desc) rn
from signin
group by class,username
)
select username,class,total from cte where rn<=2
Output:
username
class
total
aaa
1
2
bbb
1
2
ddd
2
2
eee
2
1
db<>fiddle here
In older versions of MySQL, you would use variables. But you have to be careful with the syntax.
The basic idea is aggregation and then enumeration:
select uc.*
from (select uc.*,
(#rn := if(#c = class, #rn + 1,
if(#c := class, 1, 1)
)
) as seqnum
from (select username, class, count(*) as cnt
from signin
group by username, class
order by class, cnt desc
) uc cross join
(select #c := '', #rn := 0) params
) uc
where seqnum <= 2;
There are three very important things to remember when using variables in a select:
They are deprecated. In more recent versions, you should be using window functions for this type of operation.
MySQL does not guarantee the order of evaluation of expressions in a SELECT. So, you should handle all variables in a single expression, which is why the #rn := is rather complicated.
MySQL does not guarantee that the ORDER BY is evaluated before the variable assignments. That is why the ORDER BY is in the subquery.
This is my table:
PACKAGE_ID ITEM_ID
1 1
1 2
1 3
2 4
2 5
3 6
4 7
4 8
I want a new column called count that count 1 to N according to package ID. E.g.:
PACKAGE_ID ITEM_ID COUNT
1 1 1
1 2 2
1 3 3
2 4 1
2 5 2
3 6 1
4 7 1
4 8 2
Thanks!
p.s: I'm using MariaDb 10.1
You can use window function.
SELECT PACKAGE_ID, ITEM_ID
, ROW_NUMBER() OVER (PARTITION BY PACKAGE_ID ORDER BY PACKAGE_ID, ITEM_ID) AS THE_COUNT
FROM your_table
In pre 8.0 MySQL, the fastest method is probably variables:
select t.*,
(#rn := if(#p = package_id, #rn + 1,
if(#p := package_id, 1, 1)
)
) as counter
from t cross join
(select #rn := 0, #p := -1) params
order by package_id, item_id;
Obviously an index on (package_id, item_id) would benefit this query.
In MySQL 8+ or similar versions of MariaDB, use row_number():
You can use subquery :
select *, (select count(1)
from table t1
where t1.PACKAGE_ID = t.PACKAGE_ID and
t1.ITEM_ID <= t.ITEM_ID
) as COUNT
from table t;
I have a table with the following columns : id, int_value, date, desc . Primary key is (id, int_value, date).
I would like to query the table to get id, int_value and date columns but with distinct id and int_value ordered in desc.
For example, imagine you have the following rows in the table
id | int_value | date | desc
1 150 2016 desccc
2 120 2014 ddd
1 160 2016 aaa
3 180 2015 ccc
2 135 2016 ddd
With my query, I would like to get that :
id | int_value | date | desc
3 180 2015 ccc
1 160 2016 aaa
2 135 2016 ddd
For the moment, I made the following query :
select id, int_value, date from table t where int_value = (select
max(int_value) from table where t.id = id) order by int_value desc;
It works well but if there are same int_value values for a given id, there will be two rows for the same id.
My question is : can you help me to create a query to avoid this problem ?
Update
It seems the following query do the job :
SELECT id, MAX(int_value) AS score, date FROM table GROUP BY id order by score desc
Thanks for your help.
Sylvain
One method is to emulate row_number() using variables:
select id, int_value, date
from (select id, int_value, date,
(#rn := if(#i = id, #rn + 1,
if#i := id, 1, 1)
)
) as rn
from table t cross join
(select #i := 0, #rn := 0) params
order by id, int_value desc
) t
where rn = 1;
select a.*, b.[desc] from
(select id, max(int_value) as int_value, max(date) as date from YourTableName group by id) a
left join YourTableName b on (a.id=b.id and a.int_value=b.int_value and a.date=b.date)
order by date
this produces the result you want:
id int_value date desc
----------- ----------- ----------- ------
3 180 2015 ccc
1 160 2016 aaa
2 135 2016 ddd
(3 row(s) affected)
I currently have a query that counts the 'parent_id' value for each row and adds it within each row.
For example, if I have 4 records that have the has the value '1432' under 'parent_id' it will show the count value 4 under the 'count' column.
I am trying to limit the amount of rows based on the count number.
For example, let's say we want to exceed the number of records per 'parent_id' to 2. if the 'parent_id' has reached the third record, it just passes on and it won't return that record.
Example of existing table:
ID parent_id count(parent_id)
1 1234 2
2 1234 2
3 3221 3
4 3221 3
5 3221 3
6 5432 1
7 4312 1
The result I'd like to get is:
ID parent_id count(parent_id)
1 1234 2
2 1234 2
3 3221 2
4 3221 2
5 5432 1
6 4312 1
This is a select per group query:
SELECT id, parent_id, rn
FROM (
SELECT #rn:=CASE WHEN #parent_id=parent_id
THEN #rn+1
ELSE 1
END AS rn
, #parent_id:=parent_id AS parent_id
, id
FROM t, (SELECT #rn:=0,#parent_id:='') AS u
ORDER BY parent_id,id
) as s
WHERE rn <= 2
FIDDLE
Mihai's answer may work, but it is not guaranteed to work. The problem is that MySQL does not guarantee the order of evaluation of expressions in the select. And, there are even occasions where it does not evaluate them in the expected order.
So, when using variables, it is safest to put all assignments in a single expression:
SELECT id, parent_id, rn
FROM (SELECT (#rn := if(#parent_id = parent_id, #rn + 1,
if(#parent_id := parent_id, 1, 1)
)
) as rn,
t.*
FROM t CROSS JOIN
(SELECT #rn := 0, #parent_id := '') params
ORDER BY parent_id, id
) t
WHERE rn <= 2;
Here's the data:
empID Date Type
----- -------- ----
1 1/1/2012 u
1 1/2/2012 u
1 1/3/2012 u
1 2/2/2012 u
4 1/1/2012 u
4 1/3/2012 u
4 1/4/2012 u
4 1/6/2012 u
Would return:
empID count
----- -----
1 2
4 3
When two dates are "together" they count as one occurrence, if the dates are separated out, they count as two occurrences. This is for tracking employee attendance... how would the SQL statement look to group by "together" dates and count them as 1... I'm really struggling with the logic.
SELECT
empID
, COUNT(*) AS cnt
FROM
tableX AS x
WHERE
NOT EXISTS
( SELECT *
FROM tableX AS y
WHERE y.empID = x.empID
AND DATEADD ("d", -1, x.[Date]) = y.[Date]
)
GROUP BY
empID ;
try this:
;WITH CTE as
(select *,ROW_NUMBER() over (partition by empID order by date) as rn from test2 t1)
select empID,COUNT(*) as count
from CTE c1
where isnull((DATEDIFF(day,(select date from CTE where c1.rn=rn+1 and empID=c1.empID ),c1.date)),0) <> 1
group by empID