如何选择两个日期与下一行和上一行之间的行

时间:2018-04-12 05:13:10

标签: sql sql-server sql-server-2016

我有一个包含以下结构的表

ID  PersonID    Date
---------------------------
1   1           2017-04-01
2   1           2017-04-15
3   1           2017-05-13
4   1           2017-06-15
5   1           2017-08-13
6   1           2017-10-02
7   2           2017-05-04
8   2           2017-09-16
9   3           2017-04-23
10  3           2017-07-06
11  4           2017-06-01

我希望Select2017-05-01之间的2017-08-26行以及每个PersonID下一行和上一行(如果存在)。

我想要这个结果:

ID  PersonID    Date
------------------------------
2   1           2017-04-15
3   1           2017-05-13
4   1           2017-06-15
5   1           2017-08-13
6   1           2017-10-02
7   2           2017-05-04
8   2           2017-09-16
9   3           2017-04-23
10  3           2017-07-06
11  4           2017-06-01

2 个答案:

答案 0 :(得分:5)

尝试以下查询

create table TestData(ID int,PersonID int,[Date] date)

insert TestData(ID,PersonID,[Date])values
(1 ,1,'20170401'),(2 ,1,'20170415'),(3 ,1,'20170513'),
(4 ,1,'20170615'),(5 ,1,'20170813'),(6 ,1,'20171002'),
(7 ,2,'20170504'),(8 ,2,'20170916'),(9 ,3,'20170423'),
(10,3,'20170706'),(11,4,'20170601')
----------------
DECLARE
  @FromDate date='20170501',
  @ToDate date='20170826'

SELECT *
FROM
  (
    SELECT
      *,
      LAG(IIF([Date] BETWEEN @FromDate AND @ToDate,1,0))OVER(PARTITION BY PersonID ORDER BY [Date],ID) LagOK,
      LEAD(IIF([Date] BETWEEN @FromDate AND @ToDate,1,0))OVER(PARTITION BY PersonID ORDER BY [Date],ID) LeadOK
    FROM TestData
  ) q
WHERE ([Date] BETWEEN @FromDate AND @ToDate OR LagOK=1 OR LeadOK=1)

CTEROW_NUMBER

的变体
;WITH numCTE AS(
  SELECT
    *,
    ROW_NUMBER()OVER(PARTITION BY PersonID ORDER BY [Date],ID) N
  FROM TestData
)
SELECT n.*
FROM
  (
    SELECT PersonID,MIN(N)-1 MinN,MAX(N)+1 MaxN
    FROM numCTE
    WHERE [Date] BETWEEN @FromDate AND @ToDate
    GROUP BY PersonID
  ) q
JOIN numCTE n on n.PersonID=q.PersonID AND n.N BETWEEN q.MinN AND q.MaxN

我也为此案例添加了新的测试数据和修改过的查询

create table TestData(ID int,PersonID int,[Date] date)

insert TestData(ID,PersonID,[Date])values
(1 ,1,'20170401'),(2 ,1,'20170415'),(3 ,1,'20170513'),
(4 ,1,'20170615'),(5 ,1,'20170813'),(6 ,1,'20171002'),
(7 ,2,'20170504'),(8 ,2,'20170916'),(9 ,3,'20170423'),
(10,3,'20170706'),(11,4,'20170601'),
(14,6,'20170415'),(15,6,'20170913'),(16,6,'20171015') -- new test data

DECLARE
  @FromDate date='20170501',
  @ToDate date='20170826'

SELECT *
FROM
  (
    SELECT
      *,
      LAG(IIF([Date] BETWEEN @FromDate AND @ToDate,1,0))OVER(PARTITION BY PersonID ORDER BY [Date],ID) LagOK,
      LEAD(IIF([Date] BETWEEN @FromDate AND @ToDate,1,0))OVER(PARTITION BY PersonID ORDER BY [Date],ID) LeadOK
    FROM
      (
        SELECT ID,PersonID,[Date]
        FROM TestData

        UNION ALL

        SELECT DISTINCT NULL,PersonID,@FromDate -- add phantom rows for some people
        FROM TestData p
        WHERE NOT EXISTS(SELECT * FROM TestData d WHERE d.[Date] BETWEEN @FromDate AND @ToDate AND d.PersonID=p.PersonID)
      ) q
  ) q
WHERE ([Date] BETWEEN @FromDate AND @ToDate OR LagOK=1 OR LeadOK=1)
  AND ID IS NOT NULL -- exclude phantom rows from result

CTE和ROW_NUMBER的新变种

;WITH numCTE AS(
  SELECT
    *,
    ROW_NUMBER()OVER(PARTITION BY PersonID ORDER BY [Date],ID) N
  FROM
    (
      SELECT ID,PersonID,[Date]
      FROM TestData

      UNION ALL

      SELECT DISTINCT NULL,PersonID,@FromDate -- add phantom rows for some people
      FROM TestData p
      WHERE NOT EXISTS(SELECT * FROM TestData d WHERE d.[Date] BETWEEN @FromDate AND @ToDate AND d.PersonID=p.PersonID)
    ) q
)
SELECT n.*
FROM
  (
    SELECT PersonID,MIN(N)-1 MinN,MAX(N)+1 MaxN
    FROM numCTE
    WHERE [Date] BETWEEN @FromDate AND @ToDate
    GROUP BY PersonID
  ) q
JOIN numCTE n on n.PersonID=q.PersonID AND n.N BETWEEN q.MinN AND q.MaxN
WHERE ID IS NOT NULL -- exclude phantom rows from result

答案 1 :(得分:0)

我只想这样做:

select id, personid, date
from (select t.*,
             lag(date) over (partition by personid) as prev_date,
             lead(date) over (partition by personid) as next_date
      from t
     ) t
where (date >= '2017-05-01' and date <= '2017-08-26') or
      (prev_date >= '2017-05-01' and prev_date <= '2017-08-26') or
      (next_date >= '2017-05-01' and next_date <= '2017-08-26');

逻辑很简单:您正在寻找您指定的时间间隔内的日期,上一个日期或下一个日期。