SQLite Syntax: Statements, Keywords and Quoting Rules (Tested Reference)

SQL from other databases runs in SQLite with few changes, and the changes that matter sit in a handful of places, like double-quoted strings that SQLite can read as column names. In this article I’ll walk you through SQLite’s syntax on one page, grouped by task, with tested output for each group.

How to use this page

Find the task you need, read the table, then check the tested output under it. The examples build on each other in one database: a products table and an orders table. The full grammar, with railroad diagrams for every statement, is in the official SQL As Understood By SQLite docs.

Statement basics

In the sqlite3 shell, a semicolon ends each statement. From application code, a single statement doesn’t need one. Keywords aren’t case-sensitive: select works the same as SELECT.

TaskSyntaxExample
End a statement;SELECT 1;
Write a stringSingle quotes'Pen'
Put a quote inside a stringDouble it'it''s'
Comment to end of line---- note
Block comment/* ... *//* note */
sqlite> select 1 + 1 AS two; SELECT 'it''s' AS quoted; -- a comment
SELECT /* inline comment */ 'done' AS status;
two
---
2
quoted
------
it's
status
------
done

Create and change tables

CREATE TABLE defines a table, and ALTER TABLE makes one change to it per statement.

TaskStatement
Create a tableCREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL);
Add a columnALTER TABLE products ADD COLUMN stock INTEGER NOT NULL DEFAULT 0;
Rename a columnALTER TABLE products RENAME COLUMN category TO section;
Drop a columnALTER TABLE products DROP COLUMN stock;
Add an indexCREATE INDEX idx_products_name ON products(name);
Drop a tableDROP TABLE IF EXISTS products;
sqlite> CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price REAL CHECK (price >= 0), category TEXT DEFAULT 'misc');
sqlite> ALTER TABLE products ADD COLUMN stock INTEGER NOT NULL DEFAULT 0;
sqlite> ALTER TABLE products RENAME COLUMN category TO section;
sqlite> CREATE INDEX idx_products_name ON products(name);
sqlite> SELECT name FROM pragma_table_info('products');
name
-------
id
name
price
section
stock

Guides: CREATE TABLE, data types, IF EXISTS.

Insert, update and delete rows

UPDATE and DELETE change every row unless a WHERE clause picks the rows.

TaskStatement
Insert rowsINSERT INTO products (name, price) VALUES ('Pen', 1.5), ('Mug', 8.0);
Change rowsUPDATE products SET price = 1.75 WHERE name = 'Pen';
Delete rowsDELETE FROM products WHERE stock < 5;
Insert or replace by keyINSERT OR REPLACE INTO products (id, name) VALUES (1, 'Pen');
Get the changed rows backadd RETURNING id, name
sqlite> INSERT INTO products (name, price, section, stock) VALUES ('Pen', 1.5, 'office', 100), ('Mug', 8.0, 'kitchen', 12), ('Lamp', 24.0, 'home', 3);
sqlite> UPDATE products SET price = 1.75 WHERE name = 'Pen';
sqlite> DELETE FROM products WHERE stock < 5;
sqlite> SELECT * FROM products;
id  name  price  section  stock
--  ----  -----  -------  -----
1   Pen   1.75   office   100
2   Mug   8.0    kitchen  12

Guides: INSERT, UPDATE, DELETE.

Query rows

The clauses of a SELECT go in this order:

OrderClausePurpose
1SELECT columnsWhat to return
2FROM table [JOIN other ON ...]Where the rows come from
3WHERE conditionFilter rows
4GROUP BY columnsGroup rows for aggregates
5HAVING conditionFilter groups
6ORDER BY columns [DESC]Sort
7LIMIT n OFFSET mPage through results
sqlite> CREATE TABLE orders (id INTEGER PRIMARY KEY, product_id INTEGER REFERENCES products(id), qty INTEGER);
sqlite> INSERT INTO orders (product_id, qty) VALUES (1, 3), (1, 2), (2, 1);
sqlite> SELECT p.name, sum(o.qty) AS sold FROM products AS p JOIN orders AS o ON o.product_id = p.id GROUP BY p.name HAVING sold > 1 ORDER BY sold DESC LIMIT 5 OFFSET 0;
name  sold
----  ----
Pen   5

Guide: SELECT.

Operators and expressions

Operators work anywhere an expression is allowed, in the SELECT list or in a WHERE clause.

OperatorMeaningExampleResult
||Join strings'Hello, ' || 'world'Hello, world
/Divide (integer if both sides are integers)7 / 23
IS NULLTest for NULLNULL IS NULL1
BETWEENIn a range, inclusive5 BETWEEN 1 AND 101
INIn a list'b' IN ('a', 'b')1
CASEIf/else inside a queryCASE WHEN 3 > 2 THEN 'yes' ELSE 'no' ENDyes
sqlite> SELECT 'Hello, ' || 'world' AS concat, 7 / 2 AS int_div, 7 / 2.0 AS real_div, NULL IS NULL AS is_null, 5 BETWEEN 1 AND 10 AS between_, 'b' IN ('a', 'b') AS in_list, CASE WHEN 3 > 2 THEN 'yes' ELSE 'no' END AS case_;
concat        int_div  real_div  is_null  between_  in_list  case_
------------  -------  --------  -------  --------  -------  -----
Hello, world  3        3.5       1        1         1        yes

Guides: operators, AND, OR.

Keywords and quoting names

SQLite’s keyword list has 147 entries, such as ORDER and GROUP (SQLite keywords). To use a keyword as a name, quote it:

QuotingMeansStandard SQL?
'word'A string literalYes
"word"An identifier (a table or column name)Yes
[word]An identifierNo, accepted for SQL Server and Access compatibility
` word `An identifierNo, accepted for MySQL compatibility
sqlite> CREATE TABLE "order" ("group" TEXT, [select] TEXT, `from` TEXT);
sqlite> INSERT INTO "order" VALUES ('a', 'b', 'c'); SELECT "group", [select], `from` FROM "order";
group  select  from
-----  ------  ----
a      b       c
sqlite> CREATE TABLE order (id INTEGER);
Error: in prepare, near "order": syntax error
  CREATE TABLE order (id INTEGER);
               ^--- error here

The docs advise quoting “any identifier that is an English language word, even if you do not have to”, because SQLite adds keywords over time.

Parameters in application code

Placeholders keep values out of the SQL text. SQLite accepts these forms (SQLite parameters):

PlaceholderKind
?Positional
?NNNNumbered
:name, @name, $nameNamed

In Python:

import sqlite3

con = sqlite3.connect(":memory:")
con.execute("CREATE TABLE users (name TEXT, age INTEGER)")
con.execute("INSERT INTO users VALUES (?, ?)", ("Ada", 36))                  # positional
con.execute("INSERT INTO users VALUES (:name, :age)", {"name": "Linus", "age": 28})  # named
print(con.execute("SELECT name FROM users WHERE age > ?", (30,)).fetchall())
$ python3 params.py
[('Ada',)]

SQLite-only syntax

These forms are specific to SQLite, so they won’t carry over to other databases unchanged.

SyntaxWhat it doesGuide
PRAGMA name;Read or change an SQLite settingPRAGMA
INSERT OR REPLACE, INSERT OR IGNOREConflict handling on insertupsert
CREATE TABLE ... STRICTEnforce column typesdata types
ATTACH DATABASE 'file' AS nameQuery two database files togetherATTACH
.tables, .schemaShell dot-commands, not SQLcommands
sqlite> INSERT OR REPLACE INTO products (id, name, price, section, stock) VALUES (1, 'Pen', 2.0, 'office', 90) RETURNING id, name, price;
id  name  price
--  ----  -----
1   Pen   2.0
sqlite> PRAGMA table_info(orders);
cid  name        type     notnull  dflt_value  pk
---  ----------  -------  -------  ----------  --
0    id          INTEGER  0                    1
1    product_id  INTEGER  0                    0
2    qty         INTEGER  0                    0
sqlite> CREATE TABLE strict_t (n INTEGER) STRICT; INSERT INTO strict_t VALUES ('ten');
Error: stepping, cannot store TEXT value in INTEGER column strict_t.n (19)

Common mistakes

  • Double quotes around a string. "word" is a name. If no column has that name, SQLite falls back to a string, so WHERE section = "office" works. If a column does have that name, WHERE name = "name" compares the column with itself and matches every row. SQLite’s own docs call this a “misfeature” (Quirks of SQLite). Use single quotes for strings.
sqlite> SELECT name FROM products WHERE name = "Pen";
name
----
Pen
sqlite> SELECT name FROM products WHERE section = "office";
name
----
Pen
sqlite> SELECT name FROM products WHERE name = "name";
name
----
Mug
Pen
  • An unquoted keyword as a name. CREATE TABLE order fails. Quote it, as shown above.
  • MySQL-only statements. SHOW TABLES doesn’t exist. Query sqlite_schema or use .tables (show tables).
sqlite> SHOW TABLES;
Error: in prepare, near "SHOW": syntax error
  SHOW TABLES;
  ^--- error here
  • Integer division. 7 / 2 is 3. Write 7 / 2.0 for 3.5.
  • Expecting types to be enforced. Plain tables accept text in an INTEGER column. Use STRICT tables to reject it.

Further reading

Tested with SQLite 3.51.0 and Python 3.9.6 on 7 October 2026.