Working Out The Time Difference Between Two Rows On Multiple Occurances - sql-server

I'm working on what will be a new function but I'm trying to get a foundation of how to work out the time difference between two rows every time a value appears in two other columns appear (prev_value, current_value). The difference will then be wrapped in a sum for a final value against each ID.
The example below is a small portion of the table, so if we use ID 194422 hopefully I can get my point across.
The Datediff I'm trying to work out would be between every record that has a Current_Value 12026 and Prev_Value 12026. So between the top 2 rows, I would expect 3 minute difference and between rows 3 and 4 , I would expect a 4 minute difference.
If the row contains a current_value and prev_value of 12026 then I would also include this in the time so on row 13,14,15 I would expect a 9 min difference between row 13 and 15.
I would then group and sum these results by the ID.
ROWID| ID | Columnname | prev_value | current_value | OperationTime
1 |197684 | STATUSID | 1 | 12026 | 2020-02-11 13:37:00.0010
2 |197684 | STATUSID | 12026 | 1 | 2020-02-11 13:40:00.000
3 |197684 | STATUSID | 1 | 12026 | 2020-02-11 13:44:00.000
4 |197684 | STATUSID | 12026 | 1 | 2020-02-11 13:48:00.000
5 |198662 | STATUSID | 1 | 12026 | 2020-02-24 15:10:00.000
6 |198662 | STATUSID | 12026 | 1 | 2020-02-24 15:20:00.000
7 |198662 | STATUSID | 1 | 12026 | 2020-02-24 15:23:00.000
8 |198662 | STATUSID | 12026 | 1 | 2020-02-24 15:41:00.000
9 |198662 | STATUSID | 1 | 12026 | 2020-02-24 16:24:00.000
10 |198662 | STATUSID | 12026 | 1 | 2020-02-24 17:05:00.000
11 |194422 | STATUSID | 1 | 12026 | 2020-02-25 09:04:00.000
12 |194422 | STATUSID | 12026 | 8 | 2020-02-25 09:07:00.000
13 |198662 | STATUSID | 1 | 12026 | 2020-02-26 15:32:00.000
14 |198662 | STATUSID | 12026 | 12026 | 2020-02-26 15:40:00.000
15 |198662 | STATUSID | 12026 | 1 | 2020-02-26 15:41:00.000
16 |194422 | STATUSID | 1 | 12026 | 2020-03-02 16:06:00.000
17 |194422 | STATUSID | 12026 | 8 | 2020-03-02 16:15:00.000
At the end result would then be:
RowID | TimeSpent(Mins)
194422 | 9
197684 | 7
198662 | 78
I've now tried a few different methods but records are either missing or incorrect times are being retrieved.
I've look at a CTE with an INNER and LEFT JOIN and OUTER JOIN back on the CTE, I've tried two CTE's, I've looked at LAG, RANK and ROW_NUMBER() in all examples.
The script below sort of works but it doesn't pull in all my results and in this example it misses ID 194422.
DECLARE #STATUSID INT
SET #STATUSID = 12026
;WITH CTE
AS (SELECT prev_value,
current_value,
OPERATIONTIME,
ROWID,
ID,
DENSE_RANK () OVER (PARTITION BY prev_value ORDER BY operationtime) AS ROWNUMBER
FROM AUDITREVIEW
WHERE columnname = 'STATUSID'
AND ( current_value = #STATUSID
OR prev_value = #STATUSID )
)
SELECT *,
DATEDIFF(MINUTE, CTE2.cte2OPERATIONTIME, CTE1.operationtime) as t
INTO #Temp
FROM
CTE AS CTE1
OUTER APPLY
(SELECT top 1
prev_value as cte2prev_value,
current_value cte2current_value,
OPERATIONTIME cte2OPERATIONTIME,
ROWID cte2rowID,
ID cte2ID,
DENSE_RANK () OVER ( ORDER BY operationtime) AS cte2ROWNUMBER
FROM CTE
WHERE CTE.ID = CTE1.ID
AND CTE.PREV_VALUE = CTE1.CURRENT_VALUE
AND CTE.ROWNUMBER < CTE1.ROWNUMBER
ORDER BY CTE.OPERATIONTIME DESC
) CTE2
--WHERE CTE1.WORKORDERID = 194422
SELECT SUM(t), ID
FROM #Temp
WHERE cte2prev_value <> #STATUSID
GROUP BY ID
DROP TABLE #Temp

try the following:
SELECT a1.ID,
SUM(DATEDIFF(MINUTE, a1.OperationTime, a2.OperationTime)) Time_Spent_in_Minutes
FROM AUDITREVIEW a1
JOIN AUDITREVIEW a2 ON a2.ID = a1.ID
AND ((a1.ROWID = a2.ROWID)
OR (a1.ROWID + 1 = a2.ROWID))
WHERE(a1.current_value = 12026 AND a2.prev_value = 12026)
AND ((a1.prev_value = 1 AND a2.current_value = 1)
OR (a1.current_value = 12026 AND a1.prev_value = 12026)
OR (a2.current_value = 12026 AND a2.prev_value = 12026))
GROUP BY a1.ID
ORDER BY 1;
db<>fiddle demo

Related

How to find difference between two tables in MSSQL

I have got two tables 'Customer'.
The first one:
ID | UserID | Date
1. | 1 | 2018-05-01
2. | 1 | 2018-05-02
The second one:
ID | UserID | Date
1. | 1 | 2018-05-01
2. | 1 | 2018-05-02
3. | 1 | 2018-05-03
So, as you can see in the second table, there is one row more.
I have written so far this code:
;with cte_table1 as (
select UserID, count(id) cnt from db1.Customer group by UserID
),
cte_table2 as (
select UserID, count(id) cnt from db2.Customer group by UserID
)
select * from cte_table1 t1
join cte_table2 t2 on t2.UserID = t1.UserID
where t1.cnt <> t2.cnt
and this gives me expected result:
UserID | cnt | UserID | cnt
1 | 2 | 1 | 3
And so far, everything is fine. The thing is, these two tables have many rows and I'd like to have result with dates, where cnt does not match.
In other words, I'd like to have something like this:
UserID | cnt | Date | UserID | cnt | Date
1 | 2 | 2018-05-01 | 1 | 3 | 2018-05-01
1 | 2 | 2018-05-02 | 1 | 3 | 2018-05-01
1 | 2 | NULL | 1 | 3 | 2018-05-03
The best soulution would be resultset where both cte's are joined to give this:
UserID | cnt | Date | UserID | cnt | Date
1 | 2 | 2018-05-01 | 1 | 3 | 2018-05-01
1 | 2 | 2018-05-02 | 1 | 3 | 2018-05-01
1 | 2 | NULL | 1 | 3 | 2018-05-03
1 | 2 | 2018-05-30 | 1 | 3 | NULL
You should do a FULL OUTER JOIN query like below
Select
C1.UserID,
C1.cnt,
C1.Date,
C2.UserID,
C2.cnt,
C2.Date
from
db1.Customer C1
FULL OUTER JOIN
db2.Customer C2
on C1.UserId=C2.UserId and C1.date=C2.Date

Get all days between two dates with all day hours in SQL Server

I have to generate a result set of a SQL query which should match the following, but let me explain both inputs and outputs:
I have a table named Orders and this table has some orders in some days at some hours, then, I have been requested to provide a result-set which should get all days between two dates (i.e. 2017-10-01 and 2017-10-07), with all 24 hours for each day, even if that day or that hour had no orders, but it should be appeared with 0 value.
+------------+------+-------------+
| Day | Hour | TotalOrders |
+------------+------+-------------+
| 2017-10-01 | 0 | 0 |
+------------+------+-------------+
| 2017-10-01 | 1 | 3 |
+------------+------+-------------+
| 2017-10-01 | 2 | 4 |
+------------+------+-------------+
| 2017-10-01 | 3 | 0 |
+------------+------+-------------+
| 2017-10-01 | 4 | 7 |
+------------+------+-------------+
| 2017-10-01 | 5 | 0 |
+------------+------+-------------+
| 2017-10-01 | 6 | 0 |
+------------+------+-------------+
| 2017-10-01 | 7 | 9 |
+------------+------+-------------+
| 2017-10-01 | 8 | 0 |
+------------+------+-------------+
| 2017-10-01 | 9 | 0 |
+------------+------+-------------+
| 2017-10-01 | 10 | 0 |
+------------+------+-------------+
| 2017-10-01 | 11 | 0 |
+------------+------+-------------+
| 2017-10-01 | 12 | 0 |
+------------+------+-------------+
| 2017-10-01 | 13 | 0 |
+------------+------+-------------+
| 2017-10-01 | 14 | 0 |
+------------+------+-------------+
| 2017-10-01 | 15 | 0 |
+------------+------+-------------+
| 2017-10-01 | 16 | 0 |
+------------+------+-------------+
| 2017-10-01 | 17 | 0 |
+------------+------+-------------+
| 2017-10-01 | 18 | 0 |
+------------+------+-------------+
| 2017-10-01 | 19 | 0 |
+------------+------+-------------+
| 2017-10-01 | 20 | 0 |
+------------+------+-------------+
| 2017-10-01 | 21 | 0 |
+------------+------+-------------+
| 2017-10-01 | 22 | 0 |
+------------+------+-------------+
| 2017-10-01 | 23 | 0 |
+------------+------+-------------+
| 2017-10-02 | 0 | 0 |
+------------+------+-------------+
| 2017-10-02 | 1 | 0 |
+------------+------+-------------+
| 2017-10-02 | 2 | 0 |
+------------+------+-------------+
| 2017-10-02 | 3 | 0 |
+------------+------+-------------+
| 2017-10-02 | 4 | 0 |
+------------+------+-------------+
| 2017-10-02 | 5 | 0 |
+------------+------+-------------+
| 2017-10-02 | 6 | 0 |
+------------+------+-------------+
| 2017-10-02 | 7 | 0 |
+------------+------+-------------+
| and so on .................. |
+------------+------+-------------+
So, the above result set should contain every day between the given two dates, and each day should have all 24 hours, irrespective off that day had orders and the same for hour (either it had orders or not)
I did it using a nested CTE:
DECLARE #MinDate DATE = '20171001',
#MaxDate DATE = '20171006';
;WITH INNER_CTE as(
SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #MinDate)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b) ,
OUTER_CTE as (
select * from INNER_CTE
cross apply (
SELECT TOP (24) n = ROW_NUMBER() OVER (ORDER BY [object_id]) -1
FROM sys.all_objects ORDER BY n)) t4
)
select t1.Date, t1.n [Hour], ISNULL(t2.TotalORders,0) TotalOrders from
OUTER_CTE t1
LEFT JOIN orders t2 on t1.Date = t2.[Day] and t1.n = t2.[Hour]
Good Reading about generating sequences using a query here: https://sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1
I prefer to do this with a tally table instead of using loops. The performance is much better. I keep a tally on my system as a view like this.
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
GO
Now that we have our tally table we can use some basic math to get the desired output. Something along these lines.
declare #Date1 datetime = '2017-10-01';
declare #Date2 datetime = '2017-10-07';
select Day = convert(date, DATEADD(hour, t.N, #Date1))
, Hour = t.N - 1
, TotalOrders = COUNT(o.OrderID)
from cteTally t
left join Orders o on o.OrderDate = DATEADD(hour, t.N, #Date1)
where t.N <= DATEDIFF(hour, #Date1, #Date2)
group by DATEDIFF(hour, #Date1, #Date2)
, t.N
The simplest way is to just use a temporary table or table variable to fill the desired result set, and then count the number of Orders for each row.
declare #Date1 date = '2017-10-01';
declare #Date2 date = '2017-10-07';
declare #Hour int;
declare #Period table (Day Date, Hour Time);
while #Date1 <= #Date2
begin
set #Hour = 0;
while #Hour < 24
begin
insert into #Period (Day, Hour) values (#Date1, TimeFromParts(#Hour,0,0,0,0));
set #Hour = #Hour + 1;
end
set #Date1 = DateAdd(Day, 1, #Date1);
end
select Day, Hour,
(select count(*)
from Orders
where Orders.Day = Period.Day and Orders.Hour = Period.Hour) as TotalOrders
from #Period as Period;

How to combine multiple rows into one row and multiple column in SQL Server?

I have different tables through I made temp table and here is the result set of temp table:
car_id | car_type | status | count
--------+----------+---------+------
100421 | 1 | 1 | 9
100421 | 1 | 2 | 8
100421 | 1 | 3 | 3
100421 | 2 | 1 | 6
100421 | 2 | 2 | 8
100421 | 2 | 3 | 3
100422 | 1 | 1 | 5
100422 | 1 | 2 | 8
100422 | 1 | 3 | 7
Here is the meaning of status column:
1 as sale
2 as purchase
3 as return
Now I want to show this result set as below
car_id | car_type | sale | purchase | return
--------+----------+------+----------+----------
100421 | 1 | 9 | 8 | 3
100421 | 2 | 6 | 8 | 3
100422 | 1 | 5 | 8 | 7
I tried but unable to generate this result set. Can anyone help?
You can also use a CASE expression.
Query
select [car_id], [car_type],
max(case [status] when 1 then [count] end) as [sale],
max(case [status] when 2 then [count] end) as [purchase],
max(case [status] when 3 then [count] end) as [return]
from [your_table_name]
group by [car_id], [car_type]
order by [car_id];
Try this
select car_id ,car_type, [1] as Sale,[2] as Purchase,[3] as [return]
from (select car_id , car_type , [status] ,[count] from tempTable)d
pivot(sum([count]) for [status] in([1],[2],[3]) ) as pvt
also you can remove the subquery if you don't have any condition
like
select car_id ,car_type, [1] as Sale,[2] as Purchase,[3] as [return]
from tempTable d
pivot(sum([count]) for [status] in([1],[2],[3]) ) as pvt

SQL Server query for next row value where previous row value

This query gives me Event values from 1 to 20 within an hour, how to add to that if a consecutive Event value is >=200 as well?
SELECT ID, count(Event) as numberoftimes
FROM table_name
WHERE Event >=1 and Event <=20
GROUP BY ID, DATEPART(HH, AtHour)
HAVING DATEPART(HH, AtHour) <= 1
ORDER BY ID desc
In this dummy 24h table:
+----+-------+--------+
| ID | Event | AtHour |
+----+-------+--------+
| 1 | 1 | 11:00 |
| 1 | 4 | 11:01 |
| 1 | 1 | 11:02 |
| 1 | 20 | 11:03 |
| 1 | 200 | 11:04 |
| 1 | 1 | 13:00 |
| 1 | 1 | 13:05 |
| 1 | 2 | 13:06 |
| 1 | 500 | 13:07 |
| 1 | 39 | 13:10 |
| 1 | 50 | 13:11 |
| 1 | 2 | 13:12 |
+----+-------+--------+
I would like to select IDs with Event with values with range between 1 and 20 followed immediately by value greater than or equal to 200 within an hour.
Expected result should be something like that:
+----+--------+
| ID | AtHour |
+----+--------+
| 1 | 11 |
| 1 | 13 |
| 2 | 11 |
| 2 | 14 |
| 3 | 09 |
| 3 | 12 |
+----+--------+
or just how many times it has happened for unique ID instead of which hour.
Please excuse me I am still rusty with post formatting!
CREATE TABLE data (Id INT, Event INT, AtHour SMALLDATETIME);
INSERT data (Id, Event, AtHour) VALUES
(1,1,'2017-03-16 11:00:00'),
(1,4,'2017-03-16 11:01:00'),
(1,1,'2017-03-16 11:02:00'),
(1,20,'2017-03-16 11:03:00'),
(1,200,'2017-03-16 11:04:00'),
(1,1,'2017-03-16 13:00:00'),
(1,1,'2017-03-16 13:05:00'),
(1,2,'2017-03-16 13:06:00'),
(1,500,'2017-03-16 13:07:00'),
(1,39,'2017-03-16 13:10:00')
;
; WITH temp as (
SELECT rownum = ROW_NUMBER() OVER (PARTITION BY id ORDER BY AtHour)
, *
FROM data
)
SELECT a.id, DATEPART(HOUR, a.AtHour) as AtHour, COUNT(*) AS NumOfPairs
FROM temp a JOIN temp b ON a.rownum = b.rownum-1
WHERE a.Event BETWEEN 1 and 20 AND b.Event >= 200
AND DATEDIFF(MINUTE, a.AtHour, b.AtHour) <= 60
GROUP BY a.id, DATEPART(HOUR, a.AtHour)
;

Find max and min of a column and update the first column sql server

Based on the product and product key, update the column ord_by. There should be only one min and max for a product and product_key .
E.g: Table
+-------------+---------+-------+--------+
| Product_key | product | price | ord_by |
+-------------+---------+-------+--------+
| 1 | ABC | 10 | |
| 1 | ABC | 10 | |
| 1 | ABC | 20 | |
| 1 | ABC | 100 | |
| 1 | ABC | 100 | |
| 2 | EFG | 20 | |
| 2 | EFG | 40 | |
| 3 | ABC | 100 | |
+-------------+---------+-------+--------+
Expected output:
+-------------+---------+-------+--------+
| Product_key | product | price | ord_by |
+-------------+---------+-------+--------+
| 1 | ABC | 10 | Min |
| 1 | ABC | 10 | Mid |
| 1 | ABC | 20 | Mid |
| 1 | ABC | 100 | Mid |
| 1 | ABC | 100 | Max |
| 2 | EFG | 20 | Min |
| 2 | EFG | 40 | Max |
| 3 | ABC | 100 | None |
+-------------+---------+-------+--------+
My try :
;WITH ord_cte
AS (
SELECT product
,product_key
,max(price) as max_price
,min(price) as min_price
FROM t_prod_ord
group by product,product_key
)
UPDATE t1
SET ord_by = case
when t2.max_price =t2.min_price then 'none'
when t2.max_price=t1.price then 'max'
when t2.min_price=t1.price then 'min'
else 'mid' end
FROM t_prod_ord t1
INNER JOIN ord_cte t2 ON t1.product_key = t2.product_key and t1.product=t2.product
using this query it is updating more than one max and min value for column ord_by.
Generate row number for each Product_key order by Price in both ASC and DESC order. Then use the row number in CASE statement to find the Min/Max values
Count() Over() aggregate window function will help you find the total count of each Product_key which we can use it for finding None
Here is one way
;WITH cte
AS (SELECT *,
Row_number()OVER(PARTITION BY Product_key ORDER BY price) AS Min_KEY,
Row_number()OVER(PARTITION BY Product_key ORDER BY price DESC) AS Max_KEY,
Count(1)OVER(partition BY Product_key) AS cnt
FROM Yourtable)
SELECT Product_key,
product,
price,
CASE
WHEN cnt = 1 THEN 'None'
WHEN Min_KEY = 1 THEN 'Min'
WHEN Max_Key = 1 THEN 'Max'
ELSE 'Mid'
END
FROM cte
Another way to do with out cte...
SELECT [Product_key],
[product],
[price],
CASE
WHEN Max(RN)
OVER(
PARTITION BY PRODUCT_KEY, PRODUCT
)=1 AND RN=1 THEN 'NONE'
WHEN Min(RN)
OVER(
PARTITION BY PRODUCT_KEY, PRODUCT
) = RN THEN 'MIN'
WHEN Max(RN)
OVER(
PARTITION BY PRODUCT_KEY, PRODUCT
) = RN THEN 'MAX'
ELSE 'MID'
END ORDER_BY
FROM (SELECT *,
Row_number()
OVER(
PARTITION BY PRODUCT_KEY, PRODUCT
ORDER BY PRICE) RN
FROM TABLE1)Z

Resources