1

如果没有意义,请原谅我的例子。我将尝试一个简化的来鼓励更多的参与。

考虑如下表:

  •        dt     |    mnth    |  foo
    --------------+------------+--------
      2012-12-01  |  December  |
        ...
      2012-08-01  |  August    |
      2012-07-01  |  July      |
      2012-06-01  |  June      |
      2012-05-01  |  May       |
      2012-04-01  |  April     |
      2012-03-01  |  March     |
        ...
      1997-01-01  |  January   |  
    

如果您查找dt最接近今天的记录而没有过去,那么同时返回3 条记录7 条记录的最佳方法是什么?

我决定尝试窗口功能:

  • WITH dates AS (
       select  row_number() over (order by dt desc)
             , dt
             , dt - now()::date as dt_diff
       from    foo
    )
    , closest_date AS (
       select * from dates
       where dt_diff = ( select max(dt_diff) from dates where dt_diff <= 0 )
    )
    
    SELECT * 
    FROM   dates
    WHERE  row_number - (select row_number from closest_date) >= -3
       AND row_number - (select row_number from closest_date) <=  7 ;
    

我觉得必须有更好的方法来使用窗口函数返回相关记录,但是我已经有一段时间没有看过它们了。

4

3 回答 3

3
create table foo (dt date);
insert into foo values
('2012-12-01'),
('2012-08-01'),
('2012-07-01'),
('2012-06-01'),
('2012-05-01'),
('2012-04-01'),
('2012-03-01'),
('2012-02-01'),
('2012-01-01'),
('1997-01-01'),
('2012-09-01'),
('2012-10-01'),
('2012-11-01'),
('2013-01-01')
;

select dt
from (
(
    select dt
    from foo
    where dt <= current_date
    order by dt desc
    limit 4
)
union all
(
    select dt
    from foo
    where dt > current_date
    order by dt
    limit 7
)) s
order by dt
;
     dt     
------------
 2012-03-01
 2012-04-01
 2012-05-01
 2012-06-01
 2012-07-01
 2012-08-01
 2012-09-01
 2012-10-01
 2012-11-01
 2012-12-01
 2013-01-01
(11 rows)
于 2012-06-05T23:36:38.613 回答
2

您可以使用窗口功能lead()

SELECT dt_lead7 AS dt
FROM  (
    SELECT *, lead(dt, 7) OVER (ORDER BY dt) AS dt_lead7
    FROM   foo
    ) d
WHERE  dt <= now()::date
ORDER  BY dt DESC
LIMIT  11;

稍微短一些,但UNION ALL使用合适的索引版本会更快。

这留下了一个极端情况,其中“最接近今天的日期”在前 7 行内。您可以用 7 行填充原始数据-infinity来处理这个问题:

SELECT d.dt_lead7 AS dt
FROM  (
    SELECT *, lead(dt, 7) OVER (ORDER BY dt) AS dt_lead7
    FROM  (
        SELECT '-infinity'::date AS dt FROM generate_series(1,7)
        UNION ALL
        SELECT dt FROM foo
        ) x
    ) d
WHERE  d.dt <= now()::date -- same as: WHERE  dt <= now()::date1
ORDER  BY d.dt_lead7 DESC  -- same as: ORDER BY dt DESC 1
LIMIT  11;

我对第二个查询中的列进行了表限定,以阐明发生了什么。见下文。如果“最接近今天的日期”在基表的最后 7 行内,则
结果将包括值。NULL如果需要,您可以使用额外的子选择过滤那些。


1要解决您对注释中的输出名称列名称的疑问- 请考虑手册中的以下引用。

在哪里使用输出列的名称

输出列的名称可用于在 ORDER BYandGROUP BY子句中引用列的值,但不能在WHEREorHAVING子句中;在那里你必须写出表达式。

大胆强调我的。WHERE dt <= now()::date引用该列d.dt,而不是同名的输出列-从而按预期工作。

解决冲突

如果ORDER BY表达式是与输出列名和输入列名都匹配的简单名称,ORDER BY则将其解释为输出列名GROUP BY 这与在相同情况下做出的选择相反。这种不一致性是为了与 SQL 标准兼容。

再次大胆强调我的。ORDER BY dt DESC在示例中引用了输出列的名称 - 正如预期的那样。无论如何,任何一列都会排序相同。唯一的区别可能NULL极端情况的值。但这也持平,因为:

默认行为是NULLS LAST何时ASC指定或暗示,以及NULLS FIRST何时DESC指定

由于NULL在最大值之后,因此无论哪种方式,顺序都是相同的。


或者,没有LIMIT(根据评论中的要求):

WITH x AS (
    SELECT *
         , row_number() OVER (ORDER BY dt)  AS rn
         , first_value(dt) OVER (ORDER BY (dt > '2011-11-02')
                                         , dt DESC) AS dt_nearest
    FROM   foo
    )
, y AS (
    SELECT rn AS rn_nearest
    FROM   x
    WHERE  dt = dt_nearest
    )
SELECT dt
FROM   x, y
WHERE  rn BETWEEN rn_nearest - 3 AND rn_nearest + 7
ORDER  BY dt;

如果性能很重要,我仍然会使用@Clodoaldo 的UNION ALL变体。这将是最快的。与数据库无关的 SQL 只能让您走这么远。其他 RDBMS 根本没有窗口函数(MySQL),或者不同的函数名称(比如first_val代替first_value)。您也可以替换LIMITTOP n(MS SQL) 或任何本地方言。

于 2012-06-06T08:44:53.950 回答
1

你可以使用类似的东西:

select * from foo 
where dt between now()- interval '7 months' and now()+ interval '3 months'

可能会帮助你。

于 2012-06-05T23:23:05.817 回答