在单个查询中获取分页行和总计数

2024-03-27

核心要求:
查找 a 的最新条目person_id by submission_date对于指定的过滤条件type, plan, status。可能有更多这样的过滤器,但无论如何,按提交日期返回最新的逻辑是相同的。有两个主要用途,一是在 UI 中分页查看,二是生成报告。

WITH cte AS (
  SELECT * FROM (
    SELECT my_table.*, rank() OVER (PARTITION BY person_id ORDER BY submission_date DESC, last_updated DESC, id DESC) FROM my_table
    )  rank_filter 
      WHERE RANK=1 AND status in ('ACCEPTED','CORRECTED') AND type != 'CR' AND h_plan_id IN (10000, 20000)
)
SELECT
SELECT count(id) FROM cte group by id,
SELECT * FROM cte limit 10 offset 0;

The group by也不适用于 CTE。与所有人的联盟null在计数查询中可能适用于组合,但不确定。

我想将这两个合并为1个查询的主要原因是因为表很大并且窗口函数很昂贵。目前我使用单独的查询,它们基本上都运行相同的查询两次。

Postgres 版本 12。

\d my_table;
                               Table "public.my_table"
                 Column   |            Type             | Collation | Nullable 
--------------------------+-----------------------------+-----------+----------
 id                       | bigint                      |           | not null 
 h_plan_id                | bigint                      |           | not null 
 h_plan_submitter_id      | bigint                      |           |          
 last_updated             | timestamp without time zone |           |          
 date_created             | timestamp without time zone |           |          
 modified_by              | character varying(255)      |           |          
 segment_number           | integer                     |           |          

 -- <bunch of other text columns>

 submission_date          | character varying(255)      |           |          
 person_id                | character varying(255)      |           |          
 status                   | character varying(255)      |           |          
 file_id                  | bigint                      |           | not null 
Indexes:
    "my_table_pkey" PRIMARY KEY, btree (id)
    "my_table_file_idx" btree (file_id)
    "my_table_hplansubmitter_idx" btree (h_plan_submitter_id)
    "my_table_key_hash_idx" btree (key_hash)
    "my_table_person_id_idx" btree (person_id)
    "my_table_segment_number_idx" btree (segment_number)
Foreign-key constraints:
    "fk38njesaryvhj7e3p4thqkq7pb" FOREIGN KEY (h_plan_id) REFERENCES health_plan(id) ON UPDATE CASCADE ON DELETE CASCADE
    "fk6by9668sowmdob7433mi3rpsu" FOREIGN KEY (h_plan_submitter_id) REFERENCES h_plan_submitter(id) ON UPDATE CASCADE ON DELETE CASCADE
    "fkb06gpo9ng6eujkhnes0eco7bj" FOREIGN KEY (file_id) REFERENCES x12file(id) ON UPDATE CASCADE ON DELETE CASCADE

附加信息 可能的值type are EN and CR with EN约占数据的 70%。 表格列宽select avg_width from pg_stats where tablename='mytable';41 列总共有 374 个,所以每列大约有 9 个。

这个想法是预先向用户显示一些页面,然后他们可以通过其他参数进行过滤,例如file_name(每个文件通常有大约5k个条目),type(基数非常低),member_add_id(高基数),plan_id(低基数,每 50 万到一百万个条目将与一个计划 ID 关联)。所有情况下的业务要求都是仅显示某个计划 ID 的特定集合的最新记录。submission_date(对于报告,每年完成一次)。按 id 排序只是防御性编码,同一天可以有多个条目,即使有人编辑了倒数第二个条目,从而触及了last_updated时间戳,我们只想显示相同数据的最后一个条目。这可能永远不会发生并且可以被删除。

用户可以使用此数据生成 csv 报告。

右连接查询的解释结果如下:

 Nested Loop Left Join  (cost=554076.32..554076.56 rows=10 width=17092) (actual time=4530.914..4530.922 rows=10 loops=1)
   CTE cte
     ->  Unique  (cost=519813.11..522319.10 rows=495358 width=1922) (actual time=2719.093..3523.029 rows=422638 loops=1)
           ->  Sort  (cost=519813.11..521066.10 rows=501198 width=1922) (actual time=2719.091..3301.622 rows=423211 loops=1)
                 Sort Key: mytable.person_id, mytable.submission_date DESC NULLS LAST, mytable.last_updated DESC NULLS LAST, mytable.id DESC
                 Sort Method: external merge  Disk: 152384kB
                 ->  Seq Scan on mytable  (cost=0.00..54367.63 rows=501198 width=1922) (actual time=293.953..468.554 rows=423211 loops=1)
                       Filter: (((status)::text = ANY ('{ACCEPTED,CORRECTED}'::text[])) AND (h_plan_id = ANY ('{1,2}'::bigint[])) AND ((type)::text <> 'CR'::text))
                       Rows Removed by Filter: 10158
   ->  Aggregate  (cost=11145.56..11145.57 rows=1 width=8) (actual time=4142.116..4142.116 rows=1 loops=1)
         ->  CTE Scan on cte  (cost=0.00..9907.16 rows=495358 width=0) (actual time=2719.095..4071.481 rows=422638 loops=1)
   ->  Limit  (cost=20611.67..20611.69 rows=10 width=17084) (actual time=388.777..388.781 rows=10 loops=1)
         ->  Sort  (cost=20611.67..21850.06 rows=495358 width=17084) (actual time=388.776..388.777 rows=10 loops=1)
               Sort Key: cte_1.person_id
               Sort Method: top-N heapsort  Memory: 30kB
               ->  CTE Scan on cte cte_1  (cost=0.00..9907.16 rows=495358 width=17084) (actual time=0.013..128.314 rows=422638 loops=1)
 Planning Time: 0.369 ms
 JIT:
   Functions: 9
   Options: Inlining true, Optimization true, Expressions true, Deforming true
   Timing: Generation 1.947 ms, Inlining 4.983 ms, Optimization 178.469 ms, Emission 110.261 ms, Total 295.660 ms
 Execution Time: 4587.711 ms

首先要说的就是:你can在同一个查询中多次使用 CTE 的结果,这是主要的CTE的特点 https://www.postgresql.org/docs/current/queries-with.html.) 你所拥有的将像这样工作(同时仍然只使用 CTE 一次):

WITH cte AS (
   SELECT * FROM (
      SELECT *, row_number()  -- see below
                OVER (PARTITION BY person_id
                      ORDER BY submission_date DESC NULLS LAST  -- see below
                             , last_updated DESC NULLS LAST  -- see below
                             , id DESC) AS rn
      FROM  tbl
      ) sub
   WHERE  rn = 1
   AND    status IN ('ACCEPTED', 'CORRECTED')
   )
SELECT *, count(*) OVER () AS total_rows_in_cte
FROM   cte
LIMIT  10
OFFSET 0;  -- see below

注意事项 1:rank()

rank()每个可以返回多行person_id with rank = 1. DISTINCT ON (person_id)(如戈登提供的)是一个适用的替代品row_number()- 正如附加信息所澄清的那样,这对您有用。看:

  • 选择每个 GROUP BY 组中的第一行? https://stackoverflow.com/questions/3800551/select-first-row-in-each-group-by-group/7630564#7630564

注意事项 2:ORDER BY submission_date DESC

Neither submission_date nor last_updated被定义NOT NULL。可能是一个问题ORDER BY submission_date DESC, last_updated DESC ... See:

  • PostgreSQL 按日期时间 asc 排序,首先为 null? https://stackoverflow.com/questions/9510509/postgresql-sort-by-datetime-asc-null-first/9511492#9511492

这些专栏真的应该是NOT NULL?

你回复:

是的,所有这些列都应该是非空的。我可以添加这个约束。我将其设置为可为空,因为我们在文件中获取的数据并不总是完美的。但这是非常罕见的情况,我可以输入空字符串。

类型不允许为空字符串date。保持列可为空。NULL是这些情况的正确值。使用NULLS LAST如所证明的,以避免NULL被排序在顶部。

注意事项 3:OFFSET

If OFFSET等于或大于 CTE 返回的行数,您将得到no row,所以也没有总数。看:

  • 使用 LIMIT/OFFSET 运行查询并获取总行数 https://stackoverflow.com/questions/28888375/run-a-query-with-a-limit-offset-and-also-get-the-total-number-of-rows/28888696#28888696

临时解决方案

解决到目前为止的所有警告,并根据添加的信息,我们可能会得到以下查询:

WITH cte AS (
   SELECT DISTINCT ON (person_id) *
   FROM   tbl
   WHERE  status IN ('ACCEPTED', 'CORRECTED')
   ORDER  BY person_id, submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC
   )
SELECT *
FROM  (
   TABLE  cte
   ORDER  BY person_id  -- ?? see below
   LIMIT  10
   OFFSET 0
   ) sub
RIGHT  JOIN (SELECT count(*) FROM cte) c(total_rows_in_cte) ON true;

现在 CTE 是actually使用过两次。这RIGHT JOIN保证我们得到总数,无论OFFSET. DISTINCT ON应该对每个中仅有的几行执行 OK-ish(person_id)在基本查询中。

But你有宽行。平均有多宽?该查询可能会导致对整个表进行顺序扫描。索引不会有太大帮助。这一切都将保留分页效率极低. See:

  • 在大表上使用 OFFSET 优化查询 https://stackoverflow.com/questions/34110504/optimize-query-with-offset-on-large-table/34291099#34291099

您不能使用用于分页的索引,因为它基于 CTE 的派生表。并且您的分页实际排序标准仍然不清楚(ORDER BY id?)。如果分页是目标,那么您迫切需要不同的查询样式。如果您只对前几页感兴趣,则还需要不同的查询样式。最好的解决方案取决于问题中仍然缺少的信息......

速度更快

对于您更新的目标:

查找 a 的最新条目person_id by submission_date

(忽略“对于指定的过滤条件、类型、计划、状态”为了简单起见。)

And:

查找每行的最新行person_id仅当有status IN ('ACCEPTED','CORRECTED')

基于这两个专业indices:

CREATE INDEX ON tbl (submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST)
WHERE  status IN ('ACCEPTED', 'CORRECTED'); -- optional

CREATE INDEX ON tbl (person_id, submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST);

运行此查询:

WITH RECURSIVE cte AS (
   (
   SELECT t  -- whole row
   FROM   tbl t
   WHERE  status IN ('ACCEPTED', 'CORRECTED')
   AND    NOT EXISTS (SELECT FROM tbl
                      WHERE  person_id = t.person_id 
                      AND   (  submission_date,   last_updated,   id)
                          > (t.submission_date, t.last_updated, t.id)  -- row-wise comparison
                      )
   ORDER  BY submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST
   LIMIT  1
   )

   UNION ALL
   SELECT (SELECT t1  -- whole row
           FROM   tbl t1
           WHERE ( t1.submission_date, t1.last_updated, t1.id)
               < ((t).submission_date,(t).last_updated,(t).id)  -- row-wise comparison
           AND    t1.status IN ('ACCEPTED', 'CORRECTED')
           AND    NOT EXISTS (SELECT FROM tbl
                              WHERE  person_id = t1.person_id 
                              AND   (   submission_date,    last_updated,    id)
                                  > (t1.submission_date, t1.last_updated, t1.id)  -- row-wise comparison
                              )
           ORDER  BY submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST
           LIMIT  1)
   FROM   cte c
   WHERE  (t).id IS NOT NULL
   )
SELECT (t).*
FROM   cte
LIMIT  10
OFFSET 0;

这里的每组括号都是必需的。

这种复杂程度应该通过使用给定的索引而不是顺序扫描来更快地检索相对较小的顶行集。看:

  • 优化 GROUP BY 查询以检索每个用户的最新行 https://stackoverflow.com/questions/25536422/optimize-group-by-query-to-retrieve-latest-row-per-user/25536748#25536748

submission_date最有可能应该是类型timestamptz or date,不是character varying(255) - 无论如何,这在 Postgres 中都是一个奇怪的类型定义。看:

  • 重构字段的外键 https://stackoverflow.com/questions/24558650/refactor-foreign-key-to-fields/24560486#24560486

可能会优化更多细节,但这已经失控了。您可以考虑专业咨询。

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

在单个查询中获取分页行和总计数 的相关文章

  • SQLAlchemy - 连接表关系上的 order_by

    我正在使用声明式 SQLAlchemy 并且有三个模型 Role Permission and RolePermission 在我的Role模型 我有以下内容 class Role Base name Column u NAME VARCH
  • 如何用NULL替换空格

    我在 sql server 2012 中有一个包含空格的列 我想用 NULL 替换这些空白 我已经编写了以下查询 但它不起作用 SELECT replace COLUMN1 NULL FROM Orders 如何实现上述功能 提前致谢 Us
  • Azure PostgreSQL 时间点还原不起作用

    我们在 Azure 中有一个 Postgre 数据库 但遇到了一个问题 表中的所有行都被删除 我们尝试使用 de azure 门户中的 时间点还原 选项 但创建的数据库与当前运行的数据库具有相同的数据 我们还尝试了其他日期和时间 数据库问题
  • MySQL 偏移无限行

    我想构造一个查询 显示表中的所有结果 但从表的开头偏移 5 据我所知 MySQLLIMIT需要一个限制和一个偏移量 有什么办法可以做到这一点吗 来自MySQL LIMIT 手册 http dev mysql com doc refman 5
  • 获取两个不同日期列的计数并按日期分组

    我有包含两个 DATE 列的表 TS customer 和 TS verified 我正在寻找一种方法来获取结果 在第一列中 我有某人创建用户 TS customer 或某人经过验证 TS verified 的日期 在第二列中 我希望 co
  • 岛屿和缺口问题

    背景故事 我有一个数据库 其中包含卡车司机的数据点 其中还包含 在卡车上时 驾驶员可以拥有 驾驶员身份 我想做的是按司机 卡车对这些状态进行分组 截至目前 我已尝试使用 LAG LEAD 来提供帮助 这样做的原因是我可以知道驱动程序状态何时
  • 数据库函数 VS Case 语句

    昨天我们遇到了一个场景 必须获取 a 的类型db field在此基础上我们必须编写该字段的描述 喜欢 Select Case DB Type When I Then Intermediate When P Then Pending Else
  • 为列名创建动态选择获取值 - 在 SQL Server 中

    请帮助我创建一个选择 SQL 语句 其中的结果列名称是从原始表中的列值获取的 表名是Device Part 用户可以输入很多DeviceCode其中有许多动态PartTypeName PartTypeName 值为PartInfo 这可能有
  • FIND_IN_SET 具有多个值[重复]

    这个问题在这里已经有答案了 我想从数据库字段搜索多个值 以下是我的查询 SELECT FROM tablename WHERE FIND IN SET 12 13 15 15 category id 我如何搜索它对我不起作用 FIND IN
  • RedGate ReadyRoll 的替代品了吗? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在寻找一种经济实惠的 RedGate ReadyRoll 替代方案 以实现 SQL 的持续部署 我
  • 在 SQL 查询中使用 fn_Split

    我一直在努力争取fn Split在我的查询中正常工作 我到处搜索 并在这里找到了似乎接近我需要的答案 但我仍然无法使其发挥作用 基本上我试图返回与数组中的条目匹配的记录 我已经有了一个与我的数据库一起使用的表值函数 如下所示 Select
  • Google BigQuery:如何使用 SQL 创建新列

    我想在不使用旧版 SQL 的情况下向现有表添加一列 基本的 SQL 语法是 ALTER TABLE table name ADD column name datatype 我格式化了 Google BigQuery 的查询 ALTER TA
  • 单个 sql 查询可以处理 sql server 中的 null 或值日期范围

    使用 SQL Server 2008 我有一个存储过程 其中开始日期和结束日期作为日期范围的输入参数 寻找一个singlesql 查询 其中在 where 子句中有一个开始日期和结束日期 可以处理日期均为空或都有值的两种情况 我不想使用 I
  • 更改 IdentityServer4 实体框架表名称

    我正在尝试更改由 IdentityServer4 的 PersistedGrantDb 和 ConfigurationDb 创建的默认表名称 并让实体框架生成正确的 SQL 例如 而不是使用实体IdentityServer4 EntityF
  • 选择表中的人员并排除妻子,但合并他们的名字

    我有一张桌子Person PersonID FirstName LastName 1 John Doe 2 Jane Doe 3 NoSpouse Morales 4 Jonathan Brand 5 Shiela Wife And a R
  • Postgres - 这是在布尔列上创建部分索引的正确方法吗?

    我有下表 CREATE TABLE recipemetadata Lots of columns diet glutenfree boolean NOT NULL 大多数每一行都会被设置为FALSE除非有人想出一些席卷全国的疯狂新无麸质饮食
  • SQL Server - 在设置 COLLATE Latin1_General_CS_AS 的情况下搜索不区分大小写

    家长提问 https stackoverflow com questions 50974562 sql server update to match and replace only exact words感谢 Iamdave 部分问题得到
  • 通过“SELECT”命令选择每组的前两条记录的最佳方法是什么?

    例如我有下表 id group data 1 1 aaa 2 1 aaa 3 2 aaa 4 2 aaa 5 2 aaa 6 3 aaa 7 3 aaa 8 3 aaa 通过 SELECT 命令选择每组的前两条记录的最佳方法是什么 如果没有
  • SQL查询:按字符长度排序?

    是否可以按字符总数对sql数据行进行排序 e g SELECT FROM database ORDER BY data length 我想你想用这个 http dev mysql com doc refman 5 0 en string f
  • 如何在 typeorm 中使用 LEFT JOIN LATERAL?

    我想在 TypeOrm 中使用以下查询 但找不到将其转换为 TypeOrm 的方法 任何帮助表示赞赏 SELECT FROM blocked times bt LEFT JOIN LATERAL SELECT FROM bookings b

随机推荐

  • Node.JS Schema.pre('save) 不更改数据

    我正在制作用户授权系统 并希望在将密码保存到数据库之前对其进行哈希处理 为了达到这个目的 我使用 bcrypt nodejs 上面标题中的问题 var mongoose require mongoose var bcrypt require
  • 如何正确处理分块编码请求?

    我有两个网站 一个使用 PHP 的 Lighttpd 第二个使用 Apache 这两个网站都不能正确处理分块传输编码 我从我的手机 J2ME 发送此请求 并且无法将此传输类型更改为任何其他类型 所以我唯一的方法是以其他方式处理分块传输编码请
  • Android Studio 2.2 预览版在实现数据绑定时抛出错误

    我已将 android studio 更新到 2 2 预览版 1 并按指定应用了 google 和 firebase 的指定依赖项 但仍然出现以下错误 EmptyThrowable Wrong dependency type class c
  • XMLHttpRequest setRequestHeader() --> 有没有办法设置标头值,而不是附加到它?

    from http www w3 org TR XMLHttpRequest the setrequestheader method http www w3 org TR XMLHttpRequest the setrequestheade
  • 验证正整数

    我只想允许数字字段使用正整数 包括零 如何使用 JSR 303 定义此验证 I tried Min value 0 message msg1 但它允许浮点值 如 1 2 Digits fraction 0 integer 10 messag
  • 线程拥有堆栈和进程拥有堆栈的策略是什么?

    线程拥有堆栈和进程拥有堆栈的策略是什么 如果我们有 10 个进程 那么我们有多少个堆栈 10 个 如果一个进程下有 10 个线程 那么我们有多少个堆栈 1 所有线程共享同一个堆栈 Thanks 如果你考虑一下堆栈是什么 那么共享堆栈是没有意
  • Wordapp 未在线程或并行进程中关闭

    下面的代码通常可以工作 并且在将 docx doc 保存到 pdf 后打开和关闭 word 但是当在线程或并行 for 循环中使用以下代码时 它不会 有任何想法吗 我已经提供了下面的所有代码 这是在函数中使用时工作正常的代码 wordApp
  • 如何正确对整数数组进行排序

    尝试从我知道仅包含整数的数组中获取最高和最低值似乎比我想象的更难 var numArray 140000 104 99 numArray numArray sort console log numArray 我希望这能显示99 104 14
  • Eclipse 在 pom.xml 文件中显示错误:cvc-datatype-valid.1.2.1: '${MYVAR}' 不是 'boolean' 的有效值

    我有一个 Maven 项目 可以在命令行上正常构建 我想在 Eclipse Luna 4 4 1 中编辑项目文件 但是当我加载项目时 它在我的 pom xml 文件中报告以下错误 cvc datatype valid 1 2 1 MYVAR
  • 根据值将逗号分隔的数字列拆分为多列

    我有一个专栏f在我的数据框中 我想根据该列中的值扩展到多个列 例如 df lt structure list f c NA 18 17 10 12 8 17 11 6 18 12 12 NA 17 11 12 Names f row nam
  • 使用特定的 url 地址从 java 代码关闭浏览器

    1 我想使用我的java代码中的url地址关闭特定的浏览器选项卡 因为它是一个客户端服务器应用程序 我想使用客户端应用程序中的 url 地址关闭浏览器选项卡 服务器端将有一个 jar 它将与客户端请求进行通信 并从客户端获取 url 并根据
  • JSON.NET序列化没有属性名称的字典[重复]

    这个问题在这里已经有答案了 大家 我有字典属性名称的 json 序列化问题 这是我的代码 public class MyClass public string A get set public string B get set public
  • 用于调试的 YII 日志记录

    在很多情况下 Xdebug不适合调试 因为它涉及点击运行到特定的代码行 我想使用类似的东西cakePHP调试功能 供开发人员将类的特定属性的值输出到浏览器 我在用Yii framework这是我的配置yii log in the main
  • 如何实例化对象的静态向量?

    我有一个 A 类 它有一个静态对象向量 对象属于 B 类 class A public static void InstantiateVector private static vector b vector of B 在函数 Instan
  • 修改现有的 Android ROM 以控制用户操作

    我正在为客户创建一个 Android 应用程序 该应用程序将预安装并与手机一起分发 现在客户要求我锁定 ROM 以防止未来的用户使用除此应用程序之外的任何其他应用程序 IE 没有浏览 没有电子邮件 没有任何可能产生任何费用的东西等 现在 经
  • 在 CMD 批处理脚本中调用标签时如何利用超过 9 个参数?

    我想知道如何在调用标签时在批处理脚本中调用超过 9 个参数 例如 下面显示我分配了 12 个参数 并尝试回显所有这些参数 CALL LABEL one two three four five six seven eight nine ten
  • Kafka消费者默认组ID

    我正在使用 Apache Kafka 及其 Java 客户端 我发现消息在属于同一组的不同 Kafka Consumer 之间进行负载平衡 即共享相同的组 id 在我的应用程序中 我需要所有消费者阅读所有消息 所以我有几个问题 如果我没有在
  • 在 IntelliJ IDEA 中编写并运行 pyspark

    我正在尝试在 IntelliJ 中使用 Pyspark 但我不知道如何正确安装它 设置项目 我可以在 IntelliJ 中使用 Python 并且可以使用 pyspark shell 但我无法告诉 IntelliJ 如何查找 Spark 文
  • 如何在Python中导入其他项目的函数?

    我在一个项目中有一些代码 我想在另一个项目中重用它们 我需要做什么 在两个文件夹中 才能执行此操作 目录结构类似于 Foo Project1 file1 py file2 py Bar Project2 fileX py fileY py
  • 在单个查询中获取分页行和总计数

    核心要求 查找 a 的最新条目person id by submission date对于指定的过滤条件type plan status 可能有更多这样的过滤器 但无论如何 按提交日期返回最新的逻辑是相同的 有两个主要用途 一是在 UI 中