Logo
Published on

SQLite Type Checking Gotchas

Introduction

sqlite type checking

One cool, yet sometimes confusing, aspect of SQLite is its type system.

Unlike other SQL databases, SQLite uses dynamic typing. Let's dive into how type checking works in SQLite, explore its unique type system, storage classes, and some best practices to keep your data tidy and consistent.

Table of Contents

Type System in SQLite

Dynamic Typing

SQLite does things a bit differently with its dynamic typing. Instead of sticking strictly to column data types, you can store pretty much any value in any column, regardless of its declared type.

Good 😃Bad 😓
Flexibility in data storagePotential for data inconsistency
Simplicity in handling typesRequires careful handling to ensure integrity

Type Affinity

Type affinity in SQLite is like a guideline for how it prefers to store data in a column. Each column has an affinity: TEXT, NUMERIC, INTEGER, REAL, or NONE.

Here's a bit more on each:

  • TEXT Affinity: SQLite tries to store data as text. Even numbers will be stored as strings if possible.
CREATE TABLE users (
    username TEXT,
    age TEXT
);
  • NUMERIC Affinity: Tries to store data as numbers. If it can't be a number, it falls back to text.
CREATE TABLE products (
    price NUMERIC,
    discount NUMERIC
);
  • INTEGER Affinity: Prefers integers. If the data can't be an integer, it'll try NUMERIC or TEXT.
CREATE TABLE orders (
    order_id INTEGER,
    quantity INTEGER
);
  • REAL Affinity: Prefers floating-point numbers. Similar fallback as INTEGER.
CREATE TABLE measurements (
    height REAL,
    weight REAL
);
  • NONE Affinity: No preferred type. Stores data exactly as it's given.
CREATE TABLE files (
    file_data BLOB
);

Type Conversion

When you insert data, SQLite tries to match it with the column's affinity. Here's how it works:

  1. For TEXT affinity, it converts data to a string if possible.

  2. For INTEGER affinity, it converts to an integer if it looks like one.

  3. For NUMERIC affinity, it converts to an integer if possible, or a real number, otherwise stores as text if neither fit.

Practical Examples

Let's look at a real-life example: a table for storing user profiles.

CREATE TABLE user_profiles (
    user_id INTEGER PRIMARY KEY,
    username TEXT,
    age NUMERIC,
    height REAL,
    profile_picture BLOB
);
  • Inserting a string into username stores it as TEXT.
  • Inserting a number into age stores it as NUMERIC, falling back to TEXT if it's not a number.
  • Inserting an integer into user_id stores it as INTEGER.
  • Inserting a float into height stores it as REAL.
  • Inserting any binary data into profile_picture stores it as BLOB.

Here's an example of inserting some data:

INSERT INTO
  user_profiles (user_id, username, age, height, profile_picture)
VALUES
  (1, 'Alice', 30, 5.5, NULL);

If you insert a string into age that can't be converted to a number:

INSERT INTO
  user_profiles (age)
VALUES
  ('twenty-five');

SQLite stores 'twenty-five' as TEXT because it's not a valid number.

Best Practices for Type Checking

To keep your data clean and consistent, follow these tips:

  • Explicit Constraints: Use CHECK constraints to enforce data validity.
CREATE TABLE user_profiles (
    username TEXT CHECK(typeof(username) = 'text'),
    age NUMERIC CHECK(typeof(age) IN ('integer', 'real'))
);
  • Type-Affinity Awareness: Know how SQLite handles type affinity to avoid surprises.

  • Data Validation: Validate data at the application level to complement SQLite's type handling.

  • Testing: Test your schema and data operations thoroughly to ensure they behave as expected.

  • Strict Tables: Use strict typing for columns where you need it.

CREATE TABLE user_profiles (
    user_id INTEGER PRIMARY KEY,
    username TEXT,
    age NUMERIC,
    height REAL,
    profile_picture BLOB
) STRICT;

In strict mode, columns must conform to their declared types, preventing the insertion of incompatible data types.

Wrapping Up

SQLite's dynamic typing and type affinity system provide a lot of flexibility, but they also require careful handling to maintain data integrity.

By understanding storage classes, type affinity, and type conversion rules, you can use SQLite effectively without running into unexpected issues. Stick to best practices like explicit constraints and thorough testing to keep your database robust and reliable.