select 10 rows forwards and 10 backwards from a date - mysql

I'm trying to select 10 rows from today's date in either direction (forward and backwards in time) and in date order. The best I've got so far is:
SELECT * FROM (
SELECT * FROM foo WHERE dt >= now() ORDER BY dt ASC LIMIT 10
UNION
SELECT * FROM foo WHERE dt < now() ORDER BY dt DESC LIMIT 10
) ORDER BY dt ASC;
Is there a nicer/more efficient way to do this?
Thanks.

Your idea is sound, but this is the correct query for it.
SELECT * FROM (
SELECT * FROM (SELECT * FROM foo WHERE dt >= now() ORDER BY dt ASC LIMIT 10) A
UNION ALL
SELECT * FROM (SELECT * FROM foo WHERE dt < now() ORDER BY dt DESC LIMIT 10) B
) C
ORDER BY dt ASC;
Only one ORDER BY clause is permitted per level of query, so you actually need to further subquery the A and B parts shown. Also, UNION ALL avoids a sort operation, since you know the two sets are distinct.
An index on foo.dt will ensure that this query is as fast as can be.

Instead of you can use simple query
(SELECT * FROM one WHERE dt >= now() ORDER BY dt ASC LIMIT 10)
UNION ALL
(SELECT * FROM one WHERE dt < now() ORDER BY dt DESC LIMIT 10)

Related

Limit total number of results across tables

I want to get the most recent 100 events that happened. The events are scattered across multiple tables. Here is an example:
SELECT * FROM log_items_purchased ORDER BY timestamp DESC LIMIT 100
UNION
SELECT * FROM log_items_fulfilled ORDER BY timestamp DESC LIMIT 100
UNION
SELECT * FROM log_items_shipped ORDER BY timestamp DESC LIMIT 100
This will return up to 300 records. I would then take the result set, order by timestamp, and take the first 100 records. How can I perform this in a single SQL query, where SQL will only return 100 records in the result set.
I realize that it would be possible to do this by removing the LIMIT 100 from each query, and then make an outer query that adds LIMIT 100, but these tables are really big, and that's really inefficient.
Put it in a subquery, then use LIMIT 100 in the main query.
SELECT *
FROM (
SELECT * FROM log_items_purchased ORDER BY timestamp DESC LIMIT 100
UNION
SELECT * FROM log_items_fulfilled ORDER BY timestamp DESC LIMIT 100
UNION
SELECT * FROM log_items_shipped ORDER BY timestamp DESC LIMIT 100
) AS x
ORDER BY timestamp DESC
LIMIT 100
If you want to do this in SQL, use a subquery:
SELECT e.*
FROM ((SELECT * FROM log_items_purchased ORDER BY timestamp DESC LIMIT 100
) UNION ALL
(SELECT * FROM log_items_fulfilled ORDER BY timestamp DESC LIMIT 100
) UNION ALL
(SELECT * FROM log_items_shipped ORDER BY timestamp DESC LIMIT 100
)
) e
ORDER BY timestamp DESC
LIMIT 100;
Note: Do not use UNION. It incurs overhead to remove duplicates.
I think this will work
(SELECT * FROM log_items_purchased ORDER BY timestamp DESC LIMIT 100)
UNION
(SELECT * FROM log_items_fulfilled ORDER BY timestamp DESC LIMIT 100)
UNION
(SELECT * FROM log_items_shipped ORDER BY timestamp DESC LIMIT 100)
ORDER BY timestamp DESC LIMIT 100

Need to optimize SQL Query - Taking lot a of time for execution

We have a below query which takes approximately 6-8 secs to execute.
Total number of records : 522954
(SELECT
*
FROM
tbl_insights_copy
WHERE insightscat = 21
AND submitedon >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))
ORDER BY submitedon DESC
LIMIT 5)
UNION
(SELECT
*
FROM
tbl_insights_copy
WHERE insightscat = 22
AND submitedon >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))
ORDER BY submitedon DESC
LIMIT 5)
UNION
(SELECT
*
FROM
tbl_insights_copy
WHERE insightscat = 23
AND submitedon >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))
ORDER BY submitedon DESC
LIMIT 5)
UNION
(SELECT
*
FROM
tbl_insights_copy
WHERE insightscat = 24
AND submitedon >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))
ORDER BY submitedon DESC
LIMIT 5)
Can someone help to optimize this query as to reduce the execution time.
Thanks in advance.
The only thing you are changing between one select and another, is the filter value of the column insightscat I am not sure that this is what you want but....
You may try the IN instruction for this. Example:
SELECT
*
FROM
tbl_insights_copy
WHERE insightscat in (20,21,22,23,24)
AND submitedon >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))
ORDER BY submitedon DESC
For this query:
SELECT ic.*
FROM tbl_insights_copy ic
WHERE insightscat = 21 AND
submitedon >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))
ORDER BY submitedon DESC
LIMIT 5
You want an index on tbl_insights_copy(insightscat, submittedon).
This should work for all the subqueries. This is probably the best approach with MySQL.
SELECT t1.*
FROM (SELECT t.* ,ROW_NUMBER() OVER (ORDER BY insightscat) AS Row
FROM
(select * from
tbl_insights_copy
WHERE insightscat in (20,21,22,23,24)
AND submitedon >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))
ORDER BY submitedon DESC ) as t ) t1
WHERE Row <= 5
Without SHOW CREATE TABLE, I am going to have to guess that you do not have the optimal
INDEX(insightscat, submitedon)
Since the SELECTs are distinct, use UNION ALL instead of the default UNION DISTINCT. This will avoid an unnecessary (but fast) de-dup pass over the 20 rows.
If you want 20 rows
If so, my suggestions above might be best.
If you want 6 rows
If you want only the latest 5 from any of those insightscats, then there are 3 possibilities.
Plan A
What Developer90 says in his Comment.
Plan B
( SELECT ... ORDER BY ORDER BY submitedon DESC LIMIT 5)
UNION ALL
( SELECT ... ORDER BY ORDER BY submitedon DESC LIMIT 5)
UNION ALL
( SELECT ... ORDER BY ORDER BY submitedon DESC LIMIT 5)
UNION ALL
( SELECT ... ORDER BY ORDER BY submitedon DESC LIMIT 5)
ORDER BY ORDER BY submitedon DESC LIMIT 5;
Each SELECT is very fast with my index. Then, the 20 rows of the UNION get sorted again and LIMIT 5 is applied. Again, very fast.
Using the IN as suggested by Developer90, may or may not effectively use my index. What version are you using?
Plan C
This option is hit or miss. That is, its performance depends heavily on the distribution of the data as to whether it will be very fast or very slow: Developer90 + INDEX(submitedon) (not including insightscat).

Select with 'negative' offset

I need to select 40 rows with date from today and 10 records with older date, ordered by date.
If MySQL supported negative offset, it would look like this:
SELECT * FROM `mytable` WHERE `date` >= '2013-10-29' ORDER BY date LIMIT -10, 40;
Negative offset is not supported. How can I solve the problem? Thanks!!!
Use UNION to combine two queries:
(
SELECT *
FROM mytable
WHERE date < '2013-10-29'
ORDER BY date DESC
LIMIT 10
) UNION ALL (
SELECT *
FROM mytable
WHERE date >= '2013-10-29'
ORDER BY date
LIMIT 40
)
ORDER BY date -- if results need to be sorted

Mysql self join with multiple order by

assume we have table
id, title, date
I need to build 1 query to:
select date = TODAY, order by id
select data < TODAY, order by date desc,
select data > TODAY, order by date asc,
I think you need to use UNION and sub queries:
SELECT * FROM (
SELECT *
FROM YourTable
WHERE Date(dateField) = Date(Now())
ORDER BY ID
) t1
UNION
SELECT * FROM (
SELECT *
FROM YourTable
WHERE dateField < Now()
ORDER BY dateField DESC
) t2
UNION
SELECT * FROM (
SELECT *
FROM YourTable
WHERE Date(dateField) > Now()
ORDER BY dateField
) t3
Here is a simplified SQL Fiddle example.
Good luck.

Count SQL results form first query in second query in single statement

Here's my SQL statement:
(select * from items
where items.created > curdate() - interval 2 week
order by items.created desc limit 0,10000000000)
union all
(select * from items
where items.created < curdate() - interval 2 week
order by items.popularity desc limit 0,15)
I'm trying to figure out a way to limit the entire result of the query to a certain number (say 25). As it is now, this result returns an unlimited number for the first result (which is what I want), then returns 15 for the second result. I want to be able to limit the whole query so that even if the first result returns 8, the second result returns 17, total 25.
I believe to do this, I have to use count() somehow in the first query, then subtract that from the total I want and use that number as the 2nd query's limit. I have no idea how this is done.
Thanks in advance!
Here is the required query -
select *
from
((select * from items
where items.created > curdate() - interval 2 week
order by items.created desc limit 0,10000000000)
union all
(select * from items
where items.created < curdate() - interval 2 week
order by items.popularity desc)) t
limit 0,25
Another select:
select * from
(
(select * from items
where items.created > curdate() - interval 2 week
order by items.created desc limit 0,10000000000)
union all
(select * from items
where items.created < curdate() - interval 2 week
order by items.popularity desc)
) uniond_tables_alias
limit 25
The uniond_tables_alias is an alias for the uniond section, you can choose any name you want.
No need for nested queries, simply do:
(select * from items
where items.created > curdate() - interval 2 week
order by items.created desc) # remove LIMIT here
UNION ALL
(select * from items
where items.created < curdate() - interval 2 week
order by items.popularity desc) # remove LIMIT here
LIMIT 25; # add LIMIT here
This'll return the 25 first results from the first SELECT if there's at least 25. Otherwise it will fill up the remaining results with the second SELECT results until the limit of 25 is reached.
SELECT 1 AS sortkey, * from items ....
UNION ALL
SELECT 2 AS sortkey, * from items ....
ORDER BY sortkey, etc.
LIMIT 25