您可以使用窗口功能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 BY
andGROUP BY
子句中引用列的值,但不能在WHERE
orHAVING
子句中;在那里你必须写出表达式。
大胆强调我的。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
)。您也可以替换LIMIT
为TOP n
(MS SQL) 或任何本地方言。