【问题标题】:Select N rows before and N rows after the record选择记录前N行和记录后N行
【发布时间】:2016-08-11 19:52:55
【问题描述】:

我有一个表,其中一些值存储了数月和数年。

例子:

Month  |   Year   |  Value
  1    |   2013   |  1.86
  2    |   2013   |  2.25
  3    |   2013   |  2.31
  ...
  3    |   2016   |  1.55
  4    |   2016   |  1.78

月份和年份组合是一个复杂的主键。保证表中存在过去所有年份的所有值。

用户可以选择特定月份和特定年份。假设用户选择 2014 作为年份和 6 作为月份,我需要显示所选组合之前的 15 行和之后的 15 行。

但是如果在选择的组合之后没有足够的行(少于 15 行),那么我需要在之前获得更多的行。

基本上我只需要返回 31 行(除非整个表中没有足够的行,否则总是 31)数据,其中所选组合将尽可能靠近中心。

这样做的正确方法是什么?

目前我被这个困住了:

;WITH R(N) AS
(
    SELECT 0
    UNION ALL
    SELECT N+1 
    FROM R
    WHERE N < 29
)
SELECT  * FROM MyTable e
LEFT OUTER JOIN (
    SELECT N, MONTH(DATEADD(MONTH,-N,iif(@year != Year(GETDATE()), DATEFROMPARTS(@year, 12, 31) ,GETDATE()))) AS [Month], 
   YEAR(DATEADD(MONTH,-N,iif(@year!= Year(GETDATE()), DATEFROMPARTS(@year, 12, 31) ,GETDATE()))) AS [Year]
FROM R) s
ON s.[Year] = e.[Year] AND s.[Month] = e.[Month]
WHERE s.[N] is not null

这不是我真正想做的,因为它只是切断了明年的几个月

【问题讨论】:

  • 什么 sql server 版本?
  • 你可能想检查这个stackoverflow.com/questions/2135418/…
  • @dfundako 更新了问题
  • 我的第一个 SWAG 将是从表中获取 MIN 和 MAX 日期(带有索引的快速。)并考虑到边缘情况计算要返回的适当日期。然后针对相应的行运行查询。

标签: sql-server tsql sql-server-2012 common-table-expression


【解决方案1】:

像这样简单的东西怎么样:

;WITH CTE AS (
    SELECT Month
        ,Year
        ,Value
        ,ROW_NUMBER() OVER (ORDER BY Year, Month) rn
    FROM MyTable
    )
SELECT Month
    ,Year
    ,Value
FROM CTE 
WHERE rn >= (SELECT rn - 15 FROM MyTable WHERE Year = @Year AND Month = @Month)
    AND rn <= (SELECT rn + 15 FROM MyTable WHERE Year = @Year AND Month = @Month);

我确信有一种更有效的方法可以做到这一点,但我觉得这是最易于维护的方法。当您选择接近表中第一条或最后一条记录的值时,它甚至应该可以工作。

无论如何,我不知道您是否想要 31 行。有时听起来像,有时又不像。

编辑:好的,所以如果可用的话,你总是想要 31 行。

好的,试试这个:

;WITH CTE AS (
    SELECT Month
        ,Year
        ,Value
        ,ROW_NUMBER() OVER (ORDER BY Year, Month) rn
    FROM MyTable
    ),
CTE_2 AS (
    SELECT TOP (31) Month
        ,Year
        ,Value
    FROM CTE
    ORDER BY ABS(rn - (SELECT rn FROM MyTable WHERE Year = @Year AND Month = @Month)) ASC
    )
SELECT Month
    ,Year
    ,Value
FROM CTE_2
ORDER BY Year, Month;

基本上,您计算与目标行号的差异,获取前 31 行,然后将它们用于输出。

【讨论】:

  • 这不是我需要的,因为如果查询的行(@Year 和@Month)是最后一行,它只会在它之前返回 15 条额外的记录。在这种情况下,我需要在它之前返回 30 条记录。基本上,如果查询行之后的行数少于 15 行,我需要在它之前获取其他行。
  • @Coffka 好的,这是有道理的。试试我更新的查询。
  • 您已经在子查询中假设了一个不存在的行号。
  • @Mihai 你觉得怎么样?行号由第一个 CTE 使用 ROW_NUMBER() 函数计算。第二个查询 CTE_2 引用第一个 CTE,最终查询引用第二个。
【解决方案2】:

看看这个,

DECLARE @iPrevRows int
DECLARE @iPostRows int
DECLARE @Year int = 2016
DECLARE @Month int = 2

SELECT @iPrevRows= Count(*) 
FROM
[GuestBook].[dbo].[tblTest]
where  (year < @Year ) 
  or (year =@Year and month < @Month)

SELECT @iPostRows= count(*)  from
[GuestBook].[dbo].[tblTest]
where  (year > @Year ) 
  or (year =@Year and month > @Month)

if (@iPrevRows > 15) 
    select @iPrevRows =15

if (@iPostRows > 15) 
    select @iPostRows =15

if (@iPrevRows  < 15 )
   select @iPostRows = @iPostRows  + (15-@iPrevRows)
else if (@iPostRows  < 15 )
   select @iPrevRows = @iPrevRows  + (15-@iPostRows)

CREATE TABLE #tempValues
(
 Year int NOT NULL,
 Month int NOT NULL,
 Value  float
)

insert into #tempValues

SELECT top (@iPrevRows) Month, Year, Value 
from
[GuestBook].[dbo].[tblTest]
where  (year < @Year )  
or (year =@Year and month < @Month)
order by 2 desc,1 desc

insert into #tempValues
SELECT Month, Year, Value   
from
[GuestBook].[dbo].[tblTest]
where     (year =@Year and month = @Month)

insert into #tempValues
SELECT top (@iPostRows) Month, Year, Value   
from
[GuestBook].[dbo].[tblTest]
where  (year > @Year ) 
  or (year =@Year and month > @Month)
  order by 2 ,1 

select * from #tempValues
order by 2,1

【讨论】:

    【解决方案3】:

    这是我所做的,似乎正在工作

    select * from (
      select top(31) * from MyTable r
      order by ABS(DATEDIFF(month, DATEFROMPARTS(r.Year, r.Month, 1), DATEFROMPARTS(@Year, @Month, 1)))) s
    order by Year, Month
    

    【讨论】:

    • 不幸的是查询不是SARGABLE,因为每行都应用了函数,所以随着表的增长,性能会受到影响。
    【解决方案4】:

    我就是这样做的。

    DECLARE @year INT = 2014, @month INT = 6;
    WITH TableAux
         AS (SELECT MyTable.Month
                  , MyTable.Year
             FROM MyTable
             WHERE MyTable.Year = @year
             AND MyTable.Month = @month)
         SELECT tb1.Month
              , tb1.Year
              , tb1.Value
         FROM
         (
             SELECT TOP 16 MyTable.Month
                         , MyTable.Year
                         , MyTable.Value
             FROM MyTable
             CROSS JOIN TableAux
             WHERE MyTable.Month <= TableAux.Month
             AND MyTable.Year <= TableAux.Year
             ORDER BY MyTable.Month DESC, MyTable.Year DESC
         ) tb1
         UNION ALL
         SELECT tb2.Month
              , tb2.Year
              , tb2.Value
         FROM
         (
             SELECT TOP 15 MyTable.Month
                         , MyTable.Year
                         , MyTable.Value
             FROM MyTable
             CROSS JOIN TableAux
             WHERE MyTable.Month > TableAux.Month
             AND MyTable.Year > TableAux.Year
             ORDER BY MyTable.Month, MyTable.Year
         ) tb2
         ORDER BY Year, Month
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-23
      • 2015-03-23
      • 1970-01-01
      • 2023-03-11
      • 2016-05-06
      • 2020-06-14
      相关资源
      最近更新 更多