【问题标题】:SQL - Select values from a table based on dates using incrementing datesSQL - 根据使用递增日期的日期从表中选择值
【发布时间】:2016-10-26 15:14:22
【问题描述】:

我有一个日期(MM/DD 格式)、目标和级别的 SQL 表,如下所示:

Date     Target     Level 
10/2     1000       1
10/4     2000       1
10/7     2000       2

我想将这些日期用作层级或检查点,以确定何时使用相应的目标和级别。因此,在这些日期或之后(直到下一个日期)的任何事情都将使用该目标/级别。第一个日期之前的任何内容都只使用第一个日期的值。

我想选择一个日期范围(一个 5 周的日期范围,该范围的开始日期和结束日期由当天确定:从今天起 3 周后,从今天起 2 周后)和相应地填写目标和级别,如下所示:

Date     Target     Level
10/1     1000       1
10/2     1000       1
10/3     1000       1
10/4     2000       1
10/5     2000       1
10/6     2000       1
10/7     2000       2
10/8     2000       2
...
11/5     2000       2

我该怎么做:

  1. 选择日期范围(尽可能高效)

  2. 使用表格中相应日期的相应目标/级别填写日期范围?

谢谢。

【问题讨论】:

  • 10/1 是 10 月 1 日?
  • 是的,所以从 10 月 1 日到 11 月 5 日(5 周范围)。很抱歉造成混乱!
  • 我有一个问题,这与当天有什么关系?你想从今天开始然后回去还是反过来?
  • 我想使用今天的日期(在任何一天),从那时起,5 周的范围应该是:从今天开始的 3 周,到今天的 2 周。
  • 你在 10 月 1 日没有有效数据,我在示例中注意到

标签: sql sql-server tsql select


【解决方案1】:

您可以使用outer apply 执行此操作。下面使用递归 CTE 创建日期列表:

with d as (
      select cast(getdate() as date) as dte
      union all
      select dateadd(day, -1, dte)
      from d
      where dte >= getdate() - 30
select d.dte, t.target, t.level
from d outer apply
     (select top 1 t.*
      from t
      where d.dte >= t.dte
      order by t.dte desc
     );

【讨论】:

  • 谢谢!现在,如果我想添加此逻辑并将其用于多个“类别”(假设我有另一列“类别”,并且我的一些日期目标级别数据属于类别“A”和我的一些日期-目标级别数据属于类别“B”),那么我希望“A”有一个 5 周的日期范围(在这个例子中是 10 月 1 日 - 11 月 2 日)并且我希望“B”有自己的 5周日期范围(10 月 1 日 - 11 月 2 日)。关于如何解决这个问题的任何想法?我知道我必须使用“WHERE”来指定类别,但是如何跨多个类别迭代这个解决方案?
  • 你可以在 OUTER APPLY 中尝试的是 '....where d.category = t.category AND d.dte >= t.dte....' 然后订购整个按类别和日期 - 并将 t.category 添加到主 SELECT
【解决方案2】:

您可以使用 CTE 生成“缺失”日期,然后使用 CROSS APPLY 获取上次活动的目标和级别(通过查询日期在当前日期或之前的 TOP 1 DESC) - 最后我引入了“最大日期”作为变量

DECLARE @MAXD as DATETIME = '20161105';

WITH DATS AS (SELECT MIN([Date]) D FROM dbo.YourTab
            UNION ALL
            SELECT dateadd(day,1,D) FROM DATS WHERE D < @MAXD)
select DATS.D, CA.Target, CA.Level from DATS 
    CROSS APPLY
    (SELECT TOP 1 Y.Target, Y.Level FROM YourTab Y 
                                     WHERE 
                                          Y.[Date] <= DATS.D 
                                     ORDER BY Y.Date DESC) CA
    option (maxrecursion 0);

我对日期进行了一些更改,将日期提前 3 周并提前两周 - 我也切换到 outer apply 以处理无效数据

DECLARE @MIND as DATETIME = dateadd(week,-3,cast(getdate() as date));
DECLARE @MAXD as DATETIME = dateadd(week, 5,@MIND);

WITH DATS AS (SELECT @MIND D 
            UNION ALL
            SELECT dateadd(day,1,D) FROM DATS WHERE D < @MAXD)
select DATS.D, CA.Target, CA.Level from DATS 
    OUTER APPLY
    (SELECT TOP 1 Y.Target, Y.Level FROM YourTab Y WHERE Y.[Date] <= DATS.D ORDER BY Y.Date DESC) CA
    ORDER BY DATS.D
    option (maxrecursion 0);

最终更改 - 如果日期没有更早的值 - 取未来的第一行

DECLARE @MIND as DATETIME = dateadd(week,-3,cast(getdate() as date));
DECLARE @MAXD as DATETIME = dateadd(week, 5,@MIND);

WITH DATS AS (SELECT @MIND D 
            UNION ALL
            SELECT dateadd(day,1,D) FROM DATS WHERE D < @MAXD)
select DATS.D, COALESCE(CA.Target, MQ.Target) Target , COALESCE(CA.Level, MQ.Level) Level from DATS 
    OUTER APPLY
    (SELECT TOP 1 Y.Target, Y.Level FROM YourTab Y WHERE Y.[Date] <= DATS.D ORDER BY Y.Date DESC) CA

    OUTER APPLY 
    (
        SELECT TOP 1 M.Target, M.Level FROM YourTab M ORDER BY M.[Date] ASC
    ) MQ
        ORDER BY DATS.D
    option (maxrecursion 0);

【讨论】:

    【解决方案3】:

    我不知道您为什么将日期存储为MM/DD,但您需要转换为正确的数据类型。这可能会奏效:

    ;WITH YourTable AS (
    SELECT *
    FROM (VALUES
    ('10/2', 1000, 1),
    ('10/4', 2000, 1),
    ('10/7', 2000, 2)
    ) as t([Date], [Target],  [Level])
    ), dates_cte AS ( --this CTE is generating dates you need
        SELECT DATEADD(week,-3,GETDATE()) as d --3 weeks back
        UNION ALL
        SELECT dateadd(day,1,d)
        FROM dates_cte
        WHERE d < DATEADD(week,2,GETDATE()) --2 weeks forward
    )
    
    SELECT  REPLACE(CONVERT(nvarchar(5),d,101),'/0','/') as [Date],
            COALESCE(t.[Target],t1.[Target]) [Target],
            COALESCE(t.[Level],t1.[Level]) [Level]
    FROM dates_cte dc
    OUTER APPLY ( --Here we got PREVIOUS values
        SELECT TOP 1 *
        FROM YourTable
        WHERE CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) <= dc.d
        ORDER BY CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) DESC
        ) t
    OUTER APPLY ( --Here we got NEXT values and use them if there is no PREV
        SELECT TOP 1 *
        FROM YourTable
        WHERE CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) >= dc.d
        ORDER BY CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) ASC
        ) t1
    

    输出:

    Date    Target  Level
    10/5    2000    1
    10/6    2000    1
    10/7    2000    2
    10/8    2000    2
    10/9    2000    2
    10/10   2000    2
    10/11   2000    2
    10/12   2000    2
    ...
    11/9    2000    2
    

    编辑

    Categories:

    ;WITH YourTable AS (
    SELECT *
    FROM (VALUES
    ('10/2', 1000, 1, 'A'),
    ('10/4', 3000, 1, 'B'),
    ('10/7', 2000, 2, 'A')
    ) as t([Date], [Target], [Level], [Category])
    ), dates_cte AS (
        SELECT DATEADD(week,-3,GETDATE()) as d
        UNION ALL
        SELECT dateadd(day,1,d)
        FROM dates_cte
        WHERE d < DATEADD(week,2,GETDATE())
    )
    
    SELECT  REPLACE(CONVERT(nvarchar(5),d,101),'/0','/') as [Date],
            COALESCE(t.[Target],t1.[Target]) [Target],
            COALESCE(t.[Level],t1.[Level]) [Level],
            c.Category
    FROM dates_cte dc
    CROSS JOIN (
        SELECT DISTINCT Category
        FROM YourTable
    ) c
    OUTER APPLY (
        SELECT TOP 1 *
        FROM YourTable
        WHERE CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) <= dc.d
            AND c.Category = Category
        ORDER BY CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) DESC
        ) t
    OUTER APPLY (
        SELECT TOP 1 *
        FROM YourTable
        WHERE CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) >= dc.d
            AND c.Category = Category
        ORDER BY CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) ASC
        ) t1
    ORDER BY c.Category, d
    

    【讨论】:

      【解决方案4】:

      不确定我是否过度简化了,但是:

      select min(X.Date) Date_Range_Start, max(X.date) Date_Range_End
            , V.<value_date>
            , isnull(X.Target, 'Out of range') Target
            , isnull(X.Level, 'Out of range') Level
         from X --replace this with your table 
         left join <value_table> V --table with dates to be assessed
                on V.<Date> between X.Date_Range_Start and X.Date_Range_End
      group by Target, Level, V.<value_date>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-05-28
        • 1970-01-01
        • 1970-01-01
        • 2018-03-30
        • 1970-01-01
        • 1970-01-01
        • 2015-09-03
        • 1970-01-01
        相关资源
        最近更新 更多