【问题标题】: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。

标签: sql postgresql aggregate-functions greatest-n-per-group crosstab


【解决方案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, ...);
    

    密切相关:

    相关基础知识:

    备选方案 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 作为标识符。

    【讨论】:

    • 干杯。我回来后会好好看看和测试。
    猜你喜欢
    • 1970-01-01
    • 2012-08-07
    • 2021-10-31
    • 1970-01-01
    • 1970-01-01
    • 2020-06-19
    • 1970-01-01
    • 2018-05-29
    • 2016-04-05
    相关资源
    最近更新 更多