简体   繁体   中英

SQL - Selecting rows with dates before and after column value change

I have a table called test.
In test I have An ID, a value and a date.
The dates are ordered for each ID.
I want to select rows for an ID, before and after a change of value, so the following example table.

RowNum--------ID------- Value -------- Date
1------------------001 ---------1----------- 01/01/2015
2------------------001 ---------1----------- 02/01/2015
3------------------001 ---------1----------- 04/01/2015
4------------------001 ---------1----------- 05/01/2015
5------------------001 ---------1----------- 06/01/2015
6------------------001 ---------1----------- 08/01/2015
7------------------001 ---------0----------- 09/01/2015
8------------------001 ---------0----------- 10/01/2015
9------------------001 ---------0----------- 11/01/2015
10-----------------001 ---------1----------- 12/01/2015
11-----------------001 ---------1----------- 14/01/2015
12------------------002 ---------1----------- 01/01/2015
13------------------002 ---------1----------- 04/01/2015
14------------------002 ---------0----------- 05/01/2015
15------------------002 ---------0----------- 07/01/2015

The result would return rows 6, 7, 9, 10, 13, 14

You could use analytic functions LAG() and LEAD() to access value in preceding and following rows, then check that it does not match value in current row.

SELECT *
FROM   (
  SELECT RowNum,
         ID,
         Value,
         Date,
         LAG(VALUE, 1, VALUE)  OVER(ORDER BY RowNum)  PrevValue,
         LEAD(VALUE, 1, VALUE) OVER(ORDER BY RowNum)  NextValue
  FROM   test)
WHERE PrevValue <> Value
OR    NextValue <> Value

Params passed to this functions are

  1. some scalar expression (column name in this case);
  2. offset (1 row before or after);
  3. default value ( LAG() will return NULL for first row and LEAD() will return NULL for last row, but they don't seem special in your question, so I used column value as default).

Refer the below one for without using LEAD and LAG:

DECLARE @i        INT = 1, 
        @cnt      INT, 
        @dstvalue INT, 
        @srcvalue INT 

CREATE TABLE #result 
  ( 
     id     INT, 
     mydate DATE 
  ) 

CREATE TABLE #temp1 
  ( 
     rn     INT IDENTITY(1, 1), 
     id     INT, 
     mydate DATE 
  ) 

INSERT INTO #temp1 
            (id, 
             mydate) 
SELECT id, 
       mydate 
FROM   table 
ORDER  BY id, 
          mydate 

SELECT @cnt = Count(*) 
FROM   #temp1 

SELECT @srcvalue = value 
FROM   #temp1 
WHERE  rn = @i 

WHILE ( @i <= @cnt ) 
  BEGIN 
      SELECT @dstvalue = value 
      FROM   #temp1 
      WHERE  rn = @i 

      IF( @srcvalue = @dstvalue ) 
        BEGIN 
            SET @i = @i + 1 

            CONTINUE; 
        END 
      ELSE 
        BEGIN 
            SET @srcvalue = @dstvalue 

            INSERT INTO #result 
                        (id, 
                         mydate) 
            SELECT id, 
                   mydate 
            FROM   #temp 
            WHERE  rn = @i - 1 
            UNION ALL 
            SELECT id, 
                   mydate 
            FROM   #temp 
            WHERE  rn = @i 
        END 

      SET @i = @i + 1 
  END 

SELECT * 
FROM   #result 

The answer using lag() and lead() is the right answer. If you are using a pre-SQL Server 2012 version, then you can do essentially the same thing using cross apply or a correlated subquery:

select t.*
from test t cross apply
     (select top 1 tprev.*
      from test tprev
      where tprev.date < t.date
      order by date desc
     ) tprev cross apply
     (select top 1 tnext.*
      from test tnext
      where tnext.date > t.date
      order by date asc
     ) tnext 
where tprev.value <> tnext.value;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM