SQL Duration that value was true - sql-server

Edited: to include sample data
Looking for guidance on a TSQL query.
I have a table that stores readings from a sensor (Amperage). The table basically has a Date/Time and a Value column.
The date/time increments every 5 seconds (a new record is added on 5 second intervals).
I am trying to build a query to determine the duration of time that the value was >X.
Example Data:
http://sqlfiddle.com/#!18/f15c0/1/0
The example data is missing chunks to make it smaller but think you would get the idea.
I am trying to get the first record to the next record that goes above 7. This I would do a datediff to get the duration in seconds from when the data started to that first record over 7. I then need to repeat this but now find when it goes below 7.
This way I can see the cycle time duration.
Think of it as your Fridge. The sensor checks in every 5 seconds and sees that the fridge is off and records that. Eventually the fridge turns on and remains on for a period of time. I am trying to get all those cycle times.
I am trying to use Lead and Lag functions...but just getting lost in regards to pulling the data.
Any help?

declare #val numeric(10,5) = 7.0
select v1.entrydate,
v1.Amps,
case when v1.fl = 1 and v1.lg is null then 1
when v1.lg != v1.fl then 1
else 0
end as fl_new
from (
select v1.entrydate,
v1.Amps,
case when v1.Amps > #val then 1
else 0
end as fl,
lag(case when v1.Amps > #val then 1
else 0
end) over(order by v1.entrydate) as lg
from (
select t.entrydate as entrydate,
t.Amps as Amps
from YourTable t
) v1
) v1
where case when v1.fl = 1 and v1.lg is null then 1
when v1.lg != v1.fl then 1
else 0
end = 1
order by v1.entrydate
And don't forget set YourTable name and #val (which is "X").

Images are blocked at my current location, so I can't see your structure. I'll assume you have the following table (I'll ignore PK and other constraints):
create table reading(
entryDate datetime,
amps int
)
Assuming anything above 3 amps is ON, and you want to compute the duty cycles in seconds, then
declare #threshold int = 3;
with
state as (
select entryDate,
case when amps>#threshold then 'ON' else 'OFF' end state,
lag( case when amps>#threshold then 'ON' else 'OFF' end )
over(order by entryDate) prev_state
from reading
),
transition as (
select entryDate, state
from state
where state <> coalesce(prev_state,'')
)
select entryDate,
state,
dateDiff(
s,
entryDate,
lead(entryDate) over(order by entryDate)
) duration
from transition
order by 1

Not sure how fast it'll be, but if you want to try with LAG?
Here's an example that checks for a difference of X>=2
SELECT entrydate, amps
FROM
(
SELECT
entrydate, amps,
amps - LAG(amps) OVER (ORDER BY entrydate) AS PrevAmpsDiff
FROM YourTable
) q
WHERE ABS(FLOOR(PrevAmpsDiff)) >= 2
ORDER BY entrydate;
A test on rextester here

Related

How can I combine 2 existing data with the same Id Li from a single column time data, one as the start time and the other as the end date in Sql?

I have a single-dated column in SQL. And I also have 2 datasets with the same Id. How can I show the older one of these data as the start time and the other as the end time in a single column? First of all, I wrote a query that shows the difference in seconds between two times with the same Id.
The query is like this:
SELECT TOP (1000)
[EventAssociationID], [SourceName], [Message],
[EventTimeStamp], [Active], [Acked],
-- TIMESTAMPDIFF(SECOND, [EventTimeStamp]) AS difference,
DATEDIFF(second, [EventTimeStamp], pTimeStamp) AS TotalTime
FROM
(SELECT
[EventTimeStamp], [EventAssociationID], [SourceName],
[Message], [Active], [Acked],
LAG([EventTimeStamp]) OVER (PARTITION BY [EventAssociationID] ORDER BY [EventTimeStamp] DESC) pTimeStamp
FROM
[dbo].[Alarms]
WHERE
[SourceName] = 'RSADD_ALM_105'
OR [SourceName] = 'RSADD_ALM_106') q
The output is as follows:
EventAssociationID   SourceName Message EventTimeStamp Active Acked TotalTime 
D1FBB8784 RSADD_ALM_105 TipperLight  '2022-12-07 00:14:34' 0 0 NULL
D1FBB8784 RSADD_ALM_105 TipperLight  '2022-12-07 00:14:16' 1 0 18
B6DA7FBD58 RSADD_ALM_106 Curtain '2022-12-07 11:35:51' 0 0 NULL
B6DA7FBD58 RSADD_ALM_106 Curtain '2022-12-07 11:35:01' 1 0 50
The output I am trying to do is I am trying to collect the same "EventAssociationID" data in a single row and the table structure I want is as follows:
EventAssociationID  SourceName Message StartTime EndTime TotalTime 
D1FBB8784 RSADD_ALM_105 TipperLight '2022-12-07 00:14:16' '2022-12-07 00:14:34' 18
B6DA7FBD58 RSADD_ALM_106 Curtain '2022-12-07 11:35:01' '2022-12-07 11:35:51' 50
I'm trying to create an output like this. There are 2 same data from each EventAssociationID data. I am trying to create the previous time data as the start time and the end time column, and I can calculate the difference in seconds.
How can I edit the SQL query to get the output I want?
Will this work?
select EventAssociationID,SourceName,Message, min(EventTimeStamp) as StartTime, max(EventTimeStamp) as EndTime, sum(TotalTime) as TotalTime
from ...
group by EventAssociationID,SourceName,Message

Grouping data into fuzzy gaps and islands

This is essentially a gaps and islands problem however it's atypical. I did cut the example down to bare minimum. I need to identify gaps that exceed a certain threshold and duplicates can't be a problem although this example removes them.
In any case the common solution of using ROW_NUMBER() doesn't help since gaps of even 1 can't be handled and the gap value is a parameter in 'real life'.
The code below actually works correctly. And it's super fast! But if you look at it you'll see why people are rather gun shy about relying upon it. The method was first published 9 years ago here http://www.sqlservercentral.com/articles/T-SQL/68467/ and I've read all 32 pages of comments. Nobody has successfully poked holes in it other than to say "it's not documented behavior". I've tried it on every version from 2005 to 2019 and it works.
The question is, beyond using a cursor or while loop to look at many millions of rows 1 by 1 - which takes, I don't know how long because I cancel after 30 min. - is there a 'supported' way to get the same results in a reasonable amount of time? Even 100x slower would complete 4M rows in 10 minutes and I can't find a way to come close to that!
CREATE TABLE #t (CreateDate date not null
,TufpID int not null
,Cnt int not null
,FuzzyGroup int null);
ALTER TABLE #t ADD CONSTRAINT PK_temp PRIMARY KEY CLUSTERED (CreateDate,TufpID);
-- Takes 40 seconds to write 4.4M rows from a source of 70M rows.
INSERT INTO #T
SELECT X.CreateDate
,X.TufpID
,Cnt = COUNT(*)
,FuzzyGroup = null
FROM SessionState SS
CROSS APPLY(VALUES (CAST(SS.CreateDate as date),SS.TestUser_Form_Part_id)) X(CreateDate,TufpID)
GROUP BY X.CreateDate
,X.TufpID
ORDER BY x.CreateDate,x.TufpID;
-- Takes 6 seconds to update 4.4M rows. They WILL update in clustered index order!
-- (Provided all the rules are followed - see the link above)
DECLARE #FuzzFactor int = 38
DECLARE #Prior int = -#FuzzFactor; -- Insure 1st row has it's own group
DECLARE #Group int;
DECLARE #CDate date;
UPDATE #T
SET #Group = FuzzyGroup = CASE WHEN t.TufpID - #PRIOR < #FuzzFactor AND t.CreateDate = #CDate
THEN #Group ELSE t.TufpID END
,#CDate = CASE WHEN #CDate = t.CreateDate THEN #CDate ELSE t.CreateDate END
,#Prior = CASE WHEN #Prior = t.TufpID-1 THEN #Prior + 1 ELSE t.TufpID END
FROM #t t WITH (TABLOCKX) OPTION(MAXDOP 1);
After the above executes the FuzzyGroup column contains the lowest value of TufpID in the group. IOW the first row (in clustered index order) contains the value of it's own TufpID column. Thereafter every row gets the same value until the date changes or a gap size (in this case 38) is exceeded. In those cases the current TufpID becomes the value put into FuzzyGroup until another change is detected. So after 6 seconds I can run queries that group by FuzzyGroup and analyze the islands.
In practice I do some running counts and totals as well in the same pass and so it takes 8 seconds not 6 but I could do those things with window functions pretty easily if I need to so I left them off.
This is the smallest table and I'll eventually need to handle 100M rows. Thus 10 minutes for 4.4M is probably not good enough but it's a place to start.
This should be reasonably efficient and avoid relying on undocumented behaviour
WITH T1
AS (SELECT *,
PrevTufpID = LAG(TufpID)
OVER (PARTITION BY CreateDate
ORDER BY TufpID)
FROM #T),
T2
AS (SELECT *,
_FuzzyGroup = MAX(CASE
WHEN PrevTufpID IS NULL
OR TufpID - PrevTufpID >= #FuzzFactor
THEN TufpID
END)
OVER (PARTITION BY CreateDate
ORDER BY TufpID ROWS UNBOUNDED PRECEDING)
FROM T1)
UPDATE T2
SET FuzzyGroup = _FuzzyGroup
The execution plan has a single ordered scan through the clustered index, with the row values then flowing through some window function operators and into the update.

SQL Server condition case doesnt work as intended

I want my SQL to display the overdue count when the condition is the status name showed closed on the exact due date then the count will be set as 1. For example, on the due date, the status name only became closed.
select
category, COUNT(overdue) as overdue2
from
(select
Category, due,
case when DATEDIFF(day, Due, SYSDATETIME()) = 0 then 1
else 0
end as overdue
from
FeedbackDetail
where
StatusName = 'Closed' and
FeedbackDatetime >= '2018-01-01') a
Group by
Category
My expected result is to display the count where the statusname is closed on the exact due date time.
Any idea on this?
The COUNT aggregate function counts existant (non-null) values, so it will count 0 as well as 1. Since you did not post the whole query and we have no idea what a1 is, the only solution that can be proposed is:
Use SUM instead of COUNT.
You can modify the query like given below for better performance and working.
DECLARE #currentDateTime DATETIME = GETDATE()
select
category, SUM(overdue) as overdue2
from
(select
Category,
case when DATEDIFF(day, Due, #currentDateTime) = 0 then 1
else 0
end as overdue
from
FeedbackDetail
where
StatusName = 'Closed' and
FeedbackDatetime >= '2018-01-01') a
Group by
Category

Is there a way to loop through recordset and identify different values?

I have a MS SQL stored Procedure that loops the records of a select statement using a cursor. When I loop and read each record it could be 3 then 3 then 3 or it could be 3 then 4 and then 5 then 3. It could be one record or more than one.
What I need to know is are all of the values the same or are they different? I don't care what they are only if they are the same or different. If I was doing this with client side code I would use a list and see if that value is in the list then when I'm finished I would count the values if one then the same if more than 1 then different.
DECLARE TC CURSOR LOCAL
FOR SELECT ProgramID FROM tblPublications
OPEN TC
FETCH NEXT FROM TC INTO #ProgramID
WHILE ##FETCH_STATUS = 0
BEGIN
Print #ProgramID
--Same or Different then the last one???
FETCH NEXT FROM TC2 INTO #ProgramID
END
CLOSE TC
DEALLOCATE TC
END
If they are the same then I will update a record one way and if they are different then update a status record another way.
Thanks
Something like this (no cursor involved) perhaps?
select PubId, count(distinct ProgId)
from tblPublications
group by PubId
You can use count(distinct col) to get the number of distinct values for a column, and use a case expression to return a value depending on if it is 1 or more.
select
PublicationId
, PublicationType = case
when count(distinct ProgramId) > 1
then 'inter'
else 'intra'
end
from tblPublications
where PublicationId = 12345
group by PublicationId
you can compare the minimum and maximum of ProgramID in a group.
If all the value is same, the max value will be same as min value.
;with tblPublications(ProgramID,ID)AS(
select 'a',1 union
select 'b',1 union
select 'c',2 union
select 'c',2
)
select case when ISNULL(max(ProgramID),'')=ISNULL(min(ProgramID),'') then 'Same' else 'Different' end
from tblPublications
group by ID
(No column name)
1 Different
2 Same

Can you have a SELECT Statement create two rows when a case condtion is met?

I have a table that contains a breakdown of some data. I want to have that every 12 rows there is a row containing the total for the previous 12 rows. My understanding is that I cannot insert a row at a specific place in a table.
So i am attempting to create a second table that selects the data from the first table and on a case condition being met adds the total row.
This is my current attempt:
INSERT INTO #loanTempTable2 (payment,principal,interest,regular)
SELECT
(CASE
WHEN MonthNumber%12!=0
THEN CAST(MonthNumber AS varchar(50))
ELSE 'Year Total'
END) AS payment,
(CASE
WHEN MonthNumber%12=0
THEN (SELECT SUM(principal) FROM #loanTempTable WHERE payment BETWEEN (MonthNumber-11) AND MonthNumber)
WHEN MonthNumber=#LoanPeriod
THEN (SELECT SUM(principal) FROM #loanTempTable WHERE payment BETWEEN (#LoanPeriod-(#LoanPeriod%12)+1) AND #LoanPeriod)
ELSE (#RegularPayment - (#Rate*#LoanAmount))*(POWER((#Rate+1),(MonthNumber-1)))
END) AS principal,
(CASE
WHEN MonthNumber%12=0
THEN (SELECT SUM(interest) FROM #loanTempTable WHERE payment BETWEEN (MonthNumber-11) AND MonthNumber)
WHEN MonthNumber=#LoanPeriod
THEN (SELECT SUM(interest) FROM #loanTempTable WHERE payment BETWEEN (#LoanPeriod-(#LoanPeriod%12)+1) AND #LoanPeriod)
ELSE #RegularPayment - (#RegularPayment - (#Rate*#LoanAmount))*(POWER((#Rate+1),(AM.MonthNumber-1)))
END) AS interest,
(CASE
WHEN MonthNumber%12=0
THEN (SELECT SUM(regular) FROM #loanTempTable WHERE payment BETWEEN (MonthNumber-11) AND MonthNumber)
WHEN MonthNumber=#LoanPeriod
THEN (SELECT SUM(regular) FROM #loanTempTable WHERE payment BETWEEN (#LoanPeriod-(#LoanPeriod%12)+1) AND #LoanPeriod)
ELSE #RegularPayment
END) AS regular
FROM AllowedMonths AM
WHERE AM.MonthNumber >= 1 AND AM.MonthNumber <= #LoanPeriod
However, this is not working as I need it to, because it causes one of the payments to be skipped. The table results in having a total row where say the payment 12 row was.
My question is whether there is a way to have two rows added each time the case
WHEN MonthNumber%12=0
Occurs, hopefully meaing the payment 12 and the total row would appear in the table. Any ideas?
This is always tricky to handle in SQL - if possible, I'd simply handle this at the presentation level or somesuch.
If you persist in trying to do this in SQL, one way to do this would be by moveing the case to a join:
-- setup test data
declare #data table ([month] int)
declare #i int;
set #i = 1;
while #i < 100
begin
insert into #data values (#i);
set #i = #i + 1;
end;
with expander (idx) as
(
select 1
union all
select 2
)
select
case when E.idx = 1 then cast(D.[month] as varchar) else 'Year total' end as [month]
from #data D
left join expander E on E.idx = 1 or (D.[month] % 12 = 0)
The idea should be pretty obvious. You can do whatever aggregations you want with windowed functions:
case
when E.idx = 1 then D.[month]
else sum(D.[month]) over (partition by (D.[month] - 1) / 12) - D.[month]
end
A more reasonable (and easier to read and use) approach would be to take the two queries separately, then union them, and finally, order them by the month again. This makes much more sense, and is a lot easier to maintain - you don't have to do the silly "If I'm in a total row, emit this value, otherwise, emit this value".

Resources