GROUP BY or COUNT Like Field Values - UNPIVOT?

前端 未结 3 941
暗喜
暗喜 2021-02-11 06:50

I have a table with test fields, Example

id         | test1    | test2    | test3    | test4    | test5
+----------+----------+----------+----------+----------+-         


        
相关标签:
3条回答
  • 2021-02-11 07:22

    You could use an auxiliary on-the-fly table to turn columns into rows, then you would be able to apply aggregate functions, something like this:

    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
    
    0 讨论(0)
  • 2021-02-11 07:30

    Essentially, you need to unpivot your data by test:

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

    ...

    - so that you can then group it by test result.

    Unfortunately, PostgreSQL doesn't have pivot/unpivot functionality built in, so the simplest way to do this would be something like:

    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
    

    ...

    There are other ways of approaching this, but with 40 columns of data this is going to get really ugly.

    EDIT: an alternative approach -

    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
    
    0 讨论(0)
  • 2021-02-11 07:32

    I may have come up with a solution:

    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
    

    The trick works like this:

    • Transform the rowtype into its text representation.
    • Measure character-length.
    • Replace the character you want to count and measure the change in length.
    • Compute the length of the original row in the subselect for repeated use.

    This requires that P, F, I are present nowhere else in the row. Use a sub-select to exclude any other columns that might interfere.

    Tested in 8.4 - 9.1. Nobody uses PostgreSQL 7.4 anymore nowadays, you'll have to test yourself. I only use basic functions, but I am not sure if casting the rowtype to text is feasible in 7.4. If that doesn't work, you'll have to concatenate all test-columns once by hand:

    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
    

    This requires all columns to be NOT NULL.

    0 讨论(0)
提交回复
热议问题