Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I convert a bunch of boolean columns to a single bitmap in PostgreSQL?

I'd like to convert a query such as:

SELECT BoolA, BoolB, BoolC, BoolD FROM MyTable;

Into a bitmask, where the bits are defined by the values above.

For example, if BoolA and BoolD were true, I'd want 1001 or 9.

I have something in mind to the effect of:

SELECT
   CASE WHEN BoolD THEN 2^0 ELSE 0 END +
   CASE WHEN BoolC THEN 2^1 ELSE 0 END +
   CASE WHEN BoolB THEN 2^2 ELSE 0 END +
   CASE WHEN BoolA THEN 2^3 ELSE 0 END
FROM MyTable;

But I'm not sure if this is the best approach and seems rather verbose. Is there an easy way to do this?

like image 655
Mike Christensen Avatar asked Jan 29 '12 08:01

Mike Christensen


People also ask

What is the difference between Bool and Boolean in PostgreSQL?

PostgreSQL Boolean is a simple data type that we have used to represent only the structure of true or false data or values. PostgreSQL will support the SQL99 defined Boolean data type of SQL standard; Boolean is also known as “bool”, bool is an alias of Boolean data type in PostgreSQL.

How do you represent a boolean in PostgreSQL?

PostgreSQL supports a single Boolean data type: BOOLEAN that can have three values: true , false and NULL . PostgreSQL uses one byte for storing a boolean value in the database. The BOOLEAN can be abbreviated as BOOL . In standard SQL, a Boolean value can be TRUE , FALSE , or NULL .


3 Answers

For a bitmask, the type bitstring would be the better choice. Could look like this then:

SELECT BoolD::int::bit
    || BoolC::int::bit
    || BoolB::int::bit
    || BoolA::int::bit
FROM tbl;

true converts to 1, false to 0. You can simply concatenate bits to a bitstring.

Cast bit(n) to integer

It seems you need an integer as result - there is a simple & fast way:

SELECT (BoolD::int::bit
     || BoolC::int::bit
     || BoolB::int::bit
     || BoolA::int::bit)::bit(4)::int
FROM tbl;

Be sure to read the fine print in the chapter "Bit String Functions and Operators" of the manual.


I came up with two more ideas and put together a quick test / reference with 10k rows to sum it all up.

Test setup:

CREATE TEMP TABLE t (boola bool, boolb bool, boolc bool, boold bool);
INSERT INTO t
SELECT random()::int::bool
     , random()::int::bool
     , random()::int::bool
     , random()::int::bool
FROM   generate_series(1,10000);

Demo:

SELECT  CASE WHEN boold THEN 1 ELSE 0 END
     + (CASE WHEN boolc THEN 1 ELSE 0 END << 1)
     + (CASE WHEN boolb THEN 1 ELSE 0 END << 2)
     + (CASE WHEN boola THEN 1 ELSE 0 END << 3) AS andriy

     ,  boold::int
     + (boolc::int << 1)
     + (boolb::int << 2)
     + (boola::int << 3) AS mike

     , (boola::int::bit
     || boolb::int::bit
     || boolc::int::bit
     || boold::int::bit)::bit(4)::int AS erwin1

     ,  boold::int
     | (boolc::int << 1)
     | (boolb::int << 2)
     | (boola::int << 3) AS erwin2

     , (((
       boola::int << 1)
     | boolb::int << 1)
     | boolc::int << 1)
     | boold::int        AS erwin3
FROM   t
LIMIT  15;

You could also use | (bitwise OR) instead of the + operator.
Individual test runs show basically the same performance for all five methods.

like image 90
Erwin Brandstetter Avatar answered Oct 26 '22 10:10

Erwin Brandstetter


Maybe like this:

SELECT
  (CASE WHEN BoolA THEN 1 ELSE 0 END << 0) +
  (CASE WHEN BoolB THEN 1 ELSE 0 END << 1) +
  (CASE WHEN BoolC THEN 1 ELSE 0 END << 2) +
  (CASE WHEN BoolD THEN 1 ELSE 0 END << 3) AS BitMask
FROM MyTable;

where << is the bitwise shift left operator.

like image 29
Andriy M Avatar answered Oct 26 '22 09:10

Andriy M


I came up with this approach as well. It's the most concise I could find short of writing a custom function. I'll accept this answer unless anyone has anything more clever.

SELECT
  (BoolD::int << 0) +
  (BoolC::int << 1) +
  (BoolB::int << 2) +
  (BoolA::int << 3)
from MyTable;
like image 38
Mike Christensen Avatar answered Oct 26 '22 10:10

Mike Christensen