合并多个表中的最新条目
Posted
技术标签:
【中文标题】合并多个表中的最新条目【英文标题】:Combine the most recent entries from a number of tables 【发布时间】:2015-03-29 20:02:44 【问题描述】:我有一个 master
表,其中包含多个 ID:
ID ...
0 ...
1 ...
还有多个表(比如vtbl1
、vtbl2
、vtbl3
),带有指向master
的外键、时间戳和值:
ID Timestamp Value
0 01/01/01.. 2
1 01/01/02.. 7
0 01/01/03.. 5
我想为master
中的每个ID
获取一个或多个条目,其中包含每个v...
表中的最新条目(按时间戳分组)的条目(如果不存在条目,则为null):
ID Timestamp vtbl1.Value vtbl2.Value vtbl3.value
0 01/01/03.. 5 2
0 01/01/01.. 4
1 01/01/02.. 7 4 9
我确信这相当简单,但我的 SQL 已经生锈了,而且我一直在绕圈子。任何帮助将不胜感激。
澄清
这些值来自一个或多个能够读取一个或多个值的传感器。因此,ID
的每个值表中的最新value
将被视为该ID
的当前系统状态。如果时间戳匹配,则将其视为一次更新。
我需要每个ID
所需的最少更新集,以提供当前状态的完整数据集。
值也可以是不同的类型。
【问题讨论】:
如果您有多个时间戳与子表中的同一个id
相关联,那么是什么决定了max(timestamp)
显示在结果中?一个中的最大值可能不是另一个中的最大值...
每个id可能有多行。请参阅示例中的 ID 0。
【参考方案1】:
如果我正确理解您的问题,一种选择是使用条件聚合和union all
:
select id, timestamp,
max(case when tbl = 'tbl1' then value end) t1value,
max(case when tbl = 'tbl2' then value end) t2value,
max(case when tbl = 'tbl3' then value end) t3value
from (
select id, timestamp, value, 'tbl1' tbl
from tbl1
union all
select id, timestamp, value, 'tbl2' tbl
from tbl2
union all
select id, timestamp, value, 'tbl3' tbl
from tbl3
) t
group by id, timestamp
或者,如果您每个 id
有多个记录,并且您希望每个 timestamp
的最高 value
,您可以在子查询中包含 row_number()
:
select id, timestamp,
max(case when tbl = 'tbl1' then value end) t1value,
max(case when tbl = 'tbl2' then value end) t2value,
max(case when tbl = 'tbl3' then value end) t3value
from (
select id, timestamp, value, 'tbl1' tbl,
row_number() over (partition by id order by timestamp desc) rn
from tbl1
union all
select id, timestamp, value, 'tbl2' tbl,
row_number() over (partition by id order by timestamp desc) rn
from tbl2
union all
select id, timestamp, value, 'tbl3' tbl,
row_number() over (partition by id order by timestamp desc) rn
from tbl3
) t
where rn = 1
group by id, timestamp
如果每个子表中的 max(timestamp) 值都不相同,这可能会变得很困难。那时你join
是哪个?
【讨论】:
这将从每个子表中获取最高的value
,而不是与每个id
的最高timestamp
关联的value
@BrianDeMilia -- 我明白你的意思并添加了一个替代问题。如果是这种情况,我们俩都可能会遇到问题 - 是什么决定了子表中的 max(timestamp)
加入?在这一点上,需要额外的数据和样本结果进行澄清。
我将对这个问题进行一些澄清 - 给我一分钟。简短的版本是我需要最少的行数来提供完整的最新数据集(完整的数据集是每个具有与 ID 关联的条目的值表的至少一个值)。
同意,目前还不清楚他想要显示什么时间戳,因为他的预期输出中只有一个时间戳列,而每个表上可能有一个不同的时间戳,对于给定的 id 来说是最高的。如果他显示了 3 个时间戳列,我可以理解,在这种情况下,他需要与 3 个值中的每一个相关联的时间戳。
添加了说明。我想要与更新关联的时间戳。如果有多个时间戳,输出中应该有多行。干杯。【参考方案2】:
select m.*, v1.value as t1_val, v2.value as t2_val, v3.value as t3_val
from master m
left join (select x.*
from vtbl1 x
join (select id, max(timestamp) as last_ts
from vtbl1
group by id) y
on x.id = y.id
and x.timestamp = y.last_ts) v1
on m.id = v1.id
left join (select x.*
from vtbl2 x
join (select id, max(timestamp) as last_ts
from vtbl2
group by id) y
on x.id = y.id
and x.timestamp = y.last_ts) v2
on m.id = v2.id
left join (select x.*
from vtbl3 x
join (select id, max(timestamp) as last_ts
from vtbl3
group by id) y
on x.id = y.id
and x.timestamp = y.last_ts) v3
on m.id = v3.id
【讨论】:
【参考方案3】:最快的查询技术取决于值的分布。 DISTINCT ON
将是 Postgres 中的一个简单解决方案,非常适合每个子表中每个 id
的几个值。但是从您的描述中猜测,我希望每个 id
有 许多 行,所以我建议使用 LATERAL
连接的解决方案。需要 Postgres 9.3+:
对于您已经不那么简单的案例还有一个复杂的问题:
值也可以是不同的类型
备选方案 1
将所有值转换为text
。每种数据类型都可以转换为text
。
基本查询
SELECT m.id, v.timestamp, 1 AS tbl, v.value -- simple int as table id
FROM master m
, LATERAL (
SELECT timestamp, value::text -- cast to text
FROM vtbl1
WHERE id = m.id -- lateral reference
ORDER BY timestamp DESC NULLS LAST
LIMIT 1
) v
UNION ALL
SELECT m.id, v.timestamp, 2 AS tbl, v.value -- ascending without gaps
FROM master m
, LATERAL (
SELECT timestamp, value::text
FROM vtbl2
WHERE id = m.id
ORDER BY timestamp DESC NULLS LAST
LIMIT 1
) v
UNION ALL
SELECT m.id, v.timestamp, 3 AS tbl, value
FROM ...
;
为了使这更快,您只需要在(id, timestamp)
上为每个子表建立一个索引。最好是这种形式(添加value
仅在您从中获得index-only scans 时才有用):
CREATE INDEX vtbl1_combo_idx ON vtbl1 (id, timestamp DESC NULLS LAST, value)
1a。聚合(伪交叉表)
要根据需要格式化 Postgres 9.3 或更早版本中的 CASE
表达式上的聚合函数(如 demonstrated by @sgeddes)或(更好)Postgres 9.4+ 中的新聚合 FILTER
子句:
SELECT id, timestamp
, max(value) FILTER (WHERE tbl = 1) AS val1
, max(value) FILTER (WHERE tbl = 2) AS val2
, ...
FROM ( <query frm above> ) t
GROUP BY 1, 2;
1b。交叉表
实际的交叉制表(在其他 RDBMS 中也称为“数据透视表”)应该快得多。您需要安装附加模块tablefunc
,说明如下。
这里的特殊困难:我们有一个复合“行名”(id, timestamp)
,但该函数需要一个单个列作为行名。所以我们用row_number()
代替,但不要在结果中显示那个代理键:
SELECT id, timestamp, val1, val2, val3, ...
-- normally SELECT * is enough; explicit list to filter rn
FROM crosstab(
$$
SELECT row_number() OVER (ORDER BY id, timestamp DESC NULLS LAST) AS rn
, id, timestamp, tbl, value
FROM ( <query from above> ) t
ORDER BY 1
$$
, 'SELECT generate_series(1,3)' -- replace 3 with highest table nr.
) AS ct (
rn int, id int, timestamp date
, val1 text, val2 text, val3 text, ...);
密切相关:
Postgres - Transpose Rows to Columns相关基础知识:
PostgreSQL Crosstab Query Pivot on Multiple Columns using Tablefunc备选方案 2
简单,但可能同样快并保留原始数据类型:
SELECT id, timestamp
, max(val1) AS val1, max(val2) AS val2, max(val3) AS val3, ...
FROM (
SELECT m.id, v.timestamp
, v.value AS val1, NULL::int AS val2, NULL::numeric AS val3, ...
-- list all values with actual data type
FROM master m
, LATERAL (
SELECT timestamp, value
FROM vtbl1
WHERE id = m.id
ORDER BY timestamp DESC NULLS LAST
LIMIT 1
) v
UNION ALL
SELECT m.id, v.timestamp
, NULL, v.value, NULL, ... -- column names & data types defined in first SELECT
FROM master m
, LATERAL (
SELECT timestamp, value
FROM vtbl2
WHERE id = m.id
ORDER BY timestamp DESC NULLS LAST
LIMIT 1
) v
UNION ALL
SELECT m.id, v.timestamp
, NULL, NULL, v.value, ...
FROM ...
) t
GROUP BY 1, 2
ORDER BY 1, 2;
除此之外:切勿使用基本类型名称或 reserved words(在标准 SQL 中)如 timestamp
作为标识符。
【讨论】:
干杯。我回来后会好好看看和测试。以上是关于合并多个表中的最新条目的主要内容,如果未能解决你的问题,请参考以下文章
如何从 SQLite 中的表中选择最新的 100 个不同条目?
从表中选择最新的带时间戳的值,该表对于一个列 id 有多个条目,对于每个唯一的列 id 和来自另一个表的数据