子查询中的动态字段名称?

时间:2014-05-17 23:22:58

标签: sql postgresql aggregate-functions dynamic-sql unpivot

我有一张类似于以下内容的表格:

CREATE TABLE stats (
  name character varying(15),
  q001001 numeric(9,0),
  q001002 numeric(9,0),
  q001003 numeric(9,0),
  q001004 numeric(9,0),
  q001005 numeric(9,0)
)

我需要在此表中查询各个字段的总和,如下所示:

SELECT sum(q001001) as total001,
       sum(q001002) as total002,
       sum(q001005) as total005,
FROM stats;

这会产生一行数据和三列数据 但是,出于报告目的,我需要以相反的方式列出结果。我需要三行和一列(好吧,实际上是两个,第一个是总和的字段是这样的):

FieldName | SUM
----------+-------
q001001   |  12345
q001002   |  5432
q001005   |  986

我想使用这样的SQL,其中field_name(来自stats表中字段名称的查找表)用于子查询:

select l.field_name, (select sum(l.field_name) from stats)
from stats_field_names_lookup as l
where l.field_name in ('Q001001', 'Q001002', 'Q001005');

这里的想法是sum(l.field_name)将替换为所讨论的实际字段名称,对于WHERE子句中的每一个,然后进行评估以提供正确的总和结果值。但是,这会因以下错误而失败:

  

函数sum(字符变化)不存在

因为值有文字/字符。如何将该字符值转换为要正确计算的未加引号的字符串?

这个SQL有效。但是,当然,为每个field_name提供相同的总和值,因为它在此处被硬编码为q001001

select l.field_name, (select sum(q001001) from stats)
from stats_field_names_lookup as l
where l.field_name in ('Q001001', 'Q001002', 'Q001005');

所以,我认为这个想法在理论上是合理的。只需要帮助弄清楚如何将该字符/字符串理解为field_name。有人有什么想法吗?

3 个答案:

答案 0 :(得分:1)

基本查询

单独计算每笔金额效率低下。在单个SELECT和“交叉制表”结果中执行此操作 为了使答案“简短”,我在结果中减少了两列。根据需要展开。

快速&脏

将两个数组并行的数组相同。有关此技术的详细信息herehere

SELECT unnest('{q001001,q001002}'::text[])       AS fieldname
      ,unnest(ARRAY[sum(q001001), sum(q001002)]) AS result
FROM   stats;

“Dirty”,因为并行取消是一种非标准的Postgres行为,有些人不赞同。虽然像魅力一样。请点击链接了解更多信息。

详细&清洁

使用CTEUNION ALL个别行:

WITH cte AS (
   SELECT sum(q001001) AS s1
         ,sum(q001002) AS s2
   FROM   stats
   )
SELECT 'q001001'::text AS fieldname, s1 AS result FROM cte
UNION ALL
SELECT 'q001002'::text, s2 FROM cte;

“清理”,因为它纯粹是标准的SQL。

简约

最短的形式,但也更难理解:

SELECT unnest(ARRAY[
          ('q001001', sum(q001001))
         ,('q001002', sum(q001002))])
FROM   stats;

这是一系列匿名记录,很难取消(但可能)。

要获取具有原始类型的单个列,请在系统中声明类型:

CREATE TYPE fld_sum AS (fld text, fldsum numeric)

您可以通过创建临时表临时对会话执行相同的操作:

CREATE TEMP TABLE fld_sum (fld text, fldsum numeric);

然后:

SELECT (unnest(ARRAY[
           ('q001001'::text, sum(q001001)::numeric)
          ,('q001002'::text, sum(q001002)::numeric)]::fld_sum[])).*
FROM   stats;

所有四种变体的性能基本相同,因为昂贵的部分是聚合 SQL Fiddle展示所有变体(基于fiddle provided by @klin)。

使用PL / pgSQL函数自动化

快速&脏

构建并执行上面相应章节中概述的代码。

CREATE OR REPLACE FUNCTION f_list_of_sums1(_tbl regclass, _flds text[])
  RETURNS TABLE (fieldname text, result numeric) AS
$func$
BEGIN

RETURN QUERY EXECUTE (
SELECT '
   SELECT unnest ($1)
         ,unnest (ARRAY[sum(' || array_to_string(_flds, '), sum(')
                              || ')])::numeric
   FROM   ' || _tbl)
USING _flds;

END
$func$  LANGUAGE plpgsql;
  • “脏”,这也是 安全的SQL注入。仅在验证输入时使用它 以下版本是安全的。

呼叫:

SELECT * FROM f_list_of_sums1('stats', '{q001001, q001002}');

详细&清洁

构建并执行上面相应章节中概述的代码。

CREATE OR REPLACE FUNCTION f_list_of_sums2(_tbl regclass, _flds text[])
  RETURNS TABLE (fieldname text, result numeric) AS
$func$
BEGIN

-- RAISE NOTICE '%', (      -- to get debug output uncomment this line ..
RETURN QUERY EXECUTE (      -- .. and comment this one
SELECT 'WITH cte AS (
   SELECT ' || string_agg(
                  format('sum(%I)::numeric AS s%s', _flds[i], i)
                 ,E'\n         ,') || '
   FROM   ' || _tbl || '
   )
' || string_agg(
        format('SELECT %L, s%s FROM cte',  _flds[i], i)
      , E'\nUNION ALL\n')
FROM   generate_subscripts(_flds, 1) i
);

END
$func$  LANGUAGE plpgsql;

如上所述。

重点

SQL Fiddle展示所有变体。

除此之外:表格定义

数据类型numeric(9,0)是表定义的一种相当低效的选择。由于您不存储小数位数且不超过9位小数,因此请使用简单的 integer 。它仅对 4字节的存储(而不是numeric(9,0)的8-12字节)执行相同的操作。如果在计算中需要数值精度,则始终可以以可忽略的成本投射色谱柱 另外,I don't use varchar(n) unless I have to. Just use text.
所以我建议:

CREATE TABLE stats (
   name    text
  ,q001001 int
  ,q001002 int
  , ...
);

答案 1 :(得分:1)

在plpgsql函数中使用execute。 SqlFiddle

create or replace function show_stats(field_names text[])
returns table ("FieldName" text, "SUM" numeric)
language plpgsql as $$
declare
    fname text;
begin
    foreach fname in array field_names loop
        return query execute format('
            select ''%s''::text, sum(%s) from stats',
            fname, fname);
    end loop;
end $$;

select * from show_stats(array['q001001', 'q001002', 'q001003', 'q001004']);
select * from show_stats(array['q001001', 'q001004']);

答案 2 :(得分:0)

其实我不知道如何动态指定列名,但我建议这样做。

SELECT 'q001001' as FieldName, sum(q001001) as SUM FROM stats
UNION SELECT 'q001002' as FieldName, sum(q001002) as SUM FROM stats
UNION SELECT 'q001003' as FieldName, sum(q001003) as SUM FROM stats;

这很容易,并且可以解决您原来的问题。