GROUP BY или COUNT, как значения полей - UNPIVOT? - PullRequest
2 голосов
/ 22 ноября 2011

У меня есть таблица с полями теста, Пример

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

Предложения?

Примечание: I 'я использую PostgreSQL 7.4 и да, мы обновляем

Ответы [ 4 ]

3 голосов
/ 22 ноября 2011

Возможно, я нашел решение:

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

Трюк работает так:

  • Преобразуйте тип строки в его текстовое представление.
  • Измерьте длину символа.
  • Замените символ, который вы хотите посчитать, и измерьте изменение длины.
  • Вычислите длину исходного ряда в подвыборке для повторного использования.*

    Для этого требуется, чтобы 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 голос
/ 22 ноября 2011

По сути, вам нужно отменить вывод данных по тестам:

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
0 голосов
/ 22 ноября 2011

Редактировать : только что увидел комментарий о 7.4, я не думаю, что это будет работать с этой древней версией (unnest () появился намного позже).Если кто-то думает, что это не стоит сохранять, я его удалю.

Принимая идею Эрвина использовать «представление строк» ​​в качестве основы для решения, немного дальше и автоматически «нормализовать» таблицу на-fly:

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

В основе решения лежит хитрость Эрвина, заключающаяся в том, чтобы сделать единственное значение из всей строки с использованием приведения not_normalized::text.Строковые функции применяются к полосе начального значения id и скобкам вокруг него.

Результат этого преобразуется в массив, а этот массив преобразуется в набор результатов с помощью функции unnest ().

Чтобы понять эту часть, просто выполните шаг за шагом внутреннего выбора.

Затем результат группируется и подсчитываются соответствующие значения.

0 голосов
/ 22 ноября 2011

Вы можете использовать вспомогательную оперативную таблицу для преобразования столбцов в строки, тогда вы сможете применять агрегатные функции, что-то вроде этого:

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
...