合并多个表中的最新条目

Posted

技术标签:

【中文标题】合并多个表中的最新条目【英文标题】:Combine the most recent entries from a number of tables 【发布时间】:2015-03-29 20:02:44 【问题描述】:

我有一个 master 表,其中包含多个 ID:

ID  ...
0   ...
1   ...

还有多个表(比如vtbl1vtbl2vtbl3),带有指向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+:

Optimize GROUP BY query to retrieve latest record per user

对于您已经不那么简单的案例还有一个复杂的问题:

值也可以是不同的类型

备选方案 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 子句:

How can I simplify this game statistics query?

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 个不同条目?

合并 SQL 表中的联系人而不创建重复条目

从表中选择最新的带时间戳的值,该表对于一个列 id 有多个条目,对于每个唯一的列 id 和来自另一个表的数据

如何使用 LINQ 从表中获取最新条目?

SQL Group By and Order -- 检索表中最新条目的详细信息

MySQL连接和连接行而不重复条目[重复]