SQL Server SQL update based on Status value

oknwwptz  于 2024-01-05  发布在  其他
关注(0)|答案(3)|浏览(116)

I have a device details as below.

The Dtatus column will have ON or OFF Status. When a device is plugged in, it will continously generate "ON" values. When it is disconnected it will show "OFF" Status.

When Status column is having "ON" value, we need to update StartDate of the first row of "ON" Value with Date column value. EndDate needs to update with the next "OFF" value date. We need to update dates when it is connected/disconnect.

I need to generate a SQL query for updating Dates for "ON" and OFF Status. I cannot use cursor. I have tried using row_number , but not able to partition by ON/OFF values. Any help is appreciated.

Expected resut is shown as values in StartDate and EndDate.

Test table and data

create table #test (
    ID int,
    Date datetime,
    Status nvarchar(3),
    StartDate datetime,
    EndDate datetime
)

insert into #test(ID, Date, status, StartDate, EndDate) 
select 1 ,'12-06-2023 00:00:00', 'ON',null,null
union all select 1 ,'12-07-2023 00:00:00', 'ON',null,null
union all select 1 ,'12-08-2023 00:00:00', 'OFF',null,null
union all select 1 ,'12-09-2023 00:00:00', 'ON',null,null
union all select 1 ,'12-10-2023 00:00:00', 'OFF',null,null
x3naxklr

x3naxklr1#

So, you can use analytical functions to group the desired records and find MAX and MIN dates to calculate required columns START_DATE and END_DATE as follows:

SELECT  T.ID, DATE, STATUS, 
        CASE WHEN PREV_STATUS = 'OFF' AND STATUS = 'ON' 
             THEN MIN(DATE) OVER (PARTITION BY ID, SM) END AS START_DATE, 
        CASE WHEN PREV_STATUS = 'OFF' AND STATUS = 'ON' 
             THEN MAX(DATE) OVER (PARTITION BY ID, SM) END AS END_DATE
  FROM
        (SELECT T.*, 
                SUM(CASE WHEN PREV_STATUS = 'OFF' AND STATUS = 'ON' THEN 1 END) 
                    OVER (PARTITION BY ID ORDER BY DATE) AS SM
           FROM
                (SELECT T.ID, DATE, STATUS, 
                        COALESCE(LAG(STATUS) 
                                      OVER (PARTITION BY ID ORDER BY DATE), 
                                 'OFF') AS PREV_STATUS
                   FROM YOUR_TABLE T
                ) T
         ) T

Please adjust column names or minor changes as required.

With Multiple IDs:

qpgpyjmq

qpgpyjmq2#

You have a gaps and islands problem here, you could use the difference between two row_numbers approach to solve it :

with cte as (
  select ID, Date, Status,
       row_number() over (partition by ID order by Date) 
       - row_number() over (partition by ID, Status order by Date) as grp
  from #test
),
cte2 as (
  select ID, Date, Status, grp,
                           min(Date) over (partition by ID, grp) as min_date
  from cte
),
cte3 as (
  select *, dense_rank() over (partition by ID order by min_date) as ordered_grp
  from cte2
),
cte4 as (
  select *,  min(min_date) over (partition by ID, (ordered_grp - 1 )/2) as Start_Date,
           max(min_date) over (partition by ID, (ordered_grp - 1 )/2) as End_Date
  from cte3
)
update  t
set     StartDate = Start_Date,
        EndDate = End_Date
from cte4 c
inner join #test t on t.ID = c.ID and t.Date = c.Start_Date

Results :

ID  Date                    Status  StartDate               EndDate
1   2023-12-06 00:00:00.000 ON      2023-12-06 00:00:00.000 2023-12-08 00:00:00.000
1   2023-12-07 00:00:00.000 ON      null                    null
1   2023-12-08 00:00:00.000 OFF     null                    null
1   2023-12-09 00:00:00.000 ON      2023-12-09 00:00:00.000 2023-12-10 00:00:00.000
1   2023-12-10 00:00:00.000 OFF     null                    null

Explanations :

First cte used to identify different groups based on successive identical status.

cte2/cte3 used to order generated groups so it can be easy to group successive groups together (ON and OFF) using the arithmetic (ordered_grp - 1 )/2

cte4 used to get min and max date per group, which will contains data with ON and OFF status

Demo here

rdlzhqv9

rdlzhqv93#

The direct way is to count up the OFF statuses. By doing it in reverse order you naturally get each OFF as the beginning of each logical partition.

with data as (
    select *,
        sum(case when status = 'OFF' then 1 else 0 end) over (partition by id order by dt desc) as grp
   from T
)
select *,
    case when dt = min(dt) over (partition by id, grp) then min(dt) over (partition by id, grp) as start_dt,
    case when dt = min(dt) over (partition by id, grp) then max(dt) over (partition by id, grp) as end_dt
from data;

相关问题