previous item in a list ordered on two columns - mysql

Let's say we have a table with columns DAY and NUMERO.
There can be numerous rows with the same value of DAY but NUMERO is always unique.
What could be the most efficient way to get the item immediately preceding an existing couple (DAY, NUMERO) in a list ordered by DAY, NUMERO ?
I precise that I need this for mysql and I don't want to add a specific index (that's the reason why I don't simply use a linear function of DAY and NUMERO).
Here's an ordered test case :
DAY | NUMERO
1 | 11
1 | 12
1 | 15
4 | 7
4 | 9
4 | 14
5 | 8
6 | 10
6 | 19
My request must do this :
(1,11) => nothing
(1,15) => (1,12)
(4,7) => (1,15)
(4,9) => (4,7)
(4,14) => (4,9)
EDIT :
my current best solution is to have two successive queries :
select * from item where day=? and numero<? order by day desc, numero desc limit 1;
select * from item where day<? order by day desc, numero desc limit 1;
If the first query gives a result, I don't have to run the second one.
A similar solution would be to use a union but mysql doesn't seem to authorize unions with more than one column.
Both solutions look too heavy for a problem seeming so simple...

UPDATE
Here is a solution that actually gives correct results!
SELECT T1.day,
T1.numero,
COALESCE(MAX(T2.[day]), MAX(T3.[day])) AS prev_day,
COALESCE(MAX(T2.numero), MAX(T3.numero)) AS prev_numero
FROM #table AS T1
LEFT JOIN #table AS T2 ON
(
T2.[day] = T1.[day] AND T2.numero < T1.numero
)
LEFT JOIN #table AS T3 ON
(
T3.[day] < T1.[day]
AND NOT EXISTS(
SELECT *
FROM #table AS T4
WHERE T4.[day] > T3.[day] AND T4.[day] < T1.[day]
)
)
-- Add where clause like so to get specific values
-- WHERE T1.day = 4 AND T1.numero = 7
GROUP BY T1.day, T1.numero
ORDER BY T1.day, T1.numero
Results:
day numero prev_day prev_numero
----------- ----------- ----------- -----------
1 11 NULL NULL
1 12 1 11
1 15 1 12
4 7 1 15
4 9 4 7
4 11 4 9
5 8 4 11
6 10 5 8
6 19 6 10

If you are only retrieving the previous pair based on a single pair as opposed to trying to do the entire table, as others have alluded to, you could try the following simple query -
:day = 4
:numero = 9
SELECT day, numero
FROM table
WHERE (day = :day AND numero < :numero)
OR (day < :day)
ORDER BY day DESC, numero DESC
LIMIT 1

This is an vanilla query, avoiding CTE's, aggregates or window functions.
-- (a Before b) := (a.day < b.day
-- OR (a.day = b.day AND a.numero < b.numero))
SELECT d1.day AS DAY
, d1.numero AS numero
, d0.day AS day0
, d0.numero AS numero0
FROM tmp.lutser d1
LEFT JOIN tmp.lutser d0
ON (d0.day < d1.day OR (d0.day = d1.day AND d0.numero < d1.numero ))
WHERE NOT EXISTS (SELECT *
FROM tmp.lutser d
WHERE (dx.day < d1.day OR (dx.day = d1.day AND dx.numero < d1.numero ))
AND (dx.day > d0.day OR (dx.day = d0.day AND dx.numero > d0.numero ))
)
ORDER BY day,numero
;
For reference, this is the query using window functions:
SELECT day
, numero
, lag(day) OVER (w1)
, lag(numero) OVER (w1)
FROM tmp.lutser
WINDOW w1 AS (ORDER BY day, numero)
;
Result:
day | numero | day0 | numero0
-----+--------+------+---------
1 | 11 | |
1 | 12 | 1 | 11
1 | 15 | 1 | 12
4 | 7 | 1 | 15
4 | 9 | 4 | 7
4 | 11 | 4 | 9
5 | 8 | 4 | 11
6 | 10 | 5 | 8
6 | 19 | 6 | 10
(9 rows)

Related

mySQL how to select some data in the same field from limit date

I got the data from my table with the query
SELECT dt, place
FROM horseri
WHERE horseid = 'C299'
AND dt < '20200715'
ORDER BY dt DESC
as below, where dt is the date and the place is the winning place
dt | place
----------------------
2020-07-12 | 8
2020-06-07 | 2
2020-05-17 | 3
2020-04-12 | 9
2020-03-29 | 12
2020-03-01 | 3
2020-02-16 | 4
2020-01-27 | 5
2019-12-18 | 3
2019-11-23 | 10
2019-10-30 | 2
2019-10-01 | 9
2019-09-08 | 2
2019-07-14 | 7
2019-07-01 | 13
2019-06-16 | 7
2019-05-18 | 8
2019-03-31 | 13
2019-03-17 | 12
How can I get the first 3 winning places from the data only by the last 10 date ?
My expected output will be
dt | place
----------------------
2020-06-07 | 2
2020-05-17 | 3
2020-03-01 | 3
2019-12-18 | 3
Use a subquery to get the most recent 10 dates. Then select the top 3 places from that.
SELECT dt, place
FROM (
SELECT dt, place
FROM horseri
where horseid = 'C299'
ORDER BY dt DESC
LIMIT 10
) as x
WHERE place <= 3
The more modern way of writing Barmar's answer (assuming it be what the OP wants here), would be to use ROW_NUMBER:
SELECT dt, place
FROM
(
SELECT *, ROW_NUMBER() OVER (ORDER BY dt DESC) rn
FROM horseri
WHERE horseid = 'C299'
) t
WHERE rn <= 10 AND place <= 3;
To isolate individual places, just change the outer WHERE clause. For example, for second place finishers in the most recent 10 dates, use:
WHERE rn <= 10 AND place = 2

List dates from specific mysql table and return 0 if day does not exists

I'm trying to generate a result from a query that list the last 7 days from today (2020/07/15) and the views matching a specific code.
If in that day the code has no views, I want the day to return 0.
Table Format
DAY | CODE | VIEWS
2020-07-10 | 123 | 5
2020-07-11 | 123 | 2
2020-07-12 | 123 | 3
2020-07-15 | 123 | 8
2020-07-15 | 124 | 2
2020-07-15 | 125 | 2
Expected result from code 123
DAY | VIEWS
2020-07-09 | 0
2020-07-10 | 5
2020-07-11 | 2
2020-07-12 | 3
2020-07-13 | 0
2020-07-14 | 0
2020-07-15 | 8
I already found a way to generate the calendar dates from here and adjust to my needs, but I don't know how to join the result with my table.
select * from
(select
adddate(NOW() - INTERVAL 7 DAY, t0) day
from
(select 1 t0
union select 1
union select 2
union select 3
union select 4
union select 5
union select 6
union select 7) t0) v
Any help would by apreceated.
One option uses a recursive query - available in MySQL 8.0:
with recursive cte as (
select current_date - interval 6 day dt
union all
select dt + interval 1 day from cte where dt < current_date
)
select c.dt, coalesce(sum(t.views), 0) views
from cte
left join mytable t on t.day = c.dt
group by c.dt
order by c.dt
You can also manually build a derived table, as you originaly intended to (this would work on all versions of MySQL):
select current_date - interval d.n day dt, coalesce(sum(t.views), 0) views
from (
select 0 n
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
) d
left join mytable t on t.day = current_date - interval d.n day
group by d.n
order by d.n desc

Enable to find all data in date range 7 days ago in mysql [duplicate]

This question already has answers here:
MySQL how to fill missing dates in range?
(6 answers)
Closed 6 years ago.
I want the count of my table data having in date range 7 days before from now. So I have tried this query :
SELECT DATE(leads_update_on), IFNULL(COUNT(*),0) leads
FROM tbl_leads
WHERE project_id=4
AND DATE(leads_update_on) >= DATE_SUB('2016-05-11', INTERVAL 6 DAY)
GROUP BY DATE(leads_update_on)
But it returns following result :
`DATE(leads_update_on)|leads
----------------------|-----
2016-05-06 | 7
2016-05-07 | 4`
Since other dates does not have any data but I want the result like below if there is no data in specific date :
`DATE(leads_update_on)|leads
----------------------|-----
2016-05-05 | 0
2016-05-06 | 7
2016-05-07 | 4
2016-05-08 | 0
2016-05-09 | 0
2016-05-10 | 0
2016-05-11 | 0`
What I have to change in my sql query so that I can find the above result. Any help will be appreciated. Thanks in advance.
Sample Input as requested :
`DATE |id
----------------------|-----
2016-05-06 | 1
2016-05-07 | 2
Here only two data is present so for others dates it should return 0 value. It should output like this :
`DATE(date) |leads
----------------------|-----
2016-05-05 | 0
2016-05-06 | 1
2016-05-07 | 1
2016-05-08 | 0
2016-05-09 | 0
2016-05-10 | 0
2016-05-11 | 0`
But using this query :-
SELECT DATE(`date`), IFNULL(COUNT(*),0) leads FROM test where DATE(`date`) >= DATE_SUB('2016-05-11', INTERVAL 6 DAY) GROUP BY DATE(`date`)
It returns below result which I don't want:
`DATE(date) |leads
----------------------|-----
2016-05-06 | 1
2016-05-07 | 1`
from what I understand you need to change your if nullcondition
Updated
select distinct(res.leadDate), res.leads from
(select mon.aDate as leadDate , ifnull(sa.leads, 0) as leads
from (
select '2016-05-11' - interval (a.a ) day as aDate from
(select 0 as a union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6 union all
select 7 union all select 8 union all select 9) a
) mon
left join tbl_leads sa on mon.aDate = sa.leads_date ) res, tbl_leads ss
where res.leadDate between ss.leads_date and '2016-05-11'
order by res.leadDate asc;

how can I group by elements from one table and group by it by 2 columns?

I have a table with a structure like this:
table_id | user_id | text | number_id | start_time
1 1 gdsgds 8 2015-10-11 07:14:44
2 2 vcxvc 8 2015-10-11 07:20:44
3 4 ewgs 8 2015-10-12 09:19:22
4 7 vvvcc 8 2015-10-13 18:12:23
etc.
I need to write a query that will return me the number of texts displayed each day on each number. So far I have the following query:
SELECT *
FROM
(
SELECT DATEDIFF(now(), start_time) AS days_ago,
COUNT(table_id) AS num_texts
FROM TABLE
GROUP BY DATE(start_time)
)
WHERE
(
days_ago <= 7
AND days_ago > 0
)
and this query returns me a table:
days_ago | num_texts
0 | 2
1 | 3
2 | 4
3 | 1
and that works almost fine, but I need to divide it by number_id too... How can I do it?
Add the column number_id to the select and group by in the subquery.
SELECT *
FROM
(
SELECT DATEDIFF(now(), start_time) AS days_ago,
number_id,
COUNT(table_id) AS num_texts
FROM TABLE
GROUP BY DATE(start_time), number_id
)
WHERE
days_ago <= 7
AND days_ago > 0

Showing today's current rank and yesterday's

I have a table with IDs, rank, chart_date, and pageviews. It's based on a cron job that is run nightly and compiles the number of pageviews for that ID.
For instance:
ID | RANK | PAGEVIEWS | CHART_DATE
5 1 100 2012-10-14
9 2 75 2012-10-14
13 3 25 2012-10-14
9 1 123 2012-10-13
5 2 74 2012-10-13
19 3 13 2012-10-13
So I'm grabbing today's chart based on 2012-10-14 and ranking the data by 1-3. But I also want to show the rank where the ID was on the previous date.
For instance, on 2012-10-14 ID 5 was ranked 1 but on 2012-10-13 it was ranked 2.
Can I do this with one query? Or do I have to loop thru the results based on today and do a query for each ID?
Can I do this with one query?
You can, but you need a JOIN between the table with today's date and the table with yesterday's date:
SELECT today.*, yesterday.rank
FROM yourtable AS today
JOIN yourtable AS yesterday
ON (today.id = yesterday.id
AND today.chart_date = date(now())
AND yesterday.chart_date = date(date_sub(now(), interval 1 day))
)
ORDER BY today.rank DESC;
You can even show the difference:
SELECT today.*, yesterday.rank AS yest, yesterday.rank-today.rank AS incr
FROM yourtable AS today
LEFT JOIN yourtable AS yesterday
ON (today.id = yesterday.id
AND today.chart_date = date(now())
AND yesterday.chart_date = date(date_sub(now(), interval 1 day))
)
ORDER BY today.rank DESC;
ID | RANK | PAGEVIEWS | CHART_DATE | YEST | INCR
5 1 100 2012-10-14 2 | 1
9 2 75 2012-10-14 1 | -1
13 3 25 2012-10-14 4 | 1
(LEFT JOIN ensures today's data is there even if yesterday's isn't).
Untested but something like this should work:
select today.id, today.rank, yesterday.rank
from mytable as today
left join mytable as yesterday on today.id = yesterday.id
where today.chart_date = 2012-10-14
order by pageviews desc limit 3