Publicado el 04/05/2004 por Enrique Martínez Montejo. Enlace original
Contenido
- Introducción
- La instrucción SELECT
- Consultas básicas
- Especificar el origen de datos
- Recuperar información de una base de datos externa
- Especificar un alias para el nombre de los campos
- Filtrado y ordenación de resultados
- Omitir registros duplicados: predicados DISTINCT y DISTINCTROW
- Seleccionar un número de registros : predicado TOP
- Criterios de selección
- La cláusula WHERE
- El operador IN
- El operador BETWEEN
- El operador LIKE
- El operador IS NULL
- Agrupamiento de registros
- Las funciones agregadas COUNT, MAX, MIN y SUM
- La cláusula GROUP BY
- La cláusula HAVING
- Ordenar los registros devueltos
- La instrucción WITH OWNERACCESS OPTION
Introducción
Las consultas de selección pueden representar el porcentaje más elevado de consultas SQL que se efectúan a una base de datos, de ahí que cobre un especial protagonismo dentro de las operaciones correspondientes al Lenguaje de Manipulación de Datos (DML) de SQL, y se utilizan mayormente para recuperar, actualizar o eliminar registros de las tablas. Para realizar estas operaciones se pueden utilizar diversas instrucciones SQL, siendo la más utilizada la correspondiente a la instrucción SELECT.
En este capítulo se estudiará la estructura general correspondiente a la instrucción SELECT, dejando para posteriores capítulos el estudio de otros tipos de consultas de selección más complejas, como las que se refieren a la subconsultas, combinaciones, consultas de referencias cruzadas y consultas de unión.
La instrucción SELECT
La instrucción SELECT se utiliza para recuperar la información almacenada en una base de datos, información ésta que es devuelta en forma de conjunto de registros para posteriormente ser manipulados por los distintos objetos de acceso a datos existentes, o bien, para presentar y crear informes a partir de los datos recuperados. En ningún caso puede modificar los datos de la base de datos mediante la instrucción SELECT. La estructura general de la instrucción SELECT es la siguiente:
SELECT lista_campos
FROM nombres_tablas IN nombre_base_datos_externa
WHERE condiciones_búsqueda
GROUP BY lista_campos
HAVING grupo_criterios
ORDER BY lista_campos
WITH OWNERACCESS OPTION
Consultas básicas
La sintaxis básica de la instrucción SELECT es la siguiente:
SELECT * FROM nombre_tabla
Con dicha instrucción recuperaríamos todos los campos de todos los registros de la tabla especificada, debido a que el predicado por defecto de la instrucción SELECT es la palabra clave ALL, por lo que no es necesario especificar dicha palabra clave si nuestra intención es recuperar todos los registros de la tabla.
El mismo resultado se produciría utilizando las siguientes sintaxis:
SELECT ALL * FROM nombre_tabla SELECT [nombre_tabla].* FROM nombre_tabla
El asterisco significa que se desea recuperar todos los campos o columnas de la tabla o tablas especificadas. Si no desea recuperar todas las columnas de la tabla tendrá que especificar los nombres de los campos que se mostrarán separados por comas, apareciendo estos en el orden en que los haya especificado en el consulta de selección:
SELECT IdCliente, [Nombre Cliente] FROM Clientes
Si el nombre del campo incluye un espacio en blanco, deberá de encerrar el nombre de la columna entre corchete [].
Especificar el origen de los datos
Toda instrucción SELECT deberá de tener una cláusula FROM que indicará la tabla o tablas de origen de los registros que se recuperarán.
Si el nombre del campo se encuentra incluido en dos o más tablas de las especificadas en la cláusula FROM, deberá de precederlo con el nombre de la tabla y de un punto, tal y como se muestra en el siguiente ejemplo:
SELECT Facturas.IdCliente, Nombre FROM Clientes, Facturas WHERE Facturas.IdCliente = CIientes.IdCliente
Cuando la cláusula FROM incluye varias tablas, no importa el orden en el que aparecen las mismas.
Recuperar información de una base de datos externa
Al igual que podemos recuperar datos de una tabla de la base de datos actual, también podemos recuperar los registros existentes en una base de datos externa, tanto si es otra base de datos Microsoft Access como si se trata de otro origen de datos distinto al que se pueda conectar el motor de datos Microsoft Jet, como bien pudiera ser una base de datos dBASE, una hoja de cálculo Excel o un simple archivo de texto delimitado.
Para conectarse a un origen de datos externo se utiliza la cláusula IN, que generalmente aparece tras el nombre de la tabla especificada en la cláusula FROM, si se refiere a la tabla de origen, o tras el nombre de la tabla definida en la cláusula INTO, cuando se trata de la tabla de destino incluida en una base de datos externa. Sólo se puede utilizar la cláusula IN para conectarse a una única base de datos externa a la vez.
Tras la cláusula IN deberá de especificar la ruta de acceso al origen de datos, disponiendo de varias sintaxis para especificar la misma dependiendo de que los datos externos se refieran a una tabla de origen o a una tabla de destino.
Dos maneras de especificar una tabla de origen en una base de datos externa Microsoft Access:
SELECT * FROM Clientes
IN ''[C:\Mis documentos\Neptuno.mdb]
SELECT * FROM Clientes IN 'C:\Mis documentos\Neptuno.mdb'
Dos formas distintas de especificar una tabla de destino correspondiente a un archivo de texto:
SELECT * INTO CIientes#txt
IN ''[TEXT;DATABASE=C:\Mis documentos]
FROM Clientes
SELECT * INTO CIientes#txt
IN 'C:\Mis documentos\' 'TEXT;' FROM Clientes
Cualquiera de las dos consultas de creación de tabla anteriores, crearán un nuevo archivo de texto en la ruta especificada con los datos de la tabla Clientes.
Cuando vaya a trabajar con datos externos, es recomendable adjuntar la tabla a la base de datos en lugar de especificar una cláusula IN que haga referencia a la misma tabla, a fin de obtener un mejor rendimiento a la hora de ejecutar una consulta SQL de selección.
Especificar un alias para el nombre de los campos
Como ya se ha indicado anteriormente, al recuperar los datos de una tabla mediante la instrucción SELECT, estos son devueltos en la típica estructura relacional de filas y columnas, teniendo los campos los mismos nombres que los especificados en la tabla de procedencia. Si desea asignar un nombre de columna diferente o alternativo a fin de hacer más descriptivo el campo recuperado, deberá de indicarlo mediante la cláusula AS, la cual se incluirá a continuación del nombre del campo.
El siguiente ejemplo utiliza el título [Num Seguridad Social] como alternativa al campo de la tabla NSS:
SELECT NSS AS [Num Seguridad Social] FROM Empleados
Filtrado y ordenación de los resultados
Con la consulta básica de selección, lo único que podemos hacer es recuperar todos los campos y registros de la tabla especificada, y en el mismo orden en el que fueron agregados a la misma. Si deseamos recuperar ciertos registros que cumplan con una condición dada, así como el orden en el que aparecerán en la consulta, el lenguaje SQL nos proporciona varias palabras clave que actuarán de predicados de la instrucción SELECT, como también cláusulas opcionales que nos permitirán depurar y ordenar el conjunto de resultados.
Un predicado es una cláusula SQL que califica una instrucción SELECT, y siempre deberán de declararse antes de la lista de campos. Los predicados pueden restringir a un más el conjunto de registros devueltos, filtrando en algunos casos los datos duplicados que puedan existir.
El único predicado utilizado hasta ahora es el que actúa como predeterminado, el cual recae sobre la palabra clave ALL, lo que significa que no será necesario especificarla si deseamos recuperar todos los registros de la tabla. A continuación pasaremos a estudiar con mayor detalle los predicados y cláusulas que más se utilizan con la instrucción SELECT.
Omitir registros duplicados: predicados DISTINCT y DISTINCTROW
Mediante la palabra clave DISTINCT evitaremos que en la consulta de selección aparezcan registros con datos duplicados en una columna seleccionada, apareciendo únicamente un solo registro de todos aquellos que se encuentren duplicados.
Por ejemplo, podemos tener una tabla de Facturas donde aparecerán los identificadores de clientes que nos han efectuado alguna compra. Si algún cliente nos ha realizado compras en varias ocasiones, tendrá tantas facturas como compras haya efectuado, por lo que el identificador del cliente se repetirá en varios registros de la tabla Facturas.
Si deseamos consultar los clientes que al menos nos han realizado una compra, ejecutaríamos la siguiente consulta de selección:
SELECT DISTINCT [IdCliente] FROM Facturas
Si omitiéramos DISTINCT, la consulta devolvería todas las facturas existentes en la tabla, por lo que no obtendríamos el resultado esperado. Por tanto, eliminando los registros que contengan el mismo identificador de cliente, obtendremos los clientes que alguna vez nos han efectuado una compra.
Para que la consulta de selección sea efectiva, deberá de especificar un único campo en la consulta de selección, campo este que se corresponderá con la columna de la tabla donde aparecen datos duplicados. Si especifica más de un campo, la consulta omitirá los registros cuyos valores se encuentren duplicados en todos los campos especificados, porque el solo hecho de que un campo de los especificados no aparezca duplicado, es más que suficiente para que la consulta devuelva todos los registros existentes en la tabla, tal y como se puede comprobar ejecutando la siguiente consulta de selección, la cual devolverá todos los registros de la tabla Facturas, ya que es poco probable que todos los clientes hayan efectuado sus compras en la misma fecha:
SELECT DISTINCT IdCliente, [Fecha Factura] FROM Facturas
El conjunto de registros devueltos estará ordenado ascendentemente de manera predeterminada. Si el resultado de la consulta DISTINCT lo utiliza para abrir un objeto del tipo Recordset, éste será de sólo lectura, lo que significa que no se podrá actualizar y no reflejará las actualizaciones efectuadas posteriormente por otros usuarios.
A diferencia del predicado DISTINCT, que se basa en la duplicidad de campos individuales, el predicado DISTINCTROW se basa en filas enteras de registros, siempre y cuando se utilice en una consulta de selección que afecte a varias tablas, lo que se conoce como una consulta de combinación, de esta manera, no solo se omiten los campos duplicados, sino que se omitirán los registros completos que se encuentren duplicados en la combinación efectuada.
Por ejemplo, si aparte de conocer el identificador del cliente, deseamos conocer también los nombres de los clientes que al menos han efectuado una compra, deberíamos de combinar las tablas Clientes y Facturas mediante el campo común IdCliente:
SELECT DISTINCTROW Clientes.Nombre, Clientes.IdCliente
FROM Clientes
INNER JOIN Facturas ON Clientes.IdCliente = Facturas.IdCliente
Si se omite el predicado DISTINCTROW, la consulta devolvería todos los registros de la tabla Facturas, por lo que los nombres de los clientes aparecerían duplicados, dado que un cliente puede tener varias facturas. DISTINCTROW es efectivo cuando se utiliza únicamente para seleccionar campos de algunas, pero no todas, de las tablas utilizadas en la consulta, y no se tiene en cuenta el predicado si la consulta afecta solo a una tabla. De manera predeterminada, el conjunto de registros devueltos no tiene un orden determinado en concreto.
Seleccionar un número de registros: predicado TOP
La palabra clave TOP es utilizada para devolver un cierto número de registros que figuren en la parte superior o inferior de un intervalo especificado por una cláusula ORDER BY. De no especificarse ninguna cláusula ORDER BY, se recuperará el número de registros indicados que figuren almacenados al inicio de la tabla, que es el valor por defecto, o un número arbitrario de registros que satisfagan lo indicado en una cláusula WHERE.
Si por ejemplo, queremos efectuar una consulta para conocer las 10 facturas de mayor importe, escribiríamos lo siguiente:
SELECT TOP 10 * FROM Facturas ORDER BY [Total Factura] DESC
El predicado TOP no selecciona entre valores iguales. Si en el ejemplo anterior, las facturas con orden 10 y 11 tienen el mismo importe, la consulta devolverá 11 registros. Si en lugar de seleccionar un número de registros deseamos recuperar un porcentaje determinado de registros que figuren en la parte superior o inferior del intervalo especificado en la cláusula ORDER BY, deberemos de especificar la palabra clave PERCENT.
Continuando con el ejemplo anterior, si en lugar de seleccionar las 10 facturas de mayor importe, deseamos conocer el 10 por ciento de las facturas con mayor importe, ejecutaríamos la siguiente consulta:
SELECT TOP 10 PERCENT * FROM Facturas ORDER BY [Total Factura] DESC
Criterios de selección
Anteriormente hemos visto la forma de recuperar todos los registros, o un número limitado de ellos, según lo indicado en el predicado utilizado. A continuación vamos a estudiar la forma de filtrar los registros para recuperar únicamente aquellos que cumplan con una condición especificada mediante una cláusula, que recordamos que es la condición de modificación utilizada para definir los datos que deseamos seleccionar o manipular.
Pero antes de entrar en detalle con las distintas cláusulas existentes, conviene destacar tres detalles a tener en cuenta a la hora de especificar el criterio de selección, y que son las siguientes:
- No es posible especificar una condición de búsqueda en campos del tipo texto largo o Memo.
- Si la condición de búsqueda se refiere a valores contenidos en campos de la tabla del tipo alfanumérico, deberá encerrar entre comillas simples dicha condición de búsqueda.
- Por último, se encuentra la consulta que genera más número de dudas e inquietudes entre los usuarios de los distintos grupos de noticias de Microsoft donde participo, y es la que se refiere a especificar el criterio de búsqueda cuando el campo es del tipo Fecha.
Aunque hay muchos formatos y modos de especificar una fecha, siempre (y lo recalco de nuevo, SIEMPRE) se deberá de utilizar el formato de fecha de Estados Unidos (mes/día/año), aunque no utilice la versión norteamericana del motor de base de datos Microsoft Jet, a fin de asegurarnos un buen uso del criterio de búsqueda indicado. Asimismo, deberá de incluir el literal de fecha encerrado entre el símbolo del signo numérico (#), tal y como se muestra en el siguiente ejemplo.
Si deseamos buscar los registros con fecha 19 de marzo de 2004 en una base de datos de España, Francia o Alemania, deberemos ejecutar la siguiente consulta SQL de selección:
SELECT * FROM Facturas WHERE [Fecha Factura] = #3/19/04#
Si utiliza Microsoft Visual Basic, también puede especificar como criterio de búsqueda, el resultado devuelto por la función DateValue, la cual tiene en cuenta la configuración regional establecida en Microsoft Windows. De esta manera, la consulta para una base de datos de Estados Unidos sería:
SELECT * FROM Facturas WHERE [Fecha Factura] = DateValue('3/19/04')
Y para una base de datos de España utilizaríamos la sintaxis siguiente:
SELECT * FROM Facturas WHERE [Fecha Factura] = DateValue('19/3/04')
La siguiente tabla muestra los formatos válidos para especificar un criterio de fecha correspondiente al 19 de marzo de 2004. Cualquier otro formato distinto que se utilice no servirá para recuperar los registros deseados:
#3/19/04# | #3-19-04# | |
#19/3/2004# | * | #19-3-2004# |
#3/19/2004# | #3-19-2004# | |
#2004/3/19# | #2004-3-19# | |
#03/19/04# | #03-19-04# | |
#19/03/2004# | * | #19-03-2004# |
#03/19/2004# | #03-19-2004# | |
#2004/03/19# | #2004-03-19# |
* Estos formatos se pueden utilizar siempre y cuando la fecha correspondiente al día del mes sea superior al día 12; si es inferior, el motor Jet tomará el valor del día como si fuera el valor del mes.
Vuelvo a insistir que, si no desea memorizar todos los formatos de fecha posibles, recuerde al menos el formato estándar de Estados Unidos: #mes/día/año# (#03/19/04#).
La cláusula WHERE
La cláusula WHERE se utiliza para especificar qué registros de las tablas indicados en la cláusula FROM cumplen con las condiciones enumeradas, y por tanto, se deberán de incluir en el resultado devuelto por la instrucción SELECT. Si no se especifica una cláusula WHERE, la consulta devolverá todos los registros existentes en la tabla.
Si especifica dos o más tablas en la cláusula FROM de su consulta de selección y no incluye una cláusula WHERE o JOIN, la consulta generará un producto cartesiano de las tablas. También deberá de tener en cuenta no olvidar incluir una cláusula JOIN cuando realice combinaciones SQL entre múltiples tablas, porque si bien la cláusula WHERE puede realizar una tarea similar, el incluir esta última cláusula daría lugar a que el conjunto de datos resultante de la selección no fuera actualizable.
La palabra clave WHERE es opcional, pero si se especifica, necesariamente tendrá que aparecer después de la cláusula FROM, tal y como se muestra en los siguientes ejemplos:
Seleccionamos todos los empleados del departamento de Ventas
SELECT * FROM Empleados WHERE Departamento = 'Ventas'
Seleccionamos todos los empleados cuya edad este comprendida entre 20 y 30 años
SELECT * FROM Empleados WHERE Edad BETWEEN 20 AND 30
Cuando existen dos condiciones para evaluar, el resultado de la operación varía en función del operador lógico utilizado. El lenguaje SQL de Microsoft Jet soporta los siguientes operadores lógicos: AND, OR, XOR, EQV, IMP, IS y NOT. A excepción de los dos últimos, todos los demás presentan la siguiente sintaxis:
<expresión1> operador <expresión2>
La siguiente tabla muestra los diferentes resultados posibles en función del resultado individual de cada expresión y del operador lógico utilizado:
<expresión1> | Operador | <expresión2> | Resultado |
Verdad | AND | Falso | Falso |
Verdad | AND | Verdad | Verdad |
Falso | AND | Verdad | Falso |
Falso | AND | Falso | Falso |
Verdad | OR | Falso | Verdad |
Verdad | OR | Verdad | Verdad |
Falso | OR | Verdad | Verdad |
Falso | OR | Falso | Falso |
Verdad | XOR | Verdad | Falso |
Verdad | XOR | Falso | Verdad |
Falso | XOR | Verdad | Verdad |
Falso | XOR | Falso | Falso |
Verdad | EQV | Verdad | Verdad |
Verdad | EQV | Falso | Falso |
Falso | EQV | Verdad | Falso |
Falso | EQV | Falso | Verdad |
Verdad | IMP | Verdad | Verdad |
Verdad | IMP | Falso | Falso |
Verdad | IMP | Null | Null |
Falso | IMP | Verdad | Verdad |
Falso | IMP | Falso | Verdad |
Falso | Imp | Null | Verdad |
Null | IMP | Verdad | Verdad |
Null | IMP | Falso | Null |
Null | IMP | Null | Null |
Si anteponemos el operador NOT a cualquiera de los condiciones especificadas anteriormente, el resultado de la operación será el contrario al devuelto sin el operador NOT.
El operador IS se emplea para comparar dos variables del tipo objeto. Este operador devuelve Verdadero si los dos objetos son iguales:
<Objeto> IS <Objeto>
A continuación se muestran varias consultas de selección donde el resultado se verá alterado por las expresiones evaluadas mediante los correspondientes operadores lógicos.
Seleccionamos todos los empleados cuya edad este comprendida entre 20 y 30 años:
SELECT * FROM Empleados WHERE Edad > 19 AND Edad < 31
Seleccionamos todos los empleados comprendidos en un intervalo de Edad cuyo Sueldo sea superior a 1.000,00 euros:
SELECT * FROM Empleados WHERE (Edad > 19 AND Edad < 31) AND Sueldo > 1000.00
Seleccionamos todos los empleados que se encuentren casados:
SELECT * FROM Empleados WHERE NOT Estado = 'Soltero'
Seleccionamos todos los empleados cuyo sueldo esté comprendido exactamente entre 1000 y 2000 euros, o se encuentren en Madrid y estén casados:
SELECT * FROM Empleados WHERE (Sueldo > 999.99 AND Sueldo < 2000.01) OR (Ciudad = 'Madrid' AND Estado = 'Casado')
El operador IN
Podemos utilizar el operador IN para recuperar aquellos registros donde el valor del campo se corresponda con un valor de los incluidos en una lista de valores. Si la expresión coincide con un valor de la lista, el operador IN devolverá Verdadero; en cualquier otro caso, retornará Falso. La sintaxis del operador IN es la siguiente:
expresión [NOT] IN (valor1, valor2, ..., valorN)
Por ejemplo, deseamos recuperar todos aquellos empleados que residan en las provincias de Madrid, Barcelona y Jaén. Para ello escribiremos la siguiente consulta SQL de selección:
SELECT * FROM Empleados WHERE Provincia IN ('Madrid', 'Barcelona', 'Jaén')
Si anteponemos el operador lógico opcional NOT, lo que haremos será verificar lo contrario del operador IN. Si deseamos seleccionar los empleados que no residen en las provincias anteriormente mencionadas, escribiremos:
SELECT * FROM Empleados WHERE Provincia NOT IN ('Madrid', 'Barcelona', 'Jaén')
El operador BETWEEN
Si deseamos especificar un intervalo concreto de valores de un campo, en lugar de utilizar operadores lógicos podemos emplear el operador BETWEEN, cuya sintaxis es la siguiente:
campo [NOT] BETWEEN valor1 AND valor2
En este supuesto la consulta de selección devolvería los registros que contengan en campo un valor comprendido en el intervalo valor1 y valor2, ambos inclusive. Si anteponemos la condición opcional NOT, devolverá aquellos valores no incluidos en el intervalo.
Debo de hacer la salvedad para indicar que el lenguaje SQL de Microsoft Jet permite que valor1 sea mayor que valor2 en la sintaxis del operador BETWEEN, lo que no sucede en el SQL de ANSI, donde valor1 debe ser igual o menor que valor2. Esto puede representar un obstáculo si utiliza orígenes de datos ODBC, en cuyo caso siempre deberá especificar el intervalo de valor1 a valor2.
Algunos de los ejemplos anteriormente vistos, también se podrían haber escrito de la siguiente manera.
Seleccionamos todos los empleados cuya edad este comprendida entre 30 y 20 años:
SELECT * FROM Empleados WHERE Edad BETWEEN 30 AND 20
Seleccionamos todos los empleados comprendidos en un intervalo de Edad cuyo Sueldo sea superior a 1.000 euros:
SELECT * FROM Empleados WHERE (Edad BETWEEN 20 AND 30) AND Sueldo > 1000.00
Seleccionamos todos los empleados cuyo sueldo esté comprendido exactamente entre 1000 y 2000 euros, o se encuentren en Madrid y estén casados:
SELECT * FROM Empleados WHERE (Sueldo BETWEEN 1000.00 AND 2000.00) OR (Ciudad = 'Madrid' AND Estado = 'Casado')
Seleccionamos todos los empleados residentes en Madrid:
SELECT * FROM Empleados WHERE Ciudad = IIF(CPostal BETWEEN 28000 AND 28999, 'Madrid',")
El operador LIKE
El operador LIKE se utiliza para comparar una expresión de cadena con un modelo especificado, recuperándose únicamente aquellos registros que satisfagan el criterio de caracteres indicados en el modelo de coincidencia. La sintaxis del operador LIKE es la siguiente:
expresión LIKE modelo
Expresión se refiere al nombre del campo de la tabla que se desea comparar. Modelo puede referirse a una expresión de cadena formada por un valor completo o por una lista de caracteres comodín permitidos que se indicarán posteriormente.
El SQL de Microsoft Jet es compatible con caracteres comodín del SQL de ANSI y con caracteres comodín específicos de Microsoft Jet para ser utilizados junto con el operador LIKE. El uso de caracteres comodín ANSI y Microsoft Jet se excluyen mutuamente, por lo que podrá utilizar un conjunto o el otro, pero nunca mezclarlos.
Es importante que conozca el conjunto de caracteres comodín que puede utilizar, sobre todo cuando utiliza Jet 4.x y el proveedor Microsoft OLE DB para Jet, dado que en este caso únicamente estarán disponibles los caracteres comodín del SQL de ANSI. Si intenta utilizar caracteres comodín del SQL de ANSI mediante la interfaz de usuario de Microsoft Access o con la biblioteca de DAO, los caracteres comodín serán interpretados como caracteres literales. Sucede lo mismo a la inversa, cuando utiliza Jet 4.x y el proveedor Microsoft OLE DB para Jet, ya que en este caso se interpretarán como literales los caracteres comodín que pertenezcan al conjunto de caracteres comodín del SQL de Microsoft Jet.
Esto se debe a que el SQL del motor de base de datos Microsoft Jet suele ajustarse a la norma ANSI-89 de nivel1, por lo que ciertas características del SQL de ANSI no se encuentran implementadas en el SQL de Microsoft Jet. A partir del lanzamiento de Microsoft Jet versión 4.x, el proveedor Microsoft OLE DB para Jet introdujo más sintaxis ANSI-92 SQL, de ahí que el SQL de Microsoft Jet se ajuste más a la norma estándar cuando se utiliza únicamente con el proveedor Microsoft OLE DB para Jet. Igualmente sucede a la inversa, el SQL de Microsoft Jet incluye palabras reservadas y características que no están permitidas en el SQL de ANSI.
Los modelos de coincidencia incorporados proporcionan una herramienta versátil para realizar comparaciones de cadenas. En la siguiente tabla se muestran los caracteres comodín que puede utilizar con el operador LIKE, el número de dígitos o cadenas que comparan y las diferencias existentes entre los dos tipos de lenguajes SQL, en cuanto a caracteres comodín se refiere:
Caracteres coincidentes | Caracteres comodín | |
SQL de Microsoft Jet | SQL de ANSI | |
Cualquier carácter | ? | _ (signo de subrayado) |
Cero o más caracteres | * | % |
Cualquier número | # | |
Cualquier carácter que esté en listaCaracteres | [listaCaracteres] | |
Cualquier carácter que no esté en listaCaracteres | [!listaCaracteres] |
En el argumento listaCaracteres puede indicar un grupo de uno o más caracteres entre corchetes ([ ]) para que coincida con cualquier carácter de los indicados en la expresión especificada, teniendo la posibilidad de incluir prácticamente cualquier carácter incluido en el juego de caracteres ANSI, incluidos los dígitos, haciendo aquí una salvedad, que más tarde se comentará, en lo que respecta al carácter de signo numérico (#) cuando se utiliza Jet 4.x y el proveedor Microsoft OLE DB para Jet.
Asimismo, en listaCaracteres puede indicar un intervalo de valores, separando los límites superior e inferior del intervalo mediante un guion (-). Por ejemplo, especificar en modelo el intervalo ‘[a-z]’, hace que se genere una coincidencia si la posición del carácter de expresión contiene cualquier letra, mayúscula o minúscula, comprendida entre A y Z. Dentro de los corchetes se pueden especificar múltiples intervalos sin ningún tipo de restricción. Por ejemplo, el modelo ‘[a-Z0-9]’ hace que haya una coincidencia con cualquier carácter alfanumérico indicado en la posición correspondiente de la cadena utilizada en expresión.
Debe de tener en cuenta que los intervalos de caracteres deben de aparecer en orden ascendente (A-Z, 0-100). ‘[A-Z]’ es un intervalo válido, no así ‘[Z-A]’.
Especificar los corchetes separados por un espacio en blanco ([ ]) será considerado como una cadena de longitud cero («»), por lo que únicamente servirá para recuperar aquellos registros donde expresión coincida con una cadena de longitud cero, que es distinto de un valor NULL. En este último caso, los registros se podrán recuperar mediante el operador IS NULL, como se estudiará en un apartado posterior.
A continuación se exponen algunos ejemplos que muestran el uso del operador LIKE. Dado que este manual va dirigido fundamentalmente a aquellos usuarios que trabajan mediante ADO y ADO .NET, los ejemplos utilizaran los caracteres comodín permitidos por el proveedor Microsoft OLE DB para Jet, y que son los que se corresponden con el SQL de ANSI:
Queremos conocer todos los apellidos de los Empleados que comienzan por la leta M:
SELECT * FROM Empleados WHERE Apellidos LIKE 'M%'
Deseamos seleccionar todos los clientes cuyo nombre empiece por la letra P, y la segunda letra esté comprendida entre la C y M, seguido por último de cualquier cadena:
SELECT * FROM Clientes WHERE Nombre LIKE 'P[C-M]%'
Queremos recuperar todos los clientes cuyos nombres comiencen por las letras de la A a la D seguidas de cualquier cadena:
SELECT * FROM Clientes WHERE Nombre LIKE '[A-D]%'
En la siguiente tabla se muestran varios modelos diferentes que utilizan el operador LIKE para comparar varias expresiones de cadena:
Tipo de coincidencia | Modelo | Coincidencia | No coincide | |
SQL ANSI | SQL Jet | |||
Varios caracteres | ‘a%a’ | ‘a*a’ | ‘aa’, ‘aCDa’, ‘Abbbba’ | ‘ab’, ‘CDa’ |
Un carácter especial | ‘a[&]a’ | ‘a&a’ | ‘aaa’ | |
Varios caracteres | ‘ma%’ | ‘ma*’ | ‘malo’, ‘mañana’ | ‘ama’, ‘mmalo’ |
Un sólo carácter | ‘a_a’ | ‘a?a’ | ‘aaa’, ‘a3a’, ‘aBa’ | ‘aBBBa’ |
Un sólo dígito (*) | ‘a[!a-z]a’ | ‘a#a’ | ‘a0a’, ‘a1a’, ‘a2a’ | ‘aaa’, ‘a10a’ |
Rango de caracteres | ‘[a-z]’ | ‘b’, ‘j’, ‘k’, ‘z’ | ‘3’, ‘$’, ‘ab’ | |
Fuera de un rango | ‘[!a-z]’ | ‘9’, ‘&’, ‘%’ | ‘a’, ‘b’, ‘z’, ‘9a’ | |
Distinto de un dígito | ‘[!0-9]’ | ‘A’, ‘a’, ‘&’, ‘%’ | ‘0’, ‘1’, ‘9’ | |
Combinada (*) | ¡a[!b-m][!a-z]’ | ‘a[!b-m]#’ | ‘An9’, ‘az0’, ‘a99’ | ‘abc’, ‘aj0’ |
(*) Aunque en la documentación oficial se indica que se puede utilizar el carácter de signo numérico (#), en la práctica su funcionamiento se restringe únicamente al SQL de Microsoft Jet, y cuando se utiliza con Microsoft Access o la biblioteca de DAO. Si utilizamos ADO junto con el proveedor Microsoft OLE DB para Jet 4.x, el carácter de signo numérico será interpretado como un literal, tal y como podrá comprobar en el siguiente artículo de la Base del Conocimiento, FIX: Jet 4.0 Treats «#» as Numeric Wildcard Character, por lo que para interpretar dicho carácter numérico con el SQL de ANSI, deberá sustituir el carácter comodín numérico por un modelo que esté fuera del rango de .caracteres alfabéticos [!a-z], corriendo el peligro de obtener resultados no deseados si en la expresión de comparación existen otros símbolos distintos a los alfabéticos (%, $, &, etc.).
El operador IS NULL
Un valor NULL (nulo) es aquel que indica que un campo faltan datos o son desconocidos. Los valores NULL se pueden especificar en campos cuya información se desconoce, así como en expresiones y consultas. Si utiliza Visual Basic, puede indicar un valor nulo mediante la palabra clave Null. Debe de tener siempre presente que algunos campos como, por ejemplo, los definidos como clave principal de la tabla, no pueden contener un valor nulo.
Puede utilizar el operador IS NULL para determinar si el valor de una expresión es igual a un valor nulo. Dicho operador utiliza la siguiente sintaxis:
campo IS [NOT] NULL
Si, por ejemplo, deseamos conocer aquellos registros de clientes donde no figura su Código Postal, escribiríamos la siguiente consulta SQL:
SELECT * FROM Clientes WHERE CodPostal IS NULL
Añadiendo el operador lógico opcional NOT, podemos comprobar lo contrario del operador IS NULL. En este caso, la consulta devolverá los registros de clientes que sí tienen especificado su Código Postal:
SELECT * FROM Clientes WHERE CodPostal IS NOT NULL
Agrupamiento de registros
Puede que nos interese agrupar ciertos registros que presenten un valor coincidente en el mismo campo de la tabla definida en una cláusula FROM, con el único objetivo de presentar un informe que resuma los datos correspondientes a los registros que cumplan con la condición indicada en la consulta de selección. Para ello, necesariamente tendremos que utilizar una función agregada de las permitidas por el lenguaje SQL, a fin de recuperar en un único valor el resultado devuelto por la función agregada especificada.
Las funciones agregadas COUNT, MAX, MIN y SUM
El lenguaje SQL incluye una serie de funciones que permiten la realización de cálculos con un grupo de valores pertenecientes a los distintos registros recuperados mediante una instrucción SELECT, por lo que necesariamente deberá de indicar el nombre de la función agregada a continuación de la instrucción SELECT.
A continuación detallaré las funciones agregadas más utilizadas en SQL. Para ver una referencia de todas las funciones que puede utilizar en SQL, consulte el tema Funciones agregadas en el Capítulo I de este manual.
COUNT
Calcula el número de elementos devueltos por una consulta. Su sintaxis es la siguiente:
COUNT (expresión)
Donde expresión contiene el nombre o nombres de los campos que se desea calcular, y puede incluir el nombre de un campo de una tabla, una constante o una función (la cual puede ser intrínseca o definida por el usuario, pero en ningún caso puede especificar otra función agregada de SQL).
La función COUNT simplemente cuenta el número de registros existentes que coincidan con la expresión especificada, sin tener en cuenta aquellos valores que contienen NULL, salvo que haya indicado el carácter comodín asterisco (*), en cuyo caso, COUNT calcula el número total de registros devueltos por la instrucción SELECT, incluyendo aquellos que contienen valores NULL. Utilizar el asterisco en lugar de un nombre de campo, hace que la consulta se ejecute de una manera más rápida.
Si especifica múltiples campos en expresión, la función COUNT cuenta un registro sólo si alguno de los campos no es NULL. Si todos los campos especificados presentan valores NULL, no se contará el registro. Deberá de separar los nombres de los campos con el carácter ampersand (&).
A continuación se muestran algunos ejemplos útiles donde puede utilizar la función COUNT.
Deseamos conocer el número de clientes con los que cuenta nuestra empresa:
SELECT COUNT (*) AS [Nº Clientes] FROM Clientes
El número de clientes residentes en Madrid:
SELECT COUNT (*) AS [Total de Residentes] FROM Clientes WHERE Ciudad = 'Madrid'
El número de clientes que tienen el Código Postal asignado. En este caso, no se contarán los registros cuyo campo CodPostal sea NULL:
SELECT COUNT (CodPostal) FROM Clientes
Por último, vamos a seleccionar el número total de Clientes, y el número de ellos que tienen cumplimentados los campos Teléfono y Código Postal, de esta forma podemos saber los que todavía no tienen asignados dichos campos:
SELECT COUNT(*) AS [Nº Clientes], COUNT (Telefono & CodPostal) FROM Clientes
MAX y MIN
Calculan el máximo y mínimo respectivamente de un conjunto de valores contenidos en un campo específico de una consulta de selección. Su sintaxis es:
MIN (expresión) MAX (expresión)
Donde expresión es el campo sobre el que se desea realizar el cálculo. Expresión puede incluir el nombre de un campo de una tabla, una constante o una función (la cual puede ser intrínseca o definida por el usuario pero no otras de las funciones agregadas de SQL). Si los campos seleccionados en expresión incluyen valores NULL no se tendrán en cuenta a la hora de calcular los valores máximo y mínimo.
A continuación se muestran algunos ejemplos útiles donde puede utilizar las funciones agregadas MAX y MIN.
Queremos conocer el mayor identificador de cliente asignado a la tabla Clientes:
SELECT MAX(IdCliente) AS MaxIdentificador FROM Clientes
Queremos conocer el importe mayor y menor de las facturas realizadas:
SELECT MAX(Total) AS MaxFactura, MIN(Total) AS MinFactura FROM Facturas
Deseamos conocer la fecha de nacimiento de los empleados más jóvenes y más antiguos de nuestra empresa:
SELECT MAX(FechaNto) AS EmpleadosJovenes, MIN(FechaNto) AS EmpleadosMayores FROM Empleados
Queremos felicitar a los empleados que llevan más tiempo trabajando en nuestra empresa de la provincia de Jaén:
SELECT MIN(FechaIncorporacion) AS Veteranos FROM Empleados WHERE Ciudad = 'Jaén'
SUM
Devuelve la suma del conjunto de valores contenidos en un campo específico de una consulta de selección. Su sintaxis es:
SUM (expresión)
Donde expresión representa el nombre del campo cuyos datos desean sumarse, o una expresión que realiza un cálculo utilizando los datos de dichos campos. Los operandos de expresión pueden incluir el nombre de un campo de una tabla, una constante o una función (la cual puede ser intrínseca o definida por el usuario, pero no otra función agregada de SQL). Si los campos seleccionados en expresión incluyen valores NULL no se tendrán en cuenta a la hora de sumar los valores, cosa por cierto, bastante lógica.
A continuación se exponen algunos ejemplos útiles que muestran la utilización de la función SUM.
Deseamos conocer el importe total de las facturas emitidas:
SELECT SUM(Total) AS TotalFacturas FROM Facturas
Queremos conocer la suma de la Base, IVA y Total de todas las facturas correspondientes al segundo trimestre del año:
SELECT SUM(Base) AS TotalBase, SUM(IVA) As TotalIVA, SUM(Base+IVA) As Total
FROM Facturas
WHERE Fecha BETWEEN #04/01/2004# AND #06/30/2004#
La cláusula GROUP BY
La cláusula opcional GROUP BY generará un único registro mediante el agrupamiento de todos los registros que tengan un valor idéntico en la lista de campos seleccionada. El registro creado contiene un valor resumen como resultado de la función agregada que necesariamente tendrá que especificar en la instrucción SELECT, tal como SUM o COUNT, ya que si no existe una función agregada, se omitirán por completo los valores resumen. La sintaxis de la cláusula GROUP BY es la siguiente:
SELECT campos FROM nombreTabla [WHERE criterio] GROUP BY camposAgrupados
Respecto de los valores NULL existentes en los campos, los mismos no se omitirán, agrupándose igualmente como si de otro valor cualquiera se tratara, debiendo tener presente que los valores NULL no serán evaluados por ninguna función agregada que haya especificado, tal y como se ha visto con anterioridad.
Si no desea agrupar ciertos registros, podrá excluirlos especificando una cláusula opcional WHERE. Una vez agrupados los registros, puede que le interese filtrar los mismos, por lo que deberá de indicar una cláusula HAVING para tal cometido. La cláusula HAVING se estudiará en el siguiente apartado, aunque adelantaré un ejemplo para que lo compare con la cláusula WHERE.
Deseamos conocer el número de facturas y su importe total agrupado por el identificador del cliente, por lo que este será el campo que deberemos de indicar en la cláusula GROUP BY, aunque no es necesario que el campo se incluya en la instrucción SELECT:
SELECT IdCliente, Count(*) AS [Nº Facturas], SUM(Total) AS [Importe Total]
FROM Facturas
GROUP BY IdCliente
A continuación deseamos ver sólo aquellos clientes que todas sus compras han sido realizadas en el segundo trimestre del año, por lo que utilizaremos la cláusula WHERE para omitir los registros cuya fecha no esté comprendida en el segundo trimestre:
SELECT IdCliente, COUNT(*) AS [Nº Facturas], SUM(Total) AS [Total Facturas]
FROM Facturas
WHERE Fecha BETWEEN #04/01/2004# AND #06/30/2004#
GROUP BY IdCliente
Advierta que solamente aparecerán los registros de los clientes que hayan efectuado sus compras exclusivamente en el segundo trimestre. Si un cliente, a pesar de haber realizado una compra en el segundo trimestre, tiene otras compras realizadas fuera del intervalo de fechas indicadas, dichos registros no se agruparán, por lo que no aparecerán en el conjunto de registros devuelto.
Por último, queremos filtrar los registros devueltos anteriormente, para conocer sólo aquellos clientes cuyas compras sean igual o superior a los 1.500 euros:
SELECT IdCliente, COUNT(*) AS [Nº Facturas], SUM(Total) AS [Total Facturas]
FROM Facturas
WHERE Fecha BETWEEN #01/01/2004# AND #03/31/2004#
GROUP BY IdCliente
HAVING SUM(Total) >= 1500.00
Mediante la cláusula GROUP BY puede referenciar cualquier campo de la tabla especificada en la cláusula FROM que no sea del tipo Memo u Objeto OLE, y no es necesario que dicho campo se encuentre incluido en la instrucción SELECT, siempre que esta última instrucción incluya al menos una función agregada de SQL. Los campos especificados en la lista de campos de la instrucción SELECT, necesariamente deberán de incluirse, o bien en la misma cláusula GROUP BY, o como argumento de la función agregada utilizada.
La cláusula HAVING
La cláusula HAVING se utiliza para filtrar los registros que se desean mostrar una vez que se hayan agrupado con la cláusula GROUP BY, siendo su sintaxis la siguiente:
SELECT campos FROM nombreTabla [WHERE criterio] GROUP BY camposAgrupados HAVING nombreCampo = criterio
HAVING se asemeja a la cláusula WHERE en el sentido de que determina los registros que se seleccionarán, una vez que necesariamente hayan sido agrupados mediante la cláusula GROUP BY.
La cláusula HAVING es opcional y puede contener hasta 40 expresiones vinculadas por operadores lógicos, como AND y OR. Los siguientes ejemplos ampliarán el uso de las cláusulas GROUP BY y HAVING.
Deseamos conocer el número de empleados que tenemos en cada provincia, por lo que este será el campo de agrupamiento:
SELECT COUNT(IdEmpleado) AS Total, Ciudad FROM Empleados GROUP BY Ciudad
A continuación, sólo deseamos ver el número de empleados que hay en una provincia determinada, por lo que filtraremos la selección agrupada indicando el nombre de la provincia:
SELECT COUNT(IdEmpleado) AS Total, Ciudad
FROM Empleados
GROUP BY Ciudad
HAVING Ciudad = 'Jaén'
Por último, vamos a conocer el número de ventas efectuadas por un empleado en concreto. Para ello, combinaremos las tablas Facturas y Empleados:
SELECT COUNT(IdFactura) As [Nº Facturas], Empleados.Nombre
FROM Facturas
INNER JOIN Empleados ON Facturas.IdEmpleado=Empleados.IdEmpleado
GROUP BY Nombre
HAVING Nombre = 'Enrique Martínez'
Ordenar los registros devueltos
Una vez que los registros se encuentren seleccionados, quizás nos interese que estos aparezcan ordenados a la hora de ser recuperados por la consulta. De esto se encarga la cláusula ORDER BY, mediante la cual podemos especificar la columna o columnas que se utilizarán como clave de ordenación, así como la forma ascendente o descendente en que se ordenarán los registros devueltos. La sintaxis de la cláusula ORDER BY es la siguiente:
SELECT * FROM tabla ORDER BY campo1 [ASC|DESC] [, campo2] [ASC|DESC]
El siguiente ejemplo ordenará alfabéticamente todos los registros de la tabla Clientes:
SELECT * FROM Clientes ORDER BY Nombre ASC
Si deseamos ordenar los registros de forma ascendente, no es necesario indicar la palabra clave ASC, ya que es el valor por defecto de la cláusula ORDER BY. En cambio, si queremos obtener un orden descendente (Z-A, 9-0), tendrá que especificar la palabra clave DESC al final del nombre del campo que desea ordenar de esta manera. Con respecto al orden en el que aparecerán los valores NULL, estos figurarán en primer lugar si utiliza un orden ascendente, y al final del conjunto de registros devueltos, si los ordena de forma descendente.
En lugar de indicar el nombre del campo, si lo desea también puede especificar el número ordinal de la columna por la que desea ordenar los registros seleccionados. Deberá de tener en cuenta que el número ordinal de la columna se corresponderá con el orden en el que han sido escritos los nombres de los campos en la instrucción SELECT, si se ha especificado una lista de campos; si ha utilizado el carácter comodín de asterisco (*) para recuperar todos los campos de la tabla, el número ordinal se corresponderá con el número de orden que tiene el campo en la tabla o tablas especificadas en la cláusula FROM.
La consulta anterior también se puede escribir de la siguiente manera, si conoce de antemano que el segundo campo de la tabla corresponde a la columna ‘Nombre’:
SELECT * FROM Clientes ORDER BY 2 ASC
Como se ha indicado con anterioridad, puede incluir varios campos en la cláusula ORDER BY, ordenándose los registros por el siguiente orden:
- Se ordenarán por el primer campo especificado en la cláusula ORDER BY;
- Los registros que tengan el mismo valor en el primer campo, se ordenarán por la forma indicada en el segundo campo, y así sucesivamente.
El siguiente ejemplo ordenará de forma ascendente, y por el número de cliente, todas las facturas existentes. Si un mismo cliente tiene varias facturas, estas se ordenarán de forma descendente según el importe total de la factura:
SELECT IdCliente, IdFactura, Total FROM Facturas ORDER BY IdCliente, Total DESC
La cláusula ORDER BY generalmente suele ser el último elemento que se incluye en una instrucción SELECT, y salvo que en la misma instrucción de selección especifique los predicados TOP o TOP n PERCENT, tal y como se vió en su apartado correspondiente, no es necesario que designe una cláusula ORDER BY, en cuyo caso los datos recuperados no presentarán ningún tipo de orden en concreto, mostrándose en el mismo orden en el que los registros fueron añadidos a la tabla.
La instrucción WITH OWNERACCESS OPTION
La instrucción opcional WITH OWNERACCESS OPTION únicamente deberá de especificarse cuando su aplicación se ejecute en un entorno multiusuario y tenga definido un grupo de trabajo seguro, por lo que es necesario que al establecer la conexión con el origen de datos, proporcione la ruta donde se encuentre el archivo de información de grupos de trabajo (archivo System.mdw) que va a utilizar para verificar los permisos oportunos que tienen asignados tanto los usuarios como los grupos de trabajo incluidos en el mismo.
El motivo de que se incluya esta instrucción al final de la instrucción SELECT, no es otro que el autorizar a un usuario para que pueda ver los datos incluidos en las tablas base que se vayan a utilizar cuando ejecute una consulta de selección (SELECT), de creación de tabla (SELECT * INTO) o de datos anexados (INSERT INTO), aunque se le haya restringido de alguna otra manera los oportunos permisos de usuario para poder acceder a la información contenida en dichas tablas.
Pero no vaya a caer en la tentación de pensar que esta sería la solución para acceder al contenido de cualquier tabla, sin tener los preceptivos permisos, porque estará alejado de la realidad, dado que la selección de los datos se deberá de efectuar sobre una consulta previamente almacenada en la base de datos, creada por un usuario que se encuentre incluido en el archivo de información de grupos de trabajo utilizado, y que este disponga de los permisos necesarios para crear la consulta y acceder a los datos requeridos. Con un ejemplo se ilustrará mejor el uso de la instrucción WITH OWNERACCESS OPTION:
El usuario Admin desea crear una consulta llamada Consulta_Empleados en una base de datos Microsoft Access, para que todos aquellos usuarios que no tengan permiso de lectura sobre la tabla Empleados, pueda consultar los datos incluidos en dicha tabla base:
Nombre de la consulta:
Consulta_Empleados
Sintaxis:
SELECT * FROM Empleados WITH OWNERACCESS OPTION
Ahora, el usuario ‘Enrique’, que no tiene permisos para leer los datos de la tabla, inicia sesión mediante un objeto Connection de la biblioteca de ADO, por lo que para ejecutar la consulta, abriría un objeto Recordset de alguna de las dos formas que se indican a continuación:
oRst.Open "Consulta_Empleados", oConexion, adOpenKeyset, adLockOptimistic, adCmdTable
oRst.Open "SELECT * FROM Consulta_Empleados", oConexion, adOpenKeyset, adLockOptimistic, adCmdText
De ésta forma el usuario Enrique podrá leer los registros de la tabla Empleados, de la que no tiene permiso alguno establecido sobre la misma.
Como podrá comprobar, la seguridad del motor Microsoft Jet no permite a cualquier usuario acceder a los datos sin los preceptivos permisos oportunos, por lo que necesariamente deberá de ser un usuario con los suficientes permisos establecidos sobre la tabla, el que permita acceder a otros usuarios a los registros de la tabla base mediante una consulta almacenada que incluya la instrucción WITH OWNERACCESS OPTION.
Esta instrucción siempre deberá incluirse al final de la instrucción SELECT y solamente será útil en aplicaciones multiusuario que se encuentren protegidas.
0 comentarios