Skip to main content

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples in this guide work on VillageSQL. Install Now →
MySQL has no boolean type. BOOL and BOOLEAN are spellings of TINYINT(1), which stores any number that fits in a byte. The column will hold 5 and -1 as readily as 0 and 1, and it gives them back exactly as they went in. VillageSQL’s vsql_boolean extension adds a STRICTBOOL column type that holds two values and refuses everything else.

Why TINYINT(1) bites

Here is the column MySQL gives you, with four rows inserted:
Nothing objected. Now ask the same question two ways:
Three rows are truthy, and one equals TRUE. Both queries look correct and they disagree, because TRUE is the literal 1 and WHERE active tests for any non-zero value. A row holding 5 is active under one query and inactive under the other. Nothing in the schema stopped the 5 from being written, so the disagreement is discovered later, by a report that does not add up.

The STRICTBOOL column

The column accepts the strings 'true'/'false', 't'/'f', 'yes'/'no', 'on'/'off' and '1'/'0', in any letter case. Pass them as strings; a bare number is refused. Whichever spelling goes in, true or false comes back:
Row 5 supplied no value and took the column default. Anything outside the accepted spellings is refused where it belongs, at the insert:
Filter on the value the same way you wrote it:

Counting

Two aggregates come with the type. boolean_sum counts the true rows and boolean_avg gives the share of them:
Both aggregates ignore NULL, so the share is computed over the rows that answered:
One yes out of two answers is a share of 0.5, across three rows. If you want NULL counted as false, say so in the query rather than expecting the aggregate to assume it.

Indexing

A STRICTBOOL column indexes like any other:
Whether the index earns its place is the usual question. A column with two values is selective only when one of them is rare, such as a locked flag on an account table where almost nothing is locked.

Migrating an existing column

You cannot convert a TINYINT column in place. The server refuses it:
Add a second column and fill it instead:
Those two statements each assign a literal. A statement that computes the value is refused, because a STRICTBOOL column does not take a string expression:
The explicit conversion the error asks for is the type’s own function, so one statement does work:
The migration is also the moment to decide what those 5 and -1 rows meant. Everything non-zero became true above, which is what WHERE active would have said. If some report in your system used active = 1 instead, it disagreed, and now is when you find out. Once the new column is right, drop the old one and rename:

Troubleshooting

See also