Menu
Try in Playground

SQLite ATTACH DATABASE: Querying Across Multiple Files

How ATTACH DATABASE lets you open multiple SQLite files in one connection, query across them with schema prefixes, and detach when you're done.

This page includes runnable editors — edit, run, and see output instantly.

One Connection, Many Files

A SQLite connection isn't locked to a single file. With ATTACH DATABASE, you can open additional .db files alongside the one you started with, and query them all as if they were schemas inside one database. It's the closest thing SQLite has to "multiple databases on one server."

The basic form:

The file archive.db is created if it doesn't exist, just like the main database. From now on in this session, anything prefixed with archive. lives in that second file. Anything prefixed with main. (or unprefixed) lives in the original.

Your connection always has two implicit schemas: main (the file you opened first) and temp (a scratch space for temporary tables). Attaching adds more.

The Syntax and What the Alias Does

ATTACH DATABASE 'path/to/file.db' AS alias_name;

The alias is the schema name you'll use to qualify tables. It's local to the current connection — another connection attaching the same file can pick a different alias. Pick something short and descriptive (archive, analytics, cache) because you'll type it a lot.

A few things worth knowing:

  • The path is relative to the process's working directory unless absolute.
  • The string ':memory:' attaches a fresh in-memory database under that alias.
  • The alias can't collide with main or temp, and can't repeat across attachments.

Joining Across Databases

This is the feature most people attach for. Once two files are in the same connection, you can join their tables in a single query:

The query planner treats both schemas the same way it treats tables in main. Indexes on attached tables are used. EXPLAIN QUERY PLAN works across them. There's no network round-trip — both files are open in the same process.

This is genuinely useful for splitting hot data from cold archives, separating per-tenant files, or pulling reference data out of a read-only lookup database.

Read-Only and In-Memory Attachments

If the second database is something you want to read but never modify — a shipped reference dataset, say — attach it read-only with a URI:

The URI form requires the SQLite library to have SQLITE_OPEN_URI enabled (it is in the CLI and most language bindings). Any INSERT, UPDATE, or DELETE against ref.* will then raise an error before touching the file.

In-memory attachments are equally handy for staging data:

scratch vanishes when the connection closes. It's like temp but you control the lifetime.

Transactions Span Every Attached Database

A single BEGIN/COMMIT covers writes to main and every attached schema. Either everything commits or everything rolls back — atomicity is preserved across files:

Moving rows from a live table to an archive file is exactly the kind of operation where you want this guarantee. Without atomicity across files, a crash in the middle would leave you with duplicates or, worse, lost rows.

One caveat: when more than one attached database is being written to in a transaction, SQLite uses a more cautious commit protocol that requires a temporary journal. It's slower than single-file commits, but still safe.

Detaching

When you're done with an attached database, drop it:

DETACH DATABASE archive;

The file stays on disk untouched — DETACH just closes the handle in the current connection. Two restrictions to remember:

  • You can't detach main or temp.
  • You can't detach a database that's currently inside a transaction or has open statements against it.

If you forget to detach, it's not the end of the world: closing the connection cleans everything up.

Limits and Common Errors

A few practical limits worth knowing:

  • Default cap is 10 attached databases per connection (plus main and temp). The compile-time maximum is 125. Hit the limit and you'll see too many attached databases - max 10.
  • Each attached file uses a page cache. Attaching a dozen large databases isn't free — RAM goes up.
  • ATTACH itself can't run inside a transaction. Run it before BEGIN, or after COMMIT.

A few errors you'll likely meet:

-- File doesn't exist and the directory isn't writable:
Error: unable to open database: 'missing/path.db'

-- You tried to write to a read-only attachment:
Error: attempt to write a readonly database

-- You used the same alias twice:
Error: database archive is already in use

Most of these are obvious once you read them. The "already in use" one trips people up — ATTACH doesn't replace an existing alias; you have to DETACH first.

A Realistic Pattern: Hot/Cold Split

Putting it together — a small archive workflow that moves orders older than a year out of the main database:

Old rows move to archive.orders, recent ones stay in main. Reports that need history can join across both; day-to-day queries against main.orders stay fast because the table is smaller. Same connection, two files, one transaction.

Next: Prepared Statements

ATTACH is about giving one connection access to more data. The next set of topics is about how applications talk to SQLite safely and efficiently — starting with prepared statements, the foundation of parameter binding and injection-proof queries.

Frequently Asked Questions

What does ATTACH DATABASE do in SQLite?

ATTACH DATABASE 'file.db' AS alias opens a second SQLite database file inside the current connection and gives it a schema name. After that, you can refer to its tables as alias.table_name and join them with tables in your main database in a single query.

How many databases can SQLite attach at once?

By default, SQLite allows up to 10 attached databases per connection, plus the main and temp schemas. The hard ceiling is 125, configurable at compile time via SQLITE_MAX_ATTACHED. If you hit the limit you'll get a too many attached databases error.

Can I query across attached SQLite databases in one statement?

Yes. Once attached, qualify each table with its schema name — SELECT * FROM main.users JOIN archive.orders ON .... Joins, subqueries, and INSERT ... SELECT all work across schemas. Transactions span every attached database too, so a COMMIT is atomic across files.

How do I detach a SQLite database?

Run DETACH DATABASE alias. The file stays on disk untouched — DETACH just closes the handle in the current connection. You can't detach main or temp, and you can't detach a database that's in the middle of a transaction.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED