How to SELECT FROM the intersection of multiple subquery results? - mysql

First of all, good news: I have only 1 table.
Now, I have 3 similar queries:
SELECT id FROM (
SELECT * FROM my_table
ORDER BY columnA
DESC LIMIT 600
) AS aliasA
WHERE (revenue > 10000);
SELECT id FROM (
SELECT * FROM my_table
ORDER BY columnB
DESC LIMIT 400, 999999999999
) AS aliasB
WHERE (revenue > 10000);
SELECT id FROM (
SELECT * FROM my_table
ORDER BY columnC
DESC LIMIT 800;
) AS aliasC
WHERE (revenue > 10000);
Notice the WHERE clause are the same.
Is there a way to combine these 3 queries so that I can search from the intersection of the 3 sub-queries (find rows that match all 3 sub-queries, and also my WHERE clause)?
By the way, if my single queries (before combining) can be simplified, please let me know.
Thanks!

SELECT ID FROM
(SELECT id FROM (
SELECT *
FROM my_table
WHERE (revenue > 10000)
ORDER BY columnA DESC
LIMIT 600
) AS aliasA) AS a
INNER JOIN (
SELECT id FROM (
SELECT *
FROM my_table
WHERE (revenue > 10000)
ORDER BY columnB DESC
LIMIT 400, 999999999999
) AS aliasB
)
AS b ON a.id = b.id
INNER JOIN (
SELECT id FROM (
SELECT *
FROM my_table
WHERE (revenue > 10000)
ORDER BY columnC DESC
LIMIT 800
) AS aliasC
)
AS c ON a.id = c.id AND b.id = c.id
then you can search this query

In your case, the best solution will be JOIN USING (id):
SELECT id
FROM (SELECT id FROM my_table ORDER BY columnA DESC LIMIT 600)
aliasA
JOIN (SELECT id FROM my_table ORDER BY columnB DESC LIMIT 400, 999999999999)
aliasB USING (id)
JOIN (SELECT id FROM my_table ORDER BY columnC DESC LIMIT 800)
aliasC USING (id)
JOIN my_table USING (id)
WHERE (revenue > 10000);
If you didn't have LIMIT in your subquery, you could have used id IN:
SELECT id FROM my_table
WHERE (
id IN (SELECT id FROM my_table ORDER BY columnA DESC LIMIT 600)
AND
id IN (SELECT id FROM my_table ORDER BY columnB DESC LIMIT 400, 999999999999)
AND
id IN (SELECT id FROM my_table ORDER BY columnC DESC LIMIT 800)
AND
revenue > 10000
)
However LIMIT is not supported in subquery, so you can't do it.
If you were using other SQLs, you could have used INTERSECT:
(SELECT id FROM my_table ORDER BY columnA DESC LIMIT 600)
INTERSECT
(SELECT id FROM my_table ORDER BY columnB DESC LIMIT 400, 999999999999)
INTERSECT
(SELECT id FROM my_table ORDER BY columnC DESC LIMIT 800)
INTERSECT
(SELECT id FROM my_table WHERE revenue > 10000)
However MySql doesn't support INTERSECT.

You can use INNER JOIN on key: id then put where clause at the end. All 3 queries is making use of revenue > 10000 and it can be combined in onenwhere clause since all revenue columnn comes from the same table.
select t1.id from (
SELECT id, revenue FROM (
SELECT * FROM my_table
ORDER BY columnA
DESC LIMIT 600
) AS aliasA) as t1
INNER JOIN (
SELECT id FROM (
SELECT * FROM my_table
ORDER BY columnB
DESC LIMIT 400, 999999999999
) AS aliasB) as t2 on t1.id= t2.id
INNER JOIN (
SELECT id, revenue FROM (
SELECT * FROM my_table
ORDER BY columnC
DESC LIMIT 800;
) AS aliasC) as t3 on t1.id =t3.id
WHERE (t1.revenue > 10000);

Related

select the SECOND highest value for each group in mySQL 5

this code gets id and latest timestamps for each ID
select id, max(start_time) as start1, max(end_time) as end1 from table group by id
how do I get the second highest timestamps as start2 and end2?
One option uses correlated subqueries:
select
id,
max(start_time) as start1,
max(end_time) as end1,
(
select t1.start_time
from mytable t1
where t1.id = t.id
order by t1.start_time desc
limit 1, 1
) start2,
(
select t1.end_time
from mytable t1
where t1.id = t.id
order by t1.end_time desc
limit 1, 1
) end2
from mytable t
group by id

How to get previous/next row when ordering by date/time then ID in MySQL?

I have table like
SELECT id, name, date FROM `table` ORDER BY `date` DESC, id DESC
...
10 |a|2020-01-08 20:40:00
9 |b|2020-01-08 20:40:00
8 |c|2020-01-08 20:40:00
500 |d|2020-01-06 22:49:00
7 |e|2020-01-06 22:00:00
...
How to get next and previous of a record. (ex: i have info of a record with id = 8 then how to get a next record is 9 and a previous record is 500)
Method #1 (requires MySQL 8+):
SQL
-- Previous ID
WITH cte_desc AS (SELECT * FROM `table` ORDER BY `date` DESC, id DESC),
cte_r AS (SELECT * FROM `table` WHERE id = #r_id)
SELECT id AS prev_id
FROM cte_desc
WHERE `date` < (SELECT `date` FROM cte_r)
OR `date` = (SELECT `date` FROM cte_r) AND id < (SELECT id FROM cte_r)
LIMIT 1;
-- Next ID
WITH cte_asc AS (SELECT * FROM `table` ORDER BY `date`, id),
cte_r AS (SELECT * FROM `table` WHERE id = #r_id)
SELECT id AS next_id
FROM cte_asc
WHERE `date` > (SELECT `date` FROM cte_r)
OR `date` = (SELECT `date` FROM cte_r) AND id > (SELECT id FROM cte_r)
LIMIT 1;
where #r_id is set to the ID of the row you want to find the previous/next for = 8 in your example.
Explanation
Two Common Table Expressions are defined: cte_desc sorts the table and cte_r gets the current row. The query part then finds the top row for which either the date value is strictly less than that of the chosen row or for which it is equal but the id is strictly less.
Online Demo
Dbfiddle demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=5380e374f24243d578db28b9f89b9c8c
Method #2 (for earlier MySQL versions)
Similar to above - just slightly longer when there is no CTE support:
SQL
-- Previous ID
SELECT id AS prev_id
FROM (SELECT * FROM `table` ORDER BY `date` DESC, id DESC) sub
WHERE `date` < (SELECT `date` FROM `table` WHERE id = #r_id)
OR `date` = (SELECT `date` FROM `table` WHERE id = #r_id)
AND id < (SELECT id FROM `table` WHERE id = #r_id)
LIMIT 1;
-- Next ID
SELECT id AS next_id
FROM (SELECT * FROM `table` ORDER BY `date`, id) sub
WHERE `date` > (SELECT `date` FROM `table` WHERE id = #r_id)
OR `date` = (SELECT `date` FROM `table` WHERE id = #r_id)
AND id > (SELECT id FROM `table` WHERE id = #r_id)
LIMIT 1;
Online Demo
Rextester demo: https://rextester.com/MTW78358
Method #3 (Slower? See first comments):
-- Previous ID
SELECT id AS prev_id
FROM `table`
WHERE CONCAT(`date`, LPAD(id, 8, '0')) =
(SELECT MAX(CONCAT(`date`, LPAD(id, 8, '0')))
FROM `table`
WHERE CONCAT(`date`, LPAD(id, 8, '0')) < (SELECT CONCAT(`date`, LPAD(id, 8, '0'))
FROM `table`
WHERE id = #r_id));
-- Next ID
SELECT id AS next_id
FROM `table`
WHERE CONCAT(`date`, LPAD(id, 8, '0')) =
(SELECT MIN(CONCAT(`date`, LPAD(id, 8, '0')))
FROM `table`
WHERE CONCAT(`date`, LPAD(id, 8, '0')) > (SELECT CONCAT(`date`, LPAD(id, 8, '0'))
FROM `table`
WHERE id = #r_id));
Online Demo
Rextester demo: https://rextester.com/BSQQL24519
Explanation
The ordering is by date/time then by ID so to simplify the searching, these are concatenated into a single string - but there is the usual snag of a string ordering placing e.g. 10 after 1 rather than after 9. To overcome this, the IDs are padded with zeros up to the number of digits of the maximum integer in MySQL (4294967295) - using the LPAD function. Having done this groundwork, the previous row can then be found by looking for the largest one that is less than the one for the current id value using MAX and a subselect.
You will need to find first of all, current record's position available in the current ordered list, then you will be able to find the previous record as well as next record.
PREVIOUS RECORD
SELECT row_number, id,name,`date`
FROM (
SELECT #row := #row + 1 AS row_number, id,name,`date`
FROM `table` AS t
JOIN (SELECT #row := 0) r
ORDER BY `date` DESC, id DESC
) main
WHERE row_number = (
SELECT current_row - 1
FROM (
SELECT #curRow := #curRow + 1 AS current_row, t.id,t.name,t.`date`
FROM `table` AS t
JOIN (SELECT #curRow := 0) r
ORDER BY `date` DESC, id DESC
) t1
WHERE id = 8
);
NEXT RECORD
SELECT row_number, id,name,`date`
FROM (
SELECT #row := #row + 1 AS row_number, id,name,`date`
FROM `table` AS t
JOIN (SELECT #row := 0) r
ORDER BY `date` DESC, id DESC
) main
WHERE row_number = (
SELECT current_row + 1
FROM (
SELECT #curRow := #curRow + 1 AS current_row, t.id,t.name,t.`date`
FROM `table` AS t
JOIN (SELECT #curRow := 0) r
ORDER BY `date` DESC, id DESC
) t1
WHERE id = 8
);
Try this query:
SELECT MAX(a.id) AS id
FROM mytable a
JOIN (SELECT id,NAME,DATE FROM mytable WHERE id=8) b
ON a.id <> b.id AND a.date < b.date
UNION ALL
SELECT MIN(a.id) AS id
FROM mytable a
JOIN (SELECT id,NAME,DATE FROM mytable WHERE id=8) b
ON a.id > b.id AND a.date >= b.date;
Fiddle here : https://www.db-fiddle.com/f/gTv6Hyeq9opHW83r6Cxfck/4
Or you can use a variable to single define the value:
SET #val = 8;
SELECT MAX(a.id) AS id
FROM mytable a
JOIN (SELECT id,NAME,DATE FROM mytable WHERE id=#val) b
ON a.id <> b.id AND a.date < b.date
UNION ALL
SELECT MIN(a.id) AS id
FROM mytable a
JOIN (SELECT id,NAME,DATE FROM mytable WHERE id=#val) b
ON a.id > b.id AND a.date >= b.date;
if you know date value of record with id=8 you can query next:
SELECT * FROM table
WHERE id <> 8 AND date >= '2020-01-08 20:40:00'
ORDER BY `date` ASC, id ASC
LIMIT 1
and for previous one(inversed):
SELECT * FROM table
WHERE id <> 8 AND date <= '2020-01-08 20:40:00'
ORDER BY `date` DESC, id DESC
LIMIT 1
if you wish to get both with single query, you can use UNION: https://dev.mysql.com/doc/refman/8.0/en/union.html
If you have at least MySQL 8.0 you could use something like this:
SET #row_number = 0;
SET #target = 8;
WITH cte AS (
SELECT (#row_number:=#row_number + 1) AS num,
t.*
FROM (SELECT *
FROM table_name
ORDER BY date DESC) t
)
SELECT *
FROM cte
WHERE num BETWEEN (SELECT num-1 FROM cte WHERE id = #target)
AND (SELECT num+1 FROM cte WHERE id = #target);
The CTE gives you a row number ordered by the date column. The second part of the query pulls everything from teh CTE that has a row number within 1 of the target row you specified (in this case row id 8).
MySQL 8.0+ is required for CTEs. IF you do not have at least MySQL 8.0 you would have to use temp tables for this method.
Previous:
SELECT id, name, date FROM `table` ORDER BY `date` DESC, id DESC LIMIT 1
Next:
SELECT id, name, date FROM `table` ORDER BY `date` ASC, id ASC LIMIT 1
If you need both in a single query:
( SELECT id, name, date FROM `table` ORDER BY `date` DESC, id DESC LIMIT 1 )
UNION ALL
( SELECT id, name, date FROM `table` ORDER BY `date` ASC, id ASC LIMIT 1 )
If you are using 8.0, you can try this:
SELECT LEAD(id) OVER (ORDER BY id DESC) AS 'Next',id,LAG(id) OVER (ORDER BY id DESC) AS 'Previous'
FROM table
WHERE id = 8
ORDER BY `date` DESC, id DESC

Group by date and take the last one

This is my table :
What I'm trying to do, is to take the last disponibility of a user, by caserne. Example, I should have this result :
id id_user id_caserne id_dispo created_at
31 21 12 1 2019-10-24 01:21:46
33 21 13 1 2019-10-23 20:17:21
I've tried this sql, but it does not seems to work all the times :
SELECT * FROM
( SELECT id, id_dispo, id_user, id_caserne, MAX(created_at)
FROM disponibilites GROUP BY id_user, id_caserne, id_dispo
ORDER BY created_at desc ) AS sub
GROUP BY id_user, id_caserne
What am I doing wrong ?
I would simply use filtering in the where clause using a correlated subquery:
select d.*
from disponibilites d
where d.created_at = (select max(d2.created_at)
from disponibilites d2
where d2.id_user = d.id_user
);
EDIT:
Based on your comments:
select d.*
from disponibilites d
where d.created_at = (select max(d2.created_at)
from disponibilites d2
where d2.id_user = d.id_user and
d2.id_caserne = d.id_caserne
where date(d2.created_at) = date(d.created_at)
);
You can use a correlated subquery, as demonstrated by Gordon Linoff, or a window function if your RDBMS supports it:
select * from (
select
t.*,
rank() over(partition by id_caserne, id_user order by created_at desc) rn
from disponibilites t
) x
where rn = 1
Another option is to use a correlated subquery without aggregation, only with a sort and limit:
select *
from mytable t
where created_at = (
select created_at
from mytable t1
where t1.id_user = t.id_user and t1.id_caserne = t.id_caserne
order by created_at desc
limit 1
)
With an index on (id_user, id_caserne, created_at), this should be a very efficient option.
you can join your max(created_date) to your original table
select t1.* from disponibilites t1
inner join
(select max(created_at), id_caserne, id
from disponibilites
group by id_caserne, id) t2
on t2.id = t1.id

select data from previous row as current row data

I have the following select in my mysql database:
select t.col1, t.THE_DATE, (select ?? as PREVIOUS_DATE)
from DUMMY_TABLE t
order by date
What I am trying to achieve is have the 'PREVIOUS_DATE' contain the value of the previous row's 'THE_DATE' column if there is one.
So if DUMMY_TABLE has the data :
col1 THE_DATE
x 10-01-2010
x 10-01-2012
x 10-01-2009
my select should return
col1 THE_DATE PREVIOUS_DATE
x 10-01-2009
x 10-01-2010 10-01-2009
x 10-01-2012 10-01-2010
You need order by clause in subquery with limit clause :
select t.col1, t.the_date,
( select t1.the_date
from dummy_table t1
where t1.col = t.col and
t1.the_date < t.the_date
order by t1.the_date desc
limit 1
) as PREVIOUS_DATE
from dummy_table t
order by the_date;

Simplify SQL query of multiple SELECT statements, each with UNION and LIMIT

Need assistance with simplifying this SQL query to possibly a single SELECT:
(SELECT * FROM `deals`
WHERE category_id = 1
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 2
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 4
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 5
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 6
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 8
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 9
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 10
ORDER BY id desc
LIMIT 10)
UNION
(SELECT * FROM `deals`
WHERE category_id = 17
ORDER BY id desc
LIMIT 10)
I've been told to try using GROUP BY and HAVING. However, any query I tried didn't work in the slightest...
Any help will be greatly appreciated!
EDIT - apologies, forgot to mention database engine is MySQL
You can condense this down with a window function to limit each group bucket to 10.
SELECT
*
FROM
(
SELECT
*,
ROW_NUMBER() OVER PARTITION BY(category_id ORDER BY id DESC) AS GroupOrder
FROM `deals`
WHERE category_id BETWEEN 1 AND 10
)AS X
WHERE
GroupOrder<=10
I'm not sure, I need to know if you need that limit of 10, is this like take top 10 of all those things?
if not then
SELECT * FROM `deals`
WHERE category_id between 0 and 10 or category_id=17
ORDER BY category_id asc, id desc
For older versions of MySQL without the windowing functions, here is the code.
SELECT T1.ID, T1.Category_ID, T1.Name
FROM (
SELECT #row_num := IF(#prev_value=concat_ws('',t.Category_ID),#row_num+1,1) AS RowNumber
,t.*
,#prev_value := concat_ws('',t.Category_ID)
FROM data t,
(SELECT #row_num := 1) x,
(SELECT #prev_value := '') y
ORDER BY t.Category_ID
) T1
WHERE T1.RowNumber < 10
AND T1.Category_ID IN (1,2,3,4,5,6,7,8,9,10)
You will need to add the necessary field names to the other select.
This uses the technique described here