在非不同索引上使用递归 cte 计算不同行

2023-12-02

给出以下架构:

CREATE TABLE identifiers (
  id TEXT PRIMARY KEY
);

CREATE TABLE days (
  day DATE PRIMARY KEY
);

CREATE TABLE data (
  id TEXT REFERENCES identifiers
  , day DATE REFERENCES days
  , values NUMERIC[] 
); 
CREATE INDEX ON data (id, day);

计算两个时间戳之间所有不同天数的最佳方法是什么?我尝试过以下两种方法:

EXPLAIN ANALYZE
SELECT COUNT(DISTINCT day) 
FROM data 
WHERE day BETWEEN '2010-01-01' AND '2011-01-01';
                                                                        QUERY PLAN                                                                        
----------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=200331.32..200331.33 rows=1 width=4) (actual time=1647.574..1647.575 rows=1 loops=1)
   ->  Index Only Scan using data_day_sid_idx on data  (cost=0.56..196942.12 rows=1355678 width=4) (actual time=0.348..1180.566 rows=1362532 loops=1)
         Index Cond: ((day >= '2010-01-01'::date) AND (day <= '2011-01-01'::date))
         Heap Fetches: 0
 Total runtime: 1647.865 ms
(5 rows)

EXPLAIN ANALYZE
SELECT COUNT(DISTINCT day) 
FROM days
WHERE day BETWEEN '2010-01-01' AND '2011-01-01';
                                                           QUERY PLAN                                                           
--------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=18.95..18.96 rows=1 width=4) (actual time=0.481..0.481 rows=1 loops=1)
   ->  Index Only Scan using days_pkey on days  (cost=0.28..18.32 rows=252 width=4) (actual time=0.093..0.275 rows=252 loops=1)
         Index Cond: ((day >= '2010-01-01'::date) AND (day <= '2011-01-01'::date))
         Heap Fetches: 252
 Total runtime: 0.582 ms
(5 rows)

The COUNT(DISTINCT day)反对days运行良好,但它需要我保留一个辅助表(days)以保持性能合理。一般来说,我想测试递归 cte 是否能让我实现类似的性能without维护辅助表。我的查询如下所示,但尚未运行:

EXPLAIN ANALYZE
WITH RECURSIVE cte AS (
   (SELECT day FROM data ORDER BY 1 LIMIT 1)
   UNION ALL
   (  -- parentheses required
   SELECT d.day
   FROM   cte  c
   JOIN   data d ON d.day > c.day
   ORDER  BY 1 LIMIT 1
   )
)
SELECT day 
FROM cte
WHERE day BETWEEN '2010-01-01' AND '2011-01-01';

Updates

感谢大家的想法。看起来维护一个基于触发器的不同日期的表是最好的方法,无论是存储还是性能方面。感谢 @Erwin 的更新,递归 CTE 重新开始运行。很有用。

WITH RECURSIVE cte AS (
   (  -- parentheses required because of LIMIT
   SELECT day
   FROM   data
   WHERE  day >= '2010-01-01'::date  -- exclude irrelevant rows early
   ORDER  BY 1
   LIMIT  1
   )

   UNION ALL
   SELECT (SELECT day FROM data
           WHERE  day > c.day
           AND    day < '2011-01-01'::date  -- see comments below
           ORDER  BY 1
           LIMIT  1)
   FROM   cte c
   WHERE  day IS NOT NULL  -- necessary because corr. subq. always returns row
   )
SELECT count(*) AS ct
FROM   cte
WHERE  day IS NOT NULL;

                                                                             QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=53.35..53.36 rows=1 width=0) (actual time=18.217..18.217 rows=1 loops=1)
   CTE cte
     ->  Recursive Union  (cost=0.43..51.08 rows=101 width=4) (actual time=0.194..17.594 rows=253 loops=1)
           ->  Limit  (cost=0.43..0.46 rows=1 width=4) (actual time=0.191..0.192 rows=1 loops=1)
                 ->  Index Only Scan using data_day_idx on data data_1  (cost=0.43..235042.00 rows=8255861 width=4) (actual time=0.189..0.189 rows=1 loops=1)
                       Index Cond: (day >= '2010-01-01'::date)
                       Heap Fetches: 0
           ->  WorkTable Scan on cte c  (cost=0.00..4.86 rows=10 width=4) (actual time=0.066..0.066 rows=1 loops=253)
                 Filter: (day IS NOT NULL)
                 Rows Removed by Filter: 0
                 SubPlan 1
                   ->  Limit  (cost=0.43..0.47 rows=1 width=4) (actual time=0.062..0.063 rows=1 loops=252)
                         ->  Index Only Scan using data_day_idx on data  (cost=0.43..1625.59 rows=52458 width=4) (actual time=0.060..0.060 rows=1 loops=252)
                               Index Cond: ((day > c.day) AND (day < '2011-01-01'::date))
                               Heap Fetches: 0
   ->  CTE Scan on cte  (cost=0.00..2.02 rows=100 width=0) (actual time=0.199..18.066 rows=252 loops=1)
         Filter: (day IS NOT NULL)
         Rows Removed by Filter: 1
 Total runtime: 19.355 ms
(19 rows)

并且还讨论了EXISTS query

EXPLAIN ANALYZE
SELECT count(*) AS ct
FROM   generate_series('2010-01-01'::date, '2010-12-31'::date, '1d'::interval) d(day)
WHERE  EXISTS (SELECT 1 FROM data WHERE day = d.day::date);

                                                                  QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=674.32..674.33 rows=1 width=0) (actual time=95.049..95.049 rows=1 loops=1)
   ->  Nested Loop Semi Join  (cost=0.45..673.07 rows=500 width=0) (actual time=12.438..94.749 rows=252 loops=1)
         ->  Function Scan on generate_series d  (cost=0.01..10.01 rows=1000 width=8) (actual time=9.248..9.669 rows=365 loops=1)
         ->  Index Only Scan using data_day_idx on data  (cost=0.44..189.62 rows=6023 width=4) (actual time=0.227..0.227 rows=1 loops=365)
               Index Cond: (day = (d.day)::date)
               Heap Fetches: 0
 Total runtime: 95.620 ms
(7 rows)

几点注意事项:

简单的表查询day

SELECT COUNT(DISTINCT day) 
FROM   days
WHERE  day BETWEEN '2010-01-01' AND '2011-01-01';

While day定义为 PK,DISTINCT只是昂贵的噪音。

具有相关子查询的递归 CTE

如果有的话,这是替代方案no day table具有独特的条目。如果每天有多行到多行,则该技术是有价值的,因此相当于松散索引扫描实际上比简单的索引扫描更快DISTINCT在基表上:

WITH RECURSIVE cte AS (
   (  -- parentheses required because of LIMIT
   SELECT day
   FROM   data
   WHERE  day >= '2010-01-01'  -- exclude irrelevant rows early
   ORDER  BY 1
   LIMIT  1
   )
  
   UNION ALL
   SELECT (SELECT day FROM data
           WHERE  day > c.day
           AND    day < '2011-01-01'  -- see below
           ORDER  BY 1
           LIMIT  1)
   FROM   cte c
   WHERE  day IS NOT NULL  -- necessary because corr. subq. always returns row
   )
SELECT count(*) AS ct
FROM   cte
WHERE  day IS NOT NULL;

Index

仅与匹配索引结合使用才有意义data:

CREATE INDEX data_day_idx ON data (day);

day必须是领先的列。您在问题中的索引(id, day)也可以使用,但效率低得多:

  • PostgreSQL 中索引的工作
  • 复合索引也适合第一个字段的查询吗?

Notes

尽早排除不相关的行要便宜得多。我将您的谓词集成到查询中。

详细解释:

  • 优化 GROUP BY 查询以检索每个用户的最新行

手头的情况甚至更简单——实际上是最简单的。

您原来的时间范围是day BETWEEN '2010-01-01' AND '2011-01-01'. But BETWEEN .. AND .. includes上限和下限,因此您将获得 2010 年的全部数据以及 2011-01-01 的数据。你可能想要exclude上限。使用d.day < '2011-01-01' (not <=). See:

  • 如何向时间戳列添加日/夜指示器?

EXISTS对于这种特殊情况

由于您正在测试一系列可枚举的天数(而不是具有无限数量可能值的范围),因此您可以使用EXISTS半连接:

SELECT count(*) AS ct
FROM   generate_series(timestamp '2010-01-01'
                     , timestamp '2010-12-31'
                     , interval  '1 day') AS d(day)
WHERE  EXISTS (SELECT FROM data WHERE day = d.day::date);

为什么是这种形式generate_series()最佳的?

  • 在 PostgreSQL 中生成两个日期之间的时间序列

同样的简单索引再次必不可少。

db<>fiddle here demonstrating both with big test table.
Old sqlfiddle

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在非不同索引上使用递归 cte 计算不同行 的相关文章

随机推荐

  • 定位边界二维实体

    给定一个点和一组任意 2D 实体 圆 多边形 直线 折线 圆弧等 有谁知道现有的策略 确定该点是否被任何实体组合包围 界定 我知道对闭合形状进行 内部 测试很容易 但这并不总是能给我想要的东西 特别是对于嵌套或相交的形状 找到围绕我的点形成
  • 在 pandas 数据框中完全打印很长的字符串

    我正在为看似非常简单的事情而挣扎 我有一个包含很长字符串的 pandas 数据框 df pd DataFrame one one two This is very long string very long string very long
  • Python 3.9.1 的 dlib 安装错误。如何知道哪个版本兼容?

    我已经安装了 CMake 现在正在尝试为人脸识别项目安装 dlib 我看过一些视频 问题似乎是兼容性问题 对于 Python 3 9 1 哪个版本的 dlib 更兼容 我尝试使用 pip 安装 dlib 手动下载它 但仍然显示以下错误 WA
  • 当原型包含对象时访问“this”值?

    我有一个class像这样 function Foo this current 1 Foo prototype history Foo prototype history back function if this current undef
  • SQL复杂动态透视2

    您好 我正在尝试在 SQL Server 中进行下表的旋转 REFID COL1 COL2 Sequence 1 abc cde 1 1 lmn rst 2 1 kna asg 3 2 als zkd 2 2 zpk lad 1 我想要的输
  • 尽管有通用约束,C# 类型转换还是错误

    为什么在类 P 的类型参数 T 的泛型约束为 必须从 A 继承 的情况下 第一次调用会成功 但第二次调用会失败 并出现注释中详细说明的类型转换错误 abstract class A static class S public static
  • 三元条件中的意外标记中断

    下面的函数旨在以数组形式从 可能嵌套的 对象返回值 列表参数可以是任何对象 如果我将break语句移到for循环之后 我不会收到任何错误 但当然我的函数不会按需要运行 我使用break的方式有什么问题吗 function listToArr
  • Eclipse - 无法从“https://services.gradle.org/distributions/gradle-2.1-bin.zip”安装 Gradle 发行版

    我正在尝试在 Eclipse 中创建一个新的 gradle 项目 由于安全原因 Eclipse无法下载任何插件 软件 当我尝试创建 gradle 项目时 出现以下错误 无法找到请求目标的有效证书路径 可能 不安装 Gradle 发行版 ht
  • 定义我的轴的限制,但在两个方向上具有相同的比例[重复]

    这个问题在这里已经有答案了 我如何结合这些陈述 pyplot axis 1234 0 1773 0 497 0 1362 0 pyplot axis equal 我只想定义轴的限制 但在两个方向上具有相同的比例 PS 我试过了pyplot
  • 在 Python 中将 GET 请求代理到不同的站点

    我想将从客户端收到的 GET 请求转发到另一个站点 就我而言 m3u8 播放列表请求流媒体站点进行处理 有谁知道该怎么做 如果要代理 请先安装requests pip install requests 然后 获取服务器中的文件并提供内容 e
  • Solr 拼写检查:无法显示结果,总是收到相同的错误

    所以 我对 solr 还很陌生 也许我有很多理解问题 我现在想做的是对我的搜索栏的自动建议 例如 如果您写 glves 结果应该是 gloves 这是一个自动完成功能 现在运行良好 但是 当然 当我输入 glves 时 什么也没有出现 现在
  • android @Suppress 错误与 @TargetApi

    有时 我会为可能运行我的应用程序的某些 Android 版本编写代码 使用最新 Android 工具的 Eclipse 仍然注意到我的 Manifest 文档允许较低版本的 Android 因此判定我的代码中有错误 解决这个问题的方法是添加
  • AX的源文件存放在哪里?

    我想 仅 对 Dynamics Ax 2009 的源文件 即 XPO 源文件 运行定期 每 4 小时 备份 我想知道它们的物理存储位置 AX7 所有元数据都以 XML 形式存储 方法也存储在常规文件中 并存储在版本控制中 See 本概述 A
  • 如何为 Dash/Plotly 中的下拉菜单命名

    我对破折号还很陌生 我试图弄清楚如何将名称放置在下拉菜单和滑块上方 并在它们之间提供一些间隙 我在侧面而不是在下拉列表的顶部得到这些名称 数据集 模型类型 这是我一直在使用的代码 html Div className row childre
  • 如何使用 iTextSharp 将 HTML 转换为 PDF

    我想使用 iTextSharp 将以下 HTML 转换为 PDF 但不知道从哪里开始 p This em is em span class headline some span strong sample em text em strong
  • Arrays.binarySearch 无法正常工作

    我有字符串数组 1 2 3 我使用 Arrays binarySearch 搜索所有这些数字 它找到 1 和 2 但对于 3 它返回 1 知道为什么会这样吗 什么是比始终在数组 集合中进行搜索更好的替代方案 数组必须是有序的 String
  • 大矩阵的 SciPy 插值

    我有一个 ndarray Z 在矩形网格 X Y 上有大约 500000 个元素 现在我想在 x y 中的大约 100 个位置处插入值 这些位置不一定在网格上 我有一些在 Matlab 中运行的代码 data interp2 X Y Z x
  • str_extract 特定模式(示例)

    我对正则表达式语法仍然有点困惑 你能帮我解决这些模式吗 A00 A1234B A00 A12345B A1 A12345 到目前为止我的方法 vapply strsplit files function files files nchar
  • TPL队列处理

    我目前正在开发一个项目 我需要将一些作业排队进行处理 这是要求 作业必须一次处理一个 排队的项目必须能够等待 所以我想要类似的东西 Task
  • 在非不同索引上使用递归 cte 计算不同行

    给出以下架构 CREATE TABLE identifiers id TEXT PRIMARY KEY CREATE TABLE days day DATE PRIMARY KEY CREATE TABLE data id TEXT REF