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

时间:2015-08-11 09:44:16

标签: postgresql function plpgsql dynamic-sql multiple-tables

我有两个感兴趣的行:connection_node_start_idconnection_node_end_id。我的目标是获取所有这些ID的集合,可以是平面ARRAY,也可以是由一行组成的新TABLE

示例输出ARRAY:

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

示例输出表:

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

我的拳头尝试有点笨拙并且无法正常工作,因为SELECT语句只返回一行。似乎必须有一个简单的方法来做到这一点,有人能指出我正确的方向吗?

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 $1
  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';

测试表:

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

3 个答案:

答案 0 :(得分:1)

EXECUTE ... INTO语句只能从单行返回数据:

  

如果返回多行,则只会将第一行分配给INTO变量。

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

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

答案 1 :(得分:1)

对于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 $1
   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($1) tbl
   );
END
$func$  LANGUAGE plpgsql;

相关:

Postgres 9.3 引入了

LATERAL

对于较旧的Postgres

您也可以在SELECT列表中使用set-returning函数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($1) 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($1) tbl
   );
END
$func$  LANGUAGE plpgsql;
pg 9.1引入了

format()

对于大表可能会慢一点,因为每个表每列扫描一次(这里是2次)。结果中的排序顺序也不同 - 但这对您来说似乎并不重要。

确保清理转义标识符以防止SQL注入和其他非法语法。详细说明:

答案 2 :(得分:1)

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

  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)