The SQL language is a "standard". Even so, no two SQL database engines work exactly alike. Every SQL implementation has it own peculiarities and oddities, and SQLite is no exception to this rule.
This document strives to highlight the principal differences between SQLite and other SQL implementations, as an aid to developers that are porting to or from SQLite or who are trying to build a system that works across multiple database engines.
If you are an SQLite user whose has stumbled over some quirk of SQLite that is not mentioned here, please send us an email so that we can document the problem.
When ever comparing SQLite to other SQL database engines like SQL Server, PostgreSQL, MySQL, or Oracle, it is important first of all to realize the SQLite is not intended as a replacement or competitor to any of those systems. SQLite is serverless. There is no separate server process that manages the database. An application interact with the database engine using function calls, not be sending messages to a separate process or thread.
The fact that SQLite is embedded and serverless instead of being client/server is a feature, not a bug.
Client/server databases like MySQL, PostgreSQL, SQL Server, Oracle, and others are an important component of modern systems. These systems solve an important problem. But SQLite solves a different problem. Both SQLite and client/server databases have their role. Developers who are comparing SQLite against other SQL database engines need to clearly understand this distinction.
See the Appropriate Uses For SQLite document for additional information.
SQLite is very flexible with regard to datatypes.
Some commentators say that SQLite is "weakly typed" and that other SQL databases are "strongly typed". We consider these terms to be inaccurate and purgative. We prefer to say that SQLite is "flexibly typed" and that other SQL databases are "rigidly typed".
See the Datatypes in SQLite Version 3 document for a detailed discussion of the type system in SQLite.
The key point is that SQLite is very forgiving of the type of data that you put into the database. For example, if a column has a datatype of "INTEGER" and the application inserts a text string into that column, SQLite will first try to convert the text string into an integer, just like every other SQL database engine. Thus, if one inserts '1234' into an INTEGER column, that value is converted into an integer 1234 and stored. But, if you insert a non-numeric string like 'wxyz' into an INTEGER column, unlike other SQL databases, SQLite does not throw an error. Instead, SQLite stores the actual string value in the column.
Similarly, SQLite allows you to store a 2000-character string into a column of type VARCHAR(50). Other SQL implementions would either throw an error or truncate the string. SQLite stores the entire 2000-character string with no loss of information and without complaint.
Where this ends up causing problems is when developers do some initial coding work using SQLite and get their application working, but then try to convert to another database like PostgreSQL or SQL Server for deployment. If the application is initially taking advantage of SQLite's flexible typing, then it will fail when moved to another database that uses a more rigid and unforgiving type enforcement policy.
Flexible typing is considered a feature of SQLite, not a bug. Nevertheless, we recognize that this feature does sometimes cause confusion and pain for developers who are acustomed to working with other databases that are more judgemental with regard to data types. In retrospect, perhaps it would have been better if SQLite had merely implemented an ANY datatype so that developers could explicitly state when they wanted to use flexible typing, rather than making flexible typing the default. But that is not something that can be changed now without breaking the millions of applications and trillions of database files that already use SQLite's flexible typing feature.
Unlike most other SQL implementations, SQLite does not have a separate BOOLEAN data type. Instead, TRUE and FALSE are (normally) represented as integers 1 and 0, respectively. This does not seem to cause many problems, as we seldom get complaints about it. But it is important to recognize.
Beginning with SQLite version 3.23.0 (2018-04-02), SQLite also recognizes TRUE and FALSE keywords as aliases for integer values 1 and 0, respectively. This provides better compatibility with other SQL implementations. But to retain backwards compatibility, if there are columns named TRUE or FALSE, then the keywords are treated as identifiers referencing those columns, rather than BOOLEAN literals.
SQLite as no DATETIME datatype. Instead, dates and times can be stored in any of these ways:
The built-in date and time functions of SQLite understand date/times in all of the formats above, and can freely change between them. Which format you use, is entirely up to your application.
SQLite has parsed foreign key constraints for time out of mind, but added the ability to actually enforce those constraints much later, with version 3.6.19 (2009-10-14). By the time foreign key constraint enforcement was added, there where already countless millions of databases in circulation that contained foreign key constraints, some of which were not correct. To avoid breaking those legacy databases, foreign key constraint enforcement is turned off by default in SQLite.
Applications can activate foreign key enforcement at run-time using the PRAGMA foreign_keys statement. Or, foreign key enforcement can be activated at compile-time using the -DSQLITE_DEFAULT_FOREIGN_KEYS=1 compile-time option.
Usually (the exceptions are INTEGER PRIMARY KEY tables and WITHOUT ROWID tables) a PRIMARY KEY in an SQLite table is really the same as a UNIQUE constraint. Due to an historical oversight, the column values of such a PRIMARY KEY are allowed to be NULL. This is a bug, but by the time the problem was discovered there where so many databases in circulation that depended on the bug that the decision was made to support the bugging behavior moving forward.
In most SQL implementations, output columns of an aggregate query may only reference aggregate functions or columns named in the GROUP BY clause. It does not make good sense to reference an ordinary column in an aggregate query because each output row might be composed from two more more rows in the input table(s).
SQLite does not impose this restriction. The output columns from an aggregate query can be arbitrary expressions that include columns not found in GROUP BY clause. This feature has two uses:
With SQLite (but not any other SQL implementation that we know of) if an aggregate query contains a single min() or max() function, then the values of columns used in the output are taken from the row where the min() or max() value was achieved. If two or more rows have the same min() or max() value, then the columns values will be chosen arbitrarily from one of those rows.
For example to find the highest paid employee:
SELECT max(salary), first_name, last_name FROM employee;
In the query above, the values for the first_name and last_name columns will correspond to the row that satisfied the max(salary) condition.
If a query contains no aggregate functions at all, then a GROUP BY clause can be added as a substitute of DISTINCT ON clause. In other words, output rows are filtered so that only one row is shows for each distinct set of values in the GROUP BY clause. If two or more output rows would have otherwise had the same set of values for the GROUP BY columns, then the one of the rows is chosen arbitrarily.
SQLite does not know about the upper-case/lower-case distinction for all unicode characters. SQL functions like upper() and lower() only work on ASCII characters. There are two reasons for this:
The SQL standard says that one should use double-quotes around identifiers and single-quotes around string literals.
SQLite accepts both of the above. But, in an effort to be compatible with MySQL 3.x (which was widely popular when SQLite was first being designed) will also use content contained in double-quotes as a string literal if the content does not match any valid identifier.
An unfortunate side-effect of this is that a misspelled double-quoted identifier will be interpreted as a string literal, rather than generating an error. Another problem is that this behavior allows developers who are new to the SQL language to continue using double-quoted string literals when they really need to learn to use the correct single-quoted string literal form.
In hindsight, we should not have tried to make SQLite accept MySQL 3.x syntax, and should have never allowed double-quoted string literals. However, we continue to support that capability to avoid breaking legacy applications.
The SQL language is rich in keywords. Most SQL implementations do not allow keywords to be used as identifiers (the names of table or columns) unless they are enclosed in double-quotes. But SQLite is more flexible. Many keywords can be used as identifiers without needing to be quoted, as long as those keywords are used in a context where it is clear that they are intended to be an identifier.
For example, the following statement is valid in SQLite:
CREATE TABLE union(true INT, with BOOLEAN);
The same SQL statement will fail on every other SQL implementation that we know of due to the use of keywords "union", "true", and "with" as identifiers.
The original implementation of SQLite sought to follow Postel's Law which states in part "Be liberal in what you accept". This used to be considered good design - that a system would accept dodgy inputs and try to do the best it could without complaining too much. But lately, people have come to realize that it is sometimes better to be strict in what you accept, so as to more easily find errors in the input.
The AUTOINCREMENT feature in SQLite works differently than it does in MySQL. This often causes confusion for people who initially learned SQL on MySQL and then start using SQLite, and expect the two systems to work identically.
See the SQLite AUTOINCREMENT documentation for detailed instructions on what AUTOINCREMENT does and does not do in SQLite.