问题
The query below works, but I would like to know if there is a better way to do it.
There is one subquery that uses two subqueries. The two subqueries are identical but return two different fields. Is there a way to use only one subquery that returns two fields?
I checked similar questions (this, this and this), but I don't think they apply in this case.
Here is the query:
SELECT *,
time(strftime('%s', EndTime) - strftime('%s', StartTime), 'unixepoch') AS Duration
FROM (
SELECT (
SELECT Time
FROM Log AS LogStart
WHERE LogStart.User = Log.User AND
LogStart.Time <= Log.Time AND
LogStart.[Action] != 'done'
ORDER BY LogStart.Time DESC
LIMIT 1
)
AS StartTime,
Time AS EndTime,
User,
(
SELECT [Action]
FROM Log AS LogStart
WHERE LogStart.User = Log.User AND
LogStart.Time <= Log.Time AND
LogStart.[Action] != 'done'
ORDER BY LogStart.Time DESC
LIMIT 1
)
AS [Action]
FROM Log
WHERE [Action] = 'done'
)
ORDER BY duration DESC;
Here is some test data:
CREATE TABLE Log (
Time DATETIME,
User CHAR,
[Action] CHAR
);
insert into Log values('2017-01-01 10:00:00', 'Joe', 'Play');
insert into Log values('2017-01-01 10:01:00', 'Joe', 'done');
insert into Log values('2017-01-01 10:02:00', 'Joe', 'Sing');
insert into Log values('2017-01-01 10:03:00', 'Joe', 'done');
insert into Log values('2017-01-01 10:04:00', 'Ann', 'Play');
insert into Log values('2017-01-01 10:04:30', 'Bob', 'Action without corresponding "done" which must be ignored');
insert into Log values('2017-01-01 10:05:00', 'Joe', 'Play');
insert into Log values('2017-01-01 10:06:00', 'Ann', 'done');
insert into Log values('2017-01-01 10:07:00', 'Joe', 'done');
insert into Log values('2017-01-01 10:08:00', 'Ann', 'Play');
insert into Log values('2017-01-01 10:09:00', 'Ann', 'done');
回答1:
Use a self join... I don't have SQLLite, so the syntax may be off here, but you should get the idea...
Select e.*, time(strftime('%s', e.[Time]) -
strftime('%s', s.[Time]), 'unixepoch') AS Duration
From log e join log s -- s is for the startevent; e for end event
on s.[User] = e.[User]
and s.[Action] != 'done'
and e.[Action] = 'done'
and s.[Time] =
(Select Max([time] from log
where [User] = e.[User]
and [time] <= e.[Time]
and [Action] != 'done')
回答2:
I made this: is what you were looking for? (MSSQL, but I think should work in SQLLite as there are not "non standard" SQL commands; I should write USER inside []).
SELECT STARTTIME, MIN(ENDTIME) AS ENDTIME, [USER], ACTION
FROM (
SELECT B.TIME AS STARTTIME, A.TIME AS ENDTIME, A.[USER], B.ACTION
FROM LOG A
INNER JOIN (SELECT * FROM LOG) B ON A.[USER]= B.[USER] AND B.ACTION<>'done' AND B.TIME< A.TIME
WHERE A.Action='done'
) X
GROUP BY X.STARTTIME, [USER], ACTION ;
Output:
STARTTIME ENDTIME USER ACTION
----------------------- ----------------------- ---------- ---------------------
2017-01-01 10:00:00.000 2017-01-01 10:01:00.000 Joe Play
2017-01-01 10:02:00.000 2017-01-01 10:03:00.000 Joe Sing
2017-01-01 10:04:00.000 2017-01-01 10:06:00.000 Ann Play
2017-01-01 10:05:00.000 2017-01-01 10:07:00.000 Joe Play
2017-01-01 10:08:00.000 2017-01-01 10:09:00.000 Ann Play
With only your data, compared execution plan in MSSQL showed that your query "costs" 87% while this latter costs 13% (their sum is - of course - 100%)
来源:https://stackoverflow.com/questions/42766634/how-to-return-two-fields-from-a-subquery