Comparing value to that of previous row depending on different field - sql

I have SQL which outputs rows of a date time stamp and a status change flag (0 or 1)
I need to get the timespan from the first record, where the flag will be 0, to when the status flag changes to 1, ignore records when the flag is still at 1, then get the time span after it changes back to 0 till the last record. The status change flag may flip between 0 and 1 any number of times.
So i need to be able to compare the status change flag to the previous row, and decide whether I need to keep accumulating the difference in the date time stamps.
I have been looking into writing a cursor but keep reading about how cursors are horribly inefficient.
Hopes this make any sense.

DECLARE #test TABLE ([group] int,t DateTime,[status] bit)
INSERT INTO #test values (1,'20130101 11:11:11',0)
INSERT INTO #test values (1,'20130101 11:11:12',0)
INSERT INTO #test values (1,'20130101 11:11:13',0)
INSERT INTO #test values (1,'20130101 11:11:14',1)
INSERT INTO #test values (1,'20130101 11:11:15',1)
INSERT INTO #test values (1,'20130101 11:11:16',1)
INSERT INTO #test values (1,'20130101 11:11:17',0)
INSERT INTO #test values (1,'20130101 11:11:18',0)
INSERT INTO #test values (1,'20130101 11:11:19',0)
Select [Group],MIN(t)
,(Select MAX(t) from #test t2 where [status]=0 and t2.[group]=t.[group] and Exists(Select * from #test t3 where [status]=1 and t3.[group]=t.[group] and t3.t<t2.t))
,DateDiff(ss,MIN(t)
,(Select MAX(t) from #test t2 where [status]=0 and t2.[group]=t.[group] and Exists(Select * from #test t3 where [status]=1 and t3.[group]=t.[group] and t3.t<t2.t))
) as Seconds
from #test t where Status=0
group by [group]

I think something like this will work. But I might need more info on the table structure
WITH FirstFlag(FlagType, FlagTime)
AS
(
SELECT
FlagType
, min(DateCreated) as FlagTime
FROM TheTable
WHERE Flag = 0
)
, SecondFlag(FlagTime1, FlagTime2)
AS
(
SELECT
F.FlagTime as FlagTime
, min(T.DateCreated) as FlagTime
FROM TheTable as T
INNER JOIN FirstFlag as F
ON T.FlagType = F.FlagType
WHERE Flag = 1
AND T.DateCreated > F.FlagTime
)
SELECT datediff(min, FlagTime1, FlagTime2)
FROM SecondFlag

Related

For loop in Microsoft SQL Server

I have a data which has 3 different numbers of "Equipement" and each "Equipement" has different contract date ( start_date and end_date).
Screen Data:
I want to write a script which I can say that for every "Equipement" If the first line of "end_date" match the second line of "start_date" in days, so I should do ("start_date" - 1 day) in the second line AS a new_end_date for the first line.
I've made an attempt, but for just the two first lines ( not generalized):
SELECT[Ref]
,[Equipement]
,[start_date]
,[end_date]
,CASE WHEN DATEDIFF(day, (SELECT [end_date] FROM [DWDiagnostics].[dbo].[Test1] WHERE [Ref] = 1290), (SELECT [start_date] FROM [DWDiagnostics].[dbo].[Test1] WHERE [Ref] = 1380)) < 0 THEN DATEADD(dd, -1, [start_date]) ELSE [end_date]
END AS [new_end_date]
FROM [DWDiagnostics].[dbo].[Test1]
Here's a screen of the result I want
SQL code for the Data ==>
DECLARE #Test TABLE
(
Ref VARCHAR(10),
Equipment VARCHAR(10),
start_date DATE,
end_date DATE
)
INSERT INTO #Test VALUES ('1290','9999','2014-03-01','2016-04-16')
INSERT INTO #Test VALUES ('1380','9999','2016-04-01','2018-05-17')
INSERT INTO #Test VALUES ('2000','9999','2018-05-01','2020-06-27')
INSERT INTO #Test VALUES ('2900','9999','2020-06-01','2021-06-29')
INSERT INTO #Test VALUES ('1556','8888','2016-01-01','2017-02-27')
INSERT INTO #Test VALUES ('1876','8888','2017-02-01','2018-04-26')
INSERT INTO #Test VALUES ('2897','8888','2018-04-01','2020-03-30')
INSERT INTO #Test VALUES ('2653','7777','2017-09-01','2018-10-14')
INSERT INTO #Test VALUES ('4536','7777','2018-10-01','2019-11-13')
INSERT INTO #Test VALUES ('2987','7777','2019-11-01','2020-12-27')
INSERT INTO #Test VALUES ('2776','7777','2020-12-01','2021-11-30')
SELECT * FROM #Test;
Thanks for posting sample data and tables structures. Makes this so much easier to work on the problem. This should work based on your explanation of the issue. However, some of the new_end_date values you posted as desired do not match up to your description. For example, with Equipment 9999 you have the second start_date as 4/1/2016 but in your desired output you show 3/30. The day before 4/1 is 3/31. There are some other examples with dates like that in your desired output that are slightly off the day before.
DECLARE #Test TABLE
(
Ref VARCHAR(10),
Equipment VARCHAR(10),
start_date DATE,
end_date DATE
)
INSERT INTO #Test VALUES ('1290','9999','2014-03-01','2016-04-16')
INSERT INTO #Test VALUES ('1380','9999','2016-04-01','2018-05-17')
INSERT INTO #Test VALUES ('2000','9999','2018-05-01','2020-06-27')
INSERT INTO #Test VALUES ('2900','9999','2020-06-01','2021-06-29')
INSERT INTO #Test VALUES ('1556','8888','2016-01-01','2017-02-27')
INSERT INTO #Test VALUES ('1876','8888','2017-02-01','2018-04-26')
INSERT INTO #Test VALUES ('2897','8888','2018-04-01','2020-03-30')
INSERT INTO #Test VALUES ('2653','7777','2017-09-01','2018-10-14')
INSERT INTO #Test VALUES ('4536','7777','2018-10-01','2019-11-13')
INSERT INTO #Test VALUES ('2987','7777','2019-11-01','2020-12-27')
INSERT INTO #Test VALUES ('2776','7777','2020-12-01','2021-11-30')
select *
, new_end_date = isnull(dateadd(day, -1, lead(start_date, 1)over(partition by Equipment order by start_date)), end_date)
from #Test
ORDER BY Equipment desc
, start_date

Counting number of transactions within past 1 hour on a particular user

Is there any way how to (in the best case, without using cursor) count number of transactions that the same user made in previous 1 hour.
That means that for this table
CREATE TABLE #TR (PK INT, TR_DATE DATETIME, USER_PK INT)
INSERT INTO #TR VALUES (1,'2018-07-31 06:02:00.000',10)
INSERT INTO #TR VALUES (2,'2018-07-31 06:36:00.000',10)
INSERT INTO #TR VALUES (3,'2018-07-31 06:55:00.000',10)
INSERT INTO #TR VALUES (4,'2018-07-31 07:10:00.000',10)
INSERT INTO #TR VALUES (5,'2018-07-31 09:05:00.000',10)
INSERT INTO #TR VALUES (6,'2018-07-31 06:05:00.000',11)
INSERT INTO #TR VALUES (7,'2018-07-31 06:55:00.000',11)
INSERT INTO #TR VALUES (8,'2018-07-31 07:10:00.000',11)
INSERT INTO #TR VALUES (9,'2018-07-31 06:12:00.000',12)
The result should be:
The solution could be something like: COUNT(*) OVER (PARTITION BY USER_PK ORDER BY TR_DATE ROWS BETWEEN ((WHERE DATEADD(HH,-1,PRECENDING.TR_DATE) > CURRENT ROW.TR_DATE) AND CURRENT ROW ...but I know that ROWS BETWEEN can not be used like that...
I am guessing SQL Server based on the syntax. In SQL Server, you can use apply:
select t.*, tr2.result
from #tr tr outer apply
(select count(*) as result
from #tr tr2
where tr2.user_id = tr.user_id and
tr2.tr_date > dateadd(hour, -1, tr.date) and
tr2.tr_date <= tr.tr_date
) tr2;
SELECT USER_PK, COUNT(*) AS TransactionCount
FROM #TR
WHERE DATEDIFF(MINUTE, TR_DATE, GETDATE()) <= 60
AND DATEDIFF(MINUTE, TR_DATE, GETDATE()) >= 0
GROUP BY USER_PK
You can change GETDATE() with whatever you want, but they need to have the same value

Identify duplicates in a group with one value being different

USE TEMPDB
go
IF OBJECT_ID(N'#TEST', N'U') IS NOT NULL
DROP TABLE dbo.#TEST
CREATE TABLE #TEST
(NAME VARCHAR(50),
line int,
RANKS INT)
INSERT INTO #TEST
(name, line,RANKS)
VALUES
('Tom',1, 1), --keep
('Tom',2, 1),--keep
('Toms',1, 0), --keep
('Toms',2, 0),--keep
('Dave',1, 0),--delete
('Dave',2, 0),--keep
('Dave',1, 1),--keep
('TIm',1,1),--keep
('TIm',1,0),--delete
('Matt',1,0),--delete
('Matt',1,1)--keep
if the same name and line are falling under different ranks, then I need to delete the onde falling under rank '0' and if the same person with the same line is falling under different ranks, then the one under rank 0 should be deleted.
Something like this should work:
delete t1
from #TEST t1
where exists(select *
from #TEST t2
where t2.NAME = t1.NAME
and t2.line = t1.line
and t2.RANKS <> t1.RANKS)
and t1.RANKS = 0;

Populate a GroupID

Please see the DDL below:
CREATE TABLE #Test (ID)
INSERT INTO #Test values (1)
INSERT INTO #Test values (2)
INSERT INTO #Test values (3)
INSERT INTO #Test values (0)
INSERT INTO #Test values (4)
INSERT INTO #Test values (5)
INSERT INTO #Test values (6)
INSERT INTO #Test values (8)
INSERT INTO #Test values (12)
INSERT INTO #Test values (19)
INSERT INTO #Test values (0)
INSERT INTO #Test values (44)
I am looking for a query that outputs the following (GroupID, ID).
1,1
1,2
1,3
2,4
2,5
2,6
2,8
2,12
2,19
3,44
A row with an ID of 0 splits the groups.
The query will look something like this:
select GroupID = row_number() over (order by (select null)),ID from #Test
The GroupID needs to be split using the rows that have a zero value. The query above just creates a new GroupID for each row.
SQL tables represent unordered sets. So, you cannot do what you want with your data structure, because it has no ordering. However, that is easily fixed by adding an identity column:
CREATE TABLE #Test (
TestId int identity(1, 1) primary key, -- the primary key is not strictly necessary, just my habit with identity columns.
Val int
);
(I took the liberty of renaming your column to val instead of id to avoid confusion.) An identity column captures insertion order.
The the group is calculated as the number of zeros before a given value. In SQL Server 2012+, this is easily done using a cumulative sum:
select t.*,
1 + sum(case when val = 0 then 1 else 0 end) over (order by testid) as groupid
from #test t;
I notice that you don't want zeros in the output, for this purpose, use a subquery after the cumulative sum:
select *
from (select t.*,
1 + sum(case when val = 0 then 1 else 0 end) over (order by testid) as groupid
from #test t
) t
where val <> 0;

Select records with order of IN clause

I have
SELECT * FROM Table1 WHERE Col1 IN(4,2,6)
I want to select and return the records with the specified order which i indicate in the IN clause
(first display record with Col1=4, Col1=2, ...)
I can use
SELECT * FROM Table1 WHERE Col1 = 4
UNION ALL
SELECT * FROM Table1 WHERE Col1 = 6 , .....
but I don't want to use that, cause I want to use it as a stored procedure and not auto generated.
I know it's a bit late but the best way would be
SELECT *
FROM Table1
WHERE Col1 IN( 4, 2, 6 )
ORDER BY CHARINDEX(CAST(Col1 AS VARCHAR), '4,2,67')
Or
SELECT CHARINDEX(CAST(Col1 AS VARCHAR), '4,2,67')s_order,
*
FROM Table1
WHERE Col1 IN( 4, 2, 6 )
ORDER BY s_order
You have a couple of options. Simplest may be to put the IN parameters (they are parameters, right) in a separate table in the order you receive them, and ORDER BY that table.
The solution is along this line:
SELECT * FROM Table1
WHERE Col1 IN(4,2,6)
ORDER BY
CASE Col1
WHEN 4 THEN 1
WHEN 2 THEN 2
WHEN 6 THEN 3
END
select top 0 0 'in', 0 'order' into #i
insert into #i values(4,1)
insert into #i values(2,2)
insert into #i values(6,3)
select t.* from Table1 t inner join #i i on t.[in]=t.[col1] order by i.[order]
Replace the IN values with a table, including a column for sort order to used in the query (and be sure to expose the sort order to the calling application):
WITH OtherTable (Col1, sort_seq)
AS
(
SELECT Col1, sort_seq
FROM (
VALUES (4, 1),
(2, 2),
(6, 3)
) AS OtherTable (Col1, sort_seq)
)
SELECT T1.Col1, O1.sort_seq
FROM Table1 AS T1
INNER JOIN OtherTable AS O1
ON T1.Col1 = O1.Col1
ORDER
BY sort_seq;
In your stored proc, rather than a CTE, split the values into table (a scratch base table, temp table, function that returns a table, etc) with the sort column populated as appropriate.
I have found another solution. It's similar to the answer from onedaywhen, but it's a little shorter.
SELECT sort.n, Table1.Col1
FROM (VALUES (4), (2), (6)) AS sort(n)
JOIN Table1
ON Table1.Col1 = sort.n
I am thinking about this problem two different ways because I can't decide if this is a programming problem or a data architecture problem. Check out the code below incorporating "famous" TV animals. Let's say that we are tracking dolphins, horses, bears, dogs and orangutans. We want to return only the horses, bears, and dogs in our query and we want bears to sort ahead of horses to sort ahead of dogs. I have a personal preference to look at this as an architecture problem, but can wrap my head around looking at it as a programming problem. Let me know if you have questions.
CREATE TABLE #AnimalType (
AnimalTypeId INT NOT NULL PRIMARY KEY
, AnimalType VARCHAR(50) NOT NULL
, SortOrder INT NOT NULL)
INSERT INTO #AnimalType VALUES (1,'Dolphin',5)
INSERT INTO #AnimalType VALUES (2,'Horse',2)
INSERT INTO #AnimalType VALUES (3,'Bear',1)
INSERT INTO #AnimalType VALUES (4,'Dog',4)
INSERT INTO #AnimalType VALUES (5,'Orangutan',3)
CREATE TABLE #Actor (
ActorId INT NOT NULL PRIMARY KEY
, ActorName VARCHAR(50) NOT NULL
, AnimalTypeId INT NOT NULL)
INSERT INTO #Actor VALUES (1,'Benji',4)
INSERT INTO #Actor VALUES (2,'Lassie',4)
INSERT INTO #Actor VALUES (3,'Rin Tin Tin',4)
INSERT INTO #Actor VALUES (4,'Gentle Ben',3)
INSERT INTO #Actor VALUES (5,'Trigger',2)
INSERT INTO #Actor VALUES (6,'Flipper',1)
INSERT INTO #Actor VALUES (7,'CJ',5)
INSERT INTO #Actor VALUES (8,'Mr. Ed',2)
INSERT INTO #Actor VALUES (9,'Tiger',4)
/* If you believe this is a programming problem then this code works */
SELECT *
FROM #Actor a
WHERE a.AnimalTypeId IN (2,3,4)
ORDER BY case when a.AnimalTypeId = 3 then 1
when a.AnimalTypeId = 2 then 2
when a.AnimalTypeId = 4 then 3 end
/* If you believe that this is a data architecture problem then this code works */
SELECT *
FROM #Actor a
JOIN #AnimalType at ON a.AnimalTypeId = at.AnimalTypeId
WHERE a.AnimalTypeId IN (2,3,4)
ORDER BY at.SortOrder
DROP TABLE #Actor
DROP TABLE #AnimalType
ORDER BY CHARINDEX(','+convert(varchar,status)+',' ,
',rejected,active,submitted,approved,')
Just put a comma before and after a string in which you are finding the substring index or you can say that second parameter.
And first parameter of CHARINDEX is also surrounded by , (comma).