MSSQL: calculate time be many starts and stops - sql-server

Is it possible to make this in sql:
Have table with records:
ID LaborID OrderNr OrderStatusID OrderStatusDate
12990 3731573 OPT1814378 2 2018-05-28 09:35:30.123
13105 230687389 OPT1814378 1 2018-05-29 10:32:14.850
13106 230687389 OPT1814378 2 2018-05-29 10:52:14.403
13123 230480202 OPT1814378 1 2018-05-29 13:18:05.233
13130 230480202 OPT1814378 0 2018-05-29 13:29:17.360
12837 3731573 OPT1814089 2 2018-05-25 20:28:24.817
12906 10138504 OPT1814089 1 2018-05-26 10:41:18.680
12909 10138504 OPT1814089 2 2018-05-26 10:57:40.733
12913 10138504 OPT1814089 1 2018-05-26 11:41:48.387
12920 10138504 OPT1814089 0 2018-05-26 12:15:48.590
where
OrderStatusID
0 - End
1 - Begin
2 - pause
Need calculate working time from begin to pause (1->2) or from begin to end (1->0).
My problem is that there are some conditions that I have to adhere to:
If first record is 2 then ignore
Work begin always with 1
But can have more pause (1->2)
The last work end record everytime with 0
The result in this case will be:
OPT1814378 230687389 00:20:00
OPT1814378 230480202 00:11:12
OPT1814089 10138504 00:16:12
OPT1814089 10138504 00:34:00

Hopefully this is not that ugly.
; with
cte as
(
-- CTE for generating a sequence no
select *, rn = row_number() over (partition by OrderNr
order by OrderStatusDate)
from #table
),
cte2 as
(
-- Clean up invalid any rows and regenerate new sequence no
select ID, LaborID, OrderNr, OrderStatusID, OrderStatusDate,
rn = row_number() over (partition by OrderNr
order by OrderStatusDate)
from cte
where (rn = 1 and OrderStatusID = 1)
or rn >= 2
)
select OrderNr, LaborID,
convert(varchar(10),
dateadd(second,
datediff(second,
min(OrderStatusDate),
max(OrderStatusDate)),
0),
108)
from cte2
group by OrderNr,
LaborID,
(rn - 1) / 2
(rn - 1) / 2 will gives the value 0, 0, 1, 1, 2, 2 etc for grouping the rows two by two.

This one also works
;WITH CTE
AS (
SELECT row_number() OVER ( PARTITION BY ordernr ORDER BY id ) RN ,* FROM test_ti
)
,cte2
AS (
SELECT *
FROM cte c1
WHERE NOT EXISTS (
SELECT *
FROM cte c2
WHERE c1.id = c2.id
AND c2.rn = 1
AND c2.orderstatusid = 2
)
)
SELECT OrderNr
,LaborId
,TimeInterval
FROM ( SELECT DateDiff(MI, TIME, NxtTm) TimeInterval ,*
FROM (
SELECT * ,lead(TIME) OVER ( ORDER BY id ) NxtTm
FROM cte2
) x
WHERE orderstatusid <> 0
) y
WHERE orderstatusid = 1

Related

How to make Row_number() based on condition?

I have list of sample data. Using this I need new column which having sequence number. But condition of this sequence number is if consecutively InRange column value 1 then only it generate sequence number.In between if InRange value 0 then again sequence number start from 1 and so on.
Below query which I have created but not return expected result.
CREATE TABLE #Result (ID INT,Value INT,InRange BIT)
INSERT INTO #Result
SELECT 1 ,211,0
UNION SELECT 2 ,205,1
UNION SELECT 3 ,214,0
UNION SELECT 4 ,202,1
UNION SELECT 5 ,204,1
UNION SELECT 6 ,203,1
UNION SELECT 7 ,209,0
UNION SELECT 8 ,216,0
UNION SELECT 9 ,205,1
UNION SELECT 10 ,224,0
Query:
SELECT *
,CASE WHEN InRange=1 THEN ROW_NUMBER()OVER(Order by Id asc) ELSE 0 END AS ExpectedColumn
FROM #Result
Expected result.
ID Value InRange ExpectedColumn
1 211 0 0
2 205 1 1
3 214 0 0
4 202 1 1
5 204 1 2
6 203 1 3
7 209 0 0
8 216 0 0
9 205 1 1
10 224 0 0
This is a gaps and islands problem, with the islands being each group of records to which you want to assign its own row number sequence. One straightforward way to handle this uses the difference in row numbers method:
WITH cte1 AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY ID) rn1
FROM #Result
WHERE InRange = 1
),
cte2 AS (
SELECT t1.*,
ROW_NUMBER() OVER (ORDER BY t1.ID) - t2.rn1 AS diff
FROM #Result t1
LEFT JOIN cte1 t2
ON t1.ID = t2.ID
)
SELECT ID, Value, InRange,
CASE WHEN InRange <> 0
THEN ROW_NUMBER() OVER (PARTITION BY diff ORDER BY ID)
ELSE 0 END AS ExpectedColumn
FROM cte2
ORDER BY ID;
Demo
with grouped_data as (
select
*,
count(case when InRange = 0 then 1 else null end) over(order by ID rows between unbounded preceding and current row) as group_number
from #Result
)
select
ID,
Value,
InRange,
row_number() over(partition by group_number order by ID) - 1 as expected_column
from grouped_data
order by ID;

how to select last rows where one certain value exist but not if it's in between

I have this table. With case#, Linenumber and code#.
case# Linenumber Code#
99L1HV 1 1510
99L1HV 2 4320
99PX58 1 1510
99PX58 2 4320
99PX58 3 4500
99PX59 1 1510
99PX59 2 918
99PX59 3 4320
How can I get the records with the last LineNumber per case# where code = 4320
The output should be like this
case# Linenumber Code
99L1HV 2 4320
99PX59 3 4320
Using ROW_NUMBER to get a number that's in the opposite order of the linenumber per case#.
Then the last lines will have RN = 1
SELECT [case#], Linenumber, [Code#]
FROM
(
SELECT [case#], Linenumber, [Code#],
ROW_NUMBER() OVER (PARTITION BY [case#] ORDER BY Linenumber DESC) AS RN
FROM yourtable
) q
WHERE RN = 1
AND [Code#] = 4320
ORDER BY [case#];
Or the more concise version.
Using a TOP 1 WITH TIES in combination with an ORDER BY ROW_NUMBER.
SELECT *
FROM
(
SELECT TOP 1 WITH TIES [case#], Linenumber, [Code#]
FROM yourtable
ORDER BY ROW_NUMBER() OVER (PARTITION BY [case#] ORDER BY Linenumber DESC)
) q
WHERE [Code#] = 4320
ORDER BY [case#];
cte is to generate a running number by case#. rn = 1 will be the last row for each case#
; with cte as
(
select *, rn = row_number() over (partition by [case#] order by linenumber desc)
from yourtable
)
select *
from cte
where rn = 1
and [code#] = 4320
declare #t table (
CaseNumber varchar(10),
LineNumber int,
CodeNumber int
);
-- Filling the table with data, skipped
select t.*
from #t t
where t.CodeNumber = 4320
and not exists (
select 0 from #t x
where x.CaseNumber = t.CaseNumber
and x.LineNumber > t.LineNumber
);
with cte as
(select case#, max(linenumber)
from source_table
group by case#)
select t1.*
from source_table t1 inner join cte t2
on t1.case# = t2.case# and t1.linenumber = t2.linenumber
where t1.Code# = 4320

How to get count of consecutive dates

For example there is some table with dates:
2015-01-01
2015-01-02
2015-01-03
2015-01-06
2015-01-07
2015-01-11
I have to write ms sql query, which will return count of consecutive dates starting from every date in the table. So the result will be like:
2015-01-01 1
2015-01-02 2
2015-01-03 3
2015-01-06 1
2015-01-07 2
2015-01-11 1
It seems to me that I should use LAG and LEAD functions, but now I even can not imagine the way of thinking.
CREATE TABLE #T ( MyDate DATE) ;
INSERT #T VALUES ('2015-01-01'),('2015-01-02'),('2015-01-03'),('2015-01-06'),('2015-01-07'),('2015-01-11')
SELECT
RW=ROW_NUMBER() OVER( PARTITION BY GRP ORDER BY MyDate) ,MyDate
FROM
(
SELECT
MyDate, DATEDIFF(Day, '1900-01-01' , MyDate)- ROW_NUMBER() OVER( ORDER BY MyDate ) AS GRP
FROM #T
) A
DROP TABLE #T;
You can use this CTE:
;WITH CTE AS (
SELECT [Date],
ROW_NUMBER() OVER(ORDER BY [Date]) AS rn,
CASE WHEN DATEDIFF(Day, PrevDate, [Date]) IS NULL THEN 0
WHEN DATEDIFF(Day, PrevDate, [Date]) > 1 THEN 0
ELSE 1
END AS flag
FROM (
SELECT [Date], LAG([Date]) OVER (ORDER BY [Date]) AS PrevDate
FROM #Dates ) d
)
to produce the following result:
Date rn flag
===================
2015-01-01 1 0
2015-01-02 2 1
2015-01-03 3 1
2015-01-06 4 0
2015-01-07 5 1
2015-01-11 6 0
All you have to do now is to calculate a running total of flag up to the first occurrence of a preceding zero value:
;WITH CTE AS (
... cte statements here ...
)
SELECT [Date], b.cnt + 1
FROM CTE AS c
OUTER APPLY (
SELECT TOP 1 COALESCE(rn, 1) AS rn
FROM CTE
WHERE flag = 0 AND rn < c.rn
ORDER BY rn DESC
) a
CROSS APPLY (
SELECT COUNT(*) AS cnt
FROM CTE
WHERE c.flag <> 0 AND rn < c.rn AND rn >= a.rn
) b
OUTER APPLY calculates the rn value of the first zero-valued flag that comes before the current row. CROSS APPLY calculates the number of records preceding the current record up to the first occurrence of a preceding zero valued flag.
I'm assuming this table:
SELECT *
INTO #Dates
FROM (VALUES
(CAST('2015-01-01' AS DATE)),
(CAST('2015-01-02' AS DATE)),
(CAST('2015-01-03' AS DATE)),
(CAST('2015-01-06' AS DATE)),
(CAST('2015-01-07' AS DATE)),
(CAST('2015-01-11' AS DATE))) dates(d);
Here's a recursive solution with explanations:
WITH
dates AS (
SELECT
d,
-- This checks if the current row is the start of a new group by using LAG()
-- to see if the previous date is adjacent
CASE datediff(day, d, LAG(d, 1) OVER(ORDER BY d))
WHEN -1 THEN 0
ELSE 1 END new_group,
-- This will be used for recursion
row_number() OVER(ORDER BY d) rn
FROM #Dates
),
-- Here, the recursion happens
groups AS (
-- We initiate recursion with rows that start new groups, and calculate "GRP"
-- numbers
SELECT d, new_group, rn, row_number() OVER(ORDER BY d) grp
FROM dates
WHERE new_group = 1
UNION ALL
-- We then recurse by the previously calculated "RN" until we hit the next group
SELECT dates.d, dates.new_group, dates.rn, groups.grp
FROM dates JOIN groups ON dates.rn = groups.rn + 1
WHERE dates.new_group != 1
)
-- Finally, we enumerate rows within each group
SELECT d, row_number() OVER (PARTITION BY grp ORDER BY d)
FROM groups
ORDER BY d
SQLFiddle

SQL Server: Gap / Island, 365 day "contiguous" block

I have a table that looks like this:-
tblMeterReadings
id meter date total
1 1 03/01/2014 100.1
1 1 04/01/2014 184.1
1 1 05/01/2014 134.1
1 1 06/01/2014 132.1
1 1 07/01/2014 126.1
1 1 08/01/2014 190.1
This is an 8 day "contiguous block" from '2014-01-03' to '2014-01-08'.
In the real table there are "contiguous blocks" of years in length.
I need to select the MOST RESCENT CONTINUOUS 365 DAY BLOCK (filtered by meter column). If 365 cannot be found, then it should select next largest continuous block.
When I say CONTINUOUS I mean there must be no days missing.
This is beyond me, so if someone can solve... I will be very impressed.
using distinct to not count days with 2 sets of data
declare #gapdays int = 2 -- replace this with 365 in your case
;with x as
(
select datediff(d, '2014-01-01', [date])-dense_rank()over(order by [date]) grp
,[date]
from #t
)
select top 1 max([date]) last_date, min([date]) first_date, count(distinct [date]) days_in_a_row
from x
group by grp
having count(distinct [date]) >= #gapdays
order by max([date]) desc
There you go:
declare #tblMeterReadings table (id int, meter int, [date] date, total money)
insert into #tblMeterReadings ( id, meter, date, total )
values
(1, 1, '03/01/2014', 100.1),
(1, 1, '04/01/2014', 184.1),
(1, 1, '05/01/2014', 134.1),
(1, 1, '06/01/2014', 132.1),
(1, 1, '07/01/2014', 126.1),
(1, 1, '08/01/2014', 190.1),
(1, 1, '10/01/2014', 200.1),
(1, 1, '12/01/2014', 202.1),
(1, 1, '13/01/2014', 204.1)
;with data as (
select i = datediff(day, '2014', [date]), *
from #tblMeterReadings l
)
, islands as (
select island = l.i - row_number() over (order by i), l.*
from data l
)
, spans as (
select l = min(i), r = max(i)
from islands i
group by island
)
select *
from spans s
left join data l on s.l = l.i
left join data r on s.r = r.i
Most recent continuous block not exceeding 365 days in length will be as follows:
select top 1 *
from spans s
left join data l on s.l = l.i
left join data r on s.r = r.i
where s.l - s.r < 365
order by s.l - s.r desc, s.r desc
With recursive CTE and datepart(dayofyear, date):
with cte as
(
select id, meter, date, datepart(dayofyear, date) as x, cast(1 as int) as level, t1.date as startDate from tblMeterReadings t1
where meter = 1
and not exists(select * from tblMeterReadings t2 where (datepart(dayofyear, t1.date) - 1) = datepart(dayofyear, t2.date))
union all
select t1.id, t1.meter, t1.date, datepart(dayofyear, t1.date) as x, t2.level + 1, t2.startDate from tblMeterReadings t1
inner join cte t2 ON (datepart(dayofyear, t1.date)) = (datepart(dayofyear, t2.date) + 1)
)
select TOP 365 * from cte
where cte.startDate = (select top 1 startdate
from cte
--where Level <= 365
order by Level desc, startDate desc)
order by Level desc
OPTION ( MAXRECURSION 365 )
SQL Fiddle example

Is it possible to write single query for following scenario?

Is it possible to write single query for following scenario?
Scenario -
Table -
column name - id date isPaid
values - 1 1/1/2011 1
2 1/2/2011 1
3 1/3/2011 0
4 1/4/2011 0
5 1/5/2011 0
I want a result set which contains (all ispaid = 1) and (only 1 row of ispaid = 0 whose date is smaller).
Result set:
column name - id date isPaid
values - 1 1/1/2011 1
2 1/2/2011 1
3 1/3/2011 0
Thanks
You can use UNIONdocs
SELECT
[id],
[date],
[isPaid]
FROM
[tablename]
WHERE
[ispaid] = 1
UNION ALL
SELECT TOP 1
[id],
[date],
[isPaid]
FROM
[tablename]
WHERE
[ispaid] = 0
ORDER BY
[date] ASC
This should do what you need in SQL Server 2005 and higher.
select
[id],
[date],
isPaid
from (
select
[id],
[date],
isPaid,
ROW_NUMBER() over (partition by ispaid order by date) as row
from table_name t ) a
where ispaid = 1
or row = 1
order by [date]
Assuming date will be provided to the query by user, for which data is to be retrieved
select t.*
from table t,
(select Top 1 id, date, ispaid
from table
where ispaid = 0 and date<?) np
where (t.ispaid=1 and t.date = ? ) OR (t.id = np.id)

Resources