1

I have the following flags declared:

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

I want to write a query that will group on this bitmask and get the count of each flag used.

person  mask
  a      0
  b      3
  c      7
  d      6

The result should be:

flag        count
 none        1
 read        2
 write       3
 view        2

Any tips would be appreciated.

For Craig

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;
3
  • How're you using this mask in other queries? What have you already tried? Commented Aug 1, 2013 at 1:28
  • @CraigRinger, I have the group by so I can group on the mask and the count of that, I just have no idea how to split out the mask into the flags its comprised of. Commented Aug 1, 2013 at 1:34
  • OK, please show the partial query you already have. Commented Aug 1, 2013 at 1:41

2 Answers 2

3

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
Sign up to request clarification or add additional context in comments.

4 Comments

I can't decide if I like your use of or null or not. It's standard, which is nice compared to nullif, but it's much less clear to read for people who aren't fluent in SQL.
@Craig And you know what? I'm fluent in SQL and whenever I see the nullif function I must make an effort to remember how does it work while the truth table is ready for consumption in my head, well..., not all days but at least in the good days :) My problem with nullif is so serious that I had to test your first query to be sure it is wrong, and I'm sorry to say it is.
It is? sqlfiddle.com/#!12/8bed0/7 . Huh, it is too. Teach me not to test something, it contains two obvious and idiotic mistakes.
And one more seriously stupid mistake in the 'none' case... damn.
2

Simplest - pivoted result

Here's how I'd approach it:

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

This doesn't do a GROUP BY as such; instead it scans the table and collects the data in a single pass, producing column-oriented results.

If you need it in row form you can pivot the result, either using the crosstab function from the tablefunc module or by hand.

If you really must GROUP BY, explode the bitmask

You cannot use GROUP BY for this in a simple way, because it expects rows to fall into exactly one group. Your rows appear in multiple groups. If you must use GROUP BY you will have to do so by generating an "exploded" bitmask where one input row gets copied to produce multiple output rows. This can be done with a LATERAL function invocation in 9.3, or with a SRF-in-SELECT in 9.2, or by simply doing a join on a VALUES clause:

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;

I don't think you'll have much luck making this as efficient as a single table scan, though.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.