如何修改PL/pgSQL函数实现动态列数的查询结果返回?
问题描述
现有SQL可查询v_deg='3'时的label列和对应频次freq列,需求是根据v_deg的所有不同值生成动态频次列(格式:label|freq(v_deg='1')|freq(v_deg='2')|...)。尝试编写PL/pgSQL函数GET_REM_T4时触发错误:
ERROR: relation "current_table" does not exist
LINE 1: SELECT DISTINCT v_deg FROM current_table
基础查询SQL:
SELECT dip || ' ' || ( SELECT lib1 || ' ' || lib2 FROM bds WHERE dip_s = dip ) AS label, COUNT(dip) AS freq FROM current_table WHERE v_deg = '3' GROUP BY dip;
原错误函数:
CREATE OR REPLACE FUNCTION GET_REM_T4(current_table text) RETURNS SETOF record LANGUAGE plpgsql AS $$ DECLARE col_name text; BEGIN FOR col_name IN SELECT DISTINCT v_deg FROM current_table LOOP EXECUTE 'SELECT dip || '' '' || ( SELECT lib1 || '' '' || lib2 FROM bds WHERE dip_s = '||current_table.dip||' AND v_deg = '||col_name|| ' )' INTO col_name; RETURN NEXT (col_name); END LOOP; END; $$;
错误原因
- 函数参数
current_table是文本类型,直接在静态SQL中引用会被PostgreSQL识别为物理表名,而非传入的参数值,因此找不到名为current_table的表。 - 原函数逻辑完全偏离需求:仅循环查询label值,未实现按
v_deg生成动态频次列的透视功能。 - SQL拼接存在语法错误与注入风险,且错误地在动态SQL外部引用表字段
current_table.dip。
修正后的函数实现
以下函数会自动获取目标表中所有唯一的v_deg值,动态生成透视SQL,返回label列加上每个v_deg对应的频次列:
CREATE OR REPLACE FUNCTION GET_REM_T4(p_table_name text) RETURNS TABLE (label text, freq_columns jsonb) LANGUAGE plpgsql AS $$ DECLARE v_deg_values text[]; pivot_sql text; BEGIN -- 1. 获取所有唯一的v_deg值并排序 EXECUTE format('SELECT ARRAY_AGG(DISTINCT v_deg ORDER BY v_deg) FROM %I', p_table_name) INTO v_deg_values; -- 2. 构建透视SQL,为每个v_deg生成对应的频次统计列 pivot_sql := format( 'SELECT dip || '' '' || (SELECT lib1 || '' '' || lib2 FROM bds WHERE dip_s = ct.dip) AS label, jsonb_build_object(%s) AS freq_columns FROM %I ct GROUP BY ct.dip ORDER BY ct.dip', string_agg(format('%L, COUNT(CASE WHEN v_deg = %L THEN 1 END)', 'freq_v_deg_'||val, val), ', '), p_table_name ) FROM unnest(v_deg_values) val; -- 3. 执行动态SQL并返回结果 RETURN QUERY EXECUTE pivot_sql; END; $$;
如果需要直接返回独立的频次列而非JSON格式,可使用以下版本(调用时需指定列定义):
CREATE OR REPLACE FUNCTION GET_REM_T4(p_table_name text, OUT result record) LANGUAGE plpgsql AS $$ DECLARE v_deg_values text[]; pivot_sql text; col_defs text; BEGIN -- 获取所有唯一v_deg值 EXECUTE format('SELECT ARRAY_AGG(DISTINCT v_deg ORDER BY v_deg) FROM %I', p_table_name) INTO v_deg_values; -- 生成返回列的定义语句 col_defs := 'label text, ' || string_agg(format('freq_v_deg_%s bigint', val), ', ') FROM unnest(v_deg_values) val; -- 构建透视SQL pivot_sql := format( 'SELECT dip || '' '' || (SELECT lib1 || '' '' || lib2 FROM bds WHERE dip_s = ct.dip) AS label, %s FROM %I ct GROUP BY ct.dip ORDER BY ct.dip', string_agg(format('COUNT(CASE WHEN v_deg = %L THEN 1 END)', val), ', '), p_table_name ) FROM unnest(v_deg_values) val; -- 执行并返回动态列结果 EXECUTE format('SELECT * FROM (%s) AS t(%s)', pivot_sql, col_defs) INTO result; RETURN; END; $$;
使用示例
- JSON格式版本调用:
SELECT * FROM GET_REM_T4('your_target_table');
- 动态列版本调用(需根据实际
v_deg值指定列定义):
SELECT * FROM GET_REM_T4('your_target_table') AS t(label text, freq_v_deg_1 bigint, freq_v_deg_2 bigint, freq_v_deg_3 bigint);
内容的提问来源于stack exchange,提问作者rochard4u
相关产品推荐
相关产品推荐

