TSQL - DateTime difference between more than two rows - sql-server

I'm trying to find out how to calculate difference between multiple rows from one simple query. Here it is:
SELECT [DateTime],EmployeeId,ControlPointID,EventTypeID
FROM [Events]
WHERE Day([DateTime]) = 4
AND Month([DateTime]) = 7
AND Year([DateTime]) = 2017
AND EmployeeId = 451
AND ControlPointID IN ( 3, 6 )
AND EventTypeID IN ( 1, 2 )
ORDER BY [DateTime]
Result:
DateTime EmployeeId ControlPointID EventTypeID
2017-07-04 11:32:10.000 451 6 1
2017-07-04 16:07:00.000 451 3 2
2017-07-04 16:42:50.000 451 6 1
2017-07-04 20:04:10.000 451 3 2
I need to calculate difference between [DateTime] in minutes.
EventTypeId = 1 means that Employee enters to the building and EventTypeId=2 means that Employee leaves. I can calculate difference between first Enter Event and last Leave Event. In this case it's 512 minutes. But, i have problem to calculate work time, when someone enters twice and leaves twice. It should be 477 minutes. Calculation should looks like this:
DateDiff = (2017-07-04 16:07:00.000 - 2017-07-04 11:32:10.000) +
(2017-07-04 20:04:10.000 - 2017-07-04 16:42:50.000)
Can you help me figure it out, please ?

Given a building entry, finding the first leave after that entry can be done with cross apply:
select entry.EmployeeId, entry.DateTime, exit.DateTime
from Events entry
cross apply (select top 1 e.DateTime
from Events e
where e.EmployeeId = entry.EmployeeId
and e.DateTime > entry.DateTime
and e.EventTypeId = 2
order by e.DateTime asc
) as exit
where entry.EventTypeId = 1
at which point you just need to use the applicable T/SQL function to get the difference in whatever unit you want (eg. in minutes with datediff(minute, entry.DateTime, exit.DateTime).
To get the total of all the differences simply sum the differences:
select EmployeeId, sum(mins)
from (
select entry.EmployeeId, entry.DateTime as EntryDateTime, exit.DateTime as ExitDateTime, datediff(minute, EntryDateTime, ExitDateTime) as mins
from Events entry
cross apply (select top 1 e.DateTime
from Events e
where e.EmployeeId = entry.EmployeeId
and e.DateTime > entry.DateTime
and e.EventTypeId = 2
order by e.DateTime asc
) as exit
where entry.EventTypeId = 1
) as input
group by EmployeeId
Edit: added overall summation (with diff on the inside for clarity)

This can be done using LAG window function, since 2008 does not supports it we need to left join with Row_Number to find the previous entry
;WITH cte
AS (SELECT Row_number()OVER(Partition by EmployeeID ORDER BY [DateTime]) rn,*
FROM Yourresult)
SELECT a.EmployeeID,
Sum(Datediff(minute, b.[DateTime], a.[DateTime]))
FROM cte a
LEFT JOIN cte b
ON a.EmployeeID = b.EmployeeID
AND a.rn = b.rn + 1
WHERE a.[EventTypeId] = 2
GROUP BY a.EmployeeID
Note : This considers there isn't any wrong punches. Just like your sample data

Related

How can i use sql query for the following

My data table sampletime in one column and sample value in another column contain data like follow
sampletime value
----------------------------
2016-03-02 08:31:14 1
2016-03-02 09:31:14 2
2016-03-02 12:31:14 3
2016-03-04 08:31:14 4
2016-03-04 09:31:14 5
2016-03-05 08:31:14 3
I need two minimum sample time in each day. How can I group?
Query
SELECT rn.sampletime AS stime
FROM rn_qos_data_0007 rn
INNER JOIN s_qos_data qos
ON qos.table_id = rn.table_id
AND qos.qos = 'QOS_CPU_USAGE'
AND Substring(qos.origin, 1, 4) = 'A0C3'
AND qos.host = '10.98.48.100'
WHERE rn.sampletime BETWEEN '2016/01/01' AND '2016/06/22'
GROUP BY rn.sampletime
You need ROW_NUMBER window function
Select * From
(
select row_number()over(partition by cast(sampletime as date) order by sampletime) RN,*
From ..
) A
Where RN <=2

SQL Server 2005 - Update column where DATEDIFF between two dates is minimum

I have two tables, defined as following:
PTable:
[StartDate], [EndDate], [Type], PValue
.................................................
2011-07-01 2011-07-07 001 5
2011-07-08 2011-07-14 001 10
2011-07-01 2011-07-07 002 15
2011-07-08 2011-07-14 002 20
TTable:
[Date], [Type], [TValue]
..................................
2011-07-01 001 11
2011-07-02 001 4
2011-07-03 001 0
2011-07-08 002 12
2011-07-09 002 12
2011-07-10 002 0
I want to update Tvalue column in TTable with the PValue in PTable, where [Date] in TTable is between [StartDate] and [EndDate] in PTable and DATEDIFF(DAY,TTable.[Date],PTable.[EndDate]) is minimum, AND PTable.Type = TTable.Type
The final TTable should look like this:
[Date], [Type], [TValue]
..................................
2011-07-01 001 11
2011-07-02 001 4
2011-07-03 001 5 --updated
2011-07-08 002 12
2011-07-09 002 12
2011-07-10 002 20 --updated
What I have tried is this:
UPDATE [TTable]
SET
TValue = T1.PValue
FROM TTable
INNER JOIN PTable T1 ON
[Date] BETWEEN T1.StartDate AND T1.EndDate
AND DATEDIFF(DAY,[Date],T1.EndDate) =
(SELECT MIN( DATEDIFF(DAY,TTable.[Date],T.EndDate) )
FROM PTable T WHERE TTable.[Date] BETWEEN T.StartDate AND T.EndDate
)
AND
T1.[Type] = TTable.[Type]
It gives me this error :
"Multiple columns are specified in an aggregated expression containing an outer reference. If an expression being aggregated contains an outer reference, then that outer reference must be the only column referenced in the expression."
Later edit:
Considering TTable AS T and PTable AS P, the condition for update are:
1. T.Type = P.Type
2. T.Date BETWEEN P.StartDate AND P.EndDate
3. DATEDIFF(DAY,T.Date,P.EndDate) = minimum value of all DATEDIFFs WHERE P.Type = T.Type AND T.Date BETWEEN P.StartDate AND P.EndDate
Later Edit 2:
Sorry, because I typed wrong the last row in PTable (2011-08-10 instead 2011-07-14), the final result was wrong.
I also managed to update in a simpler way, which I obviously should have tried from the start:
UPDATE TTABLE
SET
TValue = T1.PValue
FROM TTable
INNER JOIN PTABLE T1 ON
[Date] = (SELECT TOP(1) MAX(Date) FROM [TTABLE] WHERE [Date] BETWEEN T1.StartDate AND T1.EndDate)
AND
T1.Type = [TTABLE].Type
Sorry about this.
So you said something about "DATEDIFF(DAY,TTable.[Date],PTable.[EndDate]) is minimum" which confused me. Itt would seem like if there a weekly entry per Type, then for a particular Date, Type combination it would ever only match one. You might give this a try:
UPDATE TTABLE
SET TValue = T1.PValue
FROM TTable
INNER JOIN PTABLE T1 ON T1.Type = [TTABLE].Type -- find row in PTable that the Date falls between
and [Date] BETWEEN T1.StartDate AND T1.EndDate)
where
TValue = ( select MIN(TValue) -- finds the lowest TValue, 0 in example
from TTable))
...updated...
So it appears I read the problem incorrectly the first time. I had thought we update the TTable entries that have the lowest TValue. Not sure how I got that impression. Still seems like there needs to be a check for if it is 0?
UPDATE TTable
SET TValue = T1.PValue
FROM TTable
INNER JOIN PTable T1 ON T1.Type = TTable.Type
and T1.EndDate = (
SELECT top 1 EndDate
FROM PTable
WHERE Type=TTable.Type
ORDER BY abs(DATEDIFF(day,TTable.Date,PTable.EndDate)) desc)
WHERE
TValue = 0 -- only updating entries that aren't set, have a 0
This only works if there is one is one row in PTable with an EndDate of 7/7 or whatever for a given type. If there are two entries for Type 001 with an end date of 7/7, then it will join to two entries. Also if there is two entries that are equal distant from the date in question, so an EndDate of 7/7 and one of 7/13 are both 3 days from 7/10. If the EndDates are all 7 days apart (weekly) you should be ok.

How to select a specific set of rows and then select next row relevant to the original row

I don't know if what i'm looking for it's possible with my current dataset, or if what i'm expecting it's possible at all.
what i am trying to accomplish is to get all rows with status = 2 or 7 get the date and then get the next row with different status to obtain the dateinterval and get the nuber of days that the status had.
DataSet
id_compromiso|fecha |id_actividad|status
-------------+-----------+------------+----------
32 2013-12-10 359 2
32 2013-12-16 380 5
32 2013-12-18 401 7
32 2013-12-24 485 8
58 2013-12-02 248 2
58 2013-12-03 254 2
58 2013-12-10 360 2
58 2013-12-10 378 5
58 2013-12-12 395 2
what have i tried:
SQL query:
WITH pausa AS (
SELECT tmp.id_compromiso, tmp.fecha, MIN(tact.id_actividad) as id_actividad
FROM Actividades as tact
INNER JOIN (
SELECT act.id_compromiso, CAST(act.fecha as date) as fecha
FROM actividades as act
WHERE act.[status]=7
) as tmp
ON(tmp.id_compromiso = tact.id_compromiso AND tmp.fecha = CAST(tact.fecha as date))
WHERE tact.[status]=7
GROUP BY tmp.id_compromiso, tmp.fecha
),
revision AS (
SELECT tmp.id_compromiso, tmp.fecha, MIN(tact.id_actividad) as id_actividad
FROM Actividades as tact
INNER JOIN (
SELECT act.id_compromiso, CAST(act.fecha as date) as fecha
FROM actividades as act
WHERE act.[status]=2
) as tmp
ON(tmp.id_compromiso = tact.id_compromiso AND tmp.fecha = CAST(tact.fecha as date))
WHERE tact.[status]=2
GROUP BY tmp.id_compromiso, tmp.fecha
)
SELECT * FROM revision ORDER BY id_compromiso;
but really running i'm out of ideas on how to get the next item with different status from the table ...
-- First, it extends actividades to include the minimum fecha for the status
-- on the compromiso; this is min(fecha) in the partition by compromiso/status
WITH status_start AS(
SELECT *, MIN(fecha) OVER (PARTITION BY id_compromiso, status) sStart
FROM actividades
),
-- Then, join the extended actividades table with itself (aliased a and b) by compromiso but status 2,7 with status not 2,7
-- (this is the AND a.STATUS IN (2,7) AND b.STATUS NOT IN(2,7) in the join clause)
-- and making sure it's a later status (the a.sStart <b.sStart bit)
-- at this point also calculates the date difference in days
status_start_end AS(
SELECT a.*,b.sStart sEnd, DATEDIFF(d, a.sStart, b.sStart) AS sDiff FROM status_start a
JOIN status_start b ON (a.id_compromiso =b.id_compromiso AND a.STATUS IN (2,7) AND b.STATUS NOT IN(2,7) AND a.sStart <b.sStart))
-- Finaly as the previous query would have day difference in relation to ALL later status, we need to select only the minimum difference
-- as this is when the status actually change. We also need to eliminate duplicates using 'distinct;
-- as it could be many entries for the same status and
-- also many later status.
SELECT DISTINCT id_compromiso, status ,
MIN(sDiff) OVER (PARTITION BY id_compromiso) "Nr. of days in status"
FROM status_start_end
Without knowing more about the context in question it's difficult to provide a fitting answer, but something like this may help:
SELECT TOP 1 id_compromiso, fecha, id_actividad, status
FROM Actividades
WHERE CAST(fecha AS DATE)>( SELECT MAX(CAST(fecha AS DATE))
FROM Actividades
WHERE status IN (2,7))
AND status NOT IN (2,7)
ORDER BY CAST(fecha AS DATE) DESC
I have set up a SQL Fiddle here.

T-SQL getting all unique groups with their usage count

How do I find the unique groups that are present in my table, and display how often that type of group is used?
For example (SQL Server 2008R2)
So, I would like to find out how many times the combination of
PMI 100
RT 100
VT 100
is present in my table and for how many itemid's it is used;
These three form a group because together they are assigned to a single itemid. The same combination is assigned to id 2527 and 2529, so therefore this group is used at least twice. (usagecount = 2)
(and I want to know that for all types of groups that are appearing)
The entire dataset is quite large, about 5.000.000 records, so I'd like to avoid using a cursor.
The number of code/pct combinations per itemid varies between 1 and 6.
The values in the "code" field are not known up front, there are more than a dozen values on average
I tried using pivot, but I got stuck eventually and I also tried various combinations of GROUP-BY and counts.
Any bright ideas?
Example output:
code pct groupid usagecount
PMI 100 1 234
RT 100 1 234
VT 100 1 234
CD 5 2 567
PMI 100 2 567
VT 100 2 567
PMI 100 3 123
PT 100 3 123
VT 100 3 123
RT 100 4 39
VT 100 4 39
etc
Just using a simple group:
SELECT
code
, pct
, COUNT(*)
FROM myTable
GROUP BY
code
, pct
Not too sure if that's more like what you're looking for:
select
uniqueGrp
, count(*)
from (
select distinct
itemid
from myTable
) as I
cross apply (
select
cast(code as varchar(max)) + cast(pct as varchar(max)) + '_'
from myTable
where myTable.itemid = I.itemid
order by code, pct
for xml path('')
) as x(uniqueGrp)
group by uniqueGrp
Either of these should return each combination of code and percentage with a group id for the code and the total number of instances of the code against it. You can use them for also adding the number of instances of the specific code/pct combo too for determining % contribution etc
select
distinct
t.code, t.pct, v.groupcol, v.vol
from
[tablename] t
inner join (select code, rank() over(order by count(*)) as groupcol,
count(*) as vol from [tablename] s
group by code) v on v.code=t.code
or
select
t.code, t.pct, v.groupcol, v.vol
from
(select code, pct from [tablename] group by code, pct) t
inner join (select code, rank() over(order by count(*)) as groupcol,
count(*) as vol from [tablename] s
group by code) v on v.code=t.code
Grouping by Code, and Pct should be enough I think. See the following :
select code,pct,count(p.*)
from [table] as p
group by code,pct

Select a distinct record, filtering is not working

Hello EVery I am new to SQl. query to result in the following records.
I have a table with records as
c1 c2 c3 c4 c5 c6
1 John 2.3.2010 12:09:54 4 7 99
2 mike 2.3.2010 13:09:59 8 6 88
3 ahmad 2.3.2010 13:09:59 1 9 19
4 Jim 23.3.2010 16:35:14 4 5 99
5 run 23.3.2010 12:09:54 3 8 12
I want to fecth only records.
i.e only 1 latest record per day. If both of them happen at the same time, sort by C1.so in 1&3 it should fetch 3.
3 ahmad 2.3.2010 14:09:59 1 9 19
4 Jim 23.3.2010 16:35:14 4 5 99
I have got a new problem in this.
If i filter the records based on conditions the last record is missing. I tried many ways but still it is failing. Here update_log is my table.
SELECT * FROM update_log t1
WHERE (t1.c3) =
(
SELECT MAX(t2.c3)
FROM update_log t2
WHERE DATEDIFF(dd,t2.c3, t1.c3) = 0
)
and t1.c3 > '02.03.2010' and t1.modified_at <= '22.03.2010'
ORDER BY t1.c3 ASC. But i am not able to retrieve the record
4 Jim 23.3.2010 16:35:14 4 5 99
I dont know this query results in only
3 ahmad 2.3.2010 14:09:59 1 9 19
The format of the column c3 is datetime. I am pumping the data into the column as
using $date = date("d.m.Y H:i",time()); -- simple date fetch of today.
Another query that i tried for the same purpose.
select * from (select convert(varchar(10), c3,104) as date, max(c3) as max_date, max(c1) as Nr from update_log group by convert(varchar(10), c3,104)) as t2 inner join update_log as t1 on (t2.max_date = t1.c3 and convert(varchar(10), c3,104) = date and t1.[c1]= Nr) WHERE t1.c3 >= '02.03.2010' and t1.c3 <= '16.04.2010' . I even tried this way..the same error last record is not coming..
Following steps should produce the results you are after
Find max c3 for every day.
Join the results with your original table, witholding only the max c1 values.
SQL Statement (Edited)
DECLARE #update_log TABLE (c1 INTEGER, c3 DATETIME)
INSERT INTO #update_log
SELECT 1, '3.2.2010 12:09:54'
UNION ALL SELECT 2, '3.2.2010 13:09:59'
UNION ALL SELECT 3, '3.2.2010 13:09:59'
UNION ALL SELECT 4, '3.23.2010 16:35:14'
UNION ALL SELECT 5, '3.23.2010 12:09:54'
SELECT c1 = MAX(l.c1), l.c3
FROM #update_log l
INNER JOIN (
SELECT c3_max = MAX(c3)
FROM #update_log
WHERE c3 > '3.2.2010 00:00:00'
AND c3 < '3.24.2010 00:00:00'
GROUP BY
CONVERT(VARCHAR(10), c3, 101)
) l_maxdate ON l_maxdate.c3_max = l.c3
GROUP BY
l.c3
Notes
You should read the FAQ regarding as to how this site operates. As has been mentioned, Stack Overflow is not intented to be used like one would use a newsreader where you ask follow-up questions after an answer has been given by creating a new answer.
You should edit your question for any additional information or use the comments. If the additional information is that much that it in effect changes the entire question, you should consider making a new question out of it.
That being said, enjoy SO.
Assuming c1 is unique, hopefully even the primary key. This also assumes the use of SQL Server 2008 for the DATE data type.
SELECT t1.*
FROM update_log t1
WHERE t1.c3 > '02.03.2010'
AND t1.modified_at <= '22.03.2010'
AND t1.c1 IN
( SELECT TOP 1 c1
FROM update_log t2
WHERE CAST(t1.c3 As DATE) = CAST(t2.c3 As DATE)
ORDER BY c3 DESC, c1 DESC
)
OK, Now i understood. actually i wrote the query for the same purpose this way.
select * from #temp
select * from
(select max(c1) as nr from
(select convert(varchar(10), c3,104) as date, max(c3) as max_date
from #temp where
convert(varchar(10),c3,104) >= '02.02.2010' and
convert(varchar(10),c3,104) <= '23.02.2010'
group by convert(varchar(10), c3,104))
as t2 inner join #temp as t1 on (t2.max_date = t1.c3 and
convert(varchar(10), c3,104) = date)
group by convert(varchar(10),max_date,104))
as t3 inner join #temp as t4 on (t3.nr = t4.c1 )
If i change these 2 lines to c3 >= '02.02.2010' and
c3 <= '24.02.2010'. It is working fine. but the query that i have posted is not able to filter the records properly based on dates.
I want to know where i went to wrong to enhance my knoweldge rather than just copying ur query:-)

Resources