Cualquier manipulación de los registros tiene que ir siempre condicionada a un determinado filtro que seleccione aquellos con los que queremos trabajar, ya sea para mostrar las filas correspondientes a la condición marcada por el filtro, o bien, para asegurar una modificación o borrado correctos.
La cláusula WHERE siempre trabaja con una serie de operadores que varían dependiendo del tipo de dato que estamos tratando. Es particularmente importante tener claro las condiciones de filtro para, una vez seleccionados, tratar un borrado o una modificación que en algunos casos podría ser irreversible en nuestra base de datos.
Este post forma parte del Tutorial básico de SQL Server cuyo índice completo puedes ver aquí: Tutorial básico de SQL Server
Tipos de filtros para tipos de datos
Comencemos directamente con un ejemplo:
USE [Primera]
GO
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Cuenta = 1
GO
La cláusula WHERE se sitúa a continuación de FROM <tabla o otro objeto>. En el ejemplo anterior indica que se van a seleccionar solamente aquellos registros cuyos clientes tiene cuenta es decir, Cuenta = 1 y descartando los que no, en este caso Cuenta = 0 (o null).
En otro post de esta serie vimos superficialmente los tipos de datos. Veamos ejemplos de filtros comenzando con los tipos de datos numéricos o booleanos.
Numéricos y booleanos
El tipo booleano o bit acabamos de verlo teniendo en cuenta que puede tomar tres valores 0, 1, además de null, si no le hemos puesto restricción de nulos en la creación de la tabla. No se utiliza en este caso el operador de igualdad '=' sino la palabra reservada IS, de la siguiente forma:
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Cuenta IS null
GO

En cuanto a los numéricos, con esta lógica podemos construir un filtro para nuestro único campo tipo INT: Edad:
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Edad = 41
GO
En mi caso selecciona dos registros:

Por supuesto, podemos utilizar el resto de operadores de condición de comparación: ( >, <, >=, <=, <>)
Distinto a 41:
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Edad <> 41
GO
Mayores estrictamente de 41:
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Edad > 41
GO
y su complementario, menores o IGUALES:
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Edad <= 41
GO
Existe otro operador muy útil que sirve para obtener un filtro de rango: BETWEEN. Seleccionemos el rango de edad de "treintañeros":
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Edad BETWEEN 30 AND 39
GO
Se seleccionan los límites, AMBOS INCLUSIVE, entre 30 y (AND) 39

Alfanúmericos
Para los alfanúmericos, char y varchar se puede utilizar también el operador de igualdad. Por ejemplo si queremos seleccionar los clientes de un determinado nombre:
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Nombre = 'Carlos'
Eso sí, al ser alfanumérico el string buscado debe ir entre comillas simples.

Esta igualdad es útil, pero es mucho mas poderosa utilizar LIKE para filtrar cadenas de caracteres en base a caracteres comodín. Es decir expresiones que pueden adaptarse a varios resultados.
Los caracteres comodín son fundamentalmente dos: Guión o barra baja "_" y porcentaje "%". El primero representa cualquier carácter y el segundo cualquier conjunto de caracteres consecutivos. Lo veremos mejor con ejemplos:
Imaginemos que necesitamos averiguar los clientes cuyo nombre comienza por la letra "S". Debemos fijar la letra del comienzo pero en realidad nos interesa cualquier conjunto de caracteres que tenga el nombre a continuación ("%")
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Nombre LIKE 'S%'

Ahora necesitamos los clientes de Madrid. La ciudad o provincia es un caso de dato que debería ir aislado, pero en nuestra base de datos de ejemplo no es así, por lo que tenemos que encontrar ese patrón en el campo dirección.
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Direccion LIKE '%madrid%'
GO
El nombre de la ciudad normalmente va al final pero aseguramos que no haya caracteres tras ellas añadiendo un "%" también al final.

Finalmente vemos que el LIKE también podría aplicarse a numéricos:
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Edad LIKE '3_'
GO
El "_" representa cualquier número, por lo tanto el script anterior vuelve a seleccionar el resultado de edades entre 30 y 39.
Varias condiciones
Suele ser habitual tratar de encontrar registros que cumplan varias condiciones, al mismo tiempo que esas condiciones son de varios tipos de datos.
Intervienen ahora operadores lógicos (fundamentalmente AND y OR) y el operador de negación NOT.
En los ejemplos anteriores podemos necesitar que los clientes que tienen nombres que comienzan por "S" solo necesitemos los que tienen cuenta. Utilizaremos el operador AND para indicarle que se tiene que cumplir una condición Y la siguiente:
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Nombre LIKE 'S%' AND Cuenta = 1
GO
Vemos como descarta un nombre "Sergio" que no tiene Cuenta con nosotros.

Sin embargo en ese mismo ejemplo si cambiamos por OR, se deben cumplir una o las dos condiciones:
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Nombre LIKE 'S%' OR Cuenta = 1
GO
El resultado es Clientes con "S" con y sin cuenta (Vuelve a parecer "Sergio") y clientes con cuenta que tengan "S" o no en el comienzo de su nombre.

Finalmente el Operador NOT niega o selecciona lo contrario a la condición especificada:
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Nombre NOT LIKE '%a' AND
Apellidos NOT LIKE 'B%' AND
Edad NOT BETWEEN 30 AND 49
AND Direccion IS NOT null
GO
En el script anterior no queremos la selección de los clientes cuyo nombre termine por "a", ni los que su apellido empiece por "B" ni los que tengan edad de 30 a 49 y descartando también las direcciones nulas, si existen.

Por último comentar que hay que tener especial cuidado cuando se combinan condiciones de AND y OR. En estos casos las condiciones OR deben ir entre paréntesis porque al poder cumplirse una de ellas, si tenemos un AND , éste se anula . Es decir:
Necesitamos la extracción de clientes de menos de 20 años O mas de 40, pero en ningún caso que empiecen su nombre por "A":
SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Nombre NOT LIKE 'A%' AND
Edad <20 OR Edad >40
GO

SELECT [Nombre]
,[Apellidos]
,[Edad]
,[Direccion]
,[Cuenta]
FROM [dbo].[Clientes]
WHERE Nombre NOT LIKE 'A%' AND
(Edad <20 OR Edad >40)
GO

Otra posibilidad es invertir el orden (en este caso bastaba con seguir el predicado) y que por prioridad determine primero la condición OR y después la condición AND, pero en general suele ser mas entendible con los paréntesis.
Github Copilot
Pues interviene ahora la IA en todo su potencial, porque es ideal para expresar con frases y expresiones los filtros que queremos aplicar en una SELECT.
Eso sí, el prompt tiene que estar muy elaborado, sin ambigüedades y el resultado que nos dé debemos revisarlo exhaustivamente, porque algo aparentemente bien hecho puede dar lugar a resultados erróneos si no hemos tenido precisión en lo solicitado a la IA.
Sobre la tabla Clientes necesito un script: Necesitamos la extracción de clientes de menos de 20 años O mas de 40, pero en ningún caso que empiecen su nombre por "A"
En la petición he puesto exactamente el texto explicativo del párrafo anterior y este es mi resultado:
SELECT [Nombre], [Apellidos], [Edad], [Direccion], [Cuenta]
FROM dbo.Clientes
WHERE (Edad < 20 OR Edad > 40)
AND Nombre NOT LIKE 'A%';
Lo cual lo ha interpretado correctamente.
Vamos con otro ejemplo, referenciando al contenido del campo Direccion:
Primero veamos que tenemos al hacer un SELECT de toda la tabla sin condiciones (al menos yo):

El prompt podría ser:
Un script para: Solamente de lo que tengan cuenta, necesito aquellos clientes que vivan en una calle, no en una avenida, plaza o paseo.
Esto es lo que me ha propuesto y es impreciso porque el prompt que le he indicado así lo era; hay que tener en cuenta que le hemos preguntado por el contenido del campo y eso puede ser difícil de interpretar:
SELECT [Nombre], [Apellidos], [Edad], [Direccion], [Cuenta]
FROM dbo.Clientes
WHERE Cuenta IS NOT NULL
AND LOWER(Direccion) LIKE '%calle%'
AND LOWER(Direccion) NOT LIKE '%avenida%'
AND LOWER(Direccion) NOT LIKE '%av.%'
AND LOWER(Direccion) NOT LIKE '%avda%'
AND LOWER(Direccion) NOT LIKE '%plaza%'
AND LOWER(Direccion) NOT LIKE '%paseo%';
En primer lugar, LOWER es una función integrada de SQL que convierte a minúsculas el campo indicado entre paréntesis. Copilot ha intentado prevenir las mayúsculas y minúsculas por si en el contenido del campo había varias posibilidades: "CAlle", calle, "CALLE" etc.
Eso está bien, pero no ha podido prever que "C/" es, al menos en España, una forma de abreviar "calle". Por tanto esta especificación debe hacerse.
Además "Tener cuenta" para él significa que no sea nulo. No puede saber que nosotros, hemos considerado Cuenta = 1 tenerla; Cuenta = 0 no tenerla y encima hemos puesto "Permitir nulos" en la confección de la tabla. Humanos defectuosos...
Un script para: Solamente de lo que tengan cuenta (Cuenta = 1), aquellos clientes que vivan en una calle. La dirección de una calle, además de la palabra explícita puede ir expresada como "C/", "C/." , "Cl" o "Cl.".

Este si parece un script correcto. Tras pulsar el botón Mantener y después Ejecutar obtenemos el resultado correcto:

Las posibilidades de filtros son infinitas, además de que hay otros operadores mas avanzados. En este artículo están los más usados.
Se recomienda practicar muchos ejemplos. En próximas entradas veremos como se combinan con instrucciones de ordenación y agrupación.
¡Hasta la próxima!