Postgres группа битовых масок

У меня есть следующие объявленные флаги:

0 - None
1 - Read
2 - Write
4 - View

Я хочу написать запрос, который будет группировать по этой битовой маске и получать счетчик каждого используемого флага.

person  mask
  a      0
  b      3
  c      7
  d      6

Результат должен быть:

flag        count
 none        1
 read        2
 write       3
 view        2

Любые советы будут оценены.

Для Крейга

SELECT  lea.mask as trackerStatusMask,
        count(*) as count
FROM    Live le 
INNER JOIN (
 ... --some guff
) lea on le.xId = lea.xId
WHERE   le.xId = p_xId   
GROUP BY lea.mask;

2 ответа

Решение

Самый простой - сводный результат

Вот как я подхожу к этому:

 -- (after fixing the idiotic mistakes in the first version)
 SELECT
    count(nullif(mask <> 0, True)) AS "none",
    count(nullif(mask & 2,0))      AS "write",
    count(nullif(mask & 1,0))      AS "read",
    count(nullif(mask & 4,0))      AS "view"
FROM my_table;

-- ... though @ClodAldo's version of it below is considerably clearer, per comments.

Это не делает GROUP BY в качестве таких; вместо этого он сканирует таблицу и собирает данные за один проход, получая результаты, ориентированные на столбцы.

Если вам это нужно в форме строки, вы можете изменить результат, используя crosstab функция от tablefunc модуль или вручную.

Если вы действительно должны GROUP BYвзорвать битовую маску

Вы не можете использовать GROUP BY для этого простым способом, потому что он ожидает, что ряды попадут ровно в одну группу. Ваши строки появляются в нескольких группах. Если вы должны использовать GROUP BY вам нужно будет сделать это путем генерации "разнесенной" битовой маски, в которую будет скопирована одна входная строка для создания нескольких выходных строк. Это можно сделать с помощью LATERAL вызов функции в 9.3, или с помощью SRF-in-SELECT в 9.2, или просто путем объединения VALUES пункт:

SELECT
   CASE 
     WHEN mask_bit = 1 THEN 'read'
     WHEN mask_bit = 2 THEN 'write'
     WHEN mask_bit = 4 THEN 'view'
     WHEN mask_bit IS NULL THEN 'none'
   END AS "flag",
   count(person) AS "count"
FROM t
LEFT OUTER JOIN (
    VALUES (4),(2),(1)
) mask_bits(mask_bit)
ON (mask & mask_bit = mask_bit)
GROUP BY mask_bit;

Я не думаю, что вам повезет, если вы сделаете это так же эффективно, как сканирование одной таблицы.

SQL Fiddle

select
    count(mask = 0 or null) as "None",
    count(mask & 1 > 0 or null) as "Read",
    count(mask & 2 > 0 or null) as "Write",
    count(mask & 4 > 0 or null) as "View"
from t
Другие вопросы по тегам