SQLite aggregate refer to calling SELECT statement - sql

I have a table, such as:
TimeValue
Low
14:00
123
14:30
012
15:00
456
15:30
145
16:00
678
I want to return the minimum "Low" that occurs on or after each TimeValue, so expected results would be:
TimeValue
Min(Low)
14:00
012
14:30
012
15:00
145
15:30
145
16:00
678
Have tried:
SELECT
TimeValue AS thistime,
(SELECT
MIN(Low)
FROM MyTable
AND TimeValue >= thistime)
FROM MyTable
GROUP BY thistime
;
but obviously SQLite doesn't recognize thistime from the inner SELECT statement. How do we do this?

Instead of the MIN aggregation function, you can use the corresponding homonimous window function, which will return the minimum value for each row and for each partition. In order to group on the hour, you can use the STRFTIME function as follows:
SELECT TimeValue,
MIN(Low) OVER(PARTITION BY STRFTIME('%H', TimeValue))
FROM tab
Check the demo here.

I think I figured it out:
SELECT
a.TimeValue,
(SELECT MIN(Low) FROM MyTable WHERE TimeValue >= a.TimeValue) AS MyResult
FROM MyTable a;

Related

Identify value changes in history table

I have following table, which apart from other attributes contains:
Customer ID - unique identifier
Value
CreatedDate - when the record has been created (based on ETL)
UpdatedDate - until when the record has been valid
Since there are other attributes apart from the [Value], which are being tracked for historical values, there might be cases, where there are multiple rows with the same [Value] for the same customer, but different timestamps in [CreatedDate] / [UpdatedDate]. Thus, the data may look like:
Customer ID
Value
CreatedDate
UpdatedDate
1
111
04/08/2021 15:00
04/08/2021 17:00
1
111
01/08/2021 09:00
04/08/2021 15:00
1
222
20/07/2021 01:30
01/08/2021 09:00
1
222
01/06/2021 08:00
20/07/2021 01:30
1
111
01/04/2021 07:15
01/06/2021 08:00
2
333
03/08/2021 04:30
04/08/2021 17:00
2
444
23/07/2021 01:20
03/08/2021 04:30
2
444
01/04/2021 13:50
23/07/2021 01:20
I would like to keep the unique [Values] in correct sequence, hence keep the [Value] for the earliest [CreatedDate], however, if Customer had originally Value1, then changed it to Value2 and finally, changed back to Value1. I would like to keep these 2 changes as well. Hence the ideal output should look like:
Customer ID
Value
CreatedDate
UpdatedDate
1
111
01/08/2021 09:00
04/08/2021 17:00
1
222
01/06/2021 08:00
01/08/2021 09:00
1
111
01/04/2021 07:15
01/06/2021 08:00
2
333
03/08/2021 04:30
04/08/2021 17:00
2
444
01/04/2021 13:50
03/08/2021 04:30
Based on CreatedDate / UpdatedDate identify, the chronological sequence of changes and identify the earliest CreatedDate and latest UpdatedDate. However, if particular value appeared multiple times, but has been interspersed by different value, I would like to keep it too.
I've tried the below approach and it works fine however it does not work for the scenario above and the output look like:
SELECT [Customer ID]
,Value
,MIN(CreatedDate) as CreatedDate
,MAX(UpdatedDate) as UpdatedDate
FROM #History
GROUP BY ID, Value
Customer ID
Value
CreatedDate
UpdatedDate
1
111
01/04/2021 07:15
04/08/2021 17:00
1
222
01/06/2021 08:00
01/08/2021 09:00
2
333
03/08/2021 04:30
04/08/2021 17:00
2
444
01/04/2021 13:50
03/08/2021 04:30
Any ideas, please? I've tried using LAG and LEAD as well, but was not able to make it work either.
This is a type of gaps-and-island problem that is probably best solved by looking for overlaps using a cumulative maximum:
select customerid, min(createddate), max(updateddate)
from (select t.*,
sum(case when prev_updatedate >= createddate then 0 else 1 end) over (partition by customerid, value order by createddate) as grp
from (select h.*,
max(updateddate) over (partition by customerid, value order by createddate rows between unbounded preceding and 1 preceding) as prev_updatedate
from #history h
) h
) h
group by customerid, value, grp;
The logic is to look at the most recent updatedate before each row for each customer and value. If this is earlier than the row's create date, then this starts are new group.
The final result is just aggregating the rows in each group.

How do I calculate the amount of time between multiple datetimes in multiple rows in sql

I've done a search but I can't find any that are exactly what I need. I need to be able to calculate the amount of time that someone has been in the building over time in a sql query (T-SQL on SQL Server). The data looks like this:
UserId Clocking Status
------------------------------
1 01/12/2020 09:00 In
2 01/12/2020 09:12 In
1 01/12/2020 09:25 Out
3 01/12/2020 10:00 In
2 01/12/2020 10:45 Out
3 01/12/2020 13:11 Out
1 03/12/2020 11:14 In
2 03/12/2020 15:56 In
1 03/12/2020 16:04 Out
2 03/12/2020 17:00 Out
I want the output to look like this:
UserId TimeInBuilding
----------------------
1 03:35
2 05:25
3 03:11
Assuming that the ins/outs are perfectly interleaved, you can do this by assigning the next "out" time to the "in" time and aggregating:
select userid,
sum(datediff(second, clocking, out_time)) / (60.0 * 60) as decimal_hours
from (select t.*,
lead(clocking) over (partition by userid order by clocking) as out_time
from t
) t
where status = 'In'
group by userid;
You can convert this to HH:MM format using:
select userid,
convert(varchar(5),
convert(time,
dateadd(second,
sum(datediff(second, clocking, out_time),
0)
)
) as hhmm
from (select t.*,
lead(clocking) over (partition by userid order by clocking) as out_time
from t
) t
where status = 'In'
group by userid;
Here is a db<>fiddle.

Grouping sets of data in Oracle SQL

I have been trying to separate groups in data being stored on my oracle database for more accurate analysis.
Current Output
Time Location
10:00 A111
11:00 A112
12:00 S111
13:00 S234
17:00 A234
18:00 S747
19:00 A878
Desired Output
Time Location Group Number
10:00 A111 1
11:00 A112 1
12:00 S111 1
13:00 S234 1
17:00 A234 2
18:00 S747 2
19:00 A878 3
I have been trying to use over and partition by to assign the values, however I can only get into to increment all the time not only on a change. Also tried using lag but I struggled to make use of that.
I only need the value in the second column to start from 1 and increment when the first letter of field 1 changes (using substr).
This is my attempt using row_number but I am far off I think. There would be a time column in the output as well not shown above.
select event_time, st_location, Row_Number() over(partition by
SUBSTR(location,1,1) order
by event_time)
as groupnumber from pic
Any help would be really appreciated!
Edit:
Time Location Group Number
10:00 A-10112 1
11:00 A-10421 1
12:00 ST-10621 1
13:00 ST-23412 1
17:00 A-19112 2
18:00 ST-74712 2
19:00 A-87812 3
It is a gap and island problem. Use the following code:
select location,
dense_rank() over (partition by SUBSTR(location,1,1) order by grp)
from
(
select (row_number() over (order by time)) -
(row_number() over (partition by SUBSTR(location,1,1) order by time)) grp,
location,
time
from data
) t
order by time
dbfiddle demo
The main idea is in the subquery which isolates consecutive sequences of items (computation of grp column). The rest is simple once you have the grp column.
select DENSE_RANK() over(partition by SUBSTR("location",1,1) ORDER BY SUBSTR("location",1,2))
as Rownumber,
"location" from Table1;
Demo
http://sqlfiddle.com/#!4/21120/16

SQL getting datediff from same field

I have a problem. I need to get the date difference in terms of hours in my table but the problem is it is saved in the same field. This is my table would look like.
RecNo. Employeeno recorddate recordtime recordval
1 001 8/22/2014 8:15 AM 1
2 001 8/22/2014 5:00 PM 2
3 001 8/24/2014 8:01 AM 1
4 001 8/24/2014 5:01 PM 2
1 indicates time in and 2 indicates time out. Now, How will i get the number of hours worked for each day? What i want to get is something like this.
Date hoursworked
8/22/2014 8
8/24/2014 8
I am using VS 2010 and SQL server 2005
You could self-join each "in" record with its corresponding "out" record and use datediff to subtract them:
SELECT time_in.employeeno AS "Employee No",
time_in.recorddate AS "Date",
DATEDIFF (hour, time_in.recordtime, time_out.recordtime)
AS "Hours Worked"
FROM (SELECT *
FROM my_table
WHERE recordval = 1) time_in
INNER JOIN (SELECT *
FROM my_table
WHERE recordval = 2) time_out
ON time_in.employeeno = time_out.employeeno AND
time_in.recorddate = time_out.recorddate
If you always record time in and time out for every employee, and just one per day, using a self-join should work:
SELECT
t1.Employeeno,
t1.recorddate,
t1.recordtime AS [TimeIn],
t2.recordtime AS [TimeOut],
DATEDIFF(HOUR,t1.recordtime, t2.recordtime) AS [HoursWorked]
FROM Table1 t1
INNER JOIN Table1 t2 ON
t1.Employeeno = t2.Employeeno
AND t1.recorddate = t2.recorddate
WHERE t1.recordval = 1 AND t2.recordval = 2
I included the recordtime fields as time in, time out, if you don't want them just remove them.
Note that this datediff calculation gives 9 hours, and not 8 as you suggested.
Sample SQL Fiddle
Using this sample data:
with table1 as (
select * from ( values
(1,'001', cast('20140822' as datetime),cast('08:15:00 am' as time),1)
,(2,'001', cast('20140822' as datetime),cast('05:00:00 pm' as time),2)
,(3,'001', cast('20140824' as datetime),cast('08:01:00 am' as time),1)
,(4,'001', cast('20140824' as datetime),cast('04:59:00 pm' as time),2)
,(5,'001', cast('20140825' as datetime),cast('10:01:00 pm' as time),1)
,(6,'001', cast('20140826' as datetime),cast('05:59:00 am' as time),2)
)data(RecNo,EmployeeNo,recordDate,recordTime,recordVal)
)
this query
SELECT
Employeeno
,convert(char(10),recorddate,120) as DateStart
,convert(char(5),cast(TimeIn as time)) as TimeIn
,convert(char(5),cast(TimeOut as time)) as TimeOut
,DATEDIFF(minute,timeIn, timeOut) / 60 AS [HoursWorked]
,DATEDIFF(minute,timeIn, timeOut) % 60 AS [MinutesWorked]
FROM (
SELECT
tIn.Employeeno,
tIn.recorddate,
dateadd(minute, datediff(minute,0,tIn.recordTime), tIn.recordDate)
as TimeIn,
( SELECT TOP 1
dateadd(minute, datediff(minute,0,tOut.recordTime), tOut.recordDate)
as TimeOut
FROM Table1 tOut
WHERE tOut.RecordVal = 2
AND tOut.EmployeeNo = tIn.EmployeeNo
AND tOut.RecNo > tIn.RecNo
ORDER BY tOut.EmployeeNo, tOut.RecNo
) as TimeOut
FROM Table1 tIn
WHERE tIn.recordval = 1
) T
yields (as desired)
Employeeno DateStart TimeIn TimeOut HoursWorked MinutesWorked
---------- ---------- ------ ------- ----------- -------------
001 2014-08-22 08:15 17:00 8 45
001 2014-08-24 08:01 16:59 8 58
001 2014-08-25 22:01 05:59 7 58
No assumptions are made about shifts not running across midnight (see case 3).
This particular implementation may not be the most performant way to construct this correlated subquery, so if there is a performance problem come back and we can look at it again. However running those tests requires a large dataset which I don't feel like constructing just now.

SQL Server find time slot between start time and end time

SQL Server, how to find the time slot from a schedule table like I need to output first column's end time and next column's start time?
select
s, e,
Max(cid)as c_id,
ROW_NUMBER()OVER(order by CAST(s as datetime)) as row_id
from classroom
where Room like '3310' and Days like '%T%'
group by s,e
order by CAST(s as datetime)
For example:
s e c_id row_id
------- ------- ------- ------
9:30 10:45 235 1
11:00 12:15 236 2
12:30 13:45 238 3
14:00 15:15 1415 4
15:30 16:45 273 5
17:00 18:15 270 6
I need to output
10:45-11:00
12:15-12:30
13:45-14:00
Thanks
You can insert your data in a temp table and then query that temp table
select s,e,Max(cid)as c_id,
ROW_NUMBER()OVER(order by CAST(s as datetime))as row_id
into #t
from classroom
where Room like '3310' and Days like '%T%'
group by s,e
order by CAST(s as datetime)
select t1.e, t2.s
from #t t1
INNER JOIN #t t2 on t1.row_id + 1 = t2.row_id
If you want to know only when there's a time gap between one class finishing and the next starting add where t2.s > t1.e to Abhi's answer. If you need a minimum size of slot, say 15 minutes, use where DATEDIFF(mi, t1.e, t2.s) > 15.