Hoy en día, el Lenguaje de Consulta Estructurado es el medio estándar para manipular y consultar datos en bases de datos relacionales, aunque con extensiones propietarias entre los productos. La facilidad y ubicuidad de SQL ha llevado incluso a los creadores de muchos almacenes de datos «NoSQL» o no relacionales, como Hadoop, a adoptar subconjuntos de SQL o a idear sus propios lenguajes de consulta similares a SQL.
Pero SQL no siempre fue el lenguaje «universal» para las bases de datos relacionales. Desde el principio (alrededor de 1980), SQL tuvo ciertos golpes en contra. Muchos investigadores y desarrolladores de la época, entre los que me encuentro, pensaban que la sobrecarga de SQL impediría que fuera práctico en una base de datos de producción.
Claramente, nos equivocamos. Pero muchos todavía creen que, a pesar de la facilidad y accesibilidad de SQL, el precio exigido en el rendimiento en tiempo de ejecución es a menudo demasiado alto.
Historia de SQL
Antes de que existiera SQL, las bases de datos tenían interfaces de programación ajustadas y de navegación, y normalmente se diseñaban en torno a un esquema de red llamado modelo de datos CODASYL. CODASYL (Committee on Data Systems Languages) era un consorcio responsable del lenguaje de programación COBOL (que comenzó en 1959) y de las extensiones del lenguaje de bases de datos (que comenzaron 10 años después).
Cuando se programaba contra una base de datos CODASYL, se navegaba hacia los registros a través de conjuntos, que expresan relaciones de uno a muchos. Las antiguas bases de datos jerárquicas sólo permiten que un registro pertenezca a un conjunto. Las bases de datos en red permiten que un registro pertenezca a múltiples conjuntos.
Supongamos que quieres listar los alumnos matriculados en CS 101. Primero encontrarías "CS 101"
en el conjunto Courses
por su nombre, establece eso como el propietario o padre del conjunto Enrollees
, encontrar el primer miembro (ffm
) del conjunto Enrollees
, que es un registro Student
, y listarlo. Luego entrarías en un bucle: Buscar el siguiente miembro (fnm
) y listarlo. Cuando fnm
fallaba, se salía del bucle.
Puede parecer mucho trabajo para el programador de la base de datos, pero era muy eficiente en tiempo de ejecución. Expertos como Michael Stonebraker, de la Universidad de California en Berkeley y de Ingres, señalaron que hacer ese tipo de consulta en una base de datos CODASYL como IDMS requería aproximadamente la mitad de tiempo de CPU y menos de la mitad de memoria que la misma consulta en una base de datos relacional utilizando SQL.
Para comparar, la consulta SQL equivalente para devolver todos los estudiantes de CS 101 sería algo como
SELECT student.name FROM courses, enrollees, students WHERE course.name ="CS 101"
Esa sintaxis implica un inner join relacional (en realidad dos de ellos), como explicaré a continuación, y deja fuera algunos detalles importantes, como los campos utilizados para las uniones.
Bases de datos relacionales y SQL
¿Por qué renunciar a un factor de dos de mejora en la velocidad de ejecución y el uso de memoria? Había dos grandes razones: la facilidad de desarrollo y la portabilidad. No creía que ninguna de las dos importara mucho en 1980 en comparación con los requisitos de rendimiento y memoria, pero a medida que el hardware de los ordenadores mejoraba y se abarataba la gente dejó de preocuparse por la velocidad de ejecución y la memoria y se preocupó más por el coste de desarrollo.
En otras palabras, la Ley de Moore mató a las bases de datos CODASYL en favor de las bases de datos relacionales. Tal y como sucedió, la mejora en el tiempo de desarrollo fue significativa, pero la portabilidad de SQL resultó ser una quimera.
¿De dónde salieron el modelo relacional y SQL? E.F. «Ted» Codd era un informático del Laboratorio de Investigación de IBM en San José que elaboró la teoría del modelo relacional en la década de 1960 y la publicó en 1970. IBM tardó en implantar una base de datos relacional para proteger los ingresos de su base de datos CODASYL IMS/DB. Cuando IBM finalmente comenzó su proyecto System R, el equipo de desarrollo (Don Chamberlin y Ray Boyce) no estaba bajo el mando de Codd, e ignoraron el documento de Codd sobre el lenguaje relacional Alpha de 1971 para diseñar su propio lenguaje, SEQUEL (Structured English Query Language). En 1979, antes incluso de que IBM hubiera lanzado su producto, Larry Ellison incorporó el lenguaje en su base de datos Oracle (utilizando las publicaciones de SEQUEL de IBM previas al lanzamiento como sus especificaciones). SEQUEL pronto se convirtió en SQL para evitar una violación internacional de la marca registrada.
Los «golpes de timón para SQL» (como dijo Michael Stonebraker) no sólo provenían de Oracle e IBM, sino también de los clientes. No era fácil contratar o formar a los diseñadores y programadores de bases de datos de CODASYL, por lo que SEQUEL (y SQL) parecían mucho más atractivos. SQL era tan atractivo a finales de la década de 1980 que muchos proveedores de bases de datos esencialmente graparon un procesador de consultas SQL en la parte superior de sus bases de datos CODASYL, para gran consternación de Codd, que consideraba que las bases de datos relacionales tenían que ser diseñadas desde cero para ser relacionales.
Una base de datos relacional pura, tal y como fue diseñada por Codd, se construye sobre tuplas agrupadas en relaciones, consistentes con la lógica de predicados de primer orden. Las bases de datos relacionales del mundo real tienen tablas que contienen campos, restricciones y disparadores, y las tablas se relacionan a través de claves externas. Se utiliza SQL para declarar los datos que se van a devolver, y un procesador de consultas SQL y un optimizador de consultas convierten la declaración SQL en un plan de consulta que es ejecutado por el motor de la base de datos.
SQL incluye un sublenguaje para definir esquemas, el lenguaje de definición de datos (DDL), junto con un sublenguaje para modificar datos, el lenguaje de manipulación de datos (DML). Ambos tienen sus raíces en las primeras especificaciones de CODASYL. El tercer sublenguaje de SQL declara las consultas, a través de la sentencia SELECT
y las uniones relacionales.
Sentencia SQL SELECT
La sentencia SELECT
indica al optimizador de consultas qué datos debe devolver, en qué tablas debe buscar, qué relaciones debe seguir y qué orden debe imponer a los datos devueltos. El optimizador de consultas tiene que averiguar por sí mismo qué índices utilizar para evitar los escaneos de tablas por fuerza bruta y lograr un buen rendimiento de las consultas, a menos que la base de datos concreta admita sugerencias de índices.
Parte del arte del diseño de bases de datos relacionales depende del uso juicioso de los índices. Si se omite un índice para una consulta frecuente, toda la base de datos puede ralentizarse bajo fuertes cargas de lectura. Si tiene demasiados índices, toda la base de datos puede ralentizarse bajo fuertes cargas de escritura y actualización.
Otro arte importante es elegir una buena y única clave primaria para cada tabla. No sólo hay que tener en cuenta el impacto de la clave primaria en las consultas comunes, sino cómo jugará en las uniones cuando aparezca como clave foránea en otra tabla, y cómo afectará a la localidad de referencia de los datos.
En el caso avanzado de las tablas de la base de datos que se dividen en diferentes volúmenes en función del valor de la clave primaria, llamado sharding horizontal, también hay que considerar cómo afectará la clave primaria al sharding. Sugerencia: quiere que la tabla se distribuya uniformemente entre los volúmenes, lo que sugiere que no quiere utilizar marcas de fecha o enteros consecutivos como claves primarias.
Las discusiones de la sentencia SELECT
pueden empezar de forma sencilla, pero pueden convertirse rápidamente en confusas. Considere:
SELECT * FROM Customers;
Simple, ¿verdad? Pide todos los campos y todas las filas de la tabla Customers
. Supongamos, sin embargo, que la tabla Customers
tiene cien millones de filas y cien campos, y uno de los campos es un gran campo de texto para comentarios. ¿Cuánto tiempo tardará en bajar todos esos datos a través de una conexión de red de 10 megabits por segundo si cada fila contiene una media de 1 kilobyte de datos?
Tal vez debería reducir la cantidad que envía por el cable. Considere:
SELECT TOP 100 companyName, lastSaleDate, lastSaleAmount, totalSalesAmount FROM Customers
WHERE state ="Ohio" AND city ="Cleveland"
ORDER BY lastSaleDate DESCENDING;
Ahora va a bajar muchos menos datos. Has pedido a la base de datos que te dé sólo cuatro campos, que sólo considere las empresas de Cleveland y que te dé sólo las 100 empresas con las ventas más recientes. Sin embargo, para hacer esto de forma más eficiente en el servidor de la base de datos la tabla Customers
necesita un índice en state+city
para la cláusula WHERE
y un índice en lastSaleDate
para las cláusulas ORDER BY
y TOP 100
.
Por cierto, TOP 100
es válido para SQL Server y SQL Azure, pero no para MySQL u Oracle. En MySQL, usarías LIMIT 100
después de la cláusula WHERE
. En Oracle, se utilizaría un límite en ROWNUM
como parte de la cláusula WHERE
, es decir, WHERE... AND ROWNUM <=100
. Desgraciadamente, las normas ANSI/ISO SQL (y hay nueve hasta la fecha, que van desde 1986 hasta 2016) solo llegan hasta cierto punto, más allá del cual cada base de datos introduce sus propias cláusulas y características propietarias.
Juntas SQL
Hasta ahora, he descrito la sintaxis SELECT
para tablas individuales. Antes de poder explicar las cláusulas JOIN
, es necesario entender las claves externas y las relaciones entre tablas. Lo explicaré con ejemplos en DDL, utilizando la sintaxis de SQL Server.
La versión corta de esto es bastante simple. Cada tabla que quieras utilizar en las relaciones debe tener una restricción de clave primaria; esta puede ser un solo campo o una combinación de campos definidos por una expresión. Por ejemplo:
CREATE TABLE Persons (
PersonID int NOT NULL PRIMARY KEY,
PersonName char(80),
...
Cada tabla que necesite relacionarse con Persons
debe tener un campo que corresponda a la Persons
clave primaria, y para preservar la integridad relacional ese campo debe tener una restricción de clave foránea. Por ejemplo:
CREATE TABLE Orders (
OrderID int NOT NULL PRIMARY KEY,
...
PersonID int FOREIGN KEY REFERENCES Persons(PersonID)
);
Hay versiones más largas de ambas sentencias que utilizan la palabra clave CONSTRAINT
, que permite nombrar la restricción. Eso es lo que generan la mayoría de las herramientas de diseño de bases de datos.
Las claves primarias siempre están indexadas y son únicas (los valores de los campos no pueden duplicarse). Otros campos pueden ser opcionalmente indexados. A menudo es útil crear índices para los campos de clave foránea y para los campos que aparecen en las cláusulas WHERE
y ORDER BY
, aunque no siempre, debido a la sobrecarga potencial de escrituras y actualizaciones.
¿Cómo escribirías una consulta que devuelva todos los pedidos realizados por Juan Pérez?
SELECT PersonName, OrderID FROM Persons
INNER JOIN Orders ON Persons.PersonID = Orders.PersonID
WHERE PersonName ="John Doe";
De hecho, hay cuatro tipos de JOIN
INNER
OUTER
LEFT
, y RIGHT
. El INNER JOIN
es el predeterminado (puedes omitir la palabra INNER
), y es el que incluye sólo las filas que contienen valores coincidentes en ambas tablas. Si quieres listar las personas tengan o no pedidos, utilizarías un LEFT JOIN
, por ejemplo:
SELECT PersonName, OrderID FROM Persons
LEFT JOIN Orders ON Persons.PersonID = Orders.PersonID
ORDER BY PersonName;
Cuando empiezas a hacer consultas que unen más de dos tablas, que utilizan expresiones o que coaccionan los tipos de datos, la sintaxis puede ser un poco peliaguda al principio. Afortunadamente, hay herramientas de desarrollo de bases de datos que pueden generar consultas SQL correctas para usted, a menudo arrastrando y soltando tablas y campos del diagrama del esquema en un diagrama de consulta.
Procedimientos almacenados de SQL
A veces la naturaleza declarativa de la declaración SELECT
no le lleva a donde quiere ir. La mayoría de las bases de datos tienen una facilidad llamada procedimientos almacenados; desafortunadamente, esta es un área en la que casi todas las bases de datos utilizan extensiones propietarias de los estándares ANSI/ISO SQL.
En SQL Server, el dialecto inicial para los procedimientos almacenados (o procs almacenados) era Transact-SQL, también conocido como T-SQL; en Oracle, era PL-SQL. Ambas bases de datos han añadido lenguajes adicionales para los procedimientos almacenados, como C#, Java y R. Un procedimiento almacenado T-SQL simple puede ser sólo una versión parametrizada de una sentencia SELECT
. Sus ventajas son la facilidad de uso y la eficiencia. Los procedimientos almacenados se optimizan cuando se guardan, no cada vez que se ejecutan.
Un procedimiento almacenado T-SQL más complicado podría utilizar múltiples sentencias SQL, parámetros de entrada y salida, variables locales, BEGIN...END
bloques, IF...THEN...ELSE
condiciones, cursores (procesamiento fila a fila de un conjunto), expresiones, tablas temporales, y toda una serie de otras sintaxis de procedimiento. Obviamente, si el lenguaje del procedimiento almacenado es C#, Java o R, vas a utilizar las funciones y la sintaxis de esos lenguajes de procedimiento. En otras palabras, a pesar del hecho de que la motivación de SQL era utilizar consultas declarativas estandarizadas, en el mundo real se ve un montón de programación de servidor de procedimientos específicos de la base de datos.
Eso no nos lleva de vuelta a los viejos tiempos de la programación de bases de datos CODASYL (aunque los cursores se acercan), pero sí se aleja de las ideas de que las sentencias SQL deben ser estandarizadas y que las preocupaciones de rendimiento deben dejarse al optimizador de consultas de la base de datos. Al final, duplicar el rendimiento es a menudo demasiado para dejarlo sobre la mesa.
Aprender SQL
Los sitios que se enumeran a continuación pueden ayudarle a aprender SQL, o descubrir las peculiaridades de varios dialectos de SQL.
- Codecademy. Aprende SQL. Gratis e interactivo. Actualización Pro disponible por una cuota.
- Khan Academy. Introducción a SQL: Consulta y gestión de datos. Videotutorial gratuito.
- SoloLearn. Fundamentos de SQL. Orientado a MySQL. Gratis.
- Problemas y soluciones de SQL y ejercicios de SQL. Libro de texto y ejercicios interactivos. Gratuito.
- SQLZoo. Un tutorial interactivo de SQL desarrollado y mantenido por la Universidad Napier de Edimburgo. Soporta Microsoft SQL Server, Oracle Database, MySQL, IBM DB2 y PostgreSQL. Gratis.
- Tutorialspoint. Aprenda SQL. Sólo texto, no interactivo. Gratuito.
- Udacity. Introducción a las bases de datos relacionales. Utiliza Python y requiere algunos conocimientos de Python. Gratis.
- Udemy. Entre los cursos gratuitos se encuentran Introducción a las bases de datos y consultas SQL, Base de datos MySQL para principiantes, Microsoft SQL para principiantes, SQL práctico para principiantes (
SELECT
FROM
, yWHERE
), y Sachin aprende rápidamente (SQL). - Academia Vertabelo. Fundamentos de SQL, Operar con datos en SQL, Crear tablas en SQL y otros ocho cursos interactivos de SQL. Algunos cursos tienen pruebas gratuitas, después de las cuales puede haber que pagar. Hay siete cursos adicionales para Microsoft SQL Server. El sitio también tiene una herramienta gráfica de diseño de bases de datos para PostgreSQL, MySQL, Oracle Database, SQL Server, SQLite e IBM DB2.
- W3Schools. SQL Tutorial. Gratis y disponible sin necesidad de registro.
Documentación sobre bases de datos: