如何从PostgreSQL子查询中返回两个值?

时间:2017-10-24 08:09:03

标签: sql postgresql subquery union

我有一个问题,我需要在PostgreSQL中的各个表中获取最后一项。

以下代码有效,并返回最新更新的类型以及上次更新的时间。

问题是,这个查询需要用作子查询,所以我想从这个查询中选择类型和最后更新的值,而PostgreSQL似乎不喜欢这个...(Subquery must return only one column

有什么建议吗?

SELECT last.type, last.max FROM (
    SELECT MAX(a.updated_at), 'a' AS type FROM table_a a WHERE a.ref = 5 UNION
    SELECT MAX(b.updated_at), 'b' AS type FROM table_b b WHERE b.ref = 5
) AS last ORDER BY max LIMIT 1

在CTE内部使用查询;

WITH sql_query as (
    SELECT id, name, address, (...other columns),

    last.type, last.max FROM (
      SELECT MAX(a.updated_at), 'a' AS type FROM table_a a WHERE a.ref = 5 UNION
      SELECT MAX(b.updated_at), 'b' AS type FROM table_b b WHERE b.ref = 5
    ) AS last ORDER BY max LIMIT 1

    FROM table_c
    WHERE table_c.fk_id = 1      
  )

1 个答案:

答案 0 :(得分:1)

固有的问题是SQL(所有SQL不仅仅是Postgres)要求select子句中使用的子查询只能返回单个值。如果你考虑一下这个限制,它确实有意义。 select子句返回行和一定数量的列,每行。列位置是网格中的单个位置。您可以通过将连接放入单个位置(或单个“复杂类型”,如JSON值)来稍微弯曲该规则,但无论如何,它仍然是该网格中的单个位置。

但是,您确实需要2个单独的列,并且需要从同一行返回两个列,因此我建议使用LIMIT 1来代替ROW_NUMBER(),以便实现此目的:

WITH LastVals as (
      SELECT type
           , max_date
           , row_number() over(order by max_date DESC) as rn
      FROM (
            SELECT MAX(a.updated_at) AS max_date, 'a' AS type FROM table_a a WHERE a.ref = 5
            UNION ALL
            SELECT MAX(b.updated_at) AS max_date, 'b' AS type FROM table_b b WHERE b.ref = 5
            )
     )
, sql_query as (
     SELECT id
          , name, address, (...other columns)

          , (select type from lastVals where rn = 1) as last_type
          , (select max_date from lastVals where rn = 1) as last_date 

     FROM table_c
     WHERE table_c.fk_id = 1      
    )

----

顺便提一下,在你的子查询中你应该使用UNION ALL类型为常数,如“a”或“b”,那么即使MAX(a.updated_at)对于2个或更多个表是相同的,行仍然会由于类型的不同而具有独特性。 UNION会尝试删除重复的行,但此处它不会有所帮助,因此请使用UNION ALL避免浪费精力。

----

另一种方法是使用LEFT JOIN而不是

  SELECT id
      , name, address, (...other columns)

      , lastVals.type
      , LastVals.last_date 

  FROM table_c
  WHERE table_c.fk_id = 1      
  LEFT JOIN (
        SELECT type
             , last_date
             , row_number() over(order by last_date DESC) as rn
        FROM (
              SELECT MAX(a.updated_at) AS last_date, 'a' AS type FROM table_a a WHERE a.ref = 5
              UNION ALL
              SELECT MAX(b.updated_at) AS last_date, 'b' AS type FROM table_b b WHERE b.ref = 5
              )
        ) LastVals ON LastVals.rn = 1