在PostgreSQL中为交叉表动态生成列
我正在尝试在PostgreSQL中创建在PostgreSQL中为交叉表动态生成列,postgresql,plpgsql,dynamic-sql,postgresql-9.1,crosstab,Postgresql,Plpgsql,Dynamic Sql,Postgresql 9.1,Crosstab,我正在尝试在PostgreSQL中创建交叉表查询,以便它自动生成交叉表列,而不是硬编码。我编写了一个函数,可以动态生成交叉表查询所需的列列表。其思想是使用动态sql在交叉表查询中替换此函数的结果 我知道如何在SQLServer中轻松实现这一点,但我对PostgreSQL的有限知识阻碍了我在这里的进步。我正在考虑将生成动态列列表的函数的结果存储到一个变量中,并使用该变量动态构建sql查询。如果有人能在这方面指导我,那就太好了 -- Table which has be pivoted CREAT
交叉表
查询,以便它自动生成交叉表
列,而不是硬编码。我编写了一个函数,可以动态生成交叉表查询所需的列列表。其思想是使用动态sql在交叉表
查询中替换此函数的结果
我知道如何在SQLServer中轻松实现这一点,但我对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 ?
为此,您可以使用提供的C函数交叉表\u hash
手册在这方面不是很清楚。提到了
您可以创建预定义函数,以避免必须写出
每个查询中的结果列名和类型。请参见中的示例
上一节。此形式的交叉表的底层C函数
被命名为交叉表\u 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;
电话:
请注意,您需要为每个具有不同返回类型的交叉表
函数创建一个不同的交叉表散列
函数
相关的:
生成列列表的函数非常复杂,结果不正确(int
在kernel\u id
之后缺失),可以用以下SQL查询替换它:
SELECT 'kernel_id int, '
|| string_agg(DISTINCT key::text, ' int, ' ORDER BY key::text)
|| ' int, DUMMY text'
FROM test_db;
无论如何,它都不能动态使用。@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)
编辑:如果交叉表中存在混合数据类型,则可以添加逻辑来查找每列,如下所示:
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;"
所描述的方法对我很有效。
而不是直接检索数据透视表。更简单的方法是让函数生成SQL查询字符串。根据需要动态执行生成的SQL查询字符串。我意识到这是一篇较老的文章,但在同一问题上有一段时间很纠结
我的问题陈述:
我有一个字段中包含多个值的表,并希望创建一个每行包含40多个列标题的交叉表查询
我的解决方案是创建一个函数,该函数在表列中循环,以获取我希望在交叉表查询中用作列标题的值
在这个函数中,我可以创建交叉表查询。在我的用例中,我将这个交叉表结果添加到一个单独的表中
例如
谢谢您的详细回复@Erwin。我能够使用一组静态交叉表字段使基本交叉表工作。我有点惊讶,我们没有任何方法动态生成列列表,因此在postgresql中没有动态交叉表。您知道在postgresql中有没有其他的迂回或黑客方式可以做到这一点?@invinc4u:问题是函数的返回类型不能动态更改。您可以动态地重新创建函数本身,然后立即调用它。但这是一件棘手的事情……是的,这是有道理的。但我希望有一种使用动态SQL的黑客方法可以解决这个问题。在准备好动态SQL之后,我们应该能够执行查询,它应该为我们生成动态交叉表。请参见以下示例:。我已经错过了我的SQL Server:(无论如何,非常感谢您的帮助和指导!!我有点困惑。这是如何回答这个问题的?根据解决方案,我们仍然需要键入列列表并在此处键入:返回表(kernel\u id int,key1 int,key2 int,key3 int)
。这不正是OP想要避免的吗?嗨@ErwinBrandstetter,你能帮我看看我的新神话吗。。。
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;"
CREATE OR REPLACE FUNCTION field_values_ct ()
RETURNS VOID AS $$
DECLARE rec RECORD;
DECLARE str text;
BEGIN
str := '"Issue ID" text,';
-- looping to get column heading string
FOR rec IN SELECT DISTINCT field_name
FROM issue_fields
ORDER BY field_name
LOOP
str := str || '"' || rec.field_name || '" text' ||',';
END LOOP;
str:= substring(str, 0, length(str));
EXECUTE 'CREATE EXTENSION IF NOT EXISTS tablefunc;
DROP TABLE IF EXISTS temp_issue_fields;
CREATE TABLE temp_issue_fields AS
SELECT *
FROM crosstab(''select issue_id, field_name, field_value from issue_fields order by 1'',
''SELECT DISTINCT field_name FROM issue_fields ORDER BY 1'')
AS final_result ('|| str ||')';
END;
$$ LANGUAGE plpgsql;