1. Descripción general2. SQLite está integrado, no cliente-servidor 3. Escritura flexible 3.1. Sin tipo de datos BOOLEAN separado Sin tipos de datos DATETIME separados El tipo de datos es opcional 4. La aplicación de claves externas está desactivada de forma predeterminada 5. Las CLAVES PRIMARIAS a veces pueden contener NULL 6. Las consultas agregadas pueden contener columnas de resultados no agregadas que no están en la cláusula GROUP BY 7. No realiza el plegado completo de mayúsculas y minúsculas Unicode de forma predeterminada 8. Se aceptan literales de cadena entre comillas dobles 9. Las palabras clave a menudo se pueden utilizar como identificadores 10. Se permite SQL dudoso sin ningún error o advertencia 11. AUTOINCREMENT no funciona igual que MySQL12. Se permiten caracteres NUL en cadenas de texto

El lenguaje SQL es un “estándar”. Aun así, no hay dos motores de base de datos SQL que funcionen exactamente igual. Cada implementación de SQL tiene sus propias peculiaridades y rarezas, y SQLite no es una excepción a esta regla.

Este documento se esfuerza por resaltar las principales diferencias entre SQLite y otras implementaciones de SQL, como una ayuda para los desarrolladores que están migrando hacia o desde SQLite o que están tratando de construir un sistema que funcione en múltiples motores de base de datos.

Si es un usuario de SQLite que ha tropezado con alguna peculiaridad de SQLite que no se menciona aquí, envíenos un correo electrónico para que podamos documentar el problema.

Siempre que se compare SQLite con otros motores de base de datos SQL como SQL Server, PostgreSQL, MySQL u Oracle, es importante en primer lugar darse cuenta de que SQLite no pretende ser un reemplazo o competidor de ninguno de esos sistemas. SQLite no tiene servidor. No existe un proceso de servidor independiente que gestione la base de datos. Una aplicación interactúa con el motor de la base de datos mediante llamadas a funciones, no enviando mensajes a un proceso o subproceso separado.

El hecho de que SQLite esté integrado y sin servidor en lugar de ser cliente / servidor es una característica, no un error.

Las bases de datos cliente / servidor como MySQL, PostgreSQL, SQL Server, Oracle y otras son un componente importante de los sistemas modernos. Estos sistemas resuelven un problema importante. Pero SQLite resuelve un problema diferente. Tanto las bases de datos SQLite como las de cliente / servidor tienen su función. Los desarrolladores que comparan SQLite con otros motores de bases de datos SQL deben comprender claramente esta distinción.

Consulte el documento Usos apropiados de SQLite para obtener información adicional.

SQLite es muy flexible con respecto a los tipos de datos.

Algunos comentaristas dicen que SQLite está “débilmente tipado” y que otras bases de datos SQL están “fuertemente tipadas”. Consideramos que estos términos son inexactos y peyorativos. Preferimos decir que SQLite está “tipado de forma flexible” y que otras bases de datos SQL tienen “tipado rígido”.

Consulte el documento Tipos de datos en SQLite Versión 3 para obtener una discusión detallada del sistema de tipos en SQLite.

El punto clave es que SQLite es muy indulgente con el tipo de datos que ingresa en la base de datos. Por ejemplo, si una columna tiene un tipo de datos de “INTEGER” y la aplicación inserta una cadena de texto en esa columna, SQLite primero intentará convertir la cadena de texto en un número entero, como cualquier otro motor de base de datos SQL. Por lo tanto, si uno inserta ‘1234’ en una columna INTEGER, ese valor se convierte en un entero 1234 y se almacena. Pero, si inserta una cadena no numérica como ‘W x Y Z’ en una columna INTEGER, a diferencia de otras bases de datos SQL, SQLite no arroja un error. En cambio, SQLite almacena el valor real de la cadena en la columna.

De manera similar, SQLite le permite almacenar una cadena de 2000 caracteres en una columna de tipo VARCHAR (50). Otras implementaciones de SQL arrojarían un error o truncarían la cadena. SQLite almacena la cadena completa de 2000 caracteres sin pérdida de información y sin quejas.

Donde esto termina causando problemas es cuando los desarrolladores realizan un trabajo de codificación inicial usando SQLite y hacen que su aplicación funcione, pero luego intentan convertir a otra base de datos como PostgreSQL o SQL Server para su implementación. Si la aplicación se está aprovechando inicialmente de la escritura flexible de SQLite, fallará cuando se mueva a otra base de datos que utilice una política de aplicación de tipos más rígida e implacable.

La escritura flexible se considera una característica de SQLite, no un error. Sin embargo, reconocemos que esta característica a veces causa confusión y dolor a los desarrolladores que están acostumbrados a trabajar con otras bases de datos que son más críticas con respecto a los tipos de datos. En retrospectiva, tal vez hubiera sido mejor si SQLite simplemente hubiera implementado un CUALQUIER tipo de datos para que los desarrolladores pudieran indicar explícitamente cuándo querían usar la escritura flexible, en lugar de hacer que la escritura flexible sea la predeterminada. Pero eso no es algo que pueda cambiarse ahora sin romper los millones de aplicaciones y billones de archivos de bases de datos que ya utilizan la función de escritura flexible de SQLite.

3.1. Sin tipo de datos BOOLEAN separado

A diferencia de la mayoría de las otras implementaciones de SQL, SQLite no tiene un tipo de datos BOOLEAN separado. En cambio, VERDADERO y FALSO se representan (normalmente) como números enteros 1 y 0, respectivamente. Esto no parece causar muchos problemas, ya que rara vez recibimos quejas al respecto. Pero es importante reconocer.

Comenzando con SQLite versión 3.23.0 (2018-04-02), SQLite también reconoce palabras clave TRUE y FALSE como alias para valores enteros 1 y 0, respectivamente. Esto proporciona una mejor compatibilidad con otras implementaciones de SQL. Pero para mantener la compatibilidad con versiones anteriores, si hay columnas denominadas TRUE o FALSE, las palabras clave se tratan como identificadores que hacen referencia a esas columnas, en lugar de literales BOOLEAN.

3.2. Sin tipo de datos DATETIME separado

SQLite no tiene un tipo de datos DATETIME. En cambio, las fechas y horas se pueden almacenar de cualquiera de estas formas:

  • Como cadena de TEXTO en formato ISO-8601. Ejemplo: ‘2018-04-02 12:13:46’.
  • Como un número INTEGER de segundos desde 1970 (también conocido como “tiempo unix”).
  • Como valor REAL que es la fracción Número de día juliano.

Las funciones integradas de fecha y hora de SQLite comprenden la fecha y la hora en todos los formatos anteriores y pueden cambiar libremente entre ellos. El formato que utilice depende totalmente de su aplicación.

3.3. El tipo de datos es opcional

Debido a que SQLite es flexible y tolerante con los tipos de datos, se pueden crear columnas de tabla que no tengan ningún tipo de datos especificado. Por ejemplo:

CREATETABLE t1(a,b,c,d);

La tabla “t1” tiene cuatro columnas “a”, “b”, “c” y “d” que no tienen asignado ningún tipo de datos en particular. Puede almacenar lo que quiera en cualquiera de esas columnas.

SQLite ha analizado las restricciones de clave externa por tiempo fuera de la mente, pero agregó la capacidad de hacer cumplir esas restricciones mucho más tarde, con versión 3.6.19 (14 de octubre de 2009). Cuando se agregó la aplicación de restricciones de clave externa, ya había incontables millones de bases de datos en circulación que contenían restricciones de clave externa, algunas de las cuales no eran correctas. Para evitar romper esas bases de datos heredadas, la aplicación de restricciones de clave externa está desactivada de forma predeterminada en SQLite.

Las aplicaciones pueden activar la aplicación de claves foráneas en tiempo de ejecución utilizando la instrucción PRAGMA Foreign_keys. O bien, la aplicación de claves foráneas se puede activar en tiempo de compilación usando la opción -DSQLITE_DEFAULT_FOREIGN_KEYS = 1 en tiempo de compilación.

Por lo general (las excepciones son tablas INTEGER PRIMARY KEY y tablas SIN ROWID) una PRIMARY KEY en una tabla SQLite es realmente lo mismo que una restricción ÚNICA. Debido a un descuido histórico, se permite que los valores de columna de dicha CLAVE PRIMARIA sean NULOS. Esto es un error, pero cuando se descubrió el problema, había tantas bases de datos en circulación que dependían del error que se tomó la decisión de respaldar el comportamiento de errores en el futuro.

El valor de una columna INTEGER PRIMARY KEY siempre debe ser un entero no NULL. Las columnas PRIMARY KEY de una tabla WITHOUT ROWID también deben ser no NULL.

En la mayoría de las implementaciones de SQL, las columnas de salida de una consulta agregada solo pueden hacer referencia a funciones agregadas o columnas nombradas en la cláusula GROUP BY. No tiene sentido hacer referencia a una columna ordinaria en una consulta agregada porque cada fila de salida puede estar compuesta por dos o más filas en la (s) tabla (s) de entrada.

SQLite no aplica esta restricción. Las columnas de salida de una consulta agregada pueden ser expresiones arbitrarias que incluyen columnas que no se encuentran en la cláusula GROUP BY. Esta característica tiene dos usos:

  1. Con SQLite (pero no con ninguna otra implementación de SQL que conozcamos) si una consulta agregada contiene una única función min () o max (), entonces los valores de las columnas utilizadas en la salida se toman de la fila donde el min () o Se alcanzó el valor max (). Si dos o más filas tienen el mismo valor mínimo () o máximo (), los valores de las columnas se elegirán arbitrariamente de una de esas filas.

    Por ejemplo, para encontrar el empleado mejor pagado:

    SELECTmax(salary), first_name, last_name FROM employee;

    En la consulta anterior, los valores de las columnas first_name y last_name corresponderán a la fila que cumplió con la condición máxima (salario).

  2. Si una consulta no contiene ninguna función agregada, entonces se puede agregar una cláusula GROUP BY como sustituto de la cláusula DISTINCT ON. En otras palabras, las filas de salida se filtran para que solo se muestre una fila para cada conjunto distinto de valores en la cláusula GROUP BY. Si dos o más filas de salida hubieran tenido el mismo conjunto de valores para las columnas GROUP BY, entonces una de las filas se elige arbitrariamente. (SQLite admite DISTINCT pero no DISTINCT ON, cuya funcionalidad es proporcionada en su lugar por GROUP BY).

SQLite no conoce la distinción entre mayúsculas y minúsculas para todos los caracteres Unicode. Las funciones SQL como superior () e inferior () solo funcionan con caracteres ASCII. Hay dos razones para esto:

  1. Aunque estable ahora, cuando se diseñó SQLite por primera vez, las reglas para el plegado de casos Unicode todavía estaban en constante cambio. Eso significa que el comportamiento podría haber cambiado con cada nueva versión de Unicode, interrumpiendo las aplicaciones y corrompiendo los índices en el proceso.
  2. Las tablas necesarias para realizar un plegado completo y adecuado de casos Unicode son más grandes que toda la biblioteca SQLite.

El plegado completo de casos Unicode es compatible con SQLite si se compila con la opción -DSQLITE_ENABLE_ICU y se vincula con la Componentes internacionales para Unicode Biblioteca.

El estándar SQL requiere comillas dobles en los identificadores y comillas simples en los literales de cadena. Por ejemplo:

  • "this is a legal SQL column name"
  • 'this is an SQL string literal'

SQLite acepta ambos de los anteriores. Pero, en un esfuerzo por ser compatible con MySQL 3.x (que fue uno de los RDBMS más utilizados cuando SQLite fue diseñado por primera vez) SQLite también interpretará una cadena de comillas dobles como literal de cadena si no coincide con ningún identificador válido.

Este error significa que un identificador entre comillas dobles mal escrito se interpretará como un literal de cadena, en lugar de generar un error. También atrae a los desarrolladores que son nuevos en el lenguaje SQL al mal hábito de usar literales de cadena entre comillas dobles cuando realmente necesitan aprender a usar la forma correcta de literal de cadena entre comillas simples.

En retrospectiva, no deberíamos haber intentado que SQLite aceptara la sintaxis de MySQL 3.x, y nunca deberíamos haber permitido cadenas literales entre comillas dobles. Sin embargo, hay innumerables aplicaciones que hacen uso de cadenas literales entre comillas dobles y, por lo tanto, continuamos respaldando esa capacidad para evitar romper el legado.

A partir de SQLite 3.27.0 (2019-02-07), el uso de un literal de cadena entre comillas dobles hace que se envíe un mensaje de advertencia al registro de errores.

A partir de SQLite 3.29.0 (2019-07-10), el uso de literales de cadena entre comillas dobles se puede deshabilitar en tiempo de ejecución utilizando las acciones SQLITE_DBCONFIG_DQS_DDL y SQLITE_DBCONFIG_DQS_DML para sqlite3_db_config (). La configuración predeterminada se puede modificar en tiempo de compilación usando -DSQLITE_DQS =norte opción de tiempo de compilación. Se recomienda a los desarrolladores de aplicaciones que compilen usando -DSQLITE_DQS = 0 para deshabilitar el error literal de cadena entre comillas dobles de forma predeterminada. Si eso no es posible, desactive los literales de cadena entre comillas dobles para conexiones de bases de datos individuales usando código C como este:

sqlite3_db_config(db, SQLITE_DBCONFIG_DQS_DDL,0,(void*)0);
sqlite3_db_config(db, SQLITE_DBCONFIG_DQS_DML,0,(void*)0);

O, si los literales de cadena entre comillas dobles están deshabilitados de forma predeterminada, pero deben habilitarse selectivamente para algunas conexiones históricas de bases de datos, eso se puede hacer usando el mismo código C que se muestra arriba, excepto con el tercer parámetro cambiado de 0 a 1.

El lenguaje SQL es rico en palabras clave. La mayoría de las implementaciones de SQL no permiten que las palabras clave se utilicen como identificadores (los nombres de tablas o columnas) a menos que estén entre comillas dobles. Pero SQLite es más flexible. Muchas palabras clave se pueden utilizar como identificadores sin necesidad de citarlas, siempre que esas palabras clave se utilicen en un contexto en el que esté claro que están destinadas a ser un identificador.

Por ejemplo, la siguiente declaración es válida en SQLite:

CREATETABLEunion(trueINT,withBOOLEAN);

La misma instrucción SQL fallará en cualquier otra implementación de SQL que conozcamos debido al uso de las palabras clave “unión”, “verdadero” y “con” como identificadores.

La capacidad de utilizar palabras clave como identificadores promueve la compatibilidad con versiones anteriores. A medida que se agregan nuevas palabras clave, los esquemas heredados que simplemente usan esas palabras clave como nombres de tablas o columnas continúan funcionando. Sin embargo, la capacidad de utilizar una palabra clave como identificador a veces conduce a resultados sorprendentes. Por ejemplo:

CREATETRIGGERAFTERINSERTON tableX BEGININSERTINTO tableY(b)VALUES(new.a);END;

El disparador creado por la declaración anterior se llama “DESPUÉS” y es un disparador “ANTES”. El token “AFTER” se utiliza como un identificador en lugar de como una palabra clave, ya que es la única forma de analizar la declaración. Otro ejemplo:

CREATETABLE tableZ(INTEGERPRIMARYKEY);

La tabla tableZ tiene una sola columna denominada “INTEGER”. Esa columna no tiene ningún tipo de datos especificado, pero es la CLAVE PRIMARIA. La columna es no la CLAVE PRIMARIA INTEGER para la tabla porque no tiene ningún tipo de datos. El token “INTEGER” se utiliza como un identificador para el nombre de la columna, no como una palabra clave de tipo de datos.

La implementación original de SQLite buscaba seguir Ley de Postel que dice en parte “Sea liberal en lo que acepta”. Esto solía considerarse un buen diseño: que un sistema aceptara entradas poco fiables y tratara de hacer lo mejor que pudiera sin quejarse demasiado. Pero últimamente, la gente se ha dado cuenta de que a veces es mejor ser estricto en lo que acepta, para encontrar más fácilmente errores en la entrada.

La función AUTOINCREMENT en SQLite funciona de manera diferente a como lo hace en MySQL. Esto a menudo causa confusión a las personas que inicialmente aprendieron SQL en MySQL y luego comienzan a usar SQLite, y esperan que los dos sistemas funcionen de manera idéntica.

Consulte la documentación de AUTOINCREMENT de SQLite para obtener instrucciones detalladas sobre lo que hace y no hace AUTOINCREMENT en SQLite.

Los caracteres NUL (código ASCII 0x00 y Unicode u0000) pueden aparecer en medio de cadenas en SQLite. Esto puede provocar un comportamiento inesperado. Consulte el documento “Caracteres NUL en cadenas” para obtener más información.