Capítulo 4. Filtrar

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

A veces querrás trabajar con todas las filas de una tabla, como por ejemplo

  • Purgar todos los datos de una tabla utilizada para preparar nuevas alimentaciones del almacén de datos

  • Modificar todas las filas de una tabla tras añadir una nueva columna

  • Recuperar todas las filas de una tabla de cola de mensajes

En casos como éste, tus sentencias SQL no necesitarán tener una cláusula where, ya que no necesitas excluir ninguna fila de tu consideración. La mayoría de las veces, sin embargo, querrás limitar tu enfoque a un subconjunto de filas de una tabla. Por ello, todas las sentencias SQL de datos (excepto la sentencia insert ) incluyen una cláusula opcional where que contiene una o varias condiciones de filtro utilizadas para restringir el número de filas sobre las que actúa la sentencia SQL. Además, la sentencia select incluye una cláusula having en la que se pueden incluir condiciones de filtro relativas a datos agrupados. Este capítulo explora los distintos tipos de condiciones de filtro que puedes emplear en las cláusulas where de las sentencias select, update y delete; en el Capítulo 8 demuestro el uso de condiciones de filtro en la cláusula having de una sentencia select.

Evaluación del estado

Una cláusula where puede contener una o varias condiciones, separadas por los operadores and y or. Si hay varias condiciones separadas sólo por el operador and, todas las condiciones deben evaluarse con true para que la fila se incluya en el conjunto de resultados. Considera la siguiente cláusula where:

WHERE first_name = 'STEVEN' AND create_date > '2006-01-01'

Dadas estas dos condiciones, sólo se incluirán en el conjunto de resultados las filas en las que el nombre de pila sea Steven y la fecha de creación sea posterior al 1 de enero de 2006. Aunque este ejemplo sólo utiliza dos condiciones, no importa cuántas condiciones haya en tu cláusula where, si están separadas por el operador and, todas deben evaluarse como true para que la fila se incluya en el conjunto de resultados.

Sin embargo, si todas las condiciones de la cláusula where están separadas por el operador or, sólo una de las condiciones debe evaluarse como true para que la fila se incluya en el conjunto de resultados. Considera las dos condiciones siguientes:

WHERE first_name = 'STEVEN' OR create_date > '2006-01-01'

Ahora hay varias formas de incluir una fila determinada en el conjunto de resultados:

  • El nombre es Steven, y la fecha de creación fue posterior al 1 de enero de 2006.

  • El nombre es Steven, y la fecha de creación fue el 1 de enero de 2006 o antes.

  • El nombre no es Steven, pero la fecha de creación es posterior al 1 de enero de 2006.

La Tabla 4-1 muestra los posibles resultados de una cláusula where que contiene dos condiciones separadas por el operador or.

Tabla 4-1. Evaluación de dos condiciones utilizando o
Resultado intermedio Resultado final

WHERE true OR true

true

WHERE true OR false

true

WHERE false OR true

true

WHERE false OR false

false

En el caso del ejemplo anterior, la única forma de que una fila quede excluida del conjunto de resultados es que el nombre de pila de la persona no sea Steven y que la fecha de creación sea igual o anterior al 1 de enero de 2006.

Uso de paréntesis

Si tu cláusula where incluye tres o más condiciones utilizando los operadores and y or, debes utilizar paréntesis para dejar clara tu intención, tanto para el servidor de la base de datos como para cualquier otra persona que lea tu código. Aquí tienes una cláusula where que amplía el ejemplo anterior comprobando que el nombre es Steven o el apellido es Young, y la fecha de creación es posterior al 1 de enero de 2006:

WHERE (first_name = 'STEVEN' OR last_name = 'YOUNG')
  AND create_date > '2006-01-01'

Ahora hay tres condiciones; para que una fila llegue al conjunto final de resultados, la primera o la segunda condición (o ambas) deben evaluarse como true, y la tercera condición debe evaluarse como true. La Tabla 4-2 muestra los posibles resultados de esta cláusula where.

Tabla 4-2. Evaluación de tres condiciones utilizando y, o
Resultado intermedio Resultado final

WHERE (true OR true) AND true

true

WHERE (true OR false) AND true

true

WHERE (false OR true) AND true

true

WHERE (false OR false) AND true

false

WHERE (true OR true) AND false

false

WHERE (true OR false) AND false

false

WHERE (false OR true) AND false

false

WHERE (false OR false) AND false

false

Como puedes ver, cuantas más condiciones tengas en tu cláusula where, más combinaciones tendrá que evaluar el servidor. En este caso, sólo tres de las ocho combinaciones dan como resultado final true.

Utilizar el operador no

Afortunadamente, el ejemplo anterior de las tres condiciones es bastante fácil de entender. No obstante, considera la siguiente modificación

WHERE NOT (first_name = 'STEVEN' OR last_name = 'YOUNG')
  AND create_date > '2006-01-01'

¿Te has dado cuenta del cambio respecto al ejemplo anterior? He añadido el operador not antes del primer conjunto de condiciones. Ahora, en lugar de buscar personas con el nombre Steven o el apellido Young cuyo registro se haya creado después del 1 de enero de 2006, sólo recupero filas en las que el nombre no sea Steven o el apellido no sea Young cuyo registro se haya creado después del 1 de enero de 2006. La Tabla 4-3 muestra los posibles resultados de este ejemplo.

Tabla 4-3. Evaluación de tres condiciones utilizando y, o, y no
Resultado intermedio Resultado final

WHERE NOT (true OR true) AND true

false

WHERE NOT (true OR false) AND true

false

WHERE NOT (false OR true) AND true

false

WHERE NOT (false OR false) AND true

true

WHERE NOT (true OR true) AND false

false

WHERE NOT (true OR false) AND false

false

WHERE NOT (false OR true) AND false

false

WHERE NOT (false OR false) AND false

false

Aunque es fácil de manejar para el servidor de bases de datos, suele ser difícil para una persona evaluar una cláusula where que incluya el operador not, por lo que no te la encontrarás muy a menudo. En este caso, puedes reescribir la cláusula where para evitar utilizar el operador not:

WHERE first_name <> 'STEVEN' AND last_name <> 'YOUNG'
  AND create_date > '2006-01-01'

Aunque estoy seguro de que el servidor no tiene ninguna preferencia, probablemente te resulte más fácil entender esta versión de la cláusula where.

Construir una condición

Ahora que ya has visto cómo evalúa el servidor las condiciones múltiples, vamos a dar un paso atrás y ver qué comprende una condición simple. Una condición está formada por una o varias expresiones combinadas con uno o varios operadores. Una expresión puede ser cualquiera de las siguientes

  • Un número

  • Una columna de una tabla o vista

  • Un literal de cadena, como 'Maple Street'

  • Una función incorporada, como concat('Learning', ' ', 'SQL')

  • Una subconsulta

  • Una lista de expresiones, como ('Boston', 'New York', 'Chicago')

Los operadores utilizados en las condiciones son

  • Operadores de comparación, como =, !=, <, >, <>, like, in, y between

  • Operadores aritméticos, como +, , *, y /

La siguiente sección muestra cómo puedes combinar estas expresiones y operadores para fabricar los distintos tipos de condiciones.

Tipos de afecciones

Hay muchas formas distintas de filtrar los datos no deseados. Puedes buscar valores concretos, conjuntos de valores o rangos de valores para incluirlos o excluirlos, o puedes utilizar diversas técnicas de búsqueda de patrones para buscar coincidencias parciales cuando se trate de datos de cadenas. Las cuatro subsecciones siguientes exploran en detalle cada uno de estos tipos de condiciones.

Condiciones de igualdad

Un gran porcentaje de las condiciones de filtro que escribas o con las que te encuentres serán de la forma 'column =expression' como en

title = 'RIVER OUTLAW'
fed_id = '111-11-1111'
amount = 375.25
film_id = (SELECT film_id FROM film WHERE title = 'RIVER OUTLAW')

Este tipo de condiciones se denominan condiciones de igualdad porque equiparan una expresión a otra. Los tres primeros ejemplos equiparan una columna a un literal (dos cadenas y un número), y el cuarto ejemplo equipara una columna al valor devuelto por una subconsulta. La siguiente consulta utiliza dos condiciones de igualdad, una en la cláusula on (una condición de unión) y la otra en la cláusula where (una condición de filtro):

mysql> SELECT c.email
    -> FROM customer c
    ->   INNER JOIN rental r
    ->   ON c.customer_id = r.customer_id
    -> WHERE date(r.rental_date) = '2005-06-14';
+---------------------------------------+
| email                                 |
+---------------------------------------+
| CATHERINE.CAMPBELL@sakilacustomer.org |
| JOYCE.EDWARDS@sakilacustomer.org      |
| AMBER.DIXON@sakilacustomer.org        |
| JEANETTE.GREENE@sakilacustomer.org    |
| MINNIE.ROMERO@sakilacustomer.org      |
| GWENDOLYN.MAY@sakilacustomer.org      |
| SONIA.GREGORY@sakilacustomer.org      |
| MIRIAM.MCKINNEY@sakilacustomer.org    |
| CHARLES.KOWALSKI@sakilacustomer.org   |
| DANIEL.CABRAL@sakilacustomer.org      |
| MATTHEW.MAHAN@sakilacustomer.org      |
| JEFFERY.PINSON@sakilacustomer.org     |
| HERMAN.DEVORE@sakilacustomer.org      |
| ELMER.NOE@sakilacustomer.org          |
| TERRANCE.ROUSH@sakilacustomer.org     |
| TERRENCE.GUNDERSON@sakilacustomer.org |
+---------------------------------------+
16 rows in set (0.03 sec)

Esta consulta muestra todas las direcciones de correo electrónico de todos los clientes que alquilaron una película el 14 de junio de 2005.

Condiciones de desigualdad

Otro tipo de condición bastante común es la condición de desigualdad, que afirma que dos expresiones no son iguales. Aquí tienes la consulta anterior con la condición de filtro de la cláusula where cambiada por una condición de desigualdad:

mysql> SELECT c.email
    -> FROM customer c
    ->   INNER JOIN rental r
    ->   ON c.customer_id = r.customer_id
    -> WHERE date(r.rental_date) <> '2005-06-14';

+-----------------------------------+
| email                             |
+-----------------------------------+
| MARY.SMITH@sakilacustomer.org     |
| MARY.SMITH@sakilacustomer.org     |
| MARY.SMITH@sakilacustomer.org     |
| MARY.SMITH@sakilacustomer.org     |
| MARY.SMITH@sakilacustomer.org     |
| MARY.SMITH@sakilacustomer.org     |
| MARY.SMITH@sakilacustomer.org     |
| MARY.SMITH@sakilacustomer.org     |
| MARY.SMITH@sakilacustomer.org     |
| MARY.SMITH@sakilacustomer.org     |
...
| AUSTIN.CINTRON@sakilacustomer.org |
| AUSTIN.CINTRON@sakilacustomer.org |
| AUSTIN.CINTRON@sakilacustomer.org |
| AUSTIN.CINTRON@sakilacustomer.org |
| AUSTIN.CINTRON@sakilacustomer.org |
| AUSTIN.CINTRON@sakilacustomer.org |
| AUSTIN.CINTRON@sakilacustomer.org |
| AUSTIN.CINTRON@sakilacustomer.org |
+-----------------------------------+
16028 rows in set (0.03 sec)
 

Esta consulta devuelve todas las direcciones de correo electrónico de películas alquiladas en cualquier fecha distinta al 14 de junio de 2005. Cuando construyas condiciones de desigualdad, puedes optar por utilizar el operador != o <>.

Modificación de datos mediante condiciones de igualdad

Las condiciones de igualdad/desigualdad se suelen utilizar cuando se modifican datos. Por ejemplo, supongamos que la empresa de alquiler de películas tiene la política de eliminar las filas de cuentas antiguas una vez al año. Tu tarea consiste en eliminar las filas de la tabla rental en las que la fecha de alquiler era de 2004. He aquí una forma de abordarlo:

DELETE FROM rental
WHERE year(rental_date) = 2004;

Esta sentencia incluye una única condición de igualdad; aquí tienes un ejemplo que utiliza dos condiciones de desigualdad para eliminar cualquier fila en la que la fecha de alquiler no fuera en 2005 o 2006:

DELETE FROM rental
WHERE year(rental_date) <> 2005 AND year(rental_date) <> 2006;
Nota

Al elaborar los ejemplos de las sentencias delete y update, intento escribir cada sentencia de forma que no se modifique ninguna fila. De ese modo, cuando ejecutes las sentencias, tus datos permanecerán inalterados, y tu salida de las sentencias select siempre coincidirá con la mostrada en este libro.

Como las sesiones MySQL están en modo de autocompromiso por defecto (véase el Capítulo 12), no podrías revertir (deshacer) ningún cambio realizado en los datos del ejemplo si una de mis sentencias modificara los datos. Por supuesto, puedes hacer lo que quieras con los datos del ejemplo, incluso limpiarlos y volver a ejecutar los scripts para rellenar las tablas, pero yo intento dejarlos intactos.

Condiciones de alcance

Además de comprobar que una expresión es igual (o no igual) a otra expresión, puedes crear condiciones que comprueben si una expresión se encuentra dentro de un rango determinado. Este tipo de condición es habitual cuando se trabaja con datos numéricos o temporales. Considera la siguiente consulta:

mysql> SELECT customer_id, rental_date
    -> FROM rental
    -> WHERE rental_date < '2005-05-25';
+-------------+---------------------+
| customer_id | rental_date         |
+-------------+---------------------+
|         130 | 2005-05-24 22:53:30 |
|         459 | 2005-05-24 22:54:33 |
|         408 | 2005-05-24 23:03:39 |
|         333 | 2005-05-24 23:04:41 |
|         222 | 2005-05-24 23:05:21 |
|         549 | 2005-05-24 23:08:07 |
|         269 | 2005-05-24 23:11:53 |
|         239 | 2005-05-24 23:31:46 |
+-------------+---------------------+
8 rows in set (0.00 sec)

Esta consulta encuentra todos los alquileres de películas anteriores al 25 de mayo de 2005. Además de especificar un límite superior para la fecha de alquiler, también puedes especificar un intervalo inferior:

mysql> SELECT customer_id, rental_date
    -> FROM rental
    -> WHERE rental_date <= '2005-06-16'
    ->   AND rental_date >= '2005-06-14';
+-------------+---------------------+
| customer_id | rental_date         |
+-------------+---------------------+
|         416 | 2005-06-14 22:53:33 |
|         516 | 2005-06-14 22:55:13 |
|         239 | 2005-06-14 23:00:34 |
|         285 | 2005-06-14 23:07:08 |
|         310 | 2005-06-14 23:09:38 |
|         592 | 2005-06-14 23:12:46 |
...
|         148 | 2005-06-15 23:20:26 |
|         237 | 2005-06-15 23:36:37 |
|         155 | 2005-06-15 23:55:27 |
|         341 | 2005-06-15 23:57:20 |
|         149 | 2005-06-15 23:58:53 |
+-------------+---------------------+
364 rows in set (0.00 sec)

Esta versión de la consulta recupera todas las películas alquiladas el 14 ó 15 de junio de 2005.

El operador entre

Cuando tengas un límite superior y otro inferior para tu rango, puedes optar por utilizar una única condición que utilice el operador between en lugar de utilizar dos condiciones separadas, como en:

mysql> SELECT customer_id, rental_date
    -> FROM rental
    -> WHERE rental_date BETWEEN '2005-06-14' AND '2005-06-16';
+-------------+---------------------+
| customer_id | rental_date         |
+-------------+---------------------+
|         416 | 2005-06-14 22:53:33 |
|         516 | 2005-06-14 22:55:13 |
|         239 | 2005-06-14 23:00:34 |
|         285 | 2005-06-14 23:07:08 |
|         310 | 2005-06-14 23:09:38 |
|         592 | 2005-06-14 23:12:46 |
...
|         148 | 2005-06-15 23:20:26 |
|         237 | 2005-06-15 23:36:37 |
|         155 | 2005-06-15 23:55:27 |
|         341 | 2005-06-15 23:57:20 |
|         149 | 2005-06-15 23:58:53 |
+-------------+---------------------+
364 rows in set (0.00 sec)

Cuando utilices el operador between, debes tener en cuenta un par de cosas. Siempre debes especificar primero el límite inferior del intervalo (después de between) y después el límite superior del intervalo (después de and). Esto es lo que ocurre si por error especificas primero el límite superior:

mysql> SELECT customer_id, rental_date
    -> FROM rental
    -> WHERE rental_date BETWEEN '2005-06-16' AND '2005-06-14';
Empty set (0.00 sec)

Como puedes ver, no se devuelve ningún dato. Esto se debe a que el servidor está, en efecto, generando dos condiciones a partir de tu única condición utilizando los operadores <= y >=, como en:

SELECT customer_id, rental_date
    -> FROM rental
    -> WHERE rental_date >= '2005-06-16' 
    ->   AND rental_date <= '2005-06-14';
Empty set (0.00 sec)

Como es imposible tener una fecha que sea a la vez mayor que el 16 de junio de 2005 y menor que el 14 de junio de 2005, la consulta devuelve un conjunto vacío. Esto me lleva al segundo escollo al utilizar between, que es recordar que tus límites superior e inferior son inclusivos, lo que significa que los valores que proporciones están incluidos en los límites del rango. En este caso, quiero devolver todas las películas alquiladas el 14 ó 15 de junio, así que especifico 2005-06-14 como límite inferior del rango y 2005-06-16 como límite superior. Como no estoy especificando el componente temporal de la fecha, la hora por defecto es medianoche, así que el rango efectivo es 2005-06-14 00:00:00 a 2005-06-16 00:00:00, que incluirá cualquier alquiler realizado el 14 o el 15 de junio.

Además de fechas, también puedes crear condiciones para especificar rangos numéricos. Los rangos numéricos son bastante fáciles de entender, como demuestra lo siguiente:

mysql> SELECT customer_id, payment_date, amount
    -> FROM payment
    -> WHERE amount BETWEEN 10.0 AND 11.99;
+-------------+---------------------+--------+
| customer_id | payment_date        | amount |
+-------------+---------------------+--------+
|           2 | 2005-07-30 13:47:43 |  10.99 |
|           3 | 2005-07-27 20:23:12 |  10.99 |
|          12 | 2005-08-01 06:50:26 |  10.99 |
|          13 | 2005-07-29 22:37:41 |  11.99 |
|          21 | 2005-06-21 01:04:35 |  10.99 |
|          29 | 2005-07-09 21:55:19 |  10.99 |
...
|         571 | 2005-06-20 08:15:27 |  10.99 |
|         572 | 2005-06-17 04:05:12 |  10.99 |
|         573 | 2005-07-31 12:14:19 |  10.99 |
|         591 | 2005-07-07 20:45:51 |  11.99 |
|         592 | 2005-07-06 22:58:31 |  11.99 |
|         595 | 2005-07-31 11:51:46 |  10.99 |
+-------------+---------------------+--------+
114 rows in set (0.01 sec)

Todos los pagos entre 10 y 11,99 $ se devuelven. De nuevo, asegúrate de especificar primero el importe más bajo.

Rangos de cadenas

Aunque los rangos de fechas y números son fáciles de entender, también puedes crear condiciones que busquen rangos de cadenas, que son un poco más difíciles de visualizar. Supongamos, por ejemplo, que buscas clientes cuyo apellido se encuentre dentro de un intervalo. Aquí tienes una consulta que devuelve los clientes cuyo apellido está comprendido entre FA y FR:

mysql> SELECT last_name, first_name
    -> FROM customer
    -> WHERE last_name BETWEEN 'FA' AND 'FR';
+------------+------------+
| last_name  | first_name |
+------------+------------+
| FARNSWORTH | JOHN       |
| FENNELL    | ALEXANDER  |
| FERGUSON   | BERTHA     |
| FERNANDEZ  | MELINDA    |
| FIELDS     | VICKI      |
| FISHER     | CINDY      |
| FLEMING    | MYRTLE     |
| FLETCHER   | MAE        |
| FLORES     | JULIA      |
| FORD       | CRYSTAL    |
| FORMAN     | MICHEAL    |
| FORSYTHE   | ENRIQUE    |
| FORTIER    | RAUL       |
| FORTNER    | HOWARD     |
| FOSTER     | PHYLLIS    |
| FOUST      | JACK       |
| FOWLER     | JO         |
| FOX        | HOLLY      |
+------------+------------+
18 rows in set (0.00 sec)

Aunque hay cinco clientes cuyo apellido empieza por FR, no se incluyen en los resultados, ya que un nombre como FRANKLIN está fuera del rango. Sin embargo, podemos recoger a cuatro de los cinco clientes ampliando el rango derecho a FRB:

mysql> SELECT last_name, first_name
    -> FROM customer
    -> WHERE last_name BETWEEN 'FA' AND 'FRB';
+------------+------------+
| last_name  | first_name |
+------------+------------+
| FARNSWORTH | JOHN       |
| FENNELL    | ALEXANDER  |
| FERGUSON   | BERTHA     |
| FERNANDEZ  | MELINDA    |
| FIELDS     | VICKI      |
| FISHER     | CINDY      |
| FLEMING    | MYRTLE     |
| FLETCHER   | MAE        |
| FLORES     | JULIA      |
| FORD       | CRYSTAL    |
| FORMAN     | MICHEAL    |
| FORSYTHE   | ENRIQUE    |
| FORTIER    | RAUL       |
| FORTNER    | HOWARD     |
| FOSTER     | PHYLLIS    |
| FOUST      | JACK       |
| FOWLER     | JO         |
| FOX        | HOLLY      |
| FRALEY     | JUAN       |
| FRANCISCO  | JOEL       |
| FRANKLIN   | BETH       |
| FRAZIER    | GLENDA     |
+------------+------------+
22 rows in set (0.00 sec)

Para trabajar con rangos de cadenas, necesitas conocer el orden de los caracteres dentro de tu juego de caracteres (el orden en que se clasifican los caracteres dentro de un juego de caracteres se denomina cotejo).

Condiciones de afiliación

En algunos casos, no restringirás una expresión a un único valor o rango de valores, sino a un conjunto finito de valores. Por ejemplo, puede que quieras localizar todas las películas que tengan una calificación de 'G' o 'PG':

mysql> SELECT title, rating
    -> FROM film
    -> WHERE rating = 'G' OR rating = 'PG';
+---------------------------+--------+
| title                     | rating |
+---------------------------+--------+
| ACADEMY DINOSAUR          | PG     |
| ACE GOLDFINGER            | G      |
| AFFAIR PREJUDICE          | G      |
| AFRICAN EGG               | G      |
| AGENT TRUMAN              | PG     |
| ALAMO VIDEOTAPE           | G      |
| ALASKA PHANTOM            | PG     |
| ALI FOREVER               | PG     |
| AMADEUS HOLY              | PG     |
...
| WEDDING APOLLO            | PG     |
| WEREWOLF LOLA             | G      |
| WEST LION                 | G      |
| WIZARD COLDBLOODED        | PG     |
| WON DARES                 | PG     |
| WONDERLAND CHRISTMAS      | PG     |
| WORDS HUNTER              | PG     |
| WORST BANGER              | PG     |
| YOUNG LANGUAGE            | G      |
+---------------------------+--------+
372 rows in set (0.00 sec)

Aunque esta cláusula where (dos condiciones or'd juntas) no era demasiado tediosa de generar, imagínate si el conjunto de expresiones contuviera 10 ó 20 miembros. Para estas situaciones, puedes utilizar en su lugar el operador in:

SELECT title, rating
FROM film
WHERE rating IN ('G','PG');

Con el operador in, puedes escribir una única condición sin importar cuántas expresiones haya en el conjunto.

Utilizar subconsultas

Además de escribir tu propio conjunto de expresiones, como ('G','PG'), puedes utilizar una subconsulta para generar un conjunto sobre la marcha. Por ejemplo, si puedes suponer que cualquier película cuyo título incluya la cadena 'PET' sería segura para ver en familia, podrías ejecutar una subconsulta en la tabla film para recuperar todas las clasificaciones asociadas a estas películas y, a continuación, recuperar todas las películas que tengan alguna de estas clasificaciones:

mysql> SELECT title, rating
    -> FROM film
    -> WHERE rating IN (SELECT rating FROM film WHERE title LIKE '%PET%');
+---------------------------+--------+
| title                     | rating |
+---------------------------+--------+
| ACADEMY DINOSAUR          | PG     |
| ACE GOLDFINGER            | G      |
| AFFAIR PREJUDICE          | G      |
| AFRICAN EGG               | G      |
| AGENT TRUMAN              | PG     |
| ALAMO VIDEOTAPE           | G      |
| ALASKA PHANTOM            | PG     |
| ALI FOREVER               | PG     |
| AMADEUS HOLY              | PG     |
...
| WEDDING APOLLO            | PG     |
| WEREWOLF LOLA             | G      |
| WEST LION                 | G      |
| WIZARD COLDBLOODED        | PG     |
| WON DARES                 | PG     |
| WONDERLAND CHRISTMAS      | PG     |
| WORDS HUNTER              | PG     |
| WORST BANGER              | PG     |
| YOUNG LANGUAGE            | G      |
+---------------------------+--------+
372 rows in set (0.00 sec)

La subconsulta devuelve el conjunto 'G' y 'PG', y la consulta principal comprueba si el valor de la columna rating puede encontrarse en el conjunto devuelto por la subconsulta.

Utilizar no en

A veces quieres ver si una determinada expresión existe dentro de un conjunto de expresiones, y a veces quieres ver si la expresión no existe dentro del conjunto. Para estas situaciones, puedes utilizar el operador not in:

SELECT title, rating
FROM film
WHERE rating NOT IN ('PG-13','R', 'NC-17');

Esta consulta encuentra todas las cuentas que no están clasificadas como 'PG-13','R', o 'NC-17', lo que devolverá el mismo conjunto de 372 filas que las consultas anteriores.

Condiciones de concordancia

Hasta ahora, has conocido las condiciones que identifican una cadena exacta, un rango de cadenas o un conjunto de cadenas; el último tipo de condición se ocupa de las coincidencias parciales de cadenas. Por ejemplo, puedes querer encontrar todos los clientes cuyo apellido empiece por Q. Podrías utilizar una función incorporada para eliminar la primera letra de la columna last_name, como en el siguiente ejemplo:

mysql> SELECT last_name, first_name
    -> FROM customer
    -> WHERE left(last_name, 1) = 'Q';
+-------------+------------+
| last_name   | first_name |
+-------------+------------+
| QUALLS      | STEPHEN    |
| QUINTANILLA | ROGER      |
| QUIGLEY     | TROY       |
+-------------+------------+
3 rows in set (0.00 sec)

Aunque la función incorporada left() hace el trabajo, no te da mucha flexibilidad. En su lugar, puedes utilizar caracteres comodín para construir expresiones de búsqueda, como se demuestra en la siguiente sección.

Utilizar comodines

Cuando busques coincidencias parciales de cadenas, puede que te interese

  • Cadenas que empiezan/terminan con un carácter determinado

  • Cadenas que empiezan/acaban con una subcadena

  • Cadenas que contienen un carácter determinado en cualquier parte de la cadena

  • Cadenas que contienen una subcadena en cualquier parte de la cadena

  • Cadenas con un formato específico, independientemente de los caracteres individuales

Puedes construir expresiones de búsqueda para identificar éstas y muchas otras coincidencias parciales de cadenas utilizando los caracteres comodín que se muestran en la Tabla 4-4.

Tabla 4-4. Caracteres comodín
Carácter comodín Partidos

_

Exactamente un carácter

%

Cualquier número de caracteres (incluido 0)

El carácter de subrayado ocupa el lugar de un único carácter, mientras que el signo de porcentaje puede ocupar el lugar de un número variable de caracteres. Cuando construyas condiciones que utilicen expresiones de búsqueda, utiliza el operador like, como en:

mysql> SELECT last_name, first_name
    -> FROM customer
    -> WHERE last_name LIKE '_A_T%S';
+-----------+------------+
| last_name | first_name |
+-----------+------------+
| MATTHEWS  | ERICA      |
| WALTERS   | CASSANDRA  |
| WATTS     | SHELLY     |
+-----------+------------+
3 rows in set (0.00 sec)

La expresión de búsqueda del ejemplo anterior especifica cadenas que contienen una A en la segunda posición y una T en la cuarta, seguidas de cualquier número de caracteres y terminadas en S. La Tabla 4-5 muestra algunas expresiones de búsqueda más y sus interpretaciones.

Tabla 4-5. Ejemplos de expresiones de búsqueda
Expresión de búsqueda Interpretación

F%

Cadenas que empiezan por F

%t

Cadenas acabadas en t

%bas%

Cadenas que contienen la subcadena 'bas'

_ _t_

Cadenas de cuatro caracteres con una t en la tercera posición

_ _ _-_ _-_ _ _ _

Cadenas de 11 caracteres con guiones en las posiciones cuarta y séptima

Los caracteres comodín funcionan bien para construir expresiones de búsqueda sencillas; sin embargo, si tus necesidades son un poco más sofisticadas, puedes utilizar expresiones de búsqueda múltiples, como se demuestra a continuación:

mysql> SELECT last_name, first_name
    -> FROM customer
    -> WHERE last_name LIKE 'Q%' OR last_name LIKE 'Y%';
+-------------+------------+
| last_name   | first_name |
+-------------+------------+
| QUALLS      | STEPHEN    |
| QUIGLEY     | TROY       |
| QUINTANILLA | ROGER      |
| YANEZ       | LUIS       |
| YEE         | MARVIN     |
| YOUNG       | CYNTHIA    |
+-------------+------------+
6 rows in set (0.00 sec)

Esta consulta busca todos los clientes cuyo apellido empiece por Q o Y.

Utilizar expresiones regulares

Si te parece que los caracteres comodín no proporcionan suficiente flexibilidad, puedes utilizar expresiones regulares para construir expresiones de búsqueda. Una expresión regular es, en esencia, una expresión de búsqueda con esteroides. Si eres nuevo en SQL pero has codificado utilizando lenguajes de programación como Perl, entonces puede que ya estés íntimamente familiarizado con las expresiones regulares. Si nunca has utilizado expresiones regulares, quizá quieras consultar el libro de Jeffrey E. F. Friedl Mastering Regular Expressions (O'Reilly), ya que es un tema demasiado amplio para tratar de abarcarlo en este libro.

Este es el aspecto que tendría la consulta anterior (buscar todos los clientes cuyo apellido empiece por Q o Y) utilizando la implementación MySQL de expresiones regulares:

mysql> SELECT last_name, first_name
    -> FROM customer
    -> WHERE last_name REGEXP '^[QY]';
+-------------+------------+
| last_name   | first_name |
+-------------+------------+
| YOUNG       | CYNTHIA    |
| QUALLS      | STEPHEN    |
| QUINTANILLA | ROGER      |
| YANEZ       | LUIS       |
| YEE         | MARVIN     |
| QUIGLEY     | TROY       |
+-------------+------------+
6 rows in set (0.16 sec)

El operador regexp toma una expresión regular ('^[QY]' en este ejemplo) y la aplica a la expresión del lado izquierdo de la condición (la columna last_name). La consulta contiene ahora una única condición utilizando una expresión regular, en lugar de dos condiciones utilizando caracteres comodín.

Tanto Oracle Database como Microsoft SQL Server también admiten expresiones regulares. Con Oracle Database, utilizarías la función regexp_like en lugar del operador regexp mostrado en el ejemplo anterior, mientras que SQL Server permite utilizar expresiones regulares con el operador like.

Nulo: Esa palabra de cuatro letras

Lo he pospuesto todo lo que he podido, pero ha llegado el momento de abordar un tema que suele recibirse con temor, incertidumbre y miedo: el valor null. null es la ausencia de un valor; antes de que una empleada sea despedida, por ejemplo, su columna end_date de la tabla employee debería ser null. No hay ningún valor que pueda asignarse a la columna end_date que tenga sentido en esta situación. null es un poco escurridizo, sin embargo, ya que hay varios sabores de null:

No aplicable

Como la columna de identificación del empleado para una transacción que tuvo lugar en un cajero automático

Valor aún desconocido

Por ejemplo, cuando no se conoce la identificación federal en el momento de crear una línea de cliente

Valor indefinido

Como cuando se crea una cuenta para un producto que aún no se ha añadido a la base de datos

Nota

Algunos teóricos sostienen que debería haber una expresión diferente para cubrir cada una de estas (y más) situaciones, pero la mayoría de los profesionales estarían de acuerdo en que tener múltiples valores null sería demasiado confuso.

Cuando trabajes con null, debes recordar:

  • Una expresión puede ser nula, pero nunca puede ser igual a nulo.

  • Dos nulos nunca son iguales entre sí.

Para comprobar si una expresión es null, debes utilizar el operador is null, como se demuestra a continuación:

mysql> SELECT rental_id, customer_id
    -> FROM rental
    -> WHERE return_date IS NULL;
+-----------+-------------+
| rental_id | customer_id |
+-----------+-------------+
|     11496 |         155 |
|     11541 |         335 |
|     11563 |          83 |
|     11577 |         219 |
|     11593 |          99 |
...
|     15867 |         505 |
|     15875 |          41 |
|     15894 |         168 |
|     15966 |         374 |
+-----------+-------------+
183 rows in set (0.01 sec)

Esta consulta encuentra todos los alquileres de películas que nunca se devolvieron. Aquí tienes la misma consulta utilizando = null en lugar de is null:

mysql> SELECT rental_id, customer_id
    -> FROM rental
    -> WHERE return_date = NULL;
Empty set (0.01 sec)

Como puedes ver, la consulta se analiza y ejecuta, pero no devuelve ninguna fila. Éste es un error común que cometen los programadores SQL inexpertos, y el servidor de bases de datos no te avisará de tu error, así que ten cuidado cuando construyas condiciones que comprueben null.

Si quieres ver si se ha asignado un valor a una columna, puedes utilizar el operador is not null, como en:

mysql> SELECT rental_id, customer_id, return_date
    -> FROM rental
    -> WHERE return_date IS NOT NULL;
+-----------+-------------+---------------------+
| rental_id | customer_id | return_date         |
+-----------+-------------+---------------------+
|         1 |         130 | 2005-05-26 22:04:30 |
|         2 |         459 | 2005-05-28 19:40:33 |
|         3 |         408 | 2005-06-01 22:12:39 |
|         4 |         333 | 2005-06-03 01:43:41 |
|         5 |         222 | 2005-06-02 04:33:21 |
|         6 |         549 | 2005-05-27 01:32:07 |
|         7 |         269 | 2005-05-29 20:34:53 |
...
|     16043 |         526 | 2005-08-31 03:09:03 |
|     16044 |         468 | 2005-08-25 04:08:39 |
|     16045 |          14 | 2005-08-25 23:54:26 |
|     16046 |          74 | 2005-08-27 18:02:47 |
|     16047 |         114 | 2005-08-25 02:48:48 |
|     16048 |         103 | 2005-08-31 21:33:07 |
|     16049 |         393 | 2005-08-30 01:01:12 |
+-----------+-------------+---------------------+
15861 rows in set (0.02 sec)

Esta versión de la consulta devuelve todos los alquileres devueltos, que son la mayoría de las filas de la tabla (15.861 de 16.044).

Antes de dejar null a un lado durante un tiempo, sería útil investigar otro posible escollo. Supongamos que te han pedido que busques todos los alquileres que no se devolvieron durante los meses de mayo a agosto de 2005. Tu primer instinto podría ser hacer lo siguiente:

mysql> SELECT rental_id, customer_id, return_date
    -> FROM rental
    -> WHERE return_date NOT BETWEEN '2005-05-01' AND '2005-09-01';
+-----------+-------------+---------------------+
| rental_id | customer_id | return_date         |
+-----------+-------------+---------------------+
|     15365 |         327 | 2005-09-01 03:14:17 |
|     15388 |          50 | 2005-09-01 03:50:23 |
|     15392 |         410 | 2005-09-01 01:14:15 |
|     15401 |         103 | 2005-09-01 03:44:10 |
|     15415 |         204 | 2005-09-01 02:05:56 |
...
|     15977 |         550 | 2005-09-01 22:12:10 |
|     15982 |         370 | 2005-09-01 21:51:31 |
|     16005 |         466 | 2005-09-02 02:35:22 |
|     16020 |         311 | 2005-09-01 18:17:33 |
|     16033 |         226 | 2005-09-01 02:36:15 |
|     16037 |          45 | 2005-09-01 02:48:04 |
|     16040 |         195 | 2005-09-02 02:19:33 |
+-----------+-------------+---------------------+
62 rows in set (0.01 sec)

Si bien es cierto que estos 62 alquileres se devolvieron fuera de la ventana de mayo a agosto, si observas detenidamente los datos, verás que todas las filas devueltas tienen una fecha de devolución distinta denull. Pero, ¿qué ocurre con los 183 alquileres que nunca se devolvieron? Se podría argumentar que estas 183 filas tampoco se devolvieron entre mayo y agosto, por lo que también deberían incluirse en el conjunto de resultados. Por tanto, para responder correctamente a la pregunta, tienes que tener en cuenta la posibilidad de que algunas filas contengan un null en la columna return_date:

mysql> SELECT rental_id, customer_id, return_date
    -> FROM rental
    -> WHERE return_date IS NULL
    ->   OR return_date NOT BETWEEN '2005-05-01' AND '2005-09-01';
+-----------+-------------+---------------------+
| rental_id | customer_id | return_date         |
+-----------+-------------+---------------------+
|     11496 |         155 | NULL                |
|     11541 |         335 | NULL                |
|     11563 |          83 | NULL                |
|     11577 |         219 | NULL                |
|     11593 |          99 | NULL                |
...
|     15939 |         382 | 2005-09-01 17:25:21 |
|     15942 |         210 | 2005-09-01 18:39:40 |
|     15966 |         374 | NULL                |
|     15971 |         187 | 2005-09-02 01:28:33 |
|     15973 |         343 | 2005-09-01 20:08:41 |
|     15977 |         550 | 2005-09-01 22:12:10 |
|     15982 |         370 | 2005-09-01 21:51:31 |
|     16005 |         466 | 2005-09-02 02:35:22 |
|     16020 |         311 | 2005-09-01 18:17:33 |
|     16033 |         226 | 2005-09-01 02:36:15 |
|     16037 |          45 | 2005-09-01 02:48:04 |
|     16040 |         195 | 2005-09-02 02:19:33 |
+-----------+-------------+---------------------+
245 rows in set (0.01 sec)

El conjunto de resultados incluye ahora los 62 alquileres que se devolvieron fuera de la ventana de mayo a agosto, junto con los 183 alquileres que nunca se devolvieron, para un total de 245 filas. Cuando trabajes con una base de datos con la que no estés familiarizado, es conveniente que averigües qué columnas de una tabla permiten nulls para que puedas tomar las medidas adecuadas con tus condiciones de filtrado para evitar que se te escapen datos.

Pon a prueba tus conocimientos

Los siguientes ejercicios ponen a prueba tu comprensión de las condiciones del filtro. Consulta las soluciones en el Apéndice B.

Tendrás que consultar el siguiente subconjunto de filas de la tabla payment para los dos primeros ejercicios:

+------------+-------------+--------+--------------------+
| payment_id | customer_id | amount | date(payment_date) |
+------------+-------------+--------+--------------------+
|        101 |           4 |   8.99 | 2005-08-18         |
|        102 |           4 |   1.99 | 2005-08-19         |
|        103 |           4 |   2.99 | 2005-08-20         |
|        104 |           4 |   6.99 | 2005-08-20         |
|        105 |           4 |   4.99 | 2005-08-21         |
|        106 |           4 |   2.99 | 2005-08-22         |
|        107 |           4 |   1.99 | 2005-08-23         |
|        108 |           5 |   0.99 | 2005-05-29         |
|        109 |           5 |   6.99 | 2005-05-31         |
|        110 |           5 |   1.99 | 2005-05-31         |
|        111 |           5 |   3.99 | 2005-06-15         |
|        112 |           5 |   2.99 | 2005-06-16         |
|        113 |           5 |   4.99 | 2005-06-17         |
|        114 |           5 |   2.99 | 2005-06-19         |
|        115 |           5 |   4.99 | 2005-06-20         |
|        116 |           5 |   4.99 | 2005-07-06         |
|        117 |           5 |   2.99 | 2005-07-08         |
|        118 |           5 |   4.99 | 2005-07-09         |
|        119 |           5 |   5.99 | 2005-07-09         |
|        120 |           5 |   1.99 | 2005-07-09         |
+------------+-------------+--------+--------------------+

Ejercicio 4-1

¿Cuál de los identificadores de pago sería devuelto por las siguientes condiciones de filtro?

customer_id <> 5 AND (amount > 8 OR date(payment_date) = '2005-08-23')

Ejercicio 4-2

¿Cuál de los identificadores de pago sería devuelto por las siguientes condiciones de filtro?

customer_id = 5 AND NOT (amount > 6 OR date(payment_date) = '2005-06-19')

Ejercicio 4-3

Construye una consulta que recupere todas las filas de la tabla payments en las que el importe sea 1,98, 7,98 ó 9,98.

Ejercicio 4-4

Construye una consulta que encuentre todos los clientes cuyo apellido contenga una A en la segunda posición y una W en cualquier lugar después de la A.

Get Aprender SQL, 3ª Edición now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.