我在Postgres 9.2中有以下用于用户消息(简化形式)的日志表:
CREATE TABLE log (
log_date DATE,
user_id INTEGER,
payload INTEGER
);
每个用户每天最多包含一条记录。在300天之内,每天大约有50万条记录。每个用户的有效负载都在增加(如果很重要)。
我想有效地检索每个用户在特定日期之前的最新记录。我的查询是:
SELECT user_id, max(log_date), max(payload)
FROM log
WHERE log_date <= :mydate
GROUP BY user_id
这非常慢。我也尝试过:
SELECT DISTINCT ON(user_id), log_date, payload
FROM log
WHERE log_date <= :mydate
ORDER BY user_id, log_date DESC;
具有相同的计划,并且速度同样慢。
到目前为止,我在上只有一个索引log(log_date)
,但并没有太大帮助。
我有一个users
包含所有用户的表。我还想为某些用户(具有的用户payload > :value
)检索结果。
我是否应该使用其他任何索引来加快速度,或者通过其他任何方式来实现我想要的目标?
为了获得最佳读取性能,您需要一个多列索引:
CREATE INDEX log_combo_idx
ON log (user_id, log_date DESC NULLS LAST);
为了使 仅索引扫描成为
可能,请在子句payload
的覆盖索引中添加本来不需要的列INCLUDE
(Postgres 11或更高版本):
CREATE INDEX log_combo_covering_idx
ON log (user_id, log_date DESC NULLS LAST) INCLUDE (payload);
看:
较旧版本的备用广告:
CREATE INDEX log_combo_covering_idx
ON log (user_id, log_date DESC NULLS LAST, payload);
为什么DESC NULLS LAST
呢?
对于每个或小表 _ 几_ 行,通常最快,最简单:user_id``DISTINCT ON
对于 _ 许多_ 每行user_id
的 索引跳跃扫描 (或 松散索引扫描
)
是(多)更有效。在Postgres
12之前尚未实现该功能-Postgres
14正在进行中。但是,有一些方法可以有效地对其进行仿真。
常用表表达式需要Postgres
8.4+ 。
LATERAL
需要Postgres 9.3+ 。
以下解决方案超出了 Postgres
Wiki 所涵盖的范围。
使用单独的users
表格,下面 2. 中的解决方案通常更简单,更快捷。向前跳。
LATERAL
加盟WITH RECURSIVE cte AS (
( -- parentheses required
SELECT user_id, log_date, payload
FROM log
WHERE log_date <= :mydate
ORDER BY user_id, log_date DESC NULLS LAST
LIMIT 1
)
UNION ALL
SELECT l.*
FROM cte c
CROSS JOIN LATERAL (
SELECT l.user_id, l.log_date, l.payload
FROM log l
WHERE l.user_id > c.user_id -- lateral reference
AND log_date <= :mydate -- repeat condition
ORDER BY l.user_id, l.log_date DESC NULLS LAST
LIMIT 1
) l
)
TABLE cte
ORDER BY user_id;
这很容易检索任意列,并且在当前的Postgres中可能最好。在第 2a 章中有更多解释 。 以下。
WITH RECURSIVE cte AS (
( -- parentheses required
SELECT l AS my_row -- whole row
FROM log l
WHERE log_date <= :mydate
ORDER BY user_id, log_date DESC NULLS LAST
LIMIT 1
)
UNION ALL
SELECT (SELECT l -- whole row
FROM log l
WHERE l.user_id > (c.my_row).user_id
AND l.log_date <= :mydate -- repeat condition
ORDER BY l.user_id, l.log_date DESC NULLS LAST
LIMIT 1)
FROM cte c
WHERE (c.my_row).user_id IS NOT NULL -- note parentheses
)
SELECT (my_row).* -- decompose row
FROM cte
WHERE (my_row).user_id IS NOT NULL
ORDER BY (my_row).user_id;
方便地检索 单列 或 整行 。该示例使用表的整个行类型。其他变体也是可能的。
要断言在先前的迭代中找到一行,请测试单个NOT NULL列(如主键)。
有关此查询的更多说明,请参见第2b章。 以下。
有关的:
users
桌子只要user_id
保证每个相关项仅一行,表布局就无关紧要。例子:
CREATE TABLE users (
user_id serial PRIMARY KEY
, username text NOT NULL
);
理想情况下,表在物理上与log
表同步排序。看:
或它足够小(低基数)几乎没有关系。否则,对查询中的行进行排序可以帮助进一步优化性能。参见刚亮的加成。
如果表的物理排序顺序users
恰好与on上的索引匹配log
,则可能无关紧要。
LATERAL
加入SELECT u.user_id, l.log_date, l.payload
FROM users u
CROSS JOIN LATERAL (
SELECT l.log_date, l.payload
FROM log l
WHERE l.user_id = u.user_id -- lateral reference
AND l.log_date <= :mydate
ORDER BY l.log_date DESC NULLS LAST
LIMIT 1
) l;
JOIN LATERAL
允许FROM
在同一查询级别上引用前面的项目。看:
导致每个用户一次索引(仅)查询。
对于users
表中缺少的用户,不返回任何行。通常,强制引用完整性的 外键 约束将排除这种情况。
同样,没有匹配项的用户也没有行log
-符合原始问题。为了使这些用户留在结果中,请使用 LEFT JOIN LATERAL ... ON true
而不是CROSS JOIN LATERAL
:
使用 LIMIT n
而不是为每个用户LIMIT 1
检索 多行 (但不是全部)。
有效地,所有这些都做相同的事情:
JOIN LATERAL ... ON true
CROSS JOIN LATERAL ...
, LATERAL ...
不过,最后一个优先级较低。显式JOIN
绑定在逗号之前。这种细微的差别可能与更多的联接表有关。看:
从 单行中* 检索 单列的 好选择。代码示例: *
多个列 也可以这样做,但是您需要更多的技巧:
CREATE TEMP TABLE combo (log_date date, payload int);
SELECT user_id, (combo1).* -- note parentheses
FROM (
SELECT u.user_id
, (SELECT (l.log_date, l.payload)::combo
FROM log l
WHERE l.user_id = u.user_id
AND l.log_date <= :mydate
ORDER BY l.log_date DESC NULLS LAST
LIMIT 1) AS combo1
FROM users u
) sub;
与LEFT JOIN LATERAL
上述类似,此变体包括 所有 用户,即使没有在中输入log
。你得到NULL
的combo1
,你可以很容易地过滤WHERE
外部查询子句如果需要的话。
Nitpick:在外部查询中,您无法区分子查询未找到行还是所有列值都碰巧为NULL-结果相同。您需要NOT NULL
在子查询中创建一列以避免这种歧义。
相关的子查询只能返回一个 值 。您可以将多个列包装为复合类型。但是为了稍后进行分解,Postgres需要一种众所周知的复合类型。仅提供列定义列表,才能分解匿名记录。
使用注册类型,例如现有表的行类型。或使用显式(并永久)注册复合类型CREATE TYPE
。或创建一个临时表(在会话结束时自动删除)以临时注册其行类型。强制转换语法:(log_date, payload)::combo
最后,我们不想combo1
在同一查询级别上进行分解。由于查询计划器的弱点,这将为每个列评估一次子查询(在Postgres 12中仍然适用)。而是,使其成为子查询并在外部查询中分解。
有关的:
演示所有具有100k日志条目和1k用户的4个查询:
db
<>在这里
拨弄 -第11页
旧sqlfiddle-拨第9.6页
问题内容: 如果我有一个具有以下结构和数据的表: 我将使用哪种查询来获得以下结果(随后的说明): 如你看到的: 每个仅返回一行。 最高的那一行是返回的那一行。 是否有一种 无需 使用子查询即可完成此操作的方法?该程序经过的关系代数术语中是否有名称? 问题答案: 该查询称为逐组最大值,它(至少在MySQL中)可以通过子查询实现。例如: 在sqlfiddle上看到它。
问题内容: 我有此数据: 代码(对于SQL Server 2005): 我想为每个名称获取具有最新DATE的ID。像这样: 实现此目的最优雅的方法是什么? 问题答案: 尽量避免使用保留字(和模糊的列名),例如…
问题内容: 我在Oracle数据库中有下表 现在,我想检索每个ID的最新值(及其时间)。输出示例: 我只是不知道如何将其放入查询… 此外,以下选项将是不错的选择: 选项1:查询应仅返回最近XX分钟的值。 选项2:应将id与另一个具有id和idname的表中的文本连接起来。id的输出应类似于:id-idname(例如1-testid1)。 非常感谢您的帮助! 问题答案: 给定此数据… …以下查询给出
问题内容: 可以说我有2个表:blog_posts和类别。每个博客帖子仅属于一个类别,因此此处的两个表之间基本上有一个外键。 我想从每个类别中检索2个最新的帖子,是否可以在单个请求中实现?GROUP BY会将所有内容分组,而在每个类别中只剩下一行。但我要其中两个。 执行1 + N查询(N =类别数)会很容易。首先检索类别。然后从每个类别检索2个帖子。 我相信执行M个查询(M =我希望从每个类别获得
问题内容: 我有以下表格,分别是BankDetails和Transactiondetails。使用这两个表,我想获得帐户名称的当前余额。 表格: 插入两个表的脚本: 输出将是这样的: 我需要使用以上两个表格输入帐户持有人姓名,帐户编号和当前余额。 下面是我的查询,我想获得优化的查询,即如果可能的话不使用子查询。 注意: 在我的情况下,贷方=添加到帐户中的金额,借方=从帐户中扣除的金额。 对于未遵循
本文向大家介绍Mysql使用索引实现查询优化,包括了Mysql使用索引实现查询优化的使用技巧和注意事项,需要的朋友参考一下 索引的目的在于提高查询效率,可以类比字典,如果要查“mysql”这个单词,我们肯定需要定位到m字母,然后从下往下找到y字母,再找到剩下的sql。如果没有索引,那么你可能需要把所有单词看一遍才能找到你想要的。 1.索引的优点 假设你拥有三个未索引的表t1、t2和t3,每个表都分