My table structure as follows
id parent last_date sub_type
----------------------------------
11 9 2017-02-28 1101
10 9 2016-08-26 1101
8 1 2017-02-20 1101
12 12 2016-08-31 1102
14 12 2016-12-20 1102
9 9 2016-12-31 1101
13 12 2017-03-23 1102
2 1 2017-01-25 1101
1 1 2016-12-31 1101
i want to fetch rows for each sub_type based the date (longest first) . i tried following query
SELECT * FROM mytable GROUP BY sub_type ORDER BY ISNULL(last_date) DESC, last_date DESC
and it results
id parent last_date sub_type
--------------------------------
1 1 2016-12-31 1101
12 12 2016-08-31 1102
But i expect below result .
id parent last_date sub_type
--------------------------------
13 12 2017-03-23 1102
11 9 2017-02-28 1101
Please guide me to get above result .
EDIT:
last_date may have NULL value which will max precedence over dated entries. Thatswhy i choose ISNULL DESC order.
You can use a correlated subquery in the WHERE clause with ORDER BY and LIMIT 1 to find the id of the row you are looking for.
SELECT *
FROM mytable t1
WHERE id = (
SELECT id
FROM mytable t2
WHERE t2.sub_type = t1.sub_type
ORDER BY ISNULL(last_date) DESC, last_date DESC, id DESC
LIMIT 1
)
Demo: http://rextester.com/DSPH11524
Note: sub_type should be indexed.
You can also do it by giving a row number based on the sub_type column and in the descending order of the last_date column.
Query
select t1.`id`, t1.`parent`, t1.`last_date`, t1.`sub_type` from
(
select `id`, `parent`, `last_date`, `sub_type`,
(
case `sub_type` when #A
then #R := #R + 1
else #R := 1 and #A := `sub_type` end
) as `rn`
from `your_table_name` t,
(select #R := 0, #A := '') r
order by `sub_type`, `last_date` desc
)t1
where t1.`rn` = 1;
Sql Fiddle demo
This is a typical question fetching one record in each group by some aggregation. Try this:
select
mytable.*
from mytable
join (
select max(last_date) as last_date, sub_type from mytable group by sub_type
) t1 on mytable.sub_type = t1.sub_type and mytable.last_date = t1.last_date
See this article How to select the first/least/max row per group in SQL.
and Related link at right:
Retrieving the last record in each group.
And demo in sqlfiddle.
Edit:
if there is no 2 same last date and null precedence, then try this:
select
mytable.*
from mytable
join (
select
max(last_date) as last_date,
max(isnull(last_date)) nulled,
sub_type
from mytable
group by sub_type
) t1 on mytable.sub_type = t1.sub_type
and (t1.nulled and mytable.last_date is null or (t1.nulled <> 1 and mytable.last_date = t1.last_date))
also demo in sqlfiddle.
You have written a wrong query.
Specify Where condition after where clause
Below query will give your expected result.
SELECT id,parent,max(last_Date),sub_type FROM mytable GROUP BY sub_type
Related
Here is my MySQL table. I updated the question by adding an 'id' column to it (as instructed in the comments by others).
id data_id
1 2355
2 2031
3 1232
4 9867
5 2355
6 4562
7 1232
8 2355
I want to add a new column called row_num to assign an incrementing number ONLY for duplicates, as shown below. Order of the results does not matter.
id data_id row_num
3 1232 1
7 1232 2
2 2031 null
1 2355 1
5 2355 2
8 2355 3
6 4562 null
4 9867 null
I followed this answer and came up with the code below. But following code adds a count of '1' to non-duplicate values too, how can I modify below code to add a count only for duplicates?
select data_id,row_num
from (
select data_id,
#row:=if(#prev=data_id,#row,0) + 1 as row_num,
#prev:=data_id
from my_table
)t
If you are running MySQL 8.0, you can do this more efficiently with window functions only:
select
data_id,
case when count(*) over(partition by data_id) > 1
then row_number() over(partition by data_id order by data_id) row_num
end
from mytable
When the window count returns more than 1, you know that the current data_id has duplicates, in which case you can use row_number() to assign the incrementing number.
Note that, in absence of an ordering columns to uniquely identify each record within groups sharing the same data_id, it is undefined which record will actually get each number.
I am assuming that id is the column that defines the order on the rows.
In MySQL 8 you can use row_number() to get the number of each data_id and a CASE with EXISTS to exclude the rows which have no duplicate.
SELECT t1.data_id,
CASE
WHEN EXISTS (SELECT *
FROM my_table t2
WHERE t2.data_id = t1.data_id
AND t2.id <> t1.id) THEN
row_number() OVER (PARTITION BY t1.data_id
ORDER BY t1.id)
END row_num
FROM my_table t1;
In older versions you can use a subquery counting the rows with the same data_id but smaller id. With an EXISTS in a HAVING clause you can exclude the rows that have no duplicate.
SELECT t1.data_id,
(SELECT count(*)
FROM my_table t2
WHERE t2.data_id = t1.data_id
AND t2.id < t1.id
HAVING EXISTS (SELECT *
FROM my_table t2
WHERE t2.data_id = t1.data_id
AND t2.id <> t1.id)) + 1 row_num
FROM my_table t1;
db<>fiddle
Join with a query that returns the number of duplicates.
select t1.data_id, IF(t2.dups > 1, row_num, '') AS row_num
from (
select data_id,
#row:=if(#prev=data_id,#row,0) + 1 as row_num,
#prev:=data_id
from my_table
order by data_id
) AS t1
join (
select data_id, COUNT(*) AS dups
FROM my_table
GROUP BY data_id
) AS t2 ON t1.data_id = t2.data_id
If you want to have the old "order" of the old table, you need much more code
SELECT
data_id, IF (row_num = 1 AND cntid = 1, NULL,row_num)
FROM
(SELECT
#row:=IF(#prev = t1.data_id, #row, 0) + 1 AS row_num,
cntid,
#prev:=t1.data_id data_id
FROM
(SELECT
*
FROM
my_table
ORDER BY data_id) t1
INNER JOIN (SELECT Count(*) cntid,data_id FROM my_table GROUP BY data_id)t2
ON t1.data_id = t2.data_id) t2
data_id | IF (row_num = 1 AND cntid = 1, NULL,row_num)
------: | -------------------------------------------:
1232 | 1
1232 | 2
2031 | null
2355 | 1
2355 | 2
2355 | 3
4562 | null
9867 | null
db<>fiddle here
I have this table called my_users
my_id | name | raffle_tickets
1 | Bob | 3
2 | Sam | 59
3 | Bill | 0
4 | Jane | 10
5 | Mike | 12
As you can see Sam has 59 tickets so he has the highest chance of winning.
Chance of winning:
Sam = 59/74
Bob = 3/74
Jane = 10/74
Bill = 0/74
Mike = 12/74
PS: 74 is the number of total tickets in the table (just so you know I didn't randomly pick 74)
Based on this, how can I randomly pick a winner, but ensure those who have more raffles tickets have a higher chance of being randomly picked? Then the winner which is picked, has 1 ticket deducted from their total tickets
UPDATE my_users
SET raffle_tickets = raffle_tickets - 1
WHERE my_id = --- Then I get stuck here...
Server version: 5.7.30
For MySQL 8+
WITH
cte1 AS ( SELECT name, SUM(raffle_tickets) OVER (ORDER BY my_id) cum_sum
FROM my_users ),
cte2 AS ( SELECT SUM(raffle_tickets) * RAND() random_sum
FROM my_users )
SELECT name
FROM cte1
CROSS JOIN cte2
WHERE cum_sum >= random_sum
ORDER BY cum_sum LIMIT 1;
For 5+
SELECT cte1.name
FROM ( SELECT t2.my_id id, t2.name, SUM(t1.raffle_tickets) cum_sum
FROM my_users t1
JOIN my_users t2 ON t1.my_id <= t2.my_id
WHERE t1.raffle_tickets > 0
GROUP BY t2.my_id, t2.name ) cte1
CROSS JOIN ( SELECT RAND() * SUM(raffle_tickets) random_sum
FROM my_users ) cte2
WHERE cte1.cum_sum >= cte2.random_sum
ORDER BY cte1.cum_sum LIMIT 1;
fiddle
You want a weighted pull from a random sample. For this purpose, variables are probably the most efficient solution:
select u.*
from (select u.*, (#t := #t + raffle_tickets) as running_tickets
from my_users u cross join
(select #t := 0, #r := rand()) params
where raffle_tickets > 0
) u
where #r >= (running_tickets - raffle_tickets) / #t and
#r < (running_tickets / #t);
What this does is calculate the running sum of tickets and then divide by the number of tickets to get a number between 0 and 1. For example this might produce:
my_id name raffle_tickets running_tickets running_tickets / #t
1 Bob 3 3 0.03571428571428571
2 Sam 59 62 0.7380952380952381
4 Jane 10 72 0.8571428571428571
5 Mike 12 84 1
The ordering of the original rows doesn't matter -- which is why there is no ordering in the subquery.
The ratio is then used with rand() to select a particular row.
Note that in the outer query, #t is the total number of tickets.
Here is a db<>fiddle.
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 need to pull the name of the students who stood second positions from grade 1 to grade 12. each grade has separate databases with similar table structure
I have the following data:
Set 1
uid marks
1 10
2 20
3 17
4 17
5 20
6 20
Set 2
uid marks
1 10
2 20
3 17
4 17
5 20
6 17
7 20
I need a query which can say uid 3,4 are second in set 1 and 3,4,6 are second in set 2.
i need it in a single query because there are several set of databases
what could be the possible way?
I tried:
SELECT * FROM TBL WHERE marks ! = SELECT MAX(marks) from tbl
but it fetched all marks except the highest
Try this out:
SELECT uid, marks FROM (
SELECT uid, marks, #rank := #rank + (#prevMarks != marks) rank, #prevMarks := marks
FROM t, (SELECT #rank := 0, #prevMarks := 0) init
ORDER BY marks
) s
WHERE rank = 2
Fiddle here.
Another alternative without User Defined Variables:
SELECT t.uid, t.marks FROM t
JOIN (
SELECT DISTINCT marks FROM t
ORDER BY marks
LIMIT 1, 1
) s
ON t.marks = s.marks
Output:
| UID | MARKS |
|-----|-------|
| 3 | 17 |
| 4 | 17 |
Use LIMIT and ORDER BY
SELECT * FROM TBL ORDER BY marks DESC LIMIT 1,1
There you ordered all students by marks fro hi to low. And then limit return from second (0 is first record) and return only one record.
If need all students with second mark, the use subquery
SELECT * FROM TBL WHERE marks = (
SELECT marks FROM TBL ORDER BY marks DESC GROUP BY marks LIMIT 1,1
)
SELECT *
FROM table
WHERE mark = (
SELECT MAX(mark)
FROM table
WHERE mark <
(
SELECT MAX(mark)
FROM table
)
)
Try this
SELECT t.marks, t.uid, (
SELECT COUNT( marks ) +1
FROM tbl t1
WHERE t.marks < t1.marks
) AS rank
FROM tbl t
LIMIT 0 , 30
now you can use rank column with bit modification below
SELECT * from (
SELECT t.marks, t.uid, (
SELECT COUNT( marks ) +1
FROM tbl t1
WHERE t.marks < t1.marks
) AS rank
FROM tbl t
) alias where rank=n (2 here)
I have a table like this in MYSQL:
SELECT * FROM test_table
id u_id name date
1 101 name2 2012-05-14
2 305 name3 2012-05-11
3 506 name4 2012-05-05
4 207 name5 2012-05-12
5 108 name6 2012-05-03
SELECT id,u_id from test_table order by date;
id u_id
5 108
3 506
2 305
4 207
1 101
I have a application where where these things are displayed
on clicking on any u_id it takes me to a different page which displyed details stuff
Now I want to write a query which willl give me the next record
How do I achive the next record from here like
when I say u_id>305 it shoud give me 207?
and u_id<305 it should give me 506
I think this is what you are looking for:
SELECT id, u_id
FROM test_table
WHERE uid > 305
ORDER BY date ASC
LIMIT 1;
SELECT id, u_id
FROM test_table
WHERE uid < 305
ORDER BY date DESC
LIMIT 1;
Given the u_id is 305, to get the next record by date:
SELECT id,u_id
FROM test_table a
WHERE a.date > (SELECT date FROM test_table b WHERE b.u_id = 305)
ORDER BY a.date
LIMIT 1;
And to get the previous record by date:
SELECT id,u_id
FROM test_table a
WHERE a.date < (SELECT date FROM test_table b WHERE b.u_id = 305)
ORDER BY a.date DESC
LIMIT 1;
try this:
select id,u_id from
(SELECT id,u_id,#rownum:= #rownum+1 AS Sno
from test_table , (SELECT #rownum:=0) r;
order by date)a
where Sno=<#ID-1/+1>
lets say if your current Sno=5 then 4 will give the previous record and 6 will give the next record