Динамическое выполнение запросов в PL/pgSQL

Я нашел решение (я думаю) проблемы, которую я собираюсь попросить на Oracle и SQL Server, но, похоже, не могу перевести это в решение Postgres. Я использую Postgres 9.3.6.

Идея состоит в том, чтобы иметь возможность генерировать "метаданные" о содержимом таблицы для целей профилирования. Это можно сделать только (AFAIK), запустив запросы для каждого столбца, чтобы выяснить, скажем... значения min/max/count и тому подобное. Чтобы автоматизировать процедуру, предпочтительно, чтобы запросы, сгенерированные БД, затем выполнялись.

С примером salesdata Таблица, я могу сгенерировать запрос на выборку для каждого столбца, возвращая значение min(), используя следующий фрагмент:

SELECT 'SELECT min('||column_name||') as minval_'||column_name||' from salesdata '  
FROM information_schema.columns 
WHERE table_name = 'salesdata'

Преимущество в том, что БД будет генерировать код независимо от количества столбцов. Теперь я имел в виду множество мест для хранения этих запросов, либо какой-то переменной, либо столбца таблицы, идея состояла в том, чтобы затем эти запросы выполнялись. Я думал о том, чтобы сохранить сгенерированные запросы в переменной, а затем выполнить их с помощью EXECUTE (или же EXECUTE IMMEDIATE), который является подходом, используемым здесь (см. правую панель), но Postgres не позволит мне объявить переменную вне функции, и я ломал голову над тем, как это будет сочетаться, будь то направление, которому нужно следовать, возможно есть что-то попроще

Будете ли вы иметь какие-либо указатели, я в настоящее время пытаюсь что-то вроде этого, вдохновленный этим другим вопросом, но не знаю, направляюсь ли я в правильном направлении:

CREATE OR REPLACE FUNCTION foo()
RETURNS void AS
$$
DECLARE
    dyn_sql text; 
BEGIN            
dyn_sql := SELECT 'SELECT min('||column_name||') from salesdata'    
    FROM information_schema.columns 
    WHERE table_name = 'salesdata';
execute dyn_sql
END
$$ LANGUAGE PLPGSQL;    

2 ответа

Решение

Системная статистика

Перед тем как катиться самостоятельно, взгляните на системный стол pg_statistic или вид pg_stats:

Это представление разрешает доступ только к тем строкам pg_statistic, которые соответствуют таблицам, на которые пользователь имеет разрешение на чтение, и поэтому безопасно разрешить публичный доступ для чтения к этому представлению.

Возможно, он уже содержит некоторые статистические данные, которые вы собираетесь вычислить. Это населено ANALYZE так что вы можете запустить это для новых (или любых) таблиц перед проверкой.

-- ANALYZE tbl;  -- optionally, to init / refresh
SELECT * FROM pg_stats
WHERE tablename = 'tbl'
AND   schemaname = 'public';

Общая динамическая функция plpgsql

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

Эта функция делает все автоматически и безопасно. Работает для любой таблицы, пока агрегатная функция min() разрешено для каждого столбца. Но вы должны знать, как обходить plpgsql.

CREATE OR REPLACE FUNCTION f_min_of(_tbl anyelement)
  RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE (
   SELECT format('SELECT (t::%2$s).* FROM (SELECT min(%1$s) FROM %2$s) t'
                , string_agg(quote_ident(attname), '), min(' ORDER BY attnum)
                , pg_typeof(_tbl)::text)
   FROM   pg_attribute
   WHERE  attrelid = pg_typeof(_tbl)::text::regclass
   AND    NOT attisdropped  -- no dropped (dead) columns
   AND    attnum > 0        -- no system columns
   );
END
$func$  LANGUAGE plpgsql;

Вызов:

SELECT * FROM f_min_of(NULL::tbl);  -- tbl being the table name

SQL Fiddle.

Вы должны понимать эти понятия:

  • Динамический SQL в plpgsql с EXECUTE,
  • Полиморфные типы.
  • Типы строк и типы таблиц в Postgres.
  • Как защититься от внедрения SQL.
  • Агрегатные функции.
  • Системные каталоги.

Соответствующий ответ с подробным объяснением:

Особая сложность с несоответствием типов

Я использую Postgres, определяя тип строки для каждой существующей таблицы. Используя концепцию полиморфных типов, я могу создать одну функцию, которая работает для любой таблицы.

Однако некоторые агрегатные функции возвращают связанные, но разные типы данных по сравнению с базовым столбцом. Например, min(varchar_column) возвращается text, который является бит-совместимым, но не совсем того же типа данных. У функций plpgsql есть слабое место, и они настаивают на типах данных точно так, как объявлено в RETURNS пункт. Никаких попыток бросать, даже неявных приведений, не говоря уже о приведениях назначений.

Это должно быть улучшено. Протестировано с Postgres 9.3. Не тестировал с 9.4, но я уверен, что ничего не изменилось в этой области.

Вот где эта конструкция входит:

SELECT (t::tbl).* FROM (SELECT ... FROM tbl) t;

Кажется, вначале извращено. Это обходной путь: явным приведением всей строки к типу строки базовой таблицы мы принудительно приводим присваивания, чтобы получить исходные типы данных для каждого столбца.

Имейте в виду, что это может не сработать для некоторой агрегатной функции. sum() возвращается numeric для sum(bigint_column) учитывать сумму, превышающую базовый тип данных. Возвращение к bigint может потерпеть неудачу...

@Erwin Brandstetter, Большое спасибо за исчерпывающий ответ. pg_stats действительно предоставляет несколько вещей, но мне действительно нужно нарисовать полный профиль, это множество вещей, минимальных, максимальных значений, количества, количества нулей, среднего значения и т. д., поэтому необходимо выполнить несколько запросов для каждый столбец, некоторые с GROUP BY и такие.

Кроме того, спасибо за то, что подчеркнули важность типов данных, я как-то ожидал, что в какой-то момент это добавит в работу гаечный ключ, моя главная задача была в том, как автоматизировать генерацию запроса и его выполнение, этот последний бит был моим основным беспокойство.

Я попробовал функцию, которую вы предоставляете (мне, вероятно, нужно будет начать изучать некоторые plpgsql), но получить ошибку в SELECT (t::tbl):

ERROR: type "tbl" does not exist

Кстати, как обозначается обозначение (t::abc), в python это будет фрагмент списка, но, вероятно, это не так в PLPGSQL

Другие вопросы по тегам