Python CLI utility and library for manipulating SQLite databases https://sqlite-utils.datasette.io
  • Python 99.7%
  • C 0.2%
  • Just 0.1%
Find a file
feiiiiii5 8e5dfdd409
analyze_column(): handle null values in the common-value analysis
Three ways a column containing nulls produced a wrong answer, all from
nulls not being accounted for.

1. The "single distinct value" shortcut. count(distinct col) skips
   nulls in SQL, so a column holding nulls plus one other value reports
   num_distinct == 1 and took the shortcut, which reports that one value
   with a count of total_rows:

       insert [{'c': None}, {'c': None}, {'c': 'a'}, {'c': 'a'}]
       analyze_column('c').most_common   # was [(None, 4)]
       select c, count(*) group by c     # is [(None, 2), ('a', 2)]

   The shortcut now requires that the column has no nulls, so those
   columns fall through to the group by like any other.

2. The tie-break sort compared the values directly, so a null and a
   string with the same count raised TypeError:

       analyze_column('c', common_limit=5)
       # TypeError: '<' not supported between instances of 'NoneType' and 'str'

   sort_key() keeps nulls out of the comparison and sorts them last,
   which keeps the existing ordering of non-null ties - the test for
   'Terryterryterry' ahead of 'Kumar' at equal counts still passes.

3. truncate() turned a null into the string 'None', so a null was
   indistinguishable from a column really holding 'None'. That is the
   path analyze-tables takes, since it passes value_truncate=80.

num_distinct itself is unchanged: it still follows SQL and reports 0 for
an all-null column, which test_analyze_table_column_all_nulls pins. But
num_distinct can therefore be lower than len(most_common) when a column
holds nulls alongside all-distinct values - {'c': None, 'a', 'b'} gives
num_distinct 2 and three most_common entries. Whether null should count
as a distinct value is a question about what the number means, so I have
left it alone rather than change an output the tests already pin.

Tests: 6 new cases in tests/test_analyze.py, all failing on 6bc1d33.
Suite 1506 passed, 16 skipped. mypy, pyright, flake8, ty, black and
cog --check clean.
2026-09-28 18:55:53 +08:00
.github Same fix for test-coverage.yml 2026-09-01 21:44:52 -07:00
docs Release 4.2.1 2026-08-13 16:52:30 -07:00
sqlite_utils analyze_column(): handle null values in the common-value analysis 2026-09-28 18:55:53 +08:00
tests analyze_column(): handle null values in the common-value analysis 2026-09-28 18:55:53 +08:00
.gitignore Fixes for Ruff>=0.16.0 (#814) 2026-07-25 14:53:12 -07:00
.readthedocs.yaml dependency-groups.dev and uv in docs (#691) 2025-12-11 15:32:00 -08:00
codecov.yml Enable codecov.io, refs #275 2021-06-16 16:54:16 -07:00
Justfile Run no-default-groups smoke test from Justfile 2026-08-13 17:01:47 -07:00
LICENSE Added LICENSE file 2019-02-23 09:16:57 -08:00
MANIFEST.in Explicitly include tests and docs in sdist (#75) 2020-01-30 16:21:26 -08:00
mypy.ini Remove sqlean.py support (#772) 2026-07-05 16:00:52 -07:00
pyproject.toml Release 4.2.1 2026-08-13 16:52:30 -07:00
README.md README tweak ready for v4, refs #769 2026-07-06 22:26:58 -07:00

sqlite-utils

PyPI Changelog Python 3.x Tests Documentation Status codecov License discord

Python CLI utility and library for manipulating SQLite databases.

Some feature highlights

Upgrading from sqlite-utils 3.x? See the 4.0 upgrade guide.

Read more on my blog, in this series of posts on New features in sqlite-utils and other entries tagged sqlite-utils.

Installation

pip install sqlite-utils

Or if you use Homebrew for macOS:

brew install sqlite-utils

Using as a CLI tool

Now you can do things with the CLI utility like this:

$ sqlite-utils memory dogs.csv "select * from t"
[{"id": 1, "age": 4, "name": "Cleo"},
 {"id": 2, "age": 2, "name": "Pancakes"}]

$ sqlite-utils insert dogs.db dogs dogs.csv --csv
[####################################]  100%

$ sqlite-utils tables dogs.db --counts
[{"table": "dogs", "count": 2}]

$ sqlite-utils dogs.db "select id, name from dogs"
[{"id": 1, "name": "Cleo"},
 {"id": 2, "name": "Pancakes"}]

$ sqlite-utils dogs.db "select * from dogs" --csv
id,age,name
1,4,Cleo
2,2,Pancakes

$ sqlite-utils dogs.db "select * from dogs" --table
  id    age  name
----  -----  --------
   1      4  Cleo
   2      2  Pancakes

You can import JSON data into a new database table like this:

$ curl https://api.github.com/repos/simonw/sqlite-utils/releases \
    | sqlite-utils insert releases.db releases - --pk id

Or for data in a CSV file:

$ sqlite-utils insert dogs.db dogs dogs.csv --csv

sqlite-utils memory lets you import CSV or JSON data into an in-memory database and run SQL queries against it in a single command:

$ cat dogs.csv | sqlite-utils memory - "select name, age from stdin"

See the full CLI documentation for comprehensive coverage of many more commands.

Using as a library

You can also import sqlite_utils and use it as a Python library like this:

import sqlite_utils
db = sqlite_utils.Database("demo_database.db")
# This line creates a "dogs" table if one does not already exist:
db["dogs"].insert_all([
    {"id": 1, "age": 4, "name": "Cleo"},
    {"id": 2, "age": 2, "name": "Pancakes"}
], pk="id")

Check out the full library documentation for everything else you can do with the Python library.

  • Datasette: A tool for exploring and publishing data
  • csvs-to-sqlite: Convert CSV files into a SQLite database
  • db-to-sqlite: CLI tool for exporting a MySQL or PostgreSQL database as a SQLite file
  • dogsheep: A family of tools for personal analytics, built on top of sqlite-utils