GROUP BY或COUNT像字段值 - UNPIVOT?

时间:2011-11-22 12:36:33

标签: sql postgresql group-by unpivot

我有一个包含测试字段的表,示例

id         | test1    | test2    | test3    | test4    | test5
+----------+----------+----------+----------+----------+----------+
12345      | P        | P        | F        | I        | P

因此,对于每条记录,我想知道有多少通过,失败或不完整(P,F或I)

有没有办法获得GROUP BY值?

伪:

SELECT ('P' IN (fields)) AS pass
WHERE id = 12345

我有大约40个测试字段,我需要以某种方式组合在一起,我真的不想写这个超级丑陋的长查询。是的我知道我应该将表重写为两个或三个单独的表,但这是另一个问题。

预期结果:

passed     | failed   | incomplete
+----------+----------+----------+
3          | 1        | 1

建议?

注意:我正在运行PostgreSQL 7.4,是的,我们正在升级

4 个答案:

答案 0 :(得分:3)

我可能想出一个解决方案:

SELECT id
      ,l - length(replace(t, 'P', '')) AS nr_p
      ,l - length(replace(t, 'F', '')) AS nr_f
      ,l - length(replace(t, 'I', '')) AS nr_i
FROM   (SELECT id, test::text AS t, length(test::text) AS l  FROM test) t

诀窍就是这样:

  • 将rowtype转换为文本表示形式。
  • 测量字符长度。
  • 替换您想要计算的字符并测量长度的变化。
  • 计算子选择中原始行的长度以供重复使用。

这要求行中不存在P, F, I。使用子选择来排除可能会干扰的任何其他列。

测试8.4 - 9.1。现在没有人再使用PostgreSQL 7.4了,你必须自己测试一下。我只使用基本功能,但我不确定在7.4中将行类型转换为文本是否可行。如果这不起作用,则必须手动连接所有测试列:

SELECT id
      ,length(t) - length(replace(t, 'P', '')) AS nr_p
      ,length(t) - length(replace(t, 'F', '')) AS nr_f
      ,length(t) - length(replace(t, 'I', '')) AS nr_i
FROM   (SELECT id, test1||test2||test3||test4 AS t FROM test) t

这要求所有列都为NOT NULL

答案 1 :(得分:1)

基本上,您需要通过测试来取消数据:

id         | test     | result   
+----------+----------+----------+
12345      | test1    | P        
12345      | test2    | P        
12345      | test3    | F        
12345      | test4    | I        
12345      | test5    | P       

...

- 以便您可以按测试结果对其进行分组。

不幸的是,PostgreSQL没有内置的pivot / unpivot功能,所以最简单的方法就是:

select id, 'test1' test, test1 result from mytable union all
select id, 'test2' test, test2 result from mytable union all
select id, 'test3' test, test3 result from mytable union all
select id, 'test4' test, test4 result from mytable union all
select id, 'test5' test, test5 result from mytable union all

...

还有其他方法可以解决这个问题,但如果有40列数据,这将会变得非常难看。

编辑:另一种方法 -

select r.result, sum(char_length(replace(replace(test1||test2||test3||test4||test5,excl1,''),excl2,'')))
from   mytable m, 
       (select 'P' result, 'F' excl1, 'I' excl2 union all
        select 'F' result, 'P' excl1, 'I' excl2 union all
        select 'I' result, 'F' excl1, 'P' excl2) r
group by r.result

答案 2 :(得分:0)

您可以使用辅助动态表将列转换为行,然后您就可以应用聚合函数,如下所示:

SELECT
  SUM(fields = 'P') AS passed,
  SUM(fields = 'F') AS failed,
  SUM(fields = 'I') AS incomplete
FROM (
  SELECT
    t.id,
    CASE x.idx
      WHEN 1 THEN t.test1
      WHEN 2 THEN t.test2
      WHEN 3 THEN t.test3
      WHEN 4 THEN t.test4
      WHEN 5 THEN t.test5
    END AS fields
  FROM atable t
    CROSS JOIN (
      SELECT 1 AS idx
      UNION ALL SELECT 2
      UNION ALL SELECT 3
      UNION ALL SELECT 4
      UNION ALL SELECT 5
    ) x
  WHERE t.id = 12345
) s

答案 3 :(得分:0)

编辑:刚刚看到关于7.4的评论,我认为这不会适用于那个古老的版本(不久之后会出现这种情况)。如果有人认为这不值得保留,我会删除它。

将Erwin的想法用作“行表示”作为解决方案的基础,并在运行中自动“规范化”表格:

select id,
       sum(case when flag = 'F' then 1 else null end) as failed,
       sum(case when flag = 'P' then 1 else null end) as passed,
       sum(case when flag = 'I' then 1 else null end) as incomplete
from (
  select id, 
         unnest(string_to_array(trim(trailing ')' from substr(all_column_values,strpos(all_column_values, ',') + 1)), ',')) flag
  from (
    SELECT id,
           not_normalized::text AS all_column_values
    FROM not_normalized
  ) t1
) t2
group by id

解决方案的核心是Erwin使用强制转换not_normalized::text从整行中制作单个值的技巧。字符串函数应用于前导id值的条带及其周围的括号。

将其结果转换为数组,并使用unnest()函数将该数组转换为结果集。

要理解该部分,只需逐步运行内部选择。

然后将结果分组并计算相应的值。