从多个表中的多个列中获取 ID 作为一组或数组

Get IDs from multiple columns in multiple tables as one set or array

我有多个 tables,每两行感兴趣:connection_node_start_idconnection_node_end_id。我的目标是获取所有这些 ID 的集合,可以是平面 ARRAY 或包含一行的新 TABLE

示例输出数组:

result = {1,4,7,9,2,5}

示例输出 TABLE:

IDS
-------
1
4
7
9
2 
5

我的第一次尝试有些笨拙,并且无法正常工作,因为 SELECT 语句只有 returns 一行。看来一定有一个简单的方法可以做到这一点,有人能指出我正确的方向吗?

CREATE OR REPLACE FUNCTION get_connection_nodes(anyarray)
  RETURNS anyarray AS
$$
DECLARE
  table_name varchar;
  result integer[];
  sel integer[];
BEGIN
  FOREACH table_name IN ARRAY 
  LOOP
     RAISE NOTICE 'table_name(%)',table_name;
     EXECUTE 'SELECT ARRAY[connection_node_end_id, 
                           connection_node_start_id] FROM ' || table_name INTO sel;
    RAISE NOTICE 'sel(%)',sel;
    result  := array_cat(result, sel);  
  END LOOP;
  RETURN result;            
END
$$
  LANGUAGE 'plpgsql';

测试table:

connection_node_start_id | connection_node_end_id
--------------------------------------------------
 1                       | 4 
 7                       | 9 

通话:

SELECT get_connection_nodes(ARRAY['test_table']);

结果:

{1,4}  -- only 1st row, rest is missing

EXECUTE ... INTO语句只能return单行数据:

If multiple rows are returned, only the first will be assigned to the INTO variable.

为了连接所有行的值,您必须先按列聚合它们,然后附加数组:

EXECUTE 'SELECT array_agg(connection_node_end_id) || 
                array_agg(connection_node_start_id) FROM ' || table_name INTO sel;

对于 Postgres 9.3+

CREATE OR REPLACE FUNCTION get_connection_nodes(text[])
  RETURNS TABLE (ids int) AS
$func$
DECLARE
   _tbl text;
BEGIN
   FOREACH _tbl IN ARRAY 
   LOOP
      RETURN QUERY EXECUTE format('
         SELECT t.id
         FROM   %I, LATERAL (VALUES (connection_node_start_id)
                                  , (connection_node_end_id)) t(id)'
       , _tbl);
   END LOOP;
END
$func$  LANGUAGE plpgsql;

dba.SE上的相关回答:

或者放弃循环并连接单个查询。大概最快:

CREATE OR REPLACE FUNCTION get_connection_nodes2(text[])
  RETURNS TABLE (ids int) AS
$func$
BEGIN
RETURN QUERY EXECUTE (    
   SELECT string_agg(format(
             'SELECT t.id FROM %I, LATERAL (VALUES (connection_node_start_id)
                                                 , (connection_node_end_id)) t(id)'
           , tbl), ' UNION ALL ')
   FROM   unnest() tbl
   );
END
$func$  LANGUAGE plpgsql;

相关:

  • Loop through like tables in a schema

是随 Postgres 9.3.

引入的

对于较旧的 Postgres

您也可以在 SELECT 列表中使用设置返回函数 unnest():

CREATE OR REPLACE FUNCTION get_connection_nodes2(text[])
  RETURNS TABLE (ids int) AS
$func$
BEGIN
   RETURN QUERY EXECUTE (
   SELECT string_agg(
            'SELECT unnest(ARRAY[connection_node_start_id
                               , connection_node_end_id]) FROM ' || tbl
          , ' UNION ALL '
          )
   FROM (SELECT quote_ident(tbl) AS tbl FROM unnest() tbl) t
   );
END
$func$  LANGUAGE plpgsql;

应该适用于 pg 8.4+(或更旧)。也适用于当前的 Postgres (9.4),但 LATERAL 更干净。

或者非常简单:

CREATE OR REPLACE FUNCTION get_connection_nodes3(text[])
  RETURNS TABLE (ids int) AS
$func$
BEGIN
   RETURN QUERY EXECUTE (
   SELECT string_agg(format(
             'SELECT connection_node_start_id FROM %1$I
              UNION ALL
              SELECT connection_node_end_id FROM %1$I'
           , tbl), ' UNION ALL ')
   FROM   unnest() tbl
   );
END
$func$  LANGUAGE plpgsql;

format() 是在 pg 9.1 中引入的。

大 tables 可能会慢一点,因为每个 table 对每一列扫描一次(所以这里是 2 次)。结果中的排序顺序也不同 - 但这对您来说似乎无关紧要。

一定要清理转义标识符以防止 SQL 注入和其他非法语法。详情:

  • Table name as a PostgreSQL function parameter

您可能正在寻找这样的东西:

  CREATE OR REPLACE FUNCTION d (tblname TEXT [])
  RETURNS TABLE (c INTEGER) AS $$

  DECLARE sql TEXT;

  BEGIN
    WITH x
    AS (SELECT unnest(tblname) AS tbl),

      y AS (
      SELECT FORMAT('
      SELECT connection_node_end_id
      FROM %s

        UNION ALL

      SELECT connection_node_start_id
      FROM %s
      ', tbl, tbl) AS s
      FROM x)

    SELECT string_agg(s, ' UNION ALL ')
      INTO sql
    FROM y;

    RETURN QUERY EXECUTE sql;
  END;$$

LANGUAGE plpgsql;

CREATE TABLE a (connection_node_end_id INTEGER, connection_node_start_id INTEGER);
INSERT INTO A VALUES (1,2);

CREATE TABLE b (connection_node_end_id INTEGER, connection_node_start_id INTEGER);
INSERT INTO B VALUES (100, 101);

SELECT * from d(array['a','b']);
  c
-----
   1
   2
 100
 101
(4 rows)