SQL Query to get in/out time - sql-server

I need to create a report when the user entering and exiting time. So far I only manage to get the min and max time. Here, the example of table:
ID | Flag_Location (bit) | Time
----------------------------
1001 | 1 | 8:00
1001 | 1 | 9:00
1001 | 1 | 10:00
1001 | 0 | 11:00
1001 | 0 | 12:00
1001 | 1 | 13:00
1001 | 1 | 14:00
The output that I need for the report is like this :
ID | ENTERTIME | EXITTIME
-------------------------
1001 | 8:00 | 10:00
1001 | 13:00 | 14:00
So far I only manage to get 1 row of result :
ID | ENTERTIME | EXITTIME
-------------------------
1001 | 8:00 | 14:00

You can use the window function to create an ad-hoc Grp
Example
Select ID
,TimeIn = min(Time)
,TimeOut = max(Time)
From (
Select *
,Grp = sum(case when flag_location=0 then 1 else 0 end ) over (partition by id order by time)
From YourTable
) A
Where Flag_Location=1
Group By ID,Grp
Returns
ID TimeIn TimeOut
1001 08:00:00.0000000 10:00:00.0000000
1001 13:00:00.0000000 14:00:00.0000000
If it helps with the visualization, the nested query generates the following:

You can just bucket the to identify group by and do group by as below:
;with cte as (select *, bucket = sum(case when flag_location = 0 then 1 when flag_location = 1 and nextflag = 0 then 2 else 0 end) over (partition by id order by [time]),
[time] as endtime from
(
select *,
lag(flag_location) over(partition by id order by [time]) nextflag
from #table4
) a
)
select id, min([time]), max([time]) from cte
where flag_location = 1
group by id, bucket
Query results:
+------+------------------+------------------+
| id | Entertime | ExitTime |
+------+------------------+------------------+
| 1001 | 08:00:00.0000000 | 10:00:00.0000000 |
| 1001 | 13:00:00.0000000 | 14:00:00.0000000 |
+------+------------------+------------------+

Try below query (explanations in code)
declare #tbl table (ID int, Flag_Location bit, Time varchar(5));
insert into #tbl values
(1001,1,'8:00'),
(1001,1,'9:00'),
(1001,1,'10:00'),
(1001,0,'11:00'),
(1001,0,'12:00'),
(1001,1,'13:00'),
(1001,1,'14:00');
select ID,
cast(max(ts) as varchar(10)),
cast(min(ts) as varchar(10))
from (
select ID, ts, Flag_Location,
row_number() over (order by ts) -
row_number() over (partition by Flag_Location order by ts) grp
from (
select *,
-- add 0 at the beginning for correct cast and cast it to timestamp for correct ordering
cast(right('00000' + time, 5) as timestamp) ts
from #tbl
) a
) a where Flag_Location = 1
group by ID, grp

Related

I have a table where customer ID are being duplicated because of their reactivation date. I need to pivot the reactivation date per CustomerID

I have a following table
I need to pivot the table and have it like the table below:
How can I have the unique customer ID in a column and all the reactivation dates pivoted like in the above picture?
To attribute a numeric sequence to the reactivation dates, use row_number() over() and then you can pivot that sequence from rows to columns:
select
customer_id
, activation_date
, [1] as reactivation_dt_1
, [2] as reactivation_dt_2
, [3] as reactivation_dt_3
, [4] as reactivation_dt_4
from (
select
customer_id, activation_date, reactivation_date
, row_number() over(partition by customer_id
order by reactivation_date ASC) as pivcol
from mytable
) as d
pivot (
max(reactivation_date)
for pivcol in ([1],[2],[3],[4])
) as p
order by
customer_id
result
+-------------+-----------------+-------------------+-------------------+-------------------+-------------------+
| customer_id | activation_date | reactivation_dt_1 | reactivation_dt_2 | reactivation_dt_3 | reactivation_dt_4 |
+-------------+-----------------+-------------------+-------------------+-------------------+-------------------+
| 1 | 2010-01-01 | 2012-02-01 | 2015-03-01 | 2017-07-01 | 2022-07-01 |
| 2 | 2011-12-03 | 2013-05-01 | 2014-08-10 | 2015-12-09 | |
+-------------+-----------------+-------------------+-------------------+-------------------+-------------------+
see db<>fiddle here

Get the count of statuses by date but only count continuous rows

I have this data:
ID Name Status Date
1 Machine1 Active 2018-01-01
2 Machine2 Fault 2018-01-01
3 Machine3 Active 2018-01-01
4 Machine1 Fault 2018-01-02
5 Machine2 Active 2018-01-02
6 Machine3 Active 2018-01-02
7 Machine2 Active 2018-01-03
8 Machine1 Fault 2018-01-03
9 Machine2 Active 2018-01-04
10 Machine1 Fault 2018-01-04
11 Machine3 Active 2018-01-06
INPUT
and i want these data in output
EXPECTED OUTPUT
Name Last Status Count
Machine1 Fault 3
Machine2 Active 3
Machine3 Active 1 Because Date is not Continuous
*Count : Last number of status in continuous history
I believe it is as simple as this:
WITH cte1 AS (
SELECT
Name,
Status,
DATEADD(DAY, ROW_NUMBER() OVER (PARTITION BY Name, Status ORDER BY Date DESC) - 1, Date) AS GroupingDate
FROM testdata
), cte2 AS (
SELECT
Name,
Status,
RANK() OVER (PARTITION BY Name ORDER BY GroupingDate DESC) AS GroupingNumber
FROM cte1
)
SELECT Name, Status AS LastStatus, COUNT(*) AS LastStatusCount
FROM cte2
WHERE GroupingNumber = 1
GROUP BY Name, Status
ORDER BY Name
Result and DBFiddle:
| Name | LastStatus | LastStatusCount |
|----------|------------|-----------------|
| Machine1 | Fault | 3 |
| Machine2 | Active | 3 |
| Machine3 | Active | 1 |
In order to understand how this works, look at the intermediate values generated by CTE:
| Name | Status | Date | RowNumber | GroupingDate | GroupingNumber |
|----------|--------|---------------------|-----------|---------------------|----------------|
| Machine1 | Fault | 04/01/2018 00:00:00 | 1 | 04/01/2018 00:00:00 | 1 |
| Machine1 | Fault | 03/01/2018 00:00:00 | 2 | 04/01/2018 00:00:00 | 1 |
| Machine1 | Fault | 02/01/2018 00:00:00 | 3 | 04/01/2018 00:00:00 | 1 |
| Machine1 | Active | 01/01/2018 00:00:00 | 1 | 01/01/2018 00:00:00 | 4 |
| Machine2 | Active | 04/01/2018 00:00:00 | 1 | 04/01/2018 00:00:00 | 1 |
| Machine2 | Active | 03/01/2018 00:00:00 | 2 | 04/01/2018 00:00:00 | 1 |
| Machine2 | Active | 02/01/2018 00:00:00 | 3 | 04/01/2018 00:00:00 | 1 |
| Machine2 | Fault | 01/01/2018 00:00:00 | 1 | 01/01/2018 00:00:00 | 4 |
| Machine3 | Active | 06/01/2018 00:00:00 | 1 | 06/01/2018 00:00:00 | 1 |
| Machine3 | Active | 02/01/2018 00:00:00 | 2 | 03/01/2018 00:00:00 | 2 |
| Machine3 | Active | 01/01/2018 00:00:00 | 3 | 03/01/2018 00:00:00 | 2 |
The trick here is that if two numbers are contiguous then subtracting contiguous numbers from them will result in same value. E.g. if we have 5, 6, 8, 9 then subtracting 1, 2, 3, 4 in that order will produce 4, 4, 5, 5.
I think this will work, though SQLFiddle is having a fit at the moment, so I can't test:
SELECT [Name], [Status], ct as [Count]
FROM (
SELECT
[name],
[status],
[date],
1 + (SUM( grp ) OVER (PARTITION BY [name], [status] ORDER BY [date] ROWS BETWEEN 1 PRECEDING AND 0 FOLLOWING ) * grp) ct,
row_number() over(partition by [name] order by [date] desc) rn
FROM
(
SELECT *, CASE WHEN LAG([Date]) OVER(PARTITION BY [name], [status] ORDER BY [date] ) = DATEADD(day, -1, [date]) THEN 1 ELSE 0 END grp
FROM t
) x
) y
WHERE
rn = 1
It first off uses LAG to look at the current row and the previous row (grouping the data into machine name and status, ordering the data by date) and if the current date is 1 day different to the previous date it records a 1 else a 0
These 1s and zeroes are summed in a running total fashion, resetting when the machine name or status changes (the partition of the sum() over() )
Also we want to consider the data just in terms of the machine name, and we only want the latest record from each machine, so we partition by the machine name, and count in order of date descending, then just pick (with the where clause) the rows that are numbered 1 for each machine
It actually makes a lot more sense if you run the queries separately, like this
Calculate the "is the current report consecutive with the previous report, for the given status and machine" 1 = yes, 0 = no:
SELECT *, CASE WHEN LAG([Date]) OVER(PARTITION BY [name], [status] ORDER BY [date] ) = DATEADD(day, -1, [date]) THEN 1 ELSE 0 END grp
FROM t
Calculate the "what is the running total of the current block of consecutive reports":
SELECT
[name],
[status],
[date],
1 + (SUM( grp ) OVER (PARTITION BY [name], [status] ORDER BY [date] ROWS BETWEEN 1 PRECEDING AND 0 FOLLOWING ) * grp) ct,
row_number() over(partition by [name] order by [date] desc) rn
FROM
(
SELECT *, CASE WHEN LAG([Date]) OVER(PARTITION BY [name], [status] ORDER BY [date] ) = DATEADD(day, -1, [date]) THEN 1 ELSE 0 END grp
FROM t
) x
Then of course, the whole thing but without the where clause, so you can see the data we're discarding:
SELECT [Name], [Status], ct as [Count]
FROM (
SELECT
[name],
[status],
[date],
1 + (SUM( grp ) OVER (PARTITION BY [name], [status] ORDER BY [date] ROWS BETWEEN 1 PRECEDING AND 0 FOLLOWING ) * grp) ct,
row_number() over(partition by [name] order by [date] desc) rn
FROM
(
SELECT *, CASE WHEN LAG([Date]) OVER(PARTITION BY [name], [status] ORDER BY [date] ) = DATEADD(day, -1, [date]) THEN 1 ELSE 0 END grp
FROM t
) x
) y
Fiddle finally woke up:
http://www.sqlfiddle.com/#!18/77dae/2

calculate days with gaps

table:
+-----------+--------------+------------+------------+
| RequestID | RequestStaus | StartDate | EndDate |
+-----------+--------------+------------+------------+
| 1 | pending | 9/1/2015 | 10/2/2015 |
| 1 | in progress | 10/2/2015 | 10/20/2015 |
| 1 | completed | 10/20/2015 | 11/3/2015 |
| 1 | reopened | 11/3/2015 | null |
| 2 | pending | 9/5/2015 | 9/7/2015 |
| 2 | in progress | 9/7/2015 | 9/25/2015 |
| 2 | completed | 9/25/2015 | 10/7/2015 |
| 2 | reopened | 10/10/2015 | 10/16/2015 |
| 2 | completed | 10/16/2015 | null |
+-----------+--------------+------------+------------+
I would like to calculate the days opened but exclude the days between completed and reopened. For example, RequestID 1, the days opened will be (11/3/2015 - 9/1/2015) + (GetDate() - 11/3/2015), for request 2, the total days will be (10/7/2015 - 9/5/2015) + ( 10/16/2015 - 10/10/2015).
The result I want will be something like:
+-----------+-------------------------------+
| RequestID | DaysOpened |
+-----------+-------------------------------+
| 1 | 63 + (getdate() - 11/3/2015) |
| 2 | 38 |
+-----------+-------------------------------+
How do I approach this problem? thank you!
Tested. Works well. :)
Note:
1) I suppose the required result = (FirstCompleteEndDate - PendingStartDate)+(Sum of all the Reopen duration)
2) So I used the self joins. Table b provides the exact completed record which immediately follows the in process record for each RequestID. Table c provides Sum of all the Reopen duration.
--create tbl structure
create table #test (RequestID int, RequestStatus varchar(20), StartDate date, EndDate date)
go
--insert sample data
insert #test
select 1,'pending','09/01/2015','10/2/2015'
union all
select 1,'in progress','10/2/2015','10/20/2015'
union all
select 1,'completed','10/20/2015','11/3/2015'
union all
select 1,'reopened','11/3/2015',null
union all
select 2,'pending','09/05/2015','9/7/2015'
union all
select 2,'in progress','09/07/2015','9/25/2015'
union all
select 2,'completed','9/25/2015','10/7/2015'
union all
select 2,'reopened','10/10/2015','10/16/2015'
union all
select 2, 'completed','10/16/2015','11/12/2015'
union all
select 2,'reopened','11/20/2015',null
select * from #test
--below is solution
select a.RequestID, a.Startdate as [PendingStartDate], b.enddate as [FirstCompleteEndDate], c.startdate as [LatestReopenStartDate],
datediff(day,a.startdate,b.enddate)+c.ReopenDays as [days] from #test a
join (
select *, row_number()over(partition by RequestID,RequestStatus order by StartDate) as rid from #test
) as b
on a.RequestID = b.RequestID
join (
select distinct RequestID, RequestStatus, max(StartDate)over(partition by RequestID,RequestStatus) as StartDate,
Sum(Case when enddate is null then datediff(day,startdate,getdate())
when enddate is not null then datediff(day,startdate,enddate)
end)over(partition by RequestID,RequestStatus) as [ReopenDays]
from #test
where RequestStatus = 'reopened'
) as c
on b.RequestID = c.RequestID
where a.RequestStatus ='pending' and b.RequestStatus = 'completed' and b.rid = 1
Result:

MSSQL 2008 Merge Contiguous Dates With Groupings

I have searched high and low for weeks now trying to find a solution to my problem.
As far as I can ascertain, my SQL Server version (2008r2) is a limiting factor on this but, I am positive there is a solution out there.
My problem is as follows:
A have a table with potential contiguous dates in the form of Customer-Status-DateStart-DateEnd-EventID.
I need to merge contiguous dates by customer and status - the status field can shift up and down throughout a customers pathway.
Some example data is as follows:
DECLARE #Tbl TABLE([CustomerID] INT
,[Status] INT
,[DateStart] DATE
,[DateEnd] DATE
,[EventID] INT)
INSERT INTO #Tbl
VALUES (1,1,'20160101','20160104',1)
,(1,1,'20160104','20160108',3)
,(1,2,'20160108','20160110',4)
,(1,1,'20160110','20160113',7)
,(1,3,'20160113','20160113',9)
,(1,3,'20160113',NULL,10)
,(2,1,'20160101',NULL,2)
,(3,2,'20160109','20160110',5)
,(3,1,'20160110','20160112',6)
,(3,1,'20160112','20160114',8)
Desired output:
Customer | Status | DateStart | DateEnd
---------+--------+-----------+-----------
1 | 1 | 2016-01-01| 2016-01-08
1 | 2 | 2016-01-08| 2016-01-10
1 | 1 | 2016-01-10| 2016-01-13
1 | 3 | 2016-01-13| NULL
2 | 1 | 2016-01-01| NULL
3 | 2 | 2016-01-09| 2016-01-10
3 | 1 | 2016-01-10| 2016-01-14
Any ideas / code will be greatly received.
Thanks,
Dan
Try this
DECLARE #Tbl TABLE([CusomerID] INT
,[Status] INT
,[DateStart] DATE
,[DateEnd] DATE
,[EventID] INT)
INSERT INTO #Tbl
VALUES (1,1,'20160101','20160104',1)
,(1,1,'20160104','20160108',3)
,(1,2,'20160108','20160110',4)
,(1,1,'20160110','20160113',7)
,(1,3,'20160113','20160113',9)
,(1,3,'20160113',NULL,10)
,(2,1,'20160101',NULL,2)
,(3,2,'20160109','20160110',5)
,(3,1,'20160110','20160112',6)
,(3,1,'20160112','20160114',8)
;WITH CTE
AS
(
SELECT CusomerID ,
Status ,
DateStart ,
COALESCE(DateEnd, '9999-01-01') AS DateEnd,
EventID,
ROW_NUMBER() OVER (ORDER BY CusomerID, EventID) RowId,
ROW_NUMBER() OVER (PARTITION BY CusomerID, Status ORDER BY EventID) StatusRowId FROM #Tbl
)
SELECT
A.CusomerID ,
A.Status ,
A.DateStart ,
CASE WHEN A.DateEnd = '9999-01-01' THEN NULL
ELSE A.DateEnd END AS DateEnd
FROM
(
SELECT
CTE.CusomerID,
CTE.Status,
MIN(CTE.DateStart) AS DateStart,
MAX(CTE.DateEnd) AS DateEnd
FROM
CTE
GROUP BY
CTE.CusomerID,
CTE.Status,
CTE.StatusRowId -CTE.RowId
) A
ORDER BY A.CusomerID, A.DateStart
Output
CusomerID Status DateStart DateEnd
----------- ----------- ---------- ----------
1 1 2016-01-01 2016-01-08
1 2 2016-01-08 2016-01-10
1 1 2016-01-10 2016-01-13
1 3 2016-01-13 NULL
2 1 2016-01-01 NULL
3 2 2016-01-09 2016-01-10
3 1 2016-01-10 2016-01-14

The highest value from list-distinct

Can anyone help me with query, I have table
vendorid, agreementid, sales
12001 1004 700
5291 1004 20576
7596 1004 1908
45 103 345
41 103 9087
what is the goal ?
when agreemtneid >1 then show me data when sales is the highest
vendorid agreementid sales
5291 1004 20576
41 103 9087
Any ideas ?
Thx
Well you could try using a CTE and ROW_NUMBER something like
;WITH Vals AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY AgreementID ORDER BY Sales DESC) RowID
FROM MyTable
WHERE AgreementID > 1
)
SELECT *
FROM Vals
WHERE RowID = 1
This will avoid you returning multiple records with the same sale.
If that was OK you could try something like
SELECT *
FROM MyTable mt INNER JOIN
(
SELECT AgreementID, MAX(Sales) MaxSales
FROM MyTable
WHERE AgreementID > 1
) MaxVals ON mt.AgreementID = MaxVals.AgreementID AND mt.Sales = MaxVals.MaxSales
SELECT TOP 1 WITH TIES *
FROM MyTable
ORDER BY DENSE_RANK() OVER(PARTITION BY agreementid ORDER BY SIGN (SIGN (agreementid - 2) + 1) * sales DESC)
Explanation
We break table MyTable into partitions by agreementid.
For each partition we construct a ranking or its rows.
If agreementid is greater than 1 ranking will be equal to ORDER BY sales DESC.
Otherwise ranking for every single row in partition will be the same: ORDER BY 0 DESC.
See how it looks like:
SELECT *
, SIGN (SIGN (agreementid - 2) + 1) * sales AS x
, DENSE_RANK() OVER(PARTITION BY agreementid ORDER BY SIGN (SIGN (agreementid - 2) + 1) * sales DESC) AS rnk
FROM MyTable
+----------+-------------+-------+-------+-----+
| vendorid | agreementid | sales | x | rnk |
+----------|-------------|-------+-------+-----+
| 0 | 0 | 3 | 0 | 1 |
| -1 | 0 | 7 | 0 | 1 |
| 0 | 1 | 3 | 0 | 1 |
| -1 | 1 | 7 | 0 | 1 |
| 41 | 103 | 9087 | 9087 | 1 |
| 45 | 103 | 345 | 345 | 2 |
| 5291 | 1004 | 20576 | 20576 | 1 |
| 7596 | 1004 | 1908 | 1908 | 2 |
| 12001 | 1004 | 700 | 700 | 3 |
+----------+-------------+-------+-------+-----+
Then using TOP 1 WITH TIES construction we leave only rows where rnk equals 1.
you can try like this.
SELECT TOP 1 sales FROM MyTable WHERE agreemtneid > 1 ORDER BY sales DESC
I really do not know the business logic behind agreement_id > 1. It looks to me you want the max sales (with ties) by agreement id regardless of vendor_id.
First, lets create a simple sample database.
-- Sample table
create table #sales
(
vendor_id int,
agreement_id int,
sales_amt money
);
-- Sample data
insert into #sales values
(12001, 1004, 700),
(5291, 1004, 20576),
(7596, 1004, 1908),
(45, 103, 345),
(41, 103, 9087);
Second, let's solve this problem using a common table expression to get a result set that has each row paired with the max sales by agreement id.
The select statement just applies the business logic to filter the data to get your answer.
-- CTE = max sales for each agreement id
;
with cte_sales as
(
select
vendor_id,
agreement_id,
sales_amt,
max(sales_amt) OVER(PARTITION BY agreement_id) AS max_sales
from
#sales
)
-- Filter by your business logic
select * from cte_sales where sales_amt = max_sales and agreement_id > 1;
The screen shot below shows the exact result you wanted.

Resources