Динамической генерации столбцов для перекрестной таблицы в PostgreSQL
Я пытаюсь создать crosstab
запросы в PostgreSQL такие, что он автоматически генерирует crosstab
столбцы вместо жесткого кодирования. Я написал функцию, которая динамически генерирует список столбцов, который мне нужен для моего crosstab
запрос. Идея состоит в том, чтобы заменить результат этой функции в crosstab
запрос с использованием динамического SQL.
Я знаю, как это сделать легко в SQL Server, но мои ограниченные знания PostgreSQL препятствуют моему прогрессу здесь. Я думал хранения результата функции, которая генерирует динамический список столбцов в переменную и использовать его для динамического построения SQL-запроса. Было бы здорово, если бы кто-то мог вести меня по тому же поводу.
-- Table which has be pivoted
CREATE TABLE test_db
(
kernel_id int,
key int,
value int
);
INSERT INTO test_db VALUES
(1,1,99),
(1,2,78),
(2,1,66),
(3,1,44),
(3,2,55),
(3,3,89);
-- This function dynamically returns the list of columns for crosstab
CREATE FUNCTION test() RETURNS TEXT AS '
DECLARE
key_id int;
text_op TEXT = '' kernel_id int, '';
BEGIN
FOR key_id IN SELECT DISTINCT key FROM test_db ORDER BY key LOOP
text_op := text_op || key_id || '' int , '' ;
END LOOP;
text_op := text_op || '' DUMMY text'';
RETURN text_op;
END;
' LANGUAGE 'plpgsql';
-- This query works. I just need to convert the static list
-- of crosstab columns to be generated dynamically.
SELECT * FROM
crosstab
(
'SELECT kernel_id, key, value FROM test_db ORDER BY 1,2',
'SELECT DISTINCT key FROM test_db ORDER BY 1'
)
AS x (kernel_id int, key1 int, key2 int, key3 int); -- How can I replace ..
-- .. this static list with a dynamically generated list of columns ?
3 ответов
вы можете использовать предоставленную функцию C crosstab_hash
для этого.
руководство не очень ясно в этом отношении. Это упоминается в конце главы о crosstab()
С двумя параметрами:
вы можете создавать предопределенные функции, чтобы избежать необходимости выписывать имена и типы столбцов результатов в каждом запросе. См. примеры в предыдущий раздел. Базовая функция C для этой формы
crosstab
названныйcrosstab_hash
.
для примера:
CREATE OR REPLACE FUNCTION f_cross_test_db(text, text)
RETURNS TABLE (kernel_id int, key1 int, key2 int, key3 int)
AS '$libdir/tablefunc','crosstab_hash' LANGUAGE C STABLE STRICT;
звоните:
SELECT * FROM f_cross_test_db(
'SELECT kernel_id, key, value FROM test_db ORDER BY 1,2'
,'SELECT DISTINCT key FROM test_db ORDER BY 1');
обратите внимание, что вам нужно создать отчетливые для каждого
@erwin-brandstetter: возвращаемый тип функции не является проблемой, если вы всегда возвращаете тип JSON с преобразованными результатами.
вот функция, которую я придумал:
CREATE OR REPLACE FUNCTION report.test(
i_start_date TIMESTAMPTZ,
i_end_date TIMESTAMPTZ,
i_interval INT
) RETURNS TABLE (
tab JSON
) AS $ab$
DECLARE
_key_id TEXT;
_text_op TEXT = '';
_ret JSON;
BEGIN
-- SELECT DISTINCT for query results
FOR _key_id IN
SELECT DISTINCT at_name
FROM report.company_data_date cd
JOIN report.company_data_amount cda ON cd.id = cda.company_data_date_id
JOIN report.amount_types at ON cda.amount_type_id = at.id
WHERE date_start BETWEEN i_start_date AND i_end_date
AND interval_type_id = i_interval
LOOP
-- build function_call with datatype of column
IF char_length(_text_op) > 1 THEN
_text_op := _text_op || ', ' || _key_id || ' NUMERIC(20,2)';
ELSE
_text_op := _text_op || _key_id || ' NUMERIC(20,2)';
END IF;
END LOOP;
-- build query with parameter filters
RETURN QUERY
EXECUTE '
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT * FROM crosstab(''SELECT date_start, at.at_name, cda.amount ct
FROM report.company_data_date cd
JOIN report.company_data_amount cda ON cd.id = cda.company_data_date_id
JOIN report.amount_types at ON cda.amount_type_id = at.id
WHERE date_start between $$' || i_start_date::TEXT || '$$ AND $$' || i_end_date::TEXT || '$$
AND interval_type_id = ' || i_interval::TEXT || ' ORDER BY date_start'')
AS ct (date_start timestamptz, ' || _text_op || ')
) t;';
END;
$ab$ LANGUAGE 'plpgsql';
Итак, когда вы запускаете его, вы получаете динамические результаты в JSON, и вам не нужно знать, сколько значений было повернуто:
select * from report.test(now()- '1 week'::interval, now(), 1);
tab
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[{"date_start":"2015-07-27T08:40:01.277556-04:00","burn_rate":0.00,"monthly_revenue":5800.00,"cash_balance":0.00},{"date_start":"2015-07-27T08:50:02.458868-04:00","burn_rate":34000.00,"monthly_revenue":15800.00,"cash_balance":24000.00}]
(1 row)
редактировать: если у вас смешанные типы данных в перекрестной таблице, вы можете добавить логику, чтобы найти ее для каждого столбца с помощью что-то вроде этого:--4-->
SELECT a.attname as column_name, format_type(a.atttypid, a.atttypmod) AS data_type
FROM pg_attribute a
JOIN pg_class b ON (a.attrelid = b.relfilenode)
JOIN pg_catalog.pg_namespace n ON n.oid = b.relnamespace
WHERE n.nspname = $$schema_name$$ AND b.relname = $$table_name$$ and a.attstattarget = -1;"
подход, описанный здесь http://www.cureffi.org/2013/03/19/automatically-creating-pivot-table-column-names-in-postgresql/
работал хорошо для меня.
Вместо извлечения сводной таблицы напрямую. Более простой подход-позволить функции генерировать строку SQL-запроса. Динамически выполнить результирующую строку запроса SQL по требованию.