(MySQL 5.7) How to combine my two queries? - mysql

How can I combine my two queries in MySQL 5.7:
(windows functions doesn't work)
1: This query is finding all dialogues where more than 10 messages and spaced at least an hour apart.
SELECT `dialog_id`
FROM `messages`
GROUP BY `dialog_id`
HAVING COUNT(*) >= 10
AND MIN(`timestamp`) + INTERVAL 1 HOUR < MAX(`timestamp`)
2: The second query selects two last rows for each dialogue.
SELECT * FROM messages tbl WHERE
(SELECT COUNT(*) FROM messages tbl1 WHERE tbl1.dialog_id = tbl.dialog_id AND tbl1.id >= tbl.id AND
(user_from = :user_from OR user_to = :user_to)) <= 2 ORDER BY dialog_id DESC
So, what I want is to select last two rows for each dialogue that lasted more than an hour and where more than 10 messages were sent.

Though I didn't got you properly is this what you are looking for:
SELECT * FROM messages tbl WHERE
(SELECT COUNT(*) FROM messages tbl1 WHERE tbl1.dialog_id = tbl.dialog_id AND tbl1.id >= tbl.id AND
(user_from = :user_from OR user_to = :user_to)) <= 2
and dialog_id in (SELECT `dialog_id`
FROM `messages`
GROUP BY `dialog_id`
HAVING COUNT(*) >= 10
AND MIN(`timestamp`) + INTERVAL 1 HOUR < MAX(`timestamp`))
ORDER BY dialog_id DESC

One way you can accomplish this with a subquery:
SELECT *
FROM messages tbl
WHERE (
SELECT COUNT(*)
messages tbl1
tbl1.dialog_id = tbl.dialog_id
tbl1.id >= tbl.id
(user_from = :user_from OR user_to = :user_to)) <= 2
and dialog_id in (
SELECT `dialog_id`
FROM `messages`
GROUP BY `dialog_id`
HAVING COUNT(*) >= 10
AND MIN(`timestamp`) + INTERVAL 1 HOUR < MAX(`timestamp`))
ORDER BY dialog_id DESC
You might have to adjust it slightly, since I don't have the full structure of your tables, but the principle would go as follows: get all the dialogue_ids matching your 10 and over an hour criteria, then use it to limit the messages returned from your "Get the most recent two" logic.

Related

Getting all previous records of table by date MySQL

My table currently has 21000 records, it's daily updated and almost 300 entries are inserted. Now, what I want is to have a query which will fetch the counts of elements that my table had for the previous 10 days, so it returns:
26000
21300
21000
etc
Right now, I wrote this:
"SELECT COUNT(*) from tbl_task where `task_start_time` < '2020-12-01'"
And it returns 21000 but only for 1 day. I want by query to return records according to 10 days.
However, this does it for only 1 day.
edit : database flavor is mysql and date column is date not datetime
The most efficient method may be aggregation and cumulative sums:
select date(task_start_time) as dte, count(*) as cnt_on_day,
sum(count(*)) over (order by date(task_start_time)) as running_cnt
from tbl_task
group by dte
order by dte desc
limit 10;
This returns the last 10 days in the data. You can easily adjust to more days if you like -- in fact all of them -- without much trouble.
I don't know if I'm wrong, but could you not simple add a GROUP BY - statement? Like:
"SELECT COUNT(*) from tbl_task where `task_start_time` < '2020-12-01' GROUP
BY task_start_time"
EDIT:
This should only work if task_start_time is a date, not if it is a datetime
EDIT2:
If it is a datetime you could use the date function:
SELECT COUNT(*) from tbl_task where `task_start_time` < '2020-12-01' GROUP
BY DATE(task_start_time)
You can use UNION ALL and date arithmetic.
SELECT count(*)
FROM tbl_task
WHERE task_start_time < current_date
UNION ALL
SELECT count(*)
FROM tbl_task
WHERE task_start_time < date_sub(current_date, INTERVAL 1 DAY)
...
UNION ALL
SELECT count(*)
FROM tbl_task
WHERE task_start_time < date_sub(current_date, INTERVAL 9 DAY);
Edit:
You might also join a derived table that uses FROM-less SELECTs and UNION ALL to get the days to look back and then aggregate. This might be a little easier to construct dynamically. (But it may be slower I suspect.)
SELECT count(*)
FROM (SELECT 0 x
UNION ALL
SELECT 1
...
UNION ALL
SELECT 9)
INNER JOIN tbl_task t
ON t.task_start_time < date_sub(current_date, INTERVAL x.x DAY)
GROUP BY x.x;
In MySQL version 8+ you can even use a recursive CTE to construct the table with the days.
WITH RECURSIVE x
AS
(
SELECT 0 x
UNION ALL
SELECT x + 1
FROM x
WHERE x + 1 < 10
)
SELECT count(*)
FROM x
INNER JOIN tbl_task t
ON t.task_start_time < date_sub(current_date, INTERVAL x.x DAY)
GROUP BY x.x;

Date Field last 24H and ORDER BY Other Table

I have a small problem, want to find records with a datefield in the last 24 hours, which also works great:
SELECT * FROM `release` WHERE (date >= now() - INTERVAL 1 DAY)
Now I want to sort the records after a column from another table, also works:
SELECT * FROM `release` AS r JOIN hits as h ON h.id = r.id ORDER BY h.hits DESC LIMIT 0,8
Now to my problem, I get it not to both to cobble, here my attempt (am still very new with Mysql):
SELECT * FROM `release` WHERE (date >= now() - INTERVAL 1 DAY) AS r JOIN hits as h ON h.id = r.id ORDER BY h.hits DESC LIMIT 0,8
But that does not work, why would be nice if someone could explain it to me.
Thank you in advance already times.
Your query have join in wrong place .. for your needs you could use a From with the select result
SELECT *
FROM
( select * from `release`
WHERE (date >= now() - INTERVAL 1 DAY
DESC LIMIT 0,8 ) r
JOIN hits as h ON h.id = r.id
ORDER BY h.hits
You can simply join and add the where clause after that.
SELECT * FROMreleaser JOIN hits as h ON h.id = r.id WHERE (r.date >= now() - INTERVAL 1 DAY) ORDER BY h.hits DESC LIMIT 0,8

MySQL multiple SELECT statements in one query

I'm storing some information in a MySQL table including a date without time.
The date format is a string looking like this: "25.08.2016" (Day.Month.Year).
I want to select the top 50 records from a table descending by a column, but I only want to display the rows with a specific column entry (date).
It is a ranking system and I want to update inactive people.
I would need to combine these 3 queries:
SELECT * FROM `rank` ORDER BY `rank`.`Score` DESC LIMIT 0 , 50;
SELECT * FROM `rank` WHERE NOT (`TimeStamp1` = DATE_FORMAT(NOW(), '%d.%m.%Y') OR `TimeStamp1` = DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 DAY), '%d.%m.%Y'));
UPDATE `rank` SET `inactive` = '1';
Selecting the top 50 people.
Selecting the inactive people of the first query.
Updating the people to inactive.
The most Score is rank 1 and that's why I need DESC, I only want to mark the top 50 people as inactive nothing below, below 50 those people are irrelevant that's why I can't use a statement like this:
SELECT * FROM `rank` WHERE NOT (`TimeStamp1` = DATE_FORMAT(NOW(), '%d.%m.%Y') OR `TimeStamp1` = DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 DAY), '%d.%m.%Y')) ORDER BY `rank`.`Score` DESC LIMIT 0 , 50
Yes, it would select 50 rows but not the top 50.
BTW I'm doing it in php.
And I could solve the problem by fetching:
SELECT * FROM `rank` ORDER BY `rank`.`Score` DESC LIMIT 0 , 50;
Then storing the Accound IDs to an array, then query:
SELECT * FROM `rank` WHERE NOT (`TimeStamp1` = DATE_FORMAT(NOW(), '%d.%m.%Y') OR `TimeStamp1` = DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 DAY), '%d.%m.%Y')) ORDER BY `rank`.`Score` DESC LIMIT 0 , 50;
And compare the Accound IDs to the other result, when no match is found I just break the loop.
Can't I just do it with pure MySQL? Can't I query a thing and then filter the results?
Please help me, any more questions?
Here's my Answer.
UPDATE rank AS target
INNER JOIN (
SELECT w.id
FROM rank AS w
INNER JOIN rank AS e ON e.id = w.id
WHERE (w.`TimeStamp1` = DATE_FORMAT(NOW(), '%Y-%m-%d') OR w.`TimeStamp1` = DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 DAY),'%Y-%m-%d'))
ORDER BY w.`Score` DESC
LIMIT 50
) AS source ON source.id = target.id
SET inactive = 1;
One way of executing queries sequentially is by using transaction.
But that won't combine your queries into one.
BEGIN TRANSACTION;
SELECT *
FROM `rank`
ORDER BY `rank`.`Score` DESC LIMIT 0 , 50;
SELECT *
FROM `rank`
WHERE NOT (`TimeStamp1` = DATE_FORMAT(NOW(), '%d.%m.%Y') OR `TimeStamp1` = DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 DAY), '%d.%m.%Y'));
UPDATE `rank` SET `inactive` = '1';
COMMIT;
Maybe this might be a combination of the three queries:
update rank r
set r.inactive = 1
from
(select a.account_id from rank a
join (select account_id from rank order by rank.score desc limit 0, 50) b on (a.account_id = b.account_id )
where (a.`TimeStamp1` = DATE_FORMAT(NOW(), '%d.%m.%Y') OR a.`TimeStamp1` = DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 DAY), '%d.%m.%Y'))) l
where r.account_id = l.account_id
Let me know if anything fails :)
Edit: swapped rank_id with account_id

Select last date and all dates in the future

I'd like to select only the nearest date in the past and all the dates in the future.
I reach a result with the following query, but the results are side by side instead row per row.
How should I modifiy my query?
SELECT t1.*, t2.*
FROM
(SELECT *
FROM table1
WHERE from_p <= NOW()
AND prod = 3000
ORDER BY from_p DESC
LIMIT 1) AS t1
JOIN
(SELECT *
FROM table1
WHERE from_p >= NOW()
AND prod = 3000
ORDER BY from_p DESC
) AS t2
You need a use a subquery to first find "latest past date" and then write the main query based on that:
SELECT * FROM table1
WHERE from_p >= (
SELECT from_p FROM table1
WHERE from_p <= NOW() AND prod=3000
ORDER BY from_p DESC LIMIT 1
)
AND prod=3000
ORDER BY from_p;
Try:
(SELECT *
FROM table1
WHERE from_p <= NOW()
AND prod = 3000
ORDER BY from_p DESC
LIMIT 1)
UNION
(SELECT *
FROM table1
WHERE from_p >= NOW()
AND prod = 3000
ORDER BY from_p DESC)

MySQL query with join or subquery

I have such a schema and queries:
http://sqlfiddle.com/#!2/7b032/3
Seperately I have these queries:
SELECT COUNT(*) AS 'times', userid, name
FROM main
WHERE comedate <= DATE_SUB(CURDATE(),
INTERVAL 5 DAY)
GROUP BY userid ORDER BY times DESC LIMIT 0,2;
SELECT * FROM details WHERE 1;
By comparing userid columns of both table I need to join them.
I need an output having these columns:
"times, userid, name, age, location"
Also order, group and limits should be considered.
I would be happy if you can write one query with JOIN and one query with subquery.
I have a 60k table and I will compare the performances.
How about this:
select x.times,
x.userid,
x.name,
d.age,
d.location
from
(
SELECT COUNT(*) AS 'times', userid, name
FROM main
WHERE comedate <= DATE_SUB(CURDATE(),
INTERVAL 5 DAY)
GROUP BY userid
) x
left join details d
on x.userid = d.userid
see SQL Fiddle with Demo
edit:
select x.times,
x.userid,
x.name,
d.age,
d.location
from
(
SELECT COUNT(*) AS 'times', userid, name
FROM main
WHERE comedate <= DATE_SUB(CURDATE(),
INTERVAL 5 DAY)
GROUP BY userid
ORDER BY times DESC
LIMIT 0,2
) x
left join details d
on x.userid = d.userid
see SQL Fiddle with demo