I'm trying to retrieve all columns data along with the time difference between all consecutive rows from the following table, where (sender_id = 1 OR = 2) and (recipient_id = 2 OR = 1).
CREATE TABLE records (
id INT(11) AUTO_INCREMENT,
send_date DATETIME NOT NULL,
content TEXT NOT NULL,
sender_id INT(11) NOT NULL,
recipient_id INT(11) NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO records (send_date, content, sender_id, recipient_id) VALUES
('2013-08-23 14:50:00', 'record 1/5', 1, 2),
('2013-08-23 14:51:00', 'record 2/5', 2, 1),
('2013-08-23 15:50:00', 'record 3/5', 2, 1),
('2013-08-23 15:50:13', 'record 4/5', 1, 2),
('2013-08-23 16:50:00', 'record 5/5', 1, 2);
Problem is my select query won't output the latest record because of the WHERE clause :
SELECT t1.content, DATE_FORMAT(t1.send_date, '%b, %D, %H:%i') AS 'pprint_date',
TIMESTAMPDIFF(MINUTE, t1.send_date, t2.send_date) AS 'duration'
FROM records t1, records t2
WHERE (t1.id = t2.id - 1) /*<= this subtraction excludes latest record*/
AND ((t1.sender_id = 1 AND t1.recipient_id = 2)
OR (t1.sender_id = 2 AND t1.recipient_id = 1))
ORDER BY t1.id ASC
How can I properly get the time difference between all consecutive records while still printing all of them ?
I would use a correlated subquery:
select r.*,
(select r2.send_date
from records r2
where (r2.sender_id in (1, 2) or r2.recipient_id in (1, 2)) and
r2.send_date > r.send_date
order by r2.send_date asc
limit 1
) as next_send_date
from records r
where r.sender_id in (1, 2) or r.recipient_id in (1, 2);
You can get the duration (instead of the next time) by using TIMESTAMPDIFF(MINUTE, r.send_date, r2.send_date) in the subquery. I think the first version is easier for you to test with to see what is happening.
Related
The query below gives me one record per day for each user. How can I modify it so that it gives me the earliest record per day for each user?
I tried using MIN() on the date field in the GROUP BY part, but that obviously doesn't work. There's a date_trunc function mentioned in this answer which seems to do what I want, but it is not available in MySQL. What's the best way to go about this?
For the sample data below, the query should return records with ids 1, 3, 5, and 7.
SELECT user_id, coords, date
FROM table
WHERE draft = 0
GROUP BY user_id, DAY('date')
CREATE TABLE `table` (
`id` bigint(20) UNSIGNED NOT NULL,
`user_id` int(11) NOT NULL,
`coords` point NOT NULL,
`date` datetime NOT NULL,
`draft` tinyint(4) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `table` (`id`, `user_id`, `coords`, `date`, `draft`) VALUES
(1, 1, xxx, '2020-11-08 18:01:47', 0),
(2, 1, xxx, '2020-11-08 18:05:47', 0),
(3, 1, xxx, '2020-11-09 18:06:47', 0),
(4, 1, xxx, '2020-11-09 18:07:47', 0),
(5, 2, xxx, '2020-11-08 17:01:47', 0),
(6, 2, xxx, '2020-11-08 17:05:47', 0),
(7, 2, xxx, '2020-11-09 14:00:47', 0),
(8, 2, xxx, '2020-11-09 14:05:47', 0),
A typical approach is to filter with a correlated subquery:
select t.*
from mytable t
where t.draft = 0 and t.date = (
select min(t1.date)
from mytable t1
where t1.draft = t.draft and t1.user_id = t.user_id and date(t1.date) = date(t.date)
)
You can optimize the subquery a little by using a half-open interval for filtering:
select t.*
from mytable t
where t.draft = 0 and t.date = (
select min(t1.date)
from mytable t1
where
t1.user_id = t.user_id
and t1.draft = t.draft
and t1.date >= date(t.date)
and t1.date < date(t.date) + interval 1 day
)
The second query should be able to take advantage of an index on (draft, user_id, date).
Alternatively, if you are running MuSQL 8.0, you can also use window functions:
select *
from (
select t.*, row_number() over(partition by user_id, date(date) order by date) rn
from mytable t
where draft = 0
) t
where rn = 1
Use:
SELECT user_id, coords, date
FROM `table`
WHERE draft = 0
GROUP BY DAY('date'), user_id order by user_id, date
I have this table:
CREATE TABLE tickets (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
topic VARCHAR(64) NOT NULL,
priority TINYINT NOT NULL default 1,
created DATETIME);
INSERT INTO tickets (id, topic, priority, created) VALUES
(1, 'kin23oi5n2', 3, '2018-05-27 16:00:54'),
(2, 'ewbrv32b3', 2, '2018-05-27 16:00:54'),
(3, 'aern23523', 1, '2018-05-27 16:01:15'),
(4, '4bt43t3', 5, '2018-05-27 16:01:15'),
(5, '32n523n5235n235t3n4', 4, '2018-05-27 16:01:39'),
(6, 'e2nr2n23n5235n23n523', 3, '2018-05-27 16:01:39');
I'm running a query:
SELECT * FROM `tickets` order by `priority` ASC, `created` desc;
How do I get the row number with id 5 in this result using sql?
update:
SELECT count(*) as pos FROMticketsWHERE ...;
As a result, we obtain pos
I try to make that my result was similar to this but contained number of a line of the necessary row(id 5 for example).
in other words I want to get the number of the queue entry
If someone is interested, here is the answer
SELECT num FROM
(SET #row_number = 0;
SELECT
(#row_number:=#row_number + 1) AS num, id
FROM
tickets order by `priority` ASC, `created` desc) as tmp_table WHERE id = 6;
But now there was another question. How is this implemented in Larabel 5.6?
UP
And he did it.
Simplified schema of m:n relation implementing a subscription model:
CREATE TABLE c (
id INT(11) PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(32)
) ENGINE=MyISAM CHARACTER SET=UTF8;
CREATE TABLE t (
id INT(11) PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(32)
) ENGINE=MyISAM CHARACTER SET=UTF8;
CREATE TABLE c2t (
id INT(11) PRIMARY KEY AUTO_INCREMENT,
cid INT(11) NOT NULL,
tid INT(11) NOT NULL,
dateStart DATE NULL,
dateEnd DATE NULL
) ENGINE=MyISAM CHARACTER SET=UTF8;
INSERT INTO c (name) VALUES ('mike'),('carl'),('suzy');
INSERT INTO t (name) VALUES ('plan1'),('plan2'),('plan3'),('plan4');
INSERT INTO c2t (cid, tid, dateStart, dateEnd) VALUES
(1, 1, '2014-01-01', '2014-07-31'),
(1, 2, '2014-08-01', '2015-07-31'),
(1, 1, '2015-08-01', null),
(1, 3, '2015-09-01', null),
(2, 1, '2014-01-01', '2015-07-31'),
(2, 2, '2015-08-01', '2015-09-30'),
(2, 3, '2015-09-30', null),
(3, 1, '2014-01-01', '2014-12-31'),
(3, 2, '2014-01-01', '2014-12-31'),
(3, 3, '2015-01-01', '2015-10-31'),
(3, 4, '2015-01-01', '2015-10-31');
I've developed a query to find the c's who have active subscriptions of t's:
SELECT c.*
FROM c
LEFT JOIN c2t ON c.id = c2t.cid
AND NOW() BETWEEN COALESCE(dateStart, '0000-00-00')
AND COALESCE(dateEnd, DATE_ADD(NOW(), INTERVAL 1 DAY))
GROUP BY c2t.cid
HAVING COUNT(c2t.id) > 0;
Result as expected:
id name
1 mike
2 carl
The problem arises when I try to count the result rows. The query is almost identical, I've just dropped in a COUNT(*):
SELECT COUNT(*)
FROM c
LEFT JOIN c2t ON c.id = c2t.cid
AND NOW() BETWEEN COALESCE(dateStart, '0000-00-00')
AND COALESCE(dateEnd, DATE_ADD(NOW(), INTERVAL 1 DAY))
GROUP BY c2t.cid
HAVING COUNT(c2t.id) > 0;
Result:
`COUNT(*)`
2
1
Expected result would be a single row containing the number of rows found (2). I can only assume that the GROUP BY is interfering, but have no idea how to work around. Explanations are most welcome.
Wrap everything with subquery and use COUNT in outer query:
SELECT COUNT(*)
FROM (
SELECT c.*
FROM c
LEFT JOIN c2t ON c.id = c2t.cid
AND NOW() BETWEEN COALESCE(dateStart, '0000-00-00')
AND COALESCE(dateEnd, DATE_ADD(NOW(), INTERVAL 1 DAY))
GROUP BY c2t.cid
HAVING COUNT(c2t.id) > 0
) AS sub
If the only thing you want returned is the number of c's who have active subscriptions, then you can simplify your query like this:
SELECT COUNT(DISTINCT c.id) AS cnt
FROM c
INNER JOIN c2t ON c.id = c2t.cid
AND NOW() BETWEEN COALESCE(dateStart, '0000-00-00')
AND COALESCE(dateEnd, DATE_ADD(NOW(), INTERVAL 1 DAY))
So, INNER JOIN is used in place of LEFT JOIN: there is no need to return c's with no matches in c2t, since these are not going to have any active subscriptions.
Also, there is no need to GROUP BY: the query returns just one row with the number of c's.
Finally, DISTINCT must be used in COUNT so as to avoid counting duplicate c.id values more than once.
I have a table of things. Here is a simplified structure:
CREATE TABLE `things` (
`thing_id` int(11) NOT NULL AUTO_INCREMENT,
`thing_group` int(11) NOT NULL,
`thing_status` int(1) NOT NULL DEFAULT '0'
);
There are 2 types of things. Primary, which would have thing_id = thing_group and secondary, which would having a unqiue thing_id but the same thing_group as the primary item.
INSERT INTO `things` (`thing_id`, `thing_group`, `thing_status`) VALUES
(1, 1, 0),
(2, 1, 1),
(3, 3, 1),
(4, 3, 0),
(5, 5, 1),
(6, 5, 1),
(7, 7, 0),
(8, 7, 0),
(9, 9, 1),
(10, 9, 1),
I have thousands of these pairs.
thing_status can be 0 for either the primary or the secondary (or both), but I want to select ONLY a pair (at random) that has thing_status = 1 both for primary and secondary thing.
So from the sample data I provided, it should only return pairs which are either thing_id 5 and 6, or 9 and 10 (at random)
Hard part:
Some things can just have the primary thing only, and no secondary. The query should still return those and treat them equally to things that come in pairs.
Am i better off doing 2 queries or a convoluted single query?
Group your rows by thing_group and select those where the number of rows is the same as the sum of thing_status. Join the resulting set back to the original table on thing_group to obtain the actual rows corresponding to the groups. So:
SELECT
t.thing_id,
t.thing_group
FROM things t
INNER JOIN (
SELECT thing_group
FROM things
GROUP BY thing_group
HAVING COUNT(*) = SUM(thing_status)
ORDER BY RAND()
LIMIT 1
) g ON t.thing_group = g.thing_group
Not so hard, maybe the random part is a bit more tricky:
select *
from things
where
thing_group = (select thing_group
from things
where thing_status = 1
group by thing_group
having count(thing_id) = 2
limit 1)
limit 1
My intuition says that you should use 2 queries with a UNION ALL. But... with MySQL it's not always clear what works and what doesn't.
I believe that this query does what you want though:
SELECT t1.thing_id, t1.group_id
FROM things t1
LEFT JOIN things t2
ON t2.thing_id = t2.thing_group
AND t1.thing_id != t2.thing_id
WHERE (
t1.thing_id = t1.thing_group
AND t2.thing_id IS NULL
) OR (
t1.thing_group = t2.thing_id
AND t1.thing_id != t1.thing_group
)
GROUP BY t1.thing_id, t1.group_id
Daft SQL question. I have a table like so ('pid' is auto-increment primary col)
CREATE TABLE theTable (
`pid` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`timestamp` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`cost` INT UNSIGNED NOT NULL,
`rid` INT NOT NULL,
) Engine=InnoDB;
Actual table data:
INSERT INTO theTable (`pid`, `timestamp`, `cost`, `rid`)
VALUES
(1, '2011-04-14 01:05:07', 1122, 1),
(2, '2011-04-14 00:05:07', 2233, 1),
(3, '2011-04-14 01:05:41', 4455, 2),
(4, '2011-04-14 01:01:11', 5566, 2),
(5, '2011-04-14 01:06:06', 345, 1),
(6, '2011-04-13 22:06:06', 543, 2),
(7, '2011-04-14 01:14:14', 5435, 3),
(8, '2011-04-14 01:10:13', 6767, 3)
;
I want to get the PID of the latest row for each rid (1 result per unique RID). For the sample data, I'd like:
pid | MAX(timestamp) | rid
-----------------------------------
5 | 2011-04-14 01:06:06 | 1
3 | 2011-04-14 01:05:41 | 2
7 | 2011-04-14 01:14:14 | 3
I've tried running the following query:
SELECT MAX(timestamp),rid,pid FROM theTable GROUP BY rid
and I get:
max(timestamp) ; rid; pid
----------------------------
2011-04-14 01:06:06; 1 ; 1
2011-04-14 01:05:41; 2 ; 3
2011-04-14 01:14:14; 3 ; 7
The PID returned is always the first occurence of PID for an RID (row / pid 1 is frst time rid 1 is used, row / pid 3 the first time RID 2 is used, row / pid 7 is first time rid 3 is used). Though returning the max timestamp for each rid, the pids are not the pids for the timestamps from the original table. What query would give me the results I'm looking for?
(Tested in PostgreSQL 9.something)
Identify the rid and timestamp.
select rid, max(timestamp) as ts
from test
group by rid;
1 2011-04-14 18:46:00
2 2011-04-14 14:59:00
Join to it.
select test.pid, test.cost, test.timestamp, test.rid
from test
inner join
(select rid, max(timestamp) as ts
from test
group by rid) maxt
on (test.rid = maxt.rid and test.timestamp = maxt.ts)
select *
from (
select `pid`, `timestamp`, `cost`, `rid`
from theTable
order by `timestamp` desc
) as mynewtable
group by mynewtable.`rid`
order by mynewtable.`timestamp`
Hope I helped !
SELECT t.pid, t.cost, to.timestamp, t.rid
FROM test as t
JOIN (
SELECT rid, max(tempstamp) AS maxtimestamp
FROM test GROUP BY rid
) AS tmax
ON t.pid = tmax.pid and t.timestamp = tmax.maxtimestamp
I created an index on rid and timestamp.
SELECT test.pid, test.cost, test.timestamp, test.rid
FROM theTable AS test
LEFT JOIN theTable maxt
ON maxt.rid = test.rid
AND maxt.timestamp > test.timestamp
WHERE maxt.rid IS NULL
Showing rows 0 - 2 (3 total, Query took 0.0104 sec)
This method will select all the desired values from theTable (test), left joining itself (maxt) on all timestamps higher than the one on test with the same rid. When the timestamp is already the highest one on test there are no matches on maxt - which is what we are looking for - values on maxt become NULL. Now we use the WHERE clause maxt.rid IS NULL or any other column on maxt.
You could also have subqueries like that:
SELECT ( SELECT MIN(t2.pid)
FROM test t2
WHERE t2.rid = t.rid
AND t2.timestamp = maxtimestamp
) AS pid
, MAX(t.timestamp) AS maxtimestamp
, t.rid
FROM test t
GROUP BY t.rid
But this way, you'll need one more subquery if you want cost included in the shown columns, etc.
So, the group by and join is better solution.
If you want to avoid a JOIN, you can use:
SELECT pid, rid FROM theTable t1 WHERE t1.pid IN ( SELECT MAX(t2.pid) FROM theTable t2 GROUP BY t2.rid);
Try:
select pid,cost, timestamp, rid from theTable order by timestamp DESC limit 2;