Select with 'negative' offset - mysql

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

Related

SQL retrieve all rows between two dates and a specific number of rows before the date ordered by date

As the title says, I have a table with a date column. I am trying to retrieve all rows between 2 dates as well as X number of rows before the beginning date ordered by date.
Take a select * from table order by date DSC
20200201
20200101
20191201
20191101
20191001
20190901
20190801
20190701
I want between 20200201 and 20191201 and the previous 3 rows(not knowing the date)
result
20200201
20200101
20191201
20191101
20191001
20190901
my current query returns a random set of high dates between the union for some reason:
(SELECT * FROM Table WHERE Date BETWEEN 20200201 AND 20191201 ORDER BY Date ASC)
UNION
(SELECT * FROM Table WHERE Date < 20191201 ORDER BY Date DESC LIMIT 3)
Any idea where I am going wrong?
One method uses lead():
select t.*
from (select t.*,
lead(date, 3) over (order by date) as next_date_3
from t
) t
where (date < '202o-02-01' and (next_date_3 >= '2020-02-01' or next_date_3 is null) ) or
(date between '2020-02-01' and '2019-12-01')
I tried this query and it worked for me:
(SELECT * FROM Table WHERE Date BETWEEN 20191201 AND 20200201 ORDER BY Date ASC)
UNION
(SELECT * FROM Table WHERE Date < 20191201 ORDER BY Date DESC LIMIT 3)
The first select shows me:
20191201
20200101
20200201
The second select shows me:
20191101
20191001
20190901
The issue was as jarlh pointed out, the order of the subquery's were not kept(thats why things were jumbled) and a simple ORDER BY of the whole UNION fixed this. I did not try Gordon's response as this solved my issue so:
(SELECT * FROM Table WHERE Date BETWEEN 20191201 AND 20200201)
UNION
(SELECT * FROM Table WHERE Date < 20191201 ORDER BY Date DESC LIMIT 3)
ORDER BY Date ASC
Thank you

mySQL UNION clause with two ORDER BY clause

here's the problem
I need to fetch rows from my table where date created is less than 24 hours ago and ORDER them by likes and then UNION them with the rest of rows in the same table but I want the remaining rows to be ORDERED by date created.
in other words I need separate ORDER BY clause for each SELECT.
to be more clear, it's something like this:
SELECT *
FROM (SELECT *
FROM 'table'
WHERE 'date_created' > timestampadd(hour, -24, now())
ORDER BY 'likes' DESC
UNION
SELECT *
FROM 'table'
WHERE
ORDER BY date_created DESC ) AS Results
LIMIT 10 OFFSET 0
thanks for this awesome community :)
Try this:
SELECT *, (date_created > timestampadd(hour, -24, now()) AS recent
FROM table
ORDER BY recent DESC, IF(recent, likes, 0) DESC, IF(!recent, date_created, 0) DESC
LIMIT 10 OFFSET 0

Calculating a Moving Average MySQL?

Good Day,
I am using the following code to calculate the 9 Day Moving average.
SELECT SUM(close)
FROM tbl
WHERE date <= '2002-07-05'
AND name_id = 2
ORDER BY date DESC
LIMIT 9
But it does not work because it first calculates all of the returned fields before the limit is called. In other words it will calculate all the closes before or equal to that date, and not just the last 9.
So I need to calculate the SUM from the returned select, rather than calculate it straight.
IE. Select the SUM from the SELECT...
Now how would I go about doing this and is it very costly or is there a better way?
If you want the moving average for each date, then try this:
SELECT date, SUM(close),
(select avg(close) from tbl t2 where t2.name_id = t.name_id and datediff(t2.date, t.date) <= 9
) as mvgAvg
FROM tbl t
WHERE date <= '2002-07-05' and
name_id = 2
GROUP BY date
ORDER BY date DESC
It uses a correlated subquery to calculate the average of 9 values.
Starting from MySQL 8, you should use window functions for this. Using the window RANGE clause, you can create a logical window over an interval, which is very powerful. Something like this:
SELECT
date,
close,
AVG (close) OVER (ORDER BY date DESC RANGE INTERVAL 9 DAY PRECEDING)
FROM tbl
WHERE date <= DATE '2002-07-05'
AND name_id = 2
ORDER BY date DESC
For example:
WITH t (date, `close`) AS (
SELECT DATE '2020-01-01', 50 UNION ALL
SELECT DATE '2020-01-03', 54 UNION ALL
SELECT DATE '2020-01-05', 51 UNION ALL
SELECT DATE '2020-01-12', 49 UNION ALL
SELECT DATE '2020-01-13', 59 UNION ALL
SELECT DATE '2020-01-15', 30 UNION ALL
SELECT DATE '2020-01-17', 35 UNION ALL
SELECT DATE '2020-01-18', 39 UNION ALL
SELECT DATE '2020-01-19', 47 UNION ALL
SELECT DATE '2020-01-26', 50
)
SELECT
date,
`close`,
COUNT(*) OVER w AS c,
SUM(`close`) OVER w AS s,
AVG(`close`) OVER w AS a
FROM t
WINDOW w AS (ORDER BY date DESC RANGE INTERVAL 9 DAY PRECEDING)
ORDER BY date DESC
Leading to:
date |close|c|s |a |
----------|-----|-|---|-------|
2020-01-26| 50|1| 50|50.0000|
2020-01-19| 47|2| 97|48.5000|
2020-01-18| 39|3|136|45.3333|
2020-01-17| 35|4|171|42.7500|
2020-01-15| 30|4|151|37.7500|
2020-01-13| 59|5|210|42.0000|
2020-01-12| 49|6|259|43.1667|
2020-01-05| 51|3|159|53.0000|
2020-01-03| 54|3|154|51.3333|
2020-01-01| 50|3|155|51.6667|
Use something like
SELECT
sum(close) as sum,
avg(close) as average
FROM (
SELECT
(close)
FROM
tbl
WHERE
date <= '2002-07-05'
AND name_id = 2
ORDER BY
date DESC
LIMIT 9 ) temp
The inner query returns all filtered rows in desc order, and then you avg, sum up those rows returned.
The reason why the query given by you doesn't work is due to the fact that the sum is calculated first and the LIMIT clause is applied after the sum has already been calculated, giving you the sum of all the rows present
an other technique is to do a table:
CREATE TABLE `tinyint_asc` (
`value` tinyint(3) unsigned NOT NULL default '0',
PRIMARY KEY (value)
) ;
​
INSERT INTO `tinyint_asc` VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23),(24),(25),(26),(27),(28),(29),(30),(31),(32),(33),(34),(35),(36),(37),(38),(39),(40),(41),(42),(43),(44),(45),(46),(47),(48),(49),(50),(51),(52),(53),(54),(55),(56),(57),(58),(59),(60),(61),(62),(63),(64),(65),(66),(67),(68),(69),(70),(71),(72),(73),(74),(75),(76),(77),(78),(79),(80),(81),(82),(83),(84),(85),(86),(87),(88),(89),(90),(91),(92),(93),(94),(95),(96),(97),(98),(99),(100),(101),(102),(103),(104),(105),(106),(107),(108),(109),(110),(111),(112),(113),(114),(115),(116),(117),(118),(119),(120),(121),(122),(123),(124),(125),(126),(127),(128),(129),(130),(131),(132),(133),(134),(135),(136),(137),(138),(139),(140),(141),(142),(143),(144),(145),(146),(147),(148),(149),(150),(151),(152),(153),(154),(155),(156),(157),(158),(159),(160),(161),(162),(163),(164),(165),(166),(167),(168),(169),(170),(171),(172),(173),(174),(175),(176),(177),(178),(179),(180),(181),(182),(183),(184),(185),(186),(187),(188),(189),(190),(191),(192),(193),(194),(195),(196),(197),(198),(199),(200),(201),(202),(203),(204),(205),(206),(207),(208),(209),(210),(211),(212),(213),(214),(215),(216),(217),(218),(219),(220),(221),(222),(223),(224),(225),(226),(227),(228),(229),(230),(231),(232),(233),(234),(235),(236),(237),(238),(239),(240),(241),(242),(243),(244),(245),(246),(247),(248),(249),(250),(251),(252),(253),(254),(255);
After you can used it like that:
select
date_add(tbl.date, interval tinyint_asc.value day) as mydate,
count(*),
sum(myvalue)
from tbl inner
join tinyint_asc.value <= 30 -- for a 30 day moving average
where date( date_add(o.created_at, interval tinyint_asc.value day ) ) between '2016-01-01' and current_date()
group by mydate
This query is fast:
select date, name_id,
case #i when name_id then #i:=name_id else (#i:=name_id)
and (#n:=0)
and (#a0:=0) and (#a1:=0) and (#a2:=0) and (#a3:=0) and (#a4:=0) and (#a5:=0) and (#a6:=0) and (#a7:=0) and (#a8:=0)
end as a,
case #n when 9 then #n:=9 else #n:=#n+1 end as n,
#a0:=#a1,#a1:=#a2,#a2:=#a3,#a3:=#a4,#a4:=#a5,#a5:=#a6,#a6:=#a7,#a7:=#a8,#a8:=close,
(#a0+#a1+#a2+#a3+#a4+#a5+#a6+#a7+#a8)/#n as av
from tbl,
(select #i:=0, #n:=0,
#a0:=0, #a1:=0, #a2:=0, #a3:=0, #a4:=0, #a5:=0, #a6:=0, #a7:=0, #a8:=0) a
where name_id=2
order by name_id, date
If you need an average over 50 or 100 values, it's tedious to write, but
worth the effort. The speed is close to the ordered select.

select 10 rows forwards and 10 backwards from a date

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)

Most recent rows at least 1 hour apart in MySQL

I am collecting data every five minutes and entering it into a MySQL database. I would like to extract the most recent row that's over 2 hours old, followed by over 1 hour old, followed by the most recent row.
I was thinking something along the lines of the following, but I think this will get me the last row of each hour. Meaning if I run the query at 8:05 I might get back rows from 6:57, 7:57 and 8:02, the last 2 of which are much less than an hour apart.
SELECT *
FROM (
SELECT *
FROM mytable
ORDER BY Date DESC
GROUP BY HOUR(Date) LIMIT 3
) x
ORDER BY Date ASC
Thanks for any help or suggestions you can provide.
SELECT * FROM (
SELECT * FROM mytable
WHERE `Date`<DATE_SUB(NOW(), INTERVAL 2 HOUR)
ORDER BY DATE DESC LIMIT 1
)
UNION
SELECT * FROM (
SELECT * FROM mytable
WHERE `Date`<DATE_SUB(NOW(), INTERVAL 1 HOUR)
ORDER BY DATE DESC LIMIT 1
)
UNION
SELECT * FROM (
SELECT * FROM mytable
ORDER BY DATE DESC LIMIT 1
)
ORDER BY `Date`ASC