ВЫБОР динамических столбцов без функций в PostgreSQL

#sql #postgresql #union #plpgsql #dynamic-sql

#sql #postgresql #объединение #plpgsql #dynamic-sql

Вопрос:

Мне нужно выбрать строки из двух и более таблиц («A», «B»). У них есть столбцы различий, и я не использую для этого наследование.

Итак. Например:

 SELECT * FROM "A" UNION SELECT * FROM "B" 
  
 ERROR: each UNION query must have the same number of columns
  

Я могу понять почему.

Я пытаюсь получить пересекающиеся столбцы из корневой схемы в корневой таблице:

 SELECT column_name FROM information_schema.columns
WHERE table_schema = 'client_root' AND table_name ='conditions'
  

Все в порядке! Но я не использую запрос:

 SELECT
   (SELECT column_name FROM information_schema.columns
    WHERE table_schema = 'client_root' AND table_name ='conditions')
FROM "client_123"."A"
  

Итак. Как я могу поместить данные дополнительного выбора в root select?

Комментарии:

1. Может быть, вы скажете мне, как объединить таблицы с динамической структурой?

2. Я не верю, что есть способ сделать это. Вам придется запросить information_schema.columns , чтобы получить необходимые метаданные для каждой таблицы.

Ответ №1:

То, что вы пытаетесь сделать, вряд ли возможно полностью.

Создание динамического SQL

Во-первых, вот что вы можете сделать: функция plpgsql, которая создает SQL для такого запроса:

 CREATE OR REPLACE FUNCTION f_union_common_col_sql(text, text)
 RETURNS text
AS $function$
DECLARE 
  _cols text;
BEGIN

_cols := string_agg(attname, ', ')
FROM (
    SELECT a.attname
    FROM   pg_attribute a
    WHERE  a.attrelid = $1::regclass::oid
    AND    a.attnum >= 1
    INTERSECT
    SELECT a.attname
    FROM   pg_attribute a
    WHERE  a.attrelid = $2::regclass::oid
    AND    a.attnum >= 1
    ) x;

RETURN 'SELECT ' || _cols || '
FROM   ' || quote_ident($1) || '
UNION
SELECT ' || _cols || '
FROM   ' || quote_ident($1);

END;
$function$  LANGUAGE plpgsql;

COMMENT ON FUNCTION f_union_common_col_sql(text, text) IS 'Create SQL to query all visible columns that two tables have in common.
# Without duplicates. Use UNION ALL if you want to include duplicates.
# Depends on visibility dicatated by search_path
$1 .. table1: optionally schema-qualified, case sensitive!
$2 .. table2: optionally schema-qualified, case sensitive!';
  

Вызов:

 SELECT f_union_common_col_sql('myschema1.tbl1', 'myschema2.tbl2');
  

Выдает полный запрос. Выполните это во втором вызове.

Вы можете найти почти все, что я использовал здесь, в руководстве по функциям plpgsql.
Агрегатная функция string_agg() была введена в PostgreSQL 9.0. В более старых версиях вы бы: array_to_string(array_agg(attname), ', ') .


Выполнить динамический SQL?

Далее, вот что вы вряд ли сможете сделать:

 CREATE OR REPLACE FUNCTION f_union_common_col(text, text)
  RETURNS SETOF record AS
$BODY$
DECLARE 
  _cols text;
BEGIN

_cols := string_agg(attname, ', ')
FROM (
    SELECT a.attname
    FROM   pg_attribute a
    WHERE  a.attrelid = $1::regclass::oid
    AND    a.attnum >= 1
    INTERSECT
    SELECT a.attname
    FROM   pg_attribute a
    WHERE  a.attrelid = $2::regclass::oid
    AND    a.attnum >= 1
    ) x;

RETURN QUERY EXECUTE '
SELECT ' || _cols || '
FROM quote_ident($1)
UNION
SELECT ' || _cols || '
FROM quote_ident($2)';

END;
$BODY$
  LANGUAGE plpgsql VOLATILE;

COMMENT ON FUNCTION f_union_common_col(text, text) IS 'Query all visible columns that two tables have in common.
# Without duplicates. Use UNION ALL if you want to include duplicates.
# Depends on visibility dicatated by search_path
# !BUT! you need to specify a column definition list for every call. So, hardly useful.
$1 .. table1 (optionally schema-qualified)
$2 .. table1 (optionally schema-qualified)';
  

Вызов функции требует, чтобы вы указали список целевых столбцов. так что это вряд ли вообще полезно:

 SELECT * from f_union_common_col('myschema1.tbl1', 'myschema2.tbl2')

ERROR:  a column definition list is required for functions returning "record"
  

Простого способа обойти это нет. Вам пришлось бы динамически создавать функцию или, по крайней мере, сложный тип. На этом я останавливаюсь.

Комментарии:

1. Я спросил о запросе без создания новой функции

2. @Arturgspb: Это вообще невозможно в обычном SQL . Вы должны использовать функцию или анонимный блок кода (оператор DO) для запросов, включающих динамически определяемые идентификаторы.

3. С большим количеством обращений, которые сильно замедлят работу.

4. Поэтому используйте два запроса. Сначала запросите каталог, чтобы получить файлы, как я демонстрирую в своей первой функции (это можно сделать с помощью обычного SQL), создайте фактический запрос и выполните его. Дополнительный шаг занимает несколько миллисекунд.