DBF A SQL
De DBF a SQL (1). Introducción
3 AGOSTO, 2018 / WROV
Si aún usas DBF entonces estás desperdiciando todos los
beneficios que un motor SQL te puede proporcionar. Entre ellos
podemos mencionar:
1. Escribirás mucho menos pero obtendrás los mismos (o
mejores) resultados
2. Tendrás menos errores
3. Escribirás más rápido tu código fuente
4. Las consultas a las tablas serán mucho más rápidas
5. No podrán modificar fácilmente el contenido de tus tablas
(si usas DBF hasta con el Bloc de Notas del Windows
podrían hacerlo)
6. Podrás restringir el acceso a tus tablas. Solamente podrán
ver su contenido o cambiarlo, las personas autorizadas
7. Podrás trabajar en equipo con personas que usan otros
lenguajes de programación (Visual Basic, C, C++, Delphi,
Lazarus, Java, etc.)
8. Podrás hacer copias de seguridad aunque las tablas estén
siendo usadas
9. Podrás comprobar fácilmente si hay algún error en los
datos
10. Podrás evitar que una tabla tenga datos que no
debería tener
11. Podrás evitar que una tabla no tenga datos que sí
debería tener
12. Podrás insertar miles y miles de registros a gran
velocidad
13. Y muchas funcionalidades más
Terminología:
Hay algunas diferencias en la terminología usada
en SQL comparada con la terminología usada en DBF. Algunas
de ellas.
Fila. En DBF se le llama registro
Columna. En DBF se le llama campo
En este artículo y todos los subsiguientes usaremos
preferentemente la terminología SQL porque es la que
encontrarás en la literatura SQL.
Comandos principales:
En SQL hay 4 comandos principales. Son llamados los cuatro
grandes del SQL. Esos comandos son:
INSERT. Sirve para insertar (agregar) filas a una tabla.
UPDATE. Sirve para modificar o actualizar el contenido de
las columnas de una tabla.
DELETE. Sirve para borrar o eliminar filas de una tabla.
SELECT. Sirve para observar o consultar el contenido de
una tabla o de varias tablas.
El más difícil para aprender a usarlo perfectamente es
el SELECT, porque tiene muchas opciones y muchas formas de
ser usado. Pero sí que vale la pena aprender porque el ahorro de
tiempo que se obtendrá y la ganancia en velocidad de respuesta
pueden llegar a ser asombrosos, comparados con los obtenidos
al usar tablas DBF. Así que, aunque al principio pueda parecer
un poco complicado, es muy conveniente dedicarse a ser un
experto en el uso del SELECT SQL. Vale la pena.
Por lo anterior, muchos de los artículos de esta serie se
dedicarán a convertirte en experto en el uso del SELECT SQL.
Con respecto a los comandos UPDATE y DELETE lo más
importante a recordar es que siempre debes ponerles una
condición, porque de lo contrario la actualización o el borrado se
harán en toda la tabla, o sea en la tabla completa, y eso podría
llegar a causar un verdadero desastre.
El comando INSERT es el más sencillo de los 4 grandes. En
general nadie tiene problemas por usar ese comando.
El estándar SQL
Las siglas SQL significan Structured Query Language o en
castellano: lenguaje de consulta estructurado. Pero ya sabes
que aunque diga consulta también lo puedes usar para
insertar, para modificar, y para borrar filas. Sin embargo, la
palabra consulta está bien empleada porque es de lejos la
operación que más se realiza.
Aunque SQL es un estándar (o sea, tiene reglas muy
precisamente definidas para implementarlo) cada motor le
agrega o le quita lo que se le ocurre. De entre todas las
implementaciones posiblemente la que más se ajusta al
estándar es Firebird. Las demás (MySQL, SQL Server, DB2,
Sybase, Oracle, PostgreSQL, etc.) están más alejadas del
estándar. Eso, tiene su parte buena y su parte mala. La parte
buena es que le pueden agregar funcionalidades a su propia
implementación de SQL, la parte mala es que lo vuelven más y
más incompatible con las demás implementaciones. Eso puede
causar muchos dolores de cabeza cuando se necesita migrar de
una Base de Datos hecha con un motor a una Base de Datos
hecha con otro motor (se le llama motor a las
implementaciones: Firebird, MySQL, SQL Server, Oracle, etc.)
De todas maneras, en los 4 grandes no suelen existir diferencias
muy notorias, las mayores diferencias suelen estar en el
lenguaje empleado para escribir los triggers y los stored
procedures. ¿Por qué? porque ese lenguaje no está definido en
el estándar SQL y por lo tanto cada fabricante lo implementa
como se le antoja. Sin embargo, todos tienen construcciones
similares a IF…ELSE…ENDIF, y a DO WHILE…ENDDO, etc.,
aunque difieren en las sintaxis empleadas.
¿Qué es un trigger?
Es el código que se escribe para que se
ejecute antes o después de realizar alguna de estas operaciones
en una tabla o en una vista: INSERT, UPDATE, DELETE.
Los triggers que se ejecutan antes se usan normalmente para
validar datos. O sea para evitar que las tablas tengan datos
erróneos. Los triggers que se ejecutan después se usan
normalmente para modificar el contenido de otras tablas (por
ejemplo, cuando en la tabla de COBRANZAS se inserta una fila
que cancela una Factura, en una columna de la tabla de
FACTURAS se escribe una “C” que indica que esa Factura ya fue
cobrada totalmente).
¿Qué es un stored procedure?
Es un código que escribimos para que se ejecute cuando
nosotros queremos. Es equivalente a una rutina o a una función
de las que estamos acostumbrados a usar en Visual FoxPro. En
general, en un stored procedure se usan los datos de una
tabla o de más de una tabla para procesarlos.
Conclusión:
Si aún no estás usando SQL entonces estás desperdiciando tu
tiempo, porque con SQL escribirás menos y obtendrás los datos
más rápido.
Hay 4 comandos que deberás aprender a usar, y son
llamados los 4 grandes del SQL. Esos comandos
son: INSERT, UPDATE, DELETE, SELECT.
El más difícil de aprender pero también el más poderoso es
el SELECT. Por ese motivo en muchos artículos de esta serie
habrá múltiples ejemplos para que te sea más fácil aprenderlo.
De DBF a SQL (2).
Reemplazando comandos
4 AGOSTO, 2018 / WROV
Como son lenguajes distintos, es lógico que los comandos
usados en Visual FoxPro y en SQL tengan nombres y sintaxis
distintas, aunque hagan la misma cosa o muy similar. En este
artículo veremos las similitudes y las diferencias entre ellos.
Insertar filas
Listado 1. Como se hace en VFP con el comando REPLACE
1 USE MiTabla
3 APPEND BLANK
5 REPLACE MiColumna1 WITH MiValor1, MiColumna2 WITH MiValor2
Listado 2. Como se hace en VFP con el comando GATHER
MEMVAR
1 USE MiTabla
2
3 M.Columna1 = Valor1
4 M.Columna2 = Valor2
6 APPEND BLANK
7
GATHER MEMVAR
8
Listado 3. Como se hace en SQL usando VALUES
1 INSERT INTO MiTabla (MiColumna1, MiColumna2) VALUES (MiValor1, MiValor2)
Listado 4. Como se hace en SQL usando SELECT
1 INSERT INTO MiTabla (MiColumna1, MiColumna2) SELECT MiOtraColumna1, MiOtraColumna2
Actualizar filas
Listado 5. Como se hace en VFP para actualizar una fila
1
USE MiTabla
2
3
*--- Primero, hay que ubicarse en la fila que se quiere actualizar
4 SEEK MiColumna, o LOCATE FOR MiColumna, o recorrer con un BROWSE, o con un EDIT, o
5
6 *--- Segundo, hay que cambiar el valor de una o más columnas de esa fila, con REPL
7 *--- Método 1. Con el comando REPLACE
8 REPLACE MiColumna1 WITH MiValor1, MiColumna2 WITH MiValor2, o
10 *--- Método 2. Con el comando GATHER MEMVAR
M.MiColumna1 = MiValor1
11
M.MiColumna2 = MiValor2
12
13
GATHER MEMVAR
14
Listado 6. Como se hace en VFP para actualizar varias filas
1 REPLACE MiColumna1 WITH MiValor1, MiColumna2 WITH MiValor2 [ALL | NEXT | REST] [FOR
Listado 7. Como se hace en SQL para actualizar una fila o varias
filas
1 UPDATE
2 MiTabla
3 SET
4 MiColumna1 = MiValor1,
5 MiColumna2 = MiValor2
WHERE
6
MiCondición
7
En SQL es muy importante recordar que siempre al usar el
comando UPDATE debemos establecer una condición, porque si
no la establecemos entonces se actualizará toda la tabla, la
tabla completa, y eso puede ser motivo de graves errores.
Además, dependiendo de lo que escribamos en MiCondición se
actualizará una sola fila, varias filas, o todas las filas.
Borrar filas
Listado 8. Como se hace en VFP para borrar una fila
1 USE MiTabla
2
3 *--- Primero, hay que ubicarse en la fila que se quiere borrar
4 SEEK MiColumna, o LOCATE FOR MiColumna, o recorrer con un BROWSE, o con un EDIT, o
6 *--- Segundo, hay que borrar esa fila
DELETE
7
Listado 9. Como se hace en VFP para borrar varias filas
1 DELETE [ALL | NEXT | REST] [FOR Condición1] [WHILE Condición2]
Listado 10. Como se hace en VFP para borrar todas las filas de
una tabla
1 USE MiTabla
2 *--- Método 1.
3 DELETE ALL
5 *--- Método 2.
ZAP
6
Listado 11. Como se hace en SQL para borrar una o varias filas
1 DELETE FROM
2 MiTabla
3 WHERE
4 MiCondición
Algo MUY IMPORTANTE que debemos recordar
es siempre escribir a MiCondición porque si no lo hacemos
entonces se borrarán todas las filas de la tabla, y eso podría
causar un desastre mayúsculo.
Listado 12. Como se hace en SQL para borrar todas las filas de
una tabla
1 DELETE FROM MiTabla
Consultar
Listado 13. Como se hace en VFP para observar el contenido de
una tabla
1
USE MiTabla
2
3
*--- Método 1.
4
BROWSE
5
6
*--- Método 2.
7
EDIT
8
9 *--- Método 3.
10 DO WHILE MiCondición
11 ? MiColumna1, MiColumna2
12 SKIP
13 ENDDO
14
*--- Método 4.
15
SCAN
16
? MiColumna1, MiColumna2
17
ENDSCAN
18
19
*--- Método 5.
20 DISPLAY ALL
21
Listado 14. Como se hace en SQL para observar el contenido de
una tabla
1 SELECT
2 MiColumna1,
3 MiColumna2
4 FROM
5 MiTabla
6 WHERE
MiCondición
7
Si no escribimos a MiCondición entonces se mostrarán todas
las filas de la tabla.
Conclusión:
Como son lenguajes diferentes, es lógico que Visual
FoxPro y SQL difieran en los nombres de los comandos y en las
sintaxis de los mismos.
Pero si prestaste atención, habrás notado que en SQL se escribe
menos. Además, en SQL no hace falta decirle cual índice
deseamos utilizar, el propio motor se encarga de utilizar el más
adecuado para cada caso. Eso, por un lado nos ahorra tiempo, y
por otro lado evita que cometamos el error de elegir el índice
equivocado.
La implementación de SQL dentro de VFP es muy, pero muy
buena, porque dispone de algo que no existe en otros lenguajes
de programación y que nos permite construir aplicaciones muy
confiables, muy estables, y muy robustas: cursores.
Pero ese, … será el tema del siguiente artículo de esta serie.
De DBF a SQL (3). Entendiendo a
los cursores
5 AGOSTO, 2018 / WROV
Como seguramente sabrás, Visual FoxPro tiene una
construcción muy poderosa, que no existe en otros lenguajes de
programación: cursores.
¿Qué es un cursor?
Es una tabla temporal. O sea que existe solamente durante un
corto período de tiempo. También se le suele llamar “tabla de
memoria”. Se puede utilizar a un cursor solamente desde que
se lo crea hasta que se lo cierra o hasta que el programa o
formulario que lo creó sale de la memoria. Por ejemplo: si dentro
de un formulario se crea un cursor, ese cursor solamente
podrá ser usado mientras ese formulario esté activo, porque
cuando se salga del formulario se perderá el acceso al cursor.
¿Qué se puede hacer con un cursor?
Todo lo que se puede hacer con una tabla DBF. O sea que
puedes usar los comandos BROWSE, APPEND BLANK, EDIT,
REPLACE, DELETE, ZAP, INDEX ON, etc. Si un comando puedes
emplear con una tabla DBF entonces ese comando también lo
puedes emplear con un cursor.
¿Los cursores ocupan espacio en el disco duro?
Sí, cuando creas un cursor el Visual FoxPro crea un archivo
con extensión .TMP en el disco duro. Aunque la extensión
sea .TMP a todos los efectos funciona igual que si su extensión
fuera .DBF, pero la extensión .TMP nos recuerda que es un
archivo temporal, que será eliminado automáticamente cuando
se deje de usarlo.
¿En cuál carpeta se crean los cursores?
En la carpeta donde el VFP guarda a todos sus archivos
temporales. Para saber cual es esa carpeta tienes dos
alternativas:
a) Entrar a: Tools | Options … | File Locations | Temporary files
b) En la ventana de comandos escribir: ? SYS(2023)
Si quieres que los archivos temporales se guarden en otra
carpeta, puedes hacerlo fácilmente. En el archivo [Link]
agregas la línea TMPFILES= y a continuación escribes el nombre
completo de la carpeta. Ejemplo:
TMPFILES=D:\CONTABILIDAD\TEMPORALES\
¿Los cursores se abren de forma compartida o exclusiva?
Siempre se abren de forma exclusiva y eso sin importar que
SET EXCLUSIVE esté en ON o esté en OFF. Eso no importa,
siempre se abren de forma exclusiva. Y eso es lógico, ya que si
solamente serán usados por un programa o por un formulario, y
por un solo usuario, no tendría sentido que se abran de
forma compartida. Además, cuando una tabla se abre de
forma compartida requiere más recursos del VFP porque debe
estar verificándola constantemente.
¿Cuántos tipos de cursores hay?
Hay tres tipos de cursores:
Manuales
Automáticos de lectura
Automáticos de lectura y escritura
¿Cómo se crea un cursor manual?
Con el comando CREATE CURSOR. Tal y como podemos ver en
el Listado 1.
Listado 1. Creando un cursor manualmente
1 CREATE CURSOR ;
2 MiPrimerCursor ;
3 (NOMBRES C(40), ;
4 FECHA_NACIMIENTO D, ;
SALDO_ACTUAL N (10))
5
Donde la letra “C” indica que se guardarán caracteres en esa
columna, la letra “D” indica que se guardará una fecha, y la
letra “N” indica que se guardará un número. Hay más letras
disponibles (“L” para guardar verdadero o falso, “T” para
guardar la fecha y la hora, etc.). Si quieres conocer cuales son
todas las letras que puedes emplear, entrando en la ayuda
del VFP y buscando “CREATE CURSOR” lo sabrás.
Los cursores creados manualmente siempre son de
lectura/escritura. Eso significa que podemos agregarles filas,
borrarles filas, actualizar el contenido de las filas, etc.
¿En qué casos se usaría un cursor creado manualmente?
Cuando se necesita guardar datos de forma temporal. Por
ejemplo, al realizar un proceso se necesita guardar algunos
datos que cuando ese proceso termina ya no serán necesarios.
En tal circunstancia se podría usar una tabla DBF o un cursor.
Es preferible usar un cursor porque en ese caso al finalizar el
proceso se recuperará el espacio que ocupaba en el disco duro,
sin necesidad de escribir un comando ZAP o equivalente.
Además, como todos los cursores siempre se abren de
forma exclusiva (sin importar lo que diga SET EXCLUSIVE) eso
aumenta la rapidez (acceder a una tabla de
forma compartida siempre es más lento que acceder a la
misma tabla de forma exclusiva).
¿Cómo se crea un cursor automático que sea de sólo
lectura?
Realizando una consulta usando el comando SELECT. Por
ejemplo:
Listado 2. Creando un cursor automáticamente de solo lectura
1 SELECT MiColumna1, MiColumna2 FROM MiTabla INTO CURSOR MiCursor
En el Listado 2. le estamos pidiendo que nos muestre el
contenido de las columnas MiColumna1 y MiColumna2 que
pertenecen a la tabla MiTabla y que ese resultado lo guarde en
un cursor llamado MiCursor.
Como ese cursor es de sólo lectura, entonces podremos ver el
contenido de la tabla MiTabla pero no podremos cambiar ese
contenido. O sea, que no podremos usar los comandos INSERT,
UPDATE, o DELETE, o sus equivalentes en VFP. Si lo intentamos,
el VFP se enojará.
Listado 3. Verificando que el cursor es de sólo lectura
1 USE CUENTAS
2
3 SELECT CUE_NUMERO, CUE_NOMBRE FROM CUENTAS INTO CURSOR MiCursor1
4
5 REPLACE CUE_NOMBRE WITH "Prueba de reemplazo"
Al querer ejecutar el comando REPLACE, el VFP mostrará el
mensaje:
Captura 1. Si haces clic en la imagen la verás más grande
En castellano: “No puedo actualizar el cursor MICURSOR1,
porque es de sólo lectura”.
¿Cómo se crea un cursor automático que sea de
lectura/escritura?
Agregándole al comando SELECT las palabras READWRITE.
NOTA: Esto solamente es posible desde la versión 8 del VFP. Si
aún usas una versión anterior entonces no tendrás esta
posibilidad.
Listado 4. Creando un cursor automático que sea de lectura y de
escritura
1 USE CUENTAS
2
3 SELECT CUE_NUMERO, CUE_NOMBRE FROM CUENTAS INTO CURSOR MiCursor1 READWRITE
4
5 REPLACE CUE_NOMBRE WITH "Prueba de reemplazo"
6
BROWSE
7
Al ejecutar el comando BROWSE en el Listado 4. veremos algo
similar a:
Captura 2. Si haces clic en la imagen la verás más grande
Y como puedes ver, ahora sí se reemplazó el contenido de la
columna CUE_NOMBRE, ya que al colocar la cláusula
READWRITE le hemos indicado al VFP que ese cursor
es actualizable. O sea que le podemos agregar filas, modificar el
contenido de las filas, o eliminar filas.
Los cursores “Query”
Hasta ahora, a todos los cursores que se crearon les hemos
asignado un nombre. Pero hay otra clase de cursores, aquellos
que crea automáticamente el VFP sin necesidad de que se lo
pidamos. Estos cursores son creados cuando en el comando
SELECT no escribimos la cláusula INTO CURSOR. Por ejemplo:
Captura 3. Si haces clic en la imagen la verás más grande
Como habrás notado, en la Captura 3. después de la cláusula
FROM CUENTAS no se escribió la cláusula INTO CURSOR y en
consecuencia el VFP creó un cursor por su propia cuenta y le
llamó “Query“. Pero además, nos mostró el contenido de la
tabla. Eso nunca ocurre cuando usamos la cláusula INTO
CURSOR.
Recuerda: Si no escribimos la cláusula INTO CURSOR entonces
el VFP ejecuta la consulta y nos muestra el resultado en un
browse. En cambio, si escribimos la cláusula INTO CURSOR el
resultado de la consulta se guarda en el nombre del cursor que
nosotros elegimos y no nos muestra el browse.
Reemplazando al comando BROWSE
Si lo deseamos, podemos dejar de usar el comando BROWSE y
en su reemplazo usar el comando SELECT sin la cláusula INTO
CURSOR. Tal y como podemos ver en la Captura 3., el
resultado es el mismo, pero es muchísimo más fácil trabajar con
el comando SELECT que con el comando BROWSE.
Desde luego que si estás muy acostumbrado a usar el comando
BROWSE al principio te costará el cambio, pero en muy poco
tiempo notarás la diferencia y lo conveniente que te resulta usar
el comando SELECT en su lugar.
Actualizando un cursor
Algo que te debe quedar muy en claro es que cuando cambias el
contenido de un cursor eso solamente le afecta al cursor y
nunca a su tabla subyacente. Por ejemplo, el REPLACE que
hicimos en la Captura 2. solamente afectó a MiCursor1, la
tabla CUENTAS, de la cual provino MiCursor1, ni siquiera se
enteró que se hizo ese cambio.
Una analogía sería tener un libro y una fotocopia de ese libro.
Puedes escribir todo lo que quieras en la fotocopia que el libro
jamás se cambiará. Lo que haces en la fotocopia es
independiente de lo que haces en el libro. Lo mismo ocurre con
el cursor y la tabla de la cual proviene. Los cambios realizados
en el cursor no se reflejan en la tabla, nunca, jamás.
Actualizando una tabla con los datos de un cursor
Para que el contenido de la tabla refleje lo que se cambió en
el cursor, debes actualizar la tabla. Eso nunca se realiza
automáticamente. Si quieres que el contenido de la tabla
cambie, sí o sí tú tendrás que escribir los comandos que
realizarán esa tarea.
Listado 5. Actualizando una tabla con el contenido de un cursor
1 SELECT MiCursor1
2
M.CLI_IDENTI = CLI_IDENTI
3
M.CLI_NOMBRE = CLI_NOMBRE
4 M.CLI_TELEFO = CLI_TELEFO
5 M.CLI_EMAILX = CLI_EMAILX
6
7 USE MiTabla IN 0
8 SET ORDER TO TAG CLI01 && Ordenada según la columna CLI_IDENTI
9
SEEK M.CLI_IDENTI
10
11 IF !FOUND() THEN
12 INSERT INTO CLIENTES (CLI_IDENTI, CLI_NOMBRE, CLI_TELEFO, CLI_EMAILX) VALUES (M
13 ELSE
14 UPDATE
15 CLIENTES
SET
16 CLI_NOMBRE = M.CLI_NOMBRE,
17 CLI_TELEFO = M.CLI_TELEFO,
18 CLI_EMAILX = M.CLI_EMAILX
19 ENDIF
20
21
22 USE
23
24
¿Qué se hizo en el Listado 5.?
Primero, el contenido del cursor se colocó en variables de
memoria (no es obligatorio hacerlo así, pero el código fuente
queda más ordenado). Segundo, se abrió la tabla CLIENTES y se
buscó el Identificador del cliente en esa tabla (ese Identificador
debe estar en una columna autoincremental para mejores
resultados). Tercero, si no se encontró el Identificador del cliente
eso significa que no existe ese cliente y por ello se realizó el
INSERT correspondiente. Si el Identificador del cliente existía
entonces solamente hay que actualizar las demás columnas con
el comando UPDATE. Cuarto, se cerró la tabla .DBF
La tabla CLIENTES estuvo abierta un tiempo mínimo, el
estrictamente necesario para insertarle una fila o para actualizar
una fila. Esa, es la forma correcta de hacerlo.
La forma correcta de usar una tabla .DBF
Si ya llevas un tiempo usando tablas .DBF y no las has usado de
forma correcta entonces seguramente has tenido problemas de
tablas corruptas o de índices corruptos o de pérdidas de datos.
Tales cosas jamás ocurren cuando usas un buen motor SQL, por
ejemplo Firebird pero es algo a lo que estás expuesto al usar
tablas .DBF
Sin embargo, gran parte de esos problemas se pueden evitar o
al menos disminuirlos en gran medida y es que como en todo,
hay una forma correcta y muchas formas incorrectas de hacer
las cosas.
La forma correcta de usar tablas .DBF es manteniéndolas
abiertas el menor tiempo posible. Y es que la corrupción
solamente ocurre cuando la tabla está abierta, jamás ocurre
cuando la tabla está cerrada.
¿Y cómo conseguimos tener a una tabla .DBF abierta el menor
tiempo posible?
Pues pasando su contenido a un cursor y trabajando solamente
con ese cursor. Si es necesario actualizar la tabla entonces se
la abre, se la actualiza lo más rápidamente que se pueda, y se la
cierra. Esto, no evitará totalmente que se corrompa, pero
disminuirá grandemente la probabilidad de que eso ocurra.
La corrupción de una tabla .DBF o de sus índices ocurre cuando
hay un corte de la energía eléctrica o un corte de la conexión
con el Servidor o cuando se reseteó la computadora sin antes
haber cerrado la tabla.
Por lo tanto, lo correcto es:
Abrir la tabla .DBF
Pasar su contenido (todo o en parte, según se necesite) a
un cursor
Cerrar la tabla .DBF
Utilizar el cursor para todas las operaciones
Si hay que actualizar la tabla .DBF entonces:
Abrir la tabla .DBF
Actualizar su contenido (quizás con el contenido que tiene
el cursor)
Cerrar la tabla .DBF
Haciendo así, muy raramente se te volverá a corromper una
tabla o sus índices. Aunque claro, lo mejor es pasarte a un buen
motor SQL porque en ese caso la probabilidad de corrupción es
prácticamente cero. Al autor de este blog en los más de 8 años
que lleva usando Firebird jamás le ocurrió algún problema. Y
cuando usaba tablas .DBF sí y en varias ocasiones.
El problema con las grillas
Cuando en la propiedad RecordSource de una grilla (grid, en
inglés) se pone el nombre de un cursor a veces surgen
problemas.
¿Por qué?
Este es un error realmente insidioso para quienes no entienden
como funcionan los cursores. Y que puede hacerles perder
mucho tiempo hasta que encuentran la solución. Y muchos
aunque saben cual es la solución no saben cual es el problema.
El problema es que si en la propiedad RecordSource de tu
grilla escribes algo como:
Listado 6. Dándole un valor a la propiedad RecordSource de
una grilla
1 [Link] = "MiCursor1"
El VFP definirá a la grilla con los datos que encuentre en
el cursor llamado MiCursor1. Pero … ¿recuerdas que
un cursor se guarda en el disco duro como un archivo con
extensión .TMP? Entonces…
Listado 7. La grilla pierde su configuración en este caso
1 SELECT CUE_NUMERO, CUE_NOMBRE FROM CUENTAS INTO CURSOR MiCursor1
2
3 [Link]()
4
5 SELECT CUE_NUMERO, CUE_NOMBRE FROM CUENTAS INTO CURSOR MiCursor1
6
[Link]()
7
En ambos SELECT se le dice al VFP que el cursor se
llama MiCursor1. Y por eso mucha gente cree que la
configuración de la grilla debería continuar intacta y se
desesperan al descubrir que no es así. Lo que ocurre es que
aunque ambos cursores se llaman MiCursor1 en el disco duro
se grabaron como dos archivos .TMP con nombres distintos. En
consecuencia, el VFP los trata como si fueran tablas totalmente
distintas … y la grilla pierde su configuración.
Este problema y sus posibles soluciones están mejor explicados
en el artículo:
Error con la grilla, se pierde su configuración
Conclusión:
Los cursores son una construcción muy poderosa de la cual
disponemos en VFP y que no se dispone en otros lenguajes de
programación. Nos permiten crear aplicaciones muy seguras,
muy confiables, y muy robustas. Pero para que todo eso sea
posible debemos saber manejarlos correctamente.
Tenemos tres clases de cursores. Los que creamos nosotros
manualmente con el comando CREATE CURSOR (éstos siempre
son de lectura/escritura) y los que crea el VFP cuando
ejecutamos el comando SELECT para realizar una consulta
(éstos pueden ser de sólo lectura o de lectura/escritura).
Los cursores se guardan temporalmente en el disco duro como
archivos con extensión .TMP, por eso cuando se cierra
un cursor es eliminado automáticamente del disco duro. Por lo
tanto los podemos usar para guardar datos temporales, pero no
para guardar datos permanentes. Los datos permanentes
siempre se deben guardar en una tabla.
Hay una forma correcta de usar tablas DBF (y muchas formas
incorrectas). La forma correcta es ABRIR, PROCESAR, CERRAR.
Las tablas DBF deben estar abiertas el menor tiempo posible,
porque mientras están abiertas existe la posibilidad de que se
corrompan. Si están cerradas, están inmunes a la corrupción
(desde luego que algún idiota podría borrarlas del disco duro o
cambiarles el nombre, o la extensión, u otras idioteces, pero eso
es algo externo a nuestra aplicación y de lo cual no solemos
tener la posibilidad de evitar).
Deberías dedicarte a entender bien a los cursores, quizás
releyendo este artículo varias veces. Vale la pena.
De DBF a SQL (4). Ejemplos de usar
SELECT con una sola tabla
6 AGOSTO, 2018 / WROV
El comando SELECT es muy, pero muy poderoso. Si aún no lo
conoces, te maravillará todo lo que puedes hacer con él. Pero
por ser tan poderoso tiene sus complejidades y no es tan fácil
conocerlo bien. Para ayudarte con esa tarea en este artículo se
mostrarán varios ejemplos, para que lo vayas entendiendo ¡¡¡y
practicando!!!
Primero, aunque el nombre del comando sea SELECT deberías
pensar en él como un browse muy mejorado, un comando que
te muestra lo que le pides.
La sintaxis básica es:
Listado 1. La sintaxis más básica del comando SELECT
1 SELECT
2 MiColumna1,
3 MiColumna2,
4 ...,
5 MiColumnaN
FROM
6 MiTabla
7
En donde:
SELECT es el nombre del comando.
MiColumna1, MiColumna2, MiColumnaN son los
nombres de las columnas que deseamos obtener
FROM es la cláusula a continuación de la cual se escribe el
nombre de la tabla
MiTabla es el nombre de la tabla cuyas columnas
deseamos obtener
Captura 1. Mostrando las columnas CUE_NUMERO y
CUE_NOMBRE de la tabla CUENTAS
La tabla CUENTAS tiene muchas más columnas pero solamente
observamos a dos de ellas porque eso fue lo que le pedimos
al VFP. Si queremos ver a tres columnas entonces deberíamos
escribir sus tres nombres. ¿Y si queremos ver a todas las
columnas y la tabla tiene muchísimas columnas? Tendríamos
dos formas de resolver eso: la forma larga sería escribir los
nombres de las ochopotocientas columnas, la forma corta es
escribir un asterisco. El asterisco significa “muéstrame todas las
columnas”.
Captura 2. Mostrando a todas las columnas de la tabla
CUENTAS
Como puedes ver, al escribir un asterisco nos mostró todas las
columnas de la tabla CUENTAS (tiene más columnas, que no se
muestran en la Captura 2. por falta de espacio).
El problema de usar el asterisco
Tú podrías pensar: “bueno, ya que con el asterisco obtendré a
todas las columnas, voy a usarlo siempre”.
Podrías hacerlo, pero en general no sería una buena idea. ¿Por
qué? Porque por la red estarían viajando datos innecesarios y
eso hará que tu SELECT sea más lento de lo que debería ser.
Por ejemplo, si tu fila (registro) tiene un tamaño de 200 bytes y
tú solamente necesitas conocer a dos columnas cuyo tamaño
total es de 30 bytes estarías haciendo viajar a 170 bytes más de
los que necesitas y por cada fila, innecesariamente. En una tabla
que tiene 10.000 filas eso implicaría que 170 bytes por 10.000
filas, igual a 1.700.000 bytes estarían viajando por la red
inútilmente. No es lo que un buen profesional haría.
Realizando operaciones matemáticas
Con el SELECT no solamente puedes ver el contenido de las
columnas de una tabla, también puedes realizar las operaciones
matemáticas que necesites. Para eso, suele ser conveniente
elegir a una tabla que tenga una sola fila. ¿Por qué? porque si la
tabla tiene muchas filas entonces el resultado se mostrará en
cada una de esas filas.
Captura 3. Hallando el resultado de sumar 2 más 5
Como la tabla CONTROL tiene una sola fila, el resultado se
obtiene en una fila y en una columna de esa fila.
Realizando varias operaciones matemáticas
No es obligatorio que escribas una sola operación matemática,
puedes escribir varias operaciones matemáticas si lo deseas.
Captura 4. Obteniendo el resultado de varias operaciones
matemáticas
Usando funciones en el SELECT
Cualquier función, sea interna del VFP o que hayas escrito tú,
también puedes escribir en un SELECT.
Captura 5. Usando las funciones LEN() y ALLTRIM() en el
SELECT
Poniéndoles alias a las columnas
Habrás notado que los nombres de las columnas
son Exp_1, Exp_2, Exp_3, etc. ¿Por qué? Porque como no
estamos usando columnas de una tabla entonces el VFP les
asigna un nombre por su propia cuenta. Pero tenemos la
posibilidad de darles a esas columnas los nombres que
deseemos. Ese nombre que nosotros especificamos se
llama alias y se escribe a continuación de la palabra AS.
Captura 6. Poniéndoles nombres (alias) a las columnas
Poniéndoles condiciones a las filas
Si no queremos obtener todas las filas de una tabla sino
solamente aquellas filas que cumplen con una condición
entonces usamos la cláusula WHERE para especificar esa
condición.
Captura 7. Obteniendo todas las filas donde la primer
letra de la columna CUE_NOMBRE sea una “A”.
Captura 8. La columna que establece la condición no está
visible
Si te fijas en la Captura 8. verás que la condición para que una
fila sea mostrada es que el valor de la columna CUE_ASENTA sea
igual a 2. Pero la columna CUE_ASENTA no es mostrada. ¿Por
qué? Porque no se le pidió al VFP que la mostrara.
Poniéndoles alias a las columnas de una tabla
En la Captura 6. hemos visto como ponerles alias a las
columnas cuyo nombre inventaba el VFP. Pero eso no es
exclusivo de tales columnas, a las columnas de una tabla
también les podemos poner alias.
Captura 9. Poniéndoles alias a los nombres de las
columnas de una tabla
En la tabla CUENTAS, la columna se llama CUE_NUMERO pero en
nuestro SELECT la hemos llamado NUMERO_DE_LA_CUENTA.
Análogamente, a la columna que en la tabla CUENTAS se llama
CUE_NOMBRE nosotros la hemos llamado
NOMBRE_DE_LA_CUENTA. Esto, es muy útil cuando le
mostramos el resultado de un SELECT a los usuarios. Para
nosotros puede ser muy fácil saber lo que significa TVAXSUC
pero para ellos será mucho más entendible si leen
“Total_ventas_anuales_por_sucursal”.
Estableciendo condiciones múltiples
La condición que establecemos a continuación de la cláusula
WHERE puede ser bastante complicada, eso ya dependerá de
nuestras necesidades. Por ejemplo, podemos usar los
operadores lógicos AND, OR, y NOT en ella.
Captura 10. Estableciendo una condición múltiple en la
cláusula WHERE
El VFP solamente te mostrará las filas que cumplan con la
condición establecida a continuación de la cláusula WHERE. En
este ejemplo, aquellas filas en donde el valor de la columna
CUE_NUMERO empieza con 2 y la columna CUE_NOMBRE
empieza con una letra “P”.
Usando el operador lógico IN
Al operador lógico OR seguramente ya lo conoces muy bien y lo
has usado un montón de veces, pero en SQL tenemos un
operador que es mejor aún: el operador IN. Sirve para escribir
menos.
Captura 11. Usando el operador lógico IN
La condición después del WHERE también se podría haber
escrito como: LEFT(CUE_NOMBRE, 2) = “AC” OR
LEFT(CUE_NOMBRE, 2) = “CU” OR LEFT(CUE_NOMBRE, 2) = “PR”
y los resultados obtenidos serían exactamente los mismos. Pero
al usar el operador IN se escribe menos. Y cuanto mayor sea la
cantidad de OR que existen, mucho mayor será la conveniencia
de usar IN.
Usando la sub-cláusula BETWEEN
Escribir BETWEEN es una forma abreviada de escribir:
MiColumna >= MiValor1 AND MiColumna <= MiValor2. Por
ejemplo, para ver todos los nombres de las cuentas cuyas dos
letras iniciales se encuentren entre “CL” y “CR”, podríamos
escribir:
Captura 12. Usando la sub-cláusula BETWEEN
Podemos usar BETWEEN no solamente para columnas de tipo
carácter, sino también para columnas de tipo numérico, o de
tipo fecha, o de cualquier otro tipo.
Por ejemplo, es válido escribir:
WHERE TOTAL_VENDIDO BETWEEN 200 AND 900
WHERE FECHA_VENTA BETWEEN {^2018/02/01} AND
{^2018/02/25}
En el caso de las fechas, hay que escribirlas como se muestra en
la línea de arriba. O sea, usando llaves y el acento circunflejo y
especificando primero el año, luego el mes, y finalmente el día.
Allí, le estamos pidiendo al VFP que nos muestre todas las filas
cuyas fechas de venta estén entre el 1 de febrero de 2018 y el
25 de febrero de 2018.
Evitando que nos muestre filas duplicadas
A veces, en el resultado que obtenemos de un SELECT vemos
filas que son idénticas. Si queremos ver solamente a una de
esas filas (la primera que aparece) entonces podemos usar la
cláusula DISTINCT.
Captura 13. Evitando que en el resultado obtenido se
muestren filas duplicadas
Si comparas la Captura 12. con la Captura 13. verás que en
la Captura 12. había dos cuentas con el nombre CREDITOS
mientras que en la Captura 13. solamente hay una. También
notarás que en este último caso solamente se muestra una
columna ¿por qué? porque si se hubieran escrito los nombres de
ambas columnas entonces la cláusula DISTINCT no hubiera
funcionado, ya que necesita que todas las columnas sean
idénticas. Si en alguna columna hay un valor distinto, entonces
ambas filas no son iguales. DISTINCT por lo tanto es útil cuando
una fila es idéntica a otra (u otras) y solamente queremos ver
una de ellas, no todas las que son completamente iguales.
Conclusión:
El comando SELECT es muy, pero muy poderoso. Hasta ahora,
solamente hemos visto una pincelada de su inmenso poder. Si
aún no lo dominas entonces sería muy conveniente que
practicaras los ejemplos mostrados arriba con tus propias
tablas, una vez y otra vez y otra vez, hasta compenetrarte
completamente de su uso. El tiempo que emplees en aprender
será un tiempo muy bien empleado.
De DBF a SQL (5). Las
funciones agrupadas
7 AGOSTO, 2018 / WROV
Hay algunas tareas que se realizan tan pero tan frecuentemente
que se decidió incluirlas en el estándar SQL. Esas tareas son:
Sumar
Contar
Hallar un promedio
Hallar el mayor valor
Hallar el menor valor
Las funciones que realizan esas tareas se llaman:
SUM(). Para sumar
COUNT(). Para contar
AVG(). Para hallar un promedio
MAX(). Para hallar el mayor valor
MIN(). Para hallar el menor valor
Como típicamente esas funciones involucran a más de una fila, o
sea a un grupo de filas, se las denomina funciones agrupadas.
Para usarlas, dentro del paréntesis debemos escribir sus
argumentos.
Captura 1. Hallando el total de sumar la columna
MVC_TOTALX
Dentro de la función agrupada podemos escribir varias
columnas, si queremos. Algo como:
Captura 2. Escribiendo dos columnas en la función
agrupada SUM()
También, como ya vimos en un artículo anterior, podemos usar
un alias para cambiar el nombre de la columna.
Captura 3. Poniéndole un alias al nombre de la columna
En nuestro SELECT además de usar una función
agrupada también podemos establecer una condición con la
cláusula WHERE.
Captura 4. Estableciendo una condición a la función
agrupada
En el ejemplo de la Captura 4. se halló el total vendido en el
año 2.013
Para saber cuantas filas tiene una tabla podemos escribir:
Captura 5. Para hallar la cantidad de filas de una tabla
Desde luego que también aquí podemos poner una condición.
Por ejemplo, para saber cuantas de esas filas corresponden al
año 2.013 se podría escribir:
Captura 6. La cantidad de filas correspondientes al año
2.013
Para saber cual es el mayor valor que existe en la columna
MVC_TOTALX se puede escribir:
Captura 7. El mayor valor encontrado en la columna
MVC_TOTALX
Y también, si lo deseamos, podemos observar al mismo tiempo
los valores devueltos por varias funciones agrupadas, por
ejemplo:
Captura 8. Mostrando varias funciones agrupadas al
mismo tiempo
Y así, en una sola fila estamos observando los valores devueltos
por las funciones agrupadas.
Conclusión:
Algunas tareas son tan comunes que se las estableció en el
estándar SQL. Como normalmente involucran a varias filas se
las denomina funciones agrupadas. Sus nombres son: SUM(),
COUNT(), AVG(), MAX(), MIN(). Algunos motores SQL tienen
más funciones agrupadas pero esas cinco son las que existen
en todos los motores.
De DBF a SQL (6). Agrupando filas
8 AGOSTO, 2018 / WROV
Ya hemos visto como usar a las funciones agrupadas y su
utilidad, pero hasta ahora involucraban a toda la tabla,
completa. Ahora veremos algo mucho más útil: agrupando a las
filas que tienen algo en común. Supongamos que tenemos esta
tabla:
Captura 1. Las filas de la tabla VENTASCAB
Donde VTC_FECHAX es la fecha de la venta, VTC_CODCLI es el
código del cliente, VTC_CODVEN es el código del vendedor, y
VTC_TOTALX es el total vendido (no es correcta esta estructura,
pero se la utiliza aquí por su facilidad de ser entendida).
Y ahora queremos saber el total vendido en cada día:
Captura 2. El total de ventas de cada día
Ahora nos interesa saber el total vendido a cada cliente
Captura 3. El total vendido a cada cliente
Ahora, queremos saber el total vendido por cada vendedor
Captura 4. El total vendido por cada vendedor
Ahora lo que nos interesa es conocer lo vendido en cada mes.
Captura 5. El total vendido en cada mes
Si has prestado atención, habrás notado que hay una diferencia
en la cláusula GROUP BY. Mientras que en todos los ejemplos
anteriores se escribía el nombre de una columna aquí se escribió
un número. ¿Por qué eso? Porque el VFP no permite que se
escriba una función después de la cláusula GROUP BY, pero
siempre se puede escribir un número ahí si se desea. El número
1 indica que se agrupará por la primera columna.
También se puede agrupar por más de una columna.
Captura 6. Agrupando por dos columnas al mismo tiempo
En este caso además de la función agrupada SUM() también
se escribió la función agrupada COUNT() ¿por qué eso? Para
de un vistazo saber cuales códigos de cliente y códigos de
vendedor se repiten y además la cantidad de veces que se
repiten. Lo que nos dice la Captura 6. es que el vendedor 002
le hizo dos ventas al cliente 00009 y que el vendedor 003 le hizo
tres ventas al cliente 00010. En todos los demás casos cada
vendedor le hizo una sola venta a su cliente.
Y así como usamos a la función agrupada SUM() también
podemos usar a cualquiera de las otras funciones agrupadas.
Por ejemplo, si queremos saber el promedio de ventas de cada
día podríamos escribir:
Captura 7. El promedio de ventas de cada día, el total
vendido, y la cantidad de ventas realizadas en ese día.
Todas las columnas deben escribirse después de la
cláusula GROUP BY
Un error que muchos principiantes cometen es escribir columnas
después del SELECT pero no escribirlas después del GROUP BY.
Captura 8. Un error debido a que falta la columna
VTC_CODVEN después de la cláusula GROUP BY
Como puedes ver en la Captura 8. después del SELECT se
escribieron los nombres de dos columnas pero después del
GROUP BY se escribió solamente el nombre de una columna.
Eso, no tiene sentido y por eso el VFP se enoja. ¿Y por qué no
tiene sentido? Porque la agrupación se hace por valores
idénticos y la concatenación de dos columnas jamás será igual
al valor que tiene una sola columna. En nuestro ejemplo, la
concatenación de VTC_FECHAX y VTC_CODVEN jamás será igual
a VTC_FECHAX solamente.
Claro que tú podrías pensar: ¿y por qué simplemente no le
agrega la columna (o columnas) que faltan? No puede hacerlo
porque el orden de las columnas en el SELECT puede ser distinto
al orden de las columnas en el GROUP BY.
Captura 9. El orden de las columnas después del SELECT
es distinto al orden de las columnas después del GROUP
BY
Como ves en la Captura 9. el orden de las columnas en el
SELECT es distinto al orden de las columnas en el GROUP BY.
Eso es correcto, no hay problema ahí, se puede hacer si se
desea. El requisito es que todas las columnas que aparecen en
el SELECT también aparezcan en el GROUP BY, pero el orden en
el cual aparecen puede ser muy distinto.
Las funciones agrupadas no deben escribirse después del
GROUP BY
El VFP sabe muy bien lo que hacen las funciones agrupadas y
por eso escribirlas en el GROUP BY es redundante y no está
permitido. ¿Por qué no? Porque escribirlas ahí solamente podría
prestarse a confusión pues ningún beneficio se obtendría. Por lo
tanto, lo sabio es impedirte que escribas algo que ningún
beneficio te puede dar y sí algunos problemas.
Conclusión:
Podemos agrupar las filas que tienen datos en común para ver
un resumen de esos datos. No es obligatorio usar funciones
agrupadas en tales casos aunque es muy común usarlas.
Muchos de los informes que los clientes te pedirán requerirán
que les muestres datos agrupados. Con SQL es muy fácil
realizar esa tarea, tal y como habrás visto en este artículo.
De DBF a SQL (7). Poniéndoles
condiciones a los grupos
9 AGOSTO, 2018 / WROV
En un artículo anterior ya hemos visto como se usa la cláusula
WHERE. Su utilidad es que nos permite condicionar cuales filas
de la tabla se mostrarán y cuales no. Hay una cláusula similar
pero que se refiere solamente a las filas agrupadas: HAVING.
Así como WHERE involucra a todas las filas de una tabla,
HAVING involucra a las filas obtenidas al ejecutar la cláusula
GROUP BY. Veamos un ejemplo:
Captura 1. Hallando el total vendido en cada día
En la Captura 1. estamos viendo el total que se vendió en cada
día. Pero ¿y si solamente queremos ver aquellas filas cuyo total
sea mayor que 1.000? Para eso usaremos a la cláusula HAVING.
Captura 2. Mostrando las fechas cuyo total vendido es
mayor que 1.000
Como ya habrás observado, en la Captura 2. solamente se
encuentran las filas que obtuvimos en la Captura 1. y cuyos
totales vendidos son mayores que 1.000
Por lo tanto, primero se obtienen los datos agrupados (los que
se observan en la Captura 1.) y luego se le pone una condición
a esos datos (como se puede ver en la Captura 2.)
Hay veces en que la condición se puede poner después de la
cláusula WHERE o después de la cláusula HAVING,
indistintamente.
Por ejemplo, si solamente queremos ver las fechas que
corresponden al mes de febrero podríamos escribir:
WHERE MONTH(VTC_FECHAX) = 2
o también podríamos escribir:
HAVING MONTH(VTC_FECHAX) = 2
Aquí, escribir una u otra da lo mismo, se obtendrán exactamente
los mismos resultados. Sin embargo, cuando usamos funciones
agrupadas, no es lo mismo.
Captura 3. Escribir una función agrupada después del
WHERE causa un error
Como en la condición se está usando una función agrupada,
es obligatorio escribir esa condición después del HAVING, tal y
como se puede ver en la Captura 2.
Desde luego que se puede tener una cláusula WHERE y también
una cláusula HAVING. Por ejemplo, si solamente se desean ver
las ventas del mes de febrero cuyos totales vendidos sean
mayores que 1.000, se puede escribir:
Captura 4. Una condición en el WHERE y otra condición
en el HAVING
En el WHERE se eligió el mes, en el HAVING que el total de las
ventas diarias fuera mayor que 1.000, y tanto en uno como en
otro caso se pueden usar los operadores lógicos AND, OR, NOT,
IN, si fuera necesario.
Conclusión:
El uso de la cláusula HAVING es idéntico al uso de la cláusula
WHERE, la diferencia radica en que mientras WHERE se refiere a
todas las filas de la tabla, HAVING se refiere solamente a las filas
obtenidas al agrupar con la cláusula GROUP BY.
Por eso, una práctica común es escribir un SELECT con un
GROUP BY pero sin un HAVING. Después de observar el
resultado obtenido se escribe la cláusula HAVING
correspondiente. Si se la necesita, claro. ¿Y cuándo se la
necesita? Cuando las filas obtenidas con la cláusula GROUP BY
deben cumplir alguna condición.
En la Captura 2. esa condición fue que el Total de lo que se
vendió en cada día sea mayor que 1000.
Si comparas la Captura 1. con la Captura 2. verás que en
la Captura 2. solamente aparecieron las filas cuyo Total
vendido fue mayor que 1000.
Tanto en el WHERE como en el HAVING se pueden poner
condiciones extremadamente complejas, usando los operadores
lógicos AND, OR, NOT, e IN.
En siguientes artículos de esta serie verás condiciones mucho
más complejas que las que viste hasta ahora.
De DBF a SQL (8). Ordenando las filas
10 AGOSTO, 2018 / WROV
En SQL conseguir que las filas se muestren ordenadas según el
criterio que deseamos es extremádamente fácil, inclusive
mucho más fácil que en DBF.
Lo único que debemos hacer es agregarle la cláusula ORDER BY
a nuestro SELECT y a continuación la columna (o las columnas,
porque pueden ser varias) según la cual se ordenarán las filas
de la tabla.
Por ejemplo, la tabla VENTASCAB está originalmente ordenada
por fechas, como podemos ver a continuación:
Captura 1. La tabla VENTASCAB sin especificar el orden
de las filas
Si lo que queremos es verla ordenada según el Código del
Cliente entonces escribiríamos:
Captura 2. La tabla VENTASCAB ordenada según los
códigos de los clientes
Como puedes ver en la Captura 2. ahora las filas se encuentran
ordenadas según los códigos de los clientes. Con la cláusula
ORDER BY le hemos ordenado al VFP que haga esa tarea.
De la misma manera, si queremos ordenar a las filas según los
códigos de los vendedores, escribiríamos:
Captura 3. La tabla VENTASCAB ordenada según los
códigos de los vendedores
También podemos ordenar según varias columnas. Por ejemplo
queremos ordenar por Código del Vendedor y por Total Vendido.
En ese caso:
Captura 4. La tabla VENTASCAB ordenada por los códigos
de los vendedores y por los totales vendidos
Si comparas la Captura 3. con la Captura 4. verás que esta
última está ordenada según dos columnas: VTC_CODVEN y
VTC_TOTALX.
Hasta ahora siempre hemos ordenado de menor a mayor, pero
si lo deseamos también podemos ordenar de mayor a menor.
Para eso a continuación del nombre de la columna escribimos la
palabra DESCENDING.
Captura 5. Ordenando las filas de la tabla VENTASCAB
por Código del Vendedor (en forma descendente) y por
Total Vendido (en forma ascendente)
Como puedes observar en la Captura 5. el Código del Vendedor
va de mayor a menor, mientras que el Total Vendido va de
menor a mayor.
Si queremos, también podemos lograr que ambas columnas se
ordenen de forma descendente.
Captura 6. Ambas columnas se ordenan de mayor valor a
menor valor
Como puedes ver en la Captura 6., el Código del Vendedor va
de mayor valor a menor valor y el Total Vendido por cada
Vendedor también va de mayor valor a menor valor.
Las computadoras actuales son tan rápidas y las redes son tan
veloces que si tu tabla tiene unas pocas miles de filas entonces
no será necesario que crees archivos de índice para acelerar las
consultas. Sin embargo, si tu tabla tiene muchos miles de filas o
millones de filas, crear tales archivos índices será necesario para
obtener la respuesta del SELECT en un muy corto período de
tiempo.
Conclusión:
En SQL conseguir que las filas se muestren ordenadas según el
criterio que queremos es muy fácil, simplemente se escribe la
cláusula ORDER BY y a continuación la columna (o las columnas,
porque pueden ser varias) por la cual se ordenarán las filas. Si
no especificamos la dirección el orden siempre será de menor a
mayor. Si queremos que el orden sea de mayor a menor
entonces tenemos que agregar la palabra DESCENDING a
continuación del nombre de la columna respectiva.
De DBF a SQL (9). Uniendo tablas
11 AGOSTO, 2018 / WROV
Algo buenísimo que tiene SQL es que nos permite unir el
resultado obtenido de un SELECT con el resultado obtenido de
otro SELECT, y eso puede ser súper útil en muchas ocasiones.
Por ejemplo, tenemos a nuestra ya muy conocida tabla
VENTASCAB.
Captura 1. Las filas de nuestra tabla de Ventas
y tenemos también la tabla COBRANZASCAB, donde guardamos
los datos de las cobranzas realizadas.
Captura 2. Las filas de nuestra tabla de Cobranzas
Y ahora quisiéramos saber todo lo que se le vendió y todo lo que
se le cobró a cada cliente. Para obtener esos datos podemos
escribir:
Captura 3. Uniendo el SELECT de las ventas con el
SELECT de las cobranzas
Como el texto en la Captura 3. se ve pequeño, podrías hacer
clic en esa imagen para verlo más grande.
Los nombres de las columnas en el resultado obtenido siempre
son los que tiene el primer SELECT. Como ya sabes, puedes usar
alias si quieres para cambiar el nombre de esas columnas. En el
resultado que obtuvimos se encuentran todas las filas de la
tabla VENTASCAB y también todas las filas de la tabla
COBRANZASCAB. En cualquiera de los SELECT puedes usar las
cláusulas que ya conoces: (DISTINCT, WHERE, GROUP BY,
HAVING, ORDER BY) y también otras cláusulas que aún no
aparecieron en estos artículos.
¿Y cómo se sabe si una fila proviene de la tabla VENTASCAB o si
proviene de la tabla COBRANZASCAB?
Para tener ese conocimiento se agregó una columna virtual, una
columna que no existe en ninguna de esas tablas, y que recibió
el nombre de “MiTabla”, la cual tiene el valor 1 cuando la fila
proviene de la tabla VENTASCAB y el valor 2 cuando la fila
proviene de la tabla COBRANZASCAB.
Captura 4. La columna virtual “MiTabla” para saber cual
es el origen de cada fila
Agregar esa columna virtual no siempre es necesario, solamente
se la necesita cuando se debe conocer de cual tabla proviene
cada fila. Y no necesariamente debes definirla como numérica,
podrías poner también letras, fechas, o lo que prefieras en ella.
¿Qué requisitos se deben cumplir para poder unir un SELECT con
otro SELECT?
Es obligatorio que: a) la cantidad de columnas y b) los tipos de
cada columna, sean idénticos. Por ejemplo, si el primer SELECT
tiene 4 columnas y el segundo SELECT tiene 5 columnas,
entonces la UNION no será permitida, porque esas cantidades
son diferentes.
Si la primera columna del primer SELECT es de tipo numérico y
la primer columna del segundo SELECT es de tipo carácter, la
UNION tampoco será permitida. ¿Por qué no? porque una es de
tipo numérico y la otra es de tipo carácter. Imposible. Ambas
deben ser del mismo tipo.
Desde luego que esto afecta a todas las columnas, no solamente
a la primer columna. El tipo de la primera columna debe ser
idéntico en ambos SELECT, el tipo de la segunda columna debe
ser idéntico en ambos SELECT. Y así sucesivamente.
Entonces ahora, para ver de forma ordenada lo vendido y lo
cobrado a cada cliente, podríamos escribir:
Captura 5. El resultado de la UNION de ambos SELECT,
mostrado en forma ordenada por los códigos de los
clientes y por las tablas de las cuales provienen las filas
NOTA: La cláusula ORDER BY debe escribirse el en último
SELECT.
Entonces, para cada cliente primero se ven todas las ventas y
luego se ven todas las cobranzas. Desde luego que no es
obligatorio hacerlo así, podrías para cada cliente ordenar las
filas por las fechas, por ejemplo. O por los totales, o por lo que
se te ocurra.
Además, no es obligatorio que los SELECT correspondan a tablas
distintas, podría ser a la misma tabla, si quieres. Algo como:
Captura 6. Estableciendo la UNION de un SELECT con otro
SELECT idéntico
Esto por ejemplo puede ser útil cuando necesitas imprimir dos
copias del mismo informe.
¿La UNION solamente puede establecerse entre dos SELECT?
No, puedes escribir la cláusula UNION varias veces, para unir a
varios SELECT. Por ejemplo:
Captura 7. Uniendo a tres SELECT
Los SELECT desde luego que pueden provenir de una tabla o de
varias tablas. En la Captura 7. todos provienen de la misma
tabla, pero eso es solamente un ejemplo, podrían perfectamente
provenir de dos o de tres tablas distintas. Los requisitos, como
ya sabes, son que la cantidad de columnas sea siempre la
misma y que los tipos de las columnas sean idénticos.
Y también, como ya sabes, puedes emplear las cláusulas que ya
conoces en cualquiera de los SELECT. Por ejemplo, para ver
todas las ventas y todas las cobranzas realizadas al cliente cuyo
código es “00009”, se puede escribir:
Captura 8. Todas las ventas y todas las cobranzas que
fueron realizadas al cliente cuyo código es “00009”
Como ya dijimos, no es obligatorio que para especificar cual es
la tabla de origen escribamos un número, podemos escribir un
texto también, si así lo preferimos. Algo como:
Captura 9. Para saber si una fila proviene de la tabla
VENTASCAB o si proviene de la tabla COBRANZASCAB
En la Captura 9. es aún más fácil saber si una fila corresponde
a una venta o si corresponde a una cobranza. Desde luego que
no es obligatorio escribir la palabra completa, podrías poner una
“V” para representar a una venta y una “C” para representar a
una cobranza, si lo prefieres.
Algo más que seguramente has notado es que el comando no se
escribió en una sola línea, sino en varias líneas que finalizan con
un punto y coma. Esta es una práctica muy utilizada cuando el
comando es largo, porque escribir en varias líneas facilita la
lectura y la comprensión de lo leído.
Mostrando todas las filas de ambas tablas
¿Y qué ocurre si en las tablas tenemos filas duplicadas, se
mostrarán o no? Por defecto la cláusula UNION no devuelve las
filas duplicadas. Pero si se quiere que sí las devuelva entonces
se debe escribir la palabra ALL. Ejemplo:
Listado 1. Una UNION que muestra las filas duplicadas de ambas
tablas
1
SELECT
2
MiColumna1_1,
3 MiColumna1_2
4 FROM
5 MiTabla1
7 UNION ALL
9 SELECT
MiColumna2_1,
10
MiColumna2_2
11
FROM
12
MiTabla2
13
El Listado 1. mostrará todas las filas de ambas tablas, inclusive
las filas duplicadas también serán mostradas.
Conclusión:
En SQL se pueden unir los resultados de varios SELECT para
obtener un solo resultado que es la suma de los SELECT
involucrados. O sea, en el resultado final aparecerán todas las
filas del primer SELECT y también todas las filas del segundo
SELECT y también todas las filas del tercer SELECT (si existe,
claro), etc.
Los nombres de las columnas en el resultado final, siempre son
los nombres que esas columnas tienen en el primer SELECT.
Por defecto, las filas duplicadas no aparecen en el resultado
final, si se necesita que aparezcan hay que escribir: UNION ALL
Esta es una construcción que puede ser muy pero muy útil y sin
embargo es muy poco utilizada, probablemente porque es muy
poco conocida, o porque no se entiende bien como funciona, o
porque no se dan cuenta de todo lo que se puede lograr con
ella. Mi consejo es que trates de entenderla muy bien, porque te
servirá para ahorrar mucho tiempo en muchas ocasiones. He
visto gente que escribe procedimientos largos para obtener el
mismo resultado que podrían haber obtenido simplemente,
usando la cláusula UNION.
De DBF a SQL (10). Obteniendo las
primeras filas de una tabla
12 AGOSTO, 2018 / WROV
En ocasiones, no necesitamos obtener todas las filas de una
tabla sino solamente sus primeras filas. Para esos casos es muy
útil la cláusula TOP. Hay, sin embargo, un requisito que se debe
cumplir: en el SELECT se debe escribir la cláusula ORDER BY
para que también se pueda escribir la cláusula TOP. De lo
contrario, el VFP se enojará.
Captura 1. Todas las filas de la tabla VENTASCAB
Este SELECT nos mostró 18 filas. Si solamente queremos ver las
6 primeras filas, podemos escribir:
Captura 2. Viendo solamente las 6 primeras filas de la
tabla VENTASCAB
(Desde luego que TOP 6 es un ejemplo, podríamos escribir TOP
1, TOP2, TOP 15, o lo que sea que necesitemos).
Otra alternativa es decirle al VFP que el número que
especificamos a continuación de la cláusula TOP es un
porcentaje. Por ejemplo, la tabla VENTASCAB tiene 18 filas (tal y
como puedes ver en la Captura 1.). Si queremos ver el 50% de
esas filas (o sea, 9 filas) podemos escribir:
Captura 3. Viendo el 50% de las filas de la tabla
VENTASCAB (o sea, 9 filas)
¿Y qué pasará si queremos ver el 25% de las filas? O sea, 4.5
filas. ¿Qué hará el VFP, nos mostrará 4 filas ó 5 filas?
Captura 4. Viendo el 25% de las filas (como no es un
número entero, se redondea hacia arriba, por seguridad).
¿Por qué se redondeó hacia arriba? Porque si sobra una fila, la
podemos eliminar con el comando DELETE, en cambio si faltara
una fila no tendríamos solución. Por lo tanto, mejor es que sobre
y no que falte.
Otro ejemplo, queremos que nos muestre el 13% de las filas. El
13% de 18 es 2.34, entonces ¿mostrará 2 filas o mostrará 3
filas?
Captura 5. Mostrando el 13% de las filas de la tabla
VENTASCAB
Al igual que en la Captura 4. se redondeó hacia arriba. Mostró
3 filas. Y por la misma razón. Es preferible que sobre una fila
(porque podríamos borrarla con el comando DELETE, si fuera
necesario) antes de que falte una fila (porque eso no tendría
solución).
IMPORTANTE: El Visual FoxPro usa la palabra TOP para
devolver las primeras filas; otros motores SQL usan palabras
distintas, por ejemplo: FIRST. Deberías fijarte en la
documentación de tu motor SQL para saber cual es la palabra
que utiliza para devolver las primeras filas.
Conclusión:
Para las ocasiones en que solamente necesitamos ver una cierta
cantidad de filas, la cláusula TOP nos puede ayudar. A
continuación de la palabra TOP escribimos la cantidad de filas
que queremos obtener (1, 2, 3, 10, 15, 40, 500, ó lo que sea).
También tenemos la posibilidad de decirle que ese número es
un porcentaje. Por ejemplo: TOP 20 PERCENT le indica
al VFP que queremos ver el 20% de las filas.
De DBF a SQL (11). Relacionando una
tabla con otra tabla
13 AGOSTO, 2018 / WROV
En Visual FoxPro solamente hay una forma de relacionar una
tabla con otra tabla, en SQL tenemos cuatro formas.
Las cláusulas del comando SELECT que nos permiten relacionar
a una tabla con otra tabla son:
INNER JOIN. Requiere que para cada fila de la tabla
A exista una fila en la tabla B (esta es la forma que
también existe en Visual FoxPro).
LEFT JOIN. Los datos de la tabla de la izquierda (left, en
inglés) se muestran siempre, sí o sí. Los datos de la tabla
de la derecha se muestran solamente si se los pudo
emparejar, en caso contrario se muestra NULL
RIGHT JOIN. Los datos de la tabla de la derecha (right, en
inglés) se muestran siempre, sí o sí. Los datos de la tabla
de la izquierda se muestran solamente si se los pudo
emparejar, en caso contrario se muestra NULL
FULL JOIN. Se muestran todas las filas de ambas tablas,
poniendo NULL cuando no se puede emparejar
Aquí, la palabra “emparejar” significa que ambas tablas tengan
el mismo valor en las columnas por la cuales se están
relacionando.
La palabra JOIN significa “juntar”. Cuando se juntan de forma
interna (INNER JOIN) a cada fila de la tabla A le debe
corresponder una fila de la tabla B. Si una fila de la tabla A no
tiene una fila correspondiente en la tabla B entonces esa fila es
ignorada, no existe.
Cuando se juntan externamente (LEFT JOIN, RIGHT JOIN, FULL
JOIN) entonces no es requisito que para cada fila de la tabla
A exista una fila en la tabla B. Cuando no existe se coloca NULL
en esas columnas.
IMPORTANTE: Recuerda que en SQL la palabra NULL significa
“desconocido” o “no disponible”. No significa “nulo”, como
algunos piensan.
¿Cuál es el beneficio de juntar externamente?
Cuando juntamos internamente (mediante INNER JOIN)
obtenemos las filas que existen en ambas tablas. Cuando
juntamos externamente (mediante LEFT JOIN, RIGHT JOIN, FULL
JOIN) podemos también obtener datos que no existen en una de
las tablas.
Por ejemplo, si relacionamos “internamente” la tabla ALUMNOS
con la tabla EXÁMENES podemos mostrar los datos de todos los
alumnos que fueron examinados, pero solamente de ellos, no
podríamos saber cuales fueron los alumnos que por algún
motivo no fueron examinados. En cambio si las relacionamos
“externamente” podemos mostrar los datos de todos los
alumnos, hayan sido examinados o no.
Si relacionamos “internamente” las tablas PRODUCTOS y
VENTAS podemos mostrar los datos de todos los productos
vendidos. Pero no podríamos saber cuales fueron los productos
que no se vendieron. En cambio si relacionamos esas tablas
“externamente” sí podríamos saber cuales productos no se
vendieron.
Si relacionamos “internamente” la tabla CLIENTES con la tabla
COBRANZAS podríamos saber a cuales clientes se les cobró el
mes pasado, pero no sabríamos a cuales clientes no se les cobró
el mes pasado. En cambio si relacionamos a esas tablas
“externamente”, también podríamos saber a cuales clientes no
se les cobró.
Usando la cláusula JOIN para relacionar tablas
A continuación veremos algunos ejemplos de los resultados que
se obtienen al relacionar dos tablas entre sí. Para ello usaremos
la tabla CLIENTES y la tabla VENTASCAB (cabecera de las
ventas).
Captura 1. Las filas de nuestra tabla de CLIENTES
Si observaste la Captura 1. habrás notado que faltan los
códigos “00005” y “00011”, eso fue hecho a propósito para
entender los siguientes ejemplos.
Captura 2. Las filas de nuestra tabla VENTASCAB
La tabla VENTASCAB tiene 18 filas, como puedes verificar
contándolas.
Ejemplo 1. Relacionando internamente dos tablas
Captura 3. Relacionando internamente la tabla
VENTASCAB con la tabla CLIENTES
En la Captura 2. vimos que la tabla VENTASCAB tiene 18 filas,
sin embargo en la Captura 3. estamos viendo solamente 15
filas, ¿por qué eso? porque las filas en donde la columna
VTC_CODCLI tiene el valor “00005” o el valor “00011” no se
mostraron. Como no se pudo relacionar esas filas entonces
simplemente no se muestran.
Habrás notado también que se escribió sólo JOIN, no se escribió
INNER JOIN. ¿Por qué eso? porque como INNER JOIN es la forma
de relacionamiento más utilizada, es opcional escribir la palabra
INNER. Si se escribe solamente JOIN (sin antes escribir INNER,
LEFT, RIGHT, FULL) el SQL entiende que es un INNER JOIN. Por
eso, ya depende de cada uno escribir INNER JOIN o escribir
solamente JOIN. Lo puedes hacer como más te guste.
Ejemplo 2. Relacionando por la izquierda a dos tablas
Captura 4. Al relacionar mediante un LEFT JOIN, también
se muestran las filas de VENTASCAB que no se
emparejaron con CLIENTES
Ejemplo 3. Averiguando cuales son las filas
problemáticas
En la Captura 4. podemos ver que hay 3 filas que tienen NULL
en la columna CLI_NOMBRE y evidentemente eso está mal, se
debería corregir. Pero…¿y si la tabla no tiene 18 filas como en
ese ejemplo sino miles y miles de filas? Estar revisando las filas
una por una para descubrir a las que tienen NULL sería una gran
pérdida de tiempo. Afortunadamente, con un simple comando
SELECT podemos descubrir cuales son esas filas.
Captura 5. Hallando las filas de la tabla VENTASCAB cuyo
Código de Cliente no existe en la tabla de CLIENTES.
Aquí el truco fue poner en la cláusula WHERE la condición de IS
NULL. Entonces, se obtienen todas las filas de la tabla
VENTASCAB que no fueron emparejadas.
Ejemplo 4. Obteniendo los códigos que no se
emparejaron
En la Captura 5. podemos observar que el código “00005”
aparece 2 veces. En este caso no hay problema con eso, porque
solamente 3 filas tienen problemas, pero en la vida real un
mismo código faltante podría repetirse cientos o miles de veces,
una vez por cada fila problemática. Evidentemente que no sería
práctico observar tantas y tantas filas. Y es que agregando una
fila con el código “00005” a la tabla de CLIENTES se resolvería el
problema de todos los códigos “00005” de la tabla VENTASCAB.
Entonces, ¿cómo hacemos para que cada código problemático
se vea una sola vez?
Usando la cláusula DISTINCT, que ya conocemos.
Captura 6. Viendo solamente una vez cada código de
cliente faltante
Y ahora sí, cada código de cliente que existe en la tabla
VENTASCAB y que no existe en la tabla CLIENTES es mostrado
una sola vez. El truco fue mostrar una sola columna y usar la
cláusula DISTINCT.
Ejemplo 5. Obteniendo los clientes a los cuales no se les
ha vendido
¿Hay algunos clientes a los cuales no se les ha vendido? ¿Cuáles
son esos clientes? Podemos averiguarlo muy fácilmente.
Captura 7. Los clientes a los cuales no se les vendió
Si observas la Captura 3. o si observas la Captura 4., verás
que en ninguna fila se encuentra el código de cliente “00003”. O
sea, ninguna venta se le realizó a ese cliente. Y en la Captura
7. puedes ver como se hizo para descubrirlo.
En este caso lo que hicimos fue obtener los datos de los clientes
que existen en la tabla de CLIENTES pero que no existen en la
tabla VENTASCAB.
Conclusión:
En VFP solamente tenemos una forma de relacionar a dos
tablas entre sí. En SQL tenemos cuatro formas. Cada una de
esas cuatro formas nos puede resultar muy útil, según iremos
viendo en los ejemplos de los siguientes artículos.
Cuando relacionamos a dos tablas de forma “interna” (usando
INNER JOIN o sólo JOIN si queremos abreviar) es requisito que las
filas de ambas tablas se puedan emparejar.
Cuando relacionamos a dos tablas de forma “externa” (usando
LEFT JOIN, RIGHT JOIN, o FULL JOIN) no es requisito que las dos
tablas se puedan emparejar, en los casos en que no se
emparejan la palabra NULL aparece en la columna. Por lo tanto,
si vemos NULL sabemos que no hay
emparejamiento. Recuerda: en SQL la palabra NULL significa
“desconocido” o “no aplicable”. No significa “nulo”.
En SQL se dice que dos filas están emparejadas cuando tienen
el mismo valor en la columna que las relaciona. Por ejemplo, si
en la columna VTC_CODCLI de la tabla VENTASCAB existe el
código “00012” y en la columna CLI_CODIGO de la tabla
CLIENTES también existe el código “00012”, entonces ambas
tablas están emparejadas en esas filas.
Comentario 1: ¿Ya te has dado cuenta que el
comando SELECT es muchísimo más poderoso que el
comando BROWSE?
Comentario 2: ¿Ya te has dado cuenta que para obtener los
mismos resultados deberías escribir mucho más si no usas SQL?
De DBF a SQL (12). Relacionando una
tabla consigo misma
14 AGOSTO, 2018 / WROV
En SQL tenemos la posibilidad de que una tabla se relacione
consigo misma. Se le llama “tabla autorreferenciada” porque se
referencia (o se relaciona) a una tabla con la misma tabla. Esto
tiene varias aplicaciones prácticas, algunas de las cuales
veremos en este artículo.
Ejemplo 1. Verificando si hay números consecutivos
faltantes
Captura 1. Las filas de la tabla CLIENTES
Si en la Captura 1. observamos la columna CLI_IDENTI,
veremos que algunos números no tienen su consecutivo. Por
ejemplo a continuación del 2 debería estar el 3, pero está el 4.
¿Cómo podemos averiguar cuáles números no tienen su
consecutivo?
Captura 2. Los números de la columna CLI_IDENTI que no
tienen consecutivo
Lo que nos dice el SELECT de la Captura 2. es a cuales
números les falta el consecutivo. O sea, a continuación del 2
debería estar el 3, pero no está. A continuación del 5 debería
estar el 6, pero no está. Y así sucesivamente. Puedes volver a
mirar la Captura 1. para comprobarlo.
Como estamos usando una sola tabla, se debe poder diferenciar
si una columna pertenece a la primera instancia o a la segunda
instancia, de esa tabla. Para ello, se usa un alias en cada caso.
En el ejemplo de la Captura 2., para la primera instancia ese
alias es T1 y para la segunda instancia ese alias es T2. Recuerda
que T1 y T2 son solamente ejemplos, tú puedes ponerles
cualquier alias que se te ocurra. Al autor de este blog le gusta
usar T1 (significa: tabla 1) y T2 (significa: tabla 2), pero es
cuestión de gustos.
Ejemplo 2. Hallando los clientes a los cuales se les vendió
en dos fechas distintas
La tabla VENTASCAB tiene los datos que se ven a continuación:
Captura 3. Las filas de la tabla VENTASCAB
Se quiere saber: ¿a cuáles clientes se les vendió el 5 de enero y
también se les vendió el 12 de enero?
Captura 4. Los clientes a quienes se les vendió el 5 de
enero y también el 12 de enero
Ejemplo 3. Hallando los nombres de los padres de los
perros
Se tiene una tabla llamada PERROS que guarda los datos de
varios de esos animales. Una de sus columnas es el identificador
del padre del perro.
Captura 5. Los datos de la tabla PERROS
En la tercera columna tenemos el Identificador del padre de
cada perro. Si hay un número cero significa que no se conoce
quien es el padre. Si hay un número mayor que cero entonces sí
se sabe quien es el padre. Por ejemplo, un número 2 indica que
el padre es ZEUS.
Se necesita obtener una consulta similar a la Captura 5. pero
poniendo el Nombre del Padre de cada perro, no el Identificador
del Padre, lo que se desea es ver su Nombre.
Captura 6. Mostrando los nombres de los padres de cada
perro
En este caso, como de algunos perros no se conoce quien es el
padre, se usó un LEFT JOIN. Recuerda que un LEFT JOIN coloca
NULL cuando ambas tablas no se pueden emparejar en una fila.
Por ese motivo en las cinco primeras filas vemos NULL. También
a cada columna se le cambió su nombre por un alias. Así es más
fácil conocer el significado de cada columna.
¿Cuándo se debe autorreferenciar una tabla?
Cuando la respuesta a lo que necesitamos saber se encuentra
en esa tabla, y no se soluciona usando AND ni usando OR ni
usando NOT.
Conclusión:
Hacer que una tabla se referencie (o sea, se relacione) con ella
misma es muy útil cuando los datos que se necesitan se
encuentran en esa tabla. En este artículo hemos visto algunos
ejemplos de lo que se puede hacer, por supuesto que hay miles
de posibilidades más.
Por ejemplo, tenemos una tabla de CALIFICACIONES de los
ALUMNOS. Una columna de esa tabla es el Identificador del
Alumno, otra columna es el Identificador de la Materia, y una
tercera columna es la calificación que obtuvo en esa materia. Se
quiere saber quienes son los alumnos que se aplazaron en
MATEMATICA y que también se aplazaron en INGLÉS. Las
calificaciones posibles van de 0 puntos a 100 puntos, y un
alumno se aplaza cuando obtiene menos de 40 puntos.
Tarea para la casa: trata de resolver el problema planteado en
el párrafo superior. Si entendiste este artículo te debería resultar
muy fácil, con una tabla autorreferenciada se soluciona.
De DBF a SQL (13). Relacionando a
varias tablas entre sí
15 AGOSTO, 2018 / WROV
Ya hemos visto como relacionar a una tabla con otra tabla y
también como relacionar a una tabla consigo misma. En este
artículo veremos como relacionar a tres o más tablas.
Captura 1. Las filas de la tabla PRODUCTOS
Captura 2. Las filas de la tabla VENTASCAB
Captura 3. Las filas de la tabla VENTASDET
Ejemplo 1. Mostrando los productos vendidos en cada
Factura
Captura 4. Viendo lo que se vendió en cada Factura
Al observar la Captura 4. seguramente has notado lo siguiente:
1. Al nombre de cada columna se le puso un alias
2. Se usó dos veces la cláusula JOIN
3. Por cada venta realizada se puede ver la Fecha de esa
venta, el Número de la Factura, que productos se
vendieron, las cantidades vendidas, los precios unitarios de
venta, y los totales vendidos de cada producto en cada
venta
Ejemplo 2. Mostrando los nombres de los clientes
Vamos a hacerlo aún más interesante, mostraremos los
nombres de los clientes a quienes se les vendió.
Captura 5. Las filas de la tabla CLIENTES
Captura 6. Las ventas realizadas
(Si ves el texto muy pequeño, puedes hacer clic en la imagen
para verlo más grande)
Al observar la Captura 6. puedes notar que:
1. A los clientes se los relacionó con la cláusula LEFT JOIN y
no con la cláusula JOIN. ¿Por qué? para descubrir si hay
identificadores de clientes que están en la tabla
VENTASCAB (cabecera de ventas) pero no están en la tabla
de CLIENTES. Y sí, existe ese problema. Son esos NULL que
vemos.
2. La tabla principal de nuestro SELECT es VENTASDET
(porque se encuentra a continuación de la cláusula FROM),
sin embargo la relación con los clientes se hizo a través de
la tabla VENTASCAB. ¿Qué implica eso? Que se puede
relacionar una tabla con cualquier otra tabla, no solamente
con la tabla principal. Esa característica
de SQL es extremadamente poderosa y deberías tomarla
muy en cuenta porque te facilitará enormemente escribir
SELECT complejos.
3. El resultado obtenido del SELECT no está normalizado, pero
eso no es un problema. Las tablas son las que sí deben
estar normalizadas, no las consultas a esas tablas.
Conclusión:
Se puede relacionar a varias tablas entre sí, muy fácilmente,
simplemente escribiendo el JOIN adecuado para cada caso
(INNER, LEFT, RIGHT, FULL) y las columnas que establecen la
relación.
La tabla principal es la que se encuentra a continuación de la
cláusula FROM. Cuando se la relaciona con otra tabla, las
columnas de esa segunda tabla pueden usarse como si
pertenecieran a la tabla principal.
No es obligatorio que en una relación participe la tabla principal.
Cualquier tabla puede relacionarse con cualquier otra tabla (si
tienen columnas en común, desde luego). Y también una tabla
puede relacionarse consigo misma. En todos los casos es como
si las columnas de las tablas relacionadas pertenecieran a la
tabla principal.
En general, es muy conveniente ponerles alias a las columnas
para que a los usuarios les resulte más fácil entender las
consultas.
De DBF a SQL (14). Más sobre el uso
de DISTINCT
16 AGOSTO, 2018 / WROV
En un artículo anterior ya hemos visto como usar la cláusula
DISTINCT. Ahora la potenciaremos aún más.
Podemos escribir algo como:
Listado 1. El uso normal de la cláusula DISTINCT
1 SELECT
2 DISTINCT
3 MiColumna1
4 FROM
MiTabla
5
Si ejecutamos el Listado 1. obtendremos solamente los valores
únicos, o sea que no están repetidos, en MiColumna1.
Eso está muy bien, pero ¿cuántos valores únicos hay
en MiColumna1?
Captura 1. Las filas de la tabla VENTASCAB
Si contamos las filas, veremos que hay 13 de ellas. Sin embargo,
no hay 13 clientes porque algunos identificadores de clientes
están repetidos. Entonces ¿cuántos clientes distintos hay?
Captura 2. La cantidad de clientes distintos en la tabla
VENTASCAB
Desde luego que esta función te sirve para conocer cuantos
valores distintos hay en muchísimos otros casos. Por ejemplo,
¿cuántos apellidos distintos hay?, ¿cuántas localidades distintas
hay?, ¿cuántos precios distintos hay?, ¿cuántos países distintos
hay?
COUNT() no es la única función agrupada que podemos usar.
Si lo deseamos, también podemos usar cualquiera de las otras
funciones: SUM(), AVG(), MAX(), MIN().
También podemos escribir una función dentro de otra función,
por ejemplo:
Captura 3. La cantidad de días (no de fechas, de días)
que son distintos
En la Captura 3. hemos usado la función DAY() dentro de la
función COUNT(). Allí puedes poner cualquier otra función que
desees: LEFT(), LEN(), ALLTRIM(), SUBSTR(), etc.
Conocer la cantidad de valores distintos puede ser muy útil
muchas veces. Por ejemplo, si en tu tabla de CLIENTES hay 250
filas, y en tu tabla de VENTAS hay solamente 15 clientes
distintos en todo el año pasado, algo está mal en tu Empresa.
Conclusión:
La cláusula DISTINCT no solamente puede usarse a continuación
de la palabra SELECT, también puede usarse dentro de
una función agrupada.
Por ejemplo, si la usamos con la función COUNT(), podremos
conocer cuantos valores distintos hay.
De DBF a SQL (15).
Usando subconsultas
17 AGOSTO, 2018 / WROV
Una característica muy poderosa del lenguaje SQL es que nos
permite crear subconsultas. ¿Y qué es una subconsulta? Un
SELECT que se usa dentro de otro SELECT.
¿Y cuál es su utilidad?
Que nos permite muy fácilmente obtener cualquier columna de
cualquier tabla y usarla dentro de nuestro SELECT.
Esta característica potenciará enormemente a nuestras
consultas, ya que cualquier dato que necesitemos lo podremos
obtener, sea que esas tablas tengan alguna relación entre sí o
no tengan relación.
Veamos ahora el caso más sencillo:
Listado 1. Una subconsulta muy sencilla
1 SELECT
2 MiColumna1,
3 (SELECT MiColumna2 FROM MiTabla2 WHERE MiCondición)
4 FROM
MiTabla1
5
En el Listado 1., al SELECT que está entre paréntesis se le
denomina “subconsulta“. Hay dos requisitos que debe cumplir:
Solamente debe devolver una fila, nunca más de una fila
Solamente debe devolver una columna, nunca más de una
columna
Y eso es lógico, si devolviera más de una fila, ¿en cuál de ellas
el VFP encontraría el valor que se necesita?
Si devolviera más de una columna, ¿en cuál de ellas está el
valor que se necesita?
En casos así, podrían existir 40, 50, o más valores, ¿cuál de ellos
habría que usar? Como para el VFP sería imposible saberlo
entonces hace lo más sabio: si el resultado de
la subconsulta arroja más de un valor, eso es un error. Y listo.
Captura 1. Hay un error porque la subconsulta devolvió
varias filas
En la Captura 1. el problema ocurrió porque la tabla
VENTASCAB tiene varias filas. Para evitar este problema se
puede usar la cláusula WHERE, o la cláusula HAVING, o la
cláusula TOP, o la cláusula DISTINCT, o una función agrupada:
SUM(), COUNT(), MAX(), MIN(), AVG(), etc. Lo importante es
asegurarse de que la subconsulta solamente devolverá una fila
y nunca más de una fila.
Captura 2. Un error porque hay muchas columnas en la
subconsulta
En la Captura 2. nos aseguramos de que
la subconsulta devuelva una sola fila pero igualmente hay un
error. Eso es debido a que se escribieron dos columnas
(VTC_FECHAX y VTC_IDECLI) y solamente debe escribirse una
columna.
Entonces, ahora ya conoces el concepto más importante para
usar subconsultas: solamente debe devolver un valor, nunca
más de un valor. Jamás.
Dentro de la subconsulta puedes usar casi todas las cláusulas
que ya conoces: DISTINCT, TOP, WHERE, GROUP BY, HAVING,
ORDER BY. La que no puedes usar es UNION. ¿por qué? porque
UNION devuelve al menos dos filas y ya sabes que es requisito
que en el SELECT de la subconsulta se devuelva solamente
una fila y solamente una columna. Por lo tanto, si usas UNION
obtendrás un mensaje de error. Lógico, ¿verdad?
NOTA: Una subconsulta puede devolver cero filas y cero
columnas, eso sí está permitido. En tal caso, se obtendrá un
NULL.
Veamos un ejemplo, para entender mejor todo esto.
Se tiene una tabla de ventas y una tabla de cobranzas, y se
desea conocer, por cada venta, todo lo que se ha cobrado de
esa venta.
Captura 3. Las filas de la tabla VENTASCAB
Captura 4. Las filas de la tabla COBRANZASCAB
En la tabla COBRANZASCAB, la columna CBC_IDEVTA es el
Identificador de la Venta, o sea la columna que nos permitirá
relacionar a una fila de la tabla COBRANZASCAB con su fila
correspondiente en la tabla VENTASCAB.
Fíjate que no es necesario colocar el Identificador del Cliente, ya
que ese dato lo tenemos en la tabla VENTASCAB. Así, podremos
saber a cual cliente se le cobró.
Y ahora queremos saber: ¿por cada venta … cuánto se cobró?
Captura 5. Los datos de cada venta, incluido el total
cobrado
Como puedes ver en la Captura 5., además de saber cual es el
Identificador de cada venta, la Fecha de la venta, el Número de
la Factura, y el Identificador del Cliente, también sabemos
cuanto se cobró de esa venta. Para ello, usando
una subconsulta agregamos la columna VTC_TOTCOB (total
cobrado).
Habrás notado también que en la columna VTC_TOTCOB hay
muchos valores NULL. Eso significa que de esa venta aún no se
registraron cobranzas. Pero si no quieres ver la palabra NULL
sino el número cero, es muy fácil lograrlo.
Captura 6. Mostrando el número cero en lugar de NULL
La función NVL() que usamos en la Captura 6. devuelve:
El primer valor, si no es NULL
Si el primer valor es NULL y el segundo valor no lo es,
devuelve el segundo valor
Si ambos valores son NULL, devuelve NULL
Si comparas la Captura 5. con la Captura 6. verás que todos
los NULL desaparecieron y que en su lugar se muestra el
número cero. Para lograr eso, es que se usó a la función NVL().
Podemos mejorar aún más el resultado de nuestra consulta
poniendo los nombres de los clientes. Eso, ya lo sabes hacer, se
consigue con un simple JOIN.
Captura 7. Los datos de cada venta, con los nombres de
los clientes
Como puedes ver en la Captura 7., ahora además del
Identificador de cada cliente también está su nombre. Aquellos
clientes donde hay NULL en lugar de sus nombres son errores
cometidos al ingresar los datos o porque se borraron clientes a
quienes se les había vendido. En Visual FoxPro tal cosa es
posible (y se muestra en estos artículos para demostrarlo) pero
en un buen motor SQL, tal como Firebird, es totalmente
imposible que tal cosa ocurra. La integridad referencial no lo
permitiría.
Como la subconsulta es una consulta (que se usa como
columna de una tabla) entonces podemos usar en
las subconsultas casi todo lo que ya conocemos sobre los
SELECT, por ejemplo las cláusulas: TOP, DISTINCT, WHERE,
GROUP BY, HAVING, ORDER BY. También podemos usar las
funciones agrupadas: SUM(), COUNT(), MAX(), MIN(), AVG(), y en
general todo lo que puedes escribir en un SELECT (la cláusula
UNION no se puede usar, es la excepción).
Solamente no te olvides que debe devolver una sola fila y una
sola columna. Eso es totalmente obligatorio.
Conclusión:
En el lenguaje SQL se admite el uso de subconsultas.
Una subconsulta es un SELECT que se escribe dentro de otro
SELECT, rodeado por paréntesis. Todo lo que se puede escribir
dentro de un SELECT común también se puede escribir en un
SELECT empleado como subconsulta (con la única excepción
de la cláusula UNION, porque ésta puede devolver varias filas).
Sin embargo, no cualquier SELECT puede ser usado en una
subconsulta. Para que pueda ser usado debe cumplir dos
condiciones:
1. Debe devolver una sola fila. Jamás dos o más filas
2. Debe devolver una sola columna. Jamás dos o más
columnas
¿Y si una subconsulta devuelve cero filas, puede ser usada?
Sí, se puede. En ese caso habrá un NULL en esa columna.
¿Y si el valor devuelto por una subconsulta es NULL y no quiero
usar NULL?
Entonces puedes emplear la función NVL() para decirle cual
valor mostrar en reemplazo de NULL. Ese valor puede ser
numérico, fecha, carácter, etc.
NOTA: En VFP se usa la función NVL(), en los
motores SQL hay una función similar pero que tiene otro
nombre, por ejemplo en Firebird se llama COALESCE().
De DBF a SQL (16). Más sobre
las subconsultas
18 AGOSTO, 2018 / WROV
¿Dónde se pueden usar las subconsultas?
En el artículo anterior ya hemos visto un caso, pero esa no es la
única posibilidad. Veamos ahora donde pueden ser usadas:
Listado 1. En la lista de columnas del SELECT
1 SELECT
2 MiColumna1_1,
3 MiColumna1_2,
4 (SELECT MiColumna2_1 FROM MiTabla2 WHERE MiCondición)
5 FROM
MiTabla1
6
En el Listado 1. vemos el caso más común: en la lista de
columnas del SELECT.
Listado 2. En la cláusula FROM
1 SELECT
2 MiColumna1_1,
3 MiColumna1_2
4 FROM
(SELECT MiColumna1_1, MiColumna1_2 FROM MiTabla2 WHERE MiCondición) AS MiTabla
5
Cuando la subconsulta se encuentra a continuación de la
cláusula FROM recibe un nombre especial, se le llama tabla
derivada. Para usar una tabla derivada se deben
cumplir todas las siguientes condiciones:
La cantidad de columnas en el SELECT principal debe ser
igual a la cantidad de columnas de la tabla derivada
Los nombres de las columnas en el SELECT principal deben
ser iguales a los nombres de las columnas en la tabla
derivada
La tabla derivada debe tener un alias, sí o sí. En
el Listado 2. ese alias fue MiTabla, pero ese es solamente
un ejemplo, tú puedes elegir cualquier alias que desees.
Pero debes elegir uno.
Las tablas derivadas suelen resultar muy útiles cuando se
necesita usar la cláusula GROUP BY y las funciones
agrupadas.
Listado 3. En la cláusula JOIN
1
SELECT
2 MiColumna1_1,
3 MiColumna1_2
4 FROM
5 MiTabla1
JOIN
6
(SELECT MiColumna2_1 FROM MiTabla2 WHERE MiCondición) AS MiTabla3
7 ON MiColumna1_1 = MiColumna2_1
8
Al igual que cuando usamos una tabla derivada, a
la subconsulta que escribimos en la cláusula JOIN debemos
asignarle un alias. En nuestro ejemplo ese alias se
llama MiTabla3. Y como ya sabes, puedes escribir cualquier
otro alias que desees porque MiTabla3 es solamente un
ejemplo.
Listado 4. En la cláusula WHERE
1 SELECT
2 MiColumna1_1,
3 MiColumna1_2
4 FROM
5 MiTabla1
WHERE
6 MiValor = (SELECT MiColumna2_1 FROM MiTabla2 WHERE MiCondición)
7
En el Listado 4. vemos un caso interesante, usamos a
la subconsulta en la cláusula WHERE para validar cuales filas
se obtendrán y cuales no.
Listado 5. En la cláusula GROUP BY
1
2 SELECT
MiColumna1_1,
3 MiColumna1_2,
4 (SELECT MiColumna2_1 FROM MiTabla2 WHERE MiCondición)
5 FROM
6 MiTabla1
GROUP BY
7
MiColumna1_1,
8 MiColumna1_2,
9 3
10
En el Listado 5. no se escribió la subconsulta en la cláusula
GROUP BY sino que se puso su posición. Ese número 3 significa
que la subconsulta es la tercera columna. En la cláusula
GROUP BY no se puede poner una subconsulta, pero sí se
puede poner el número de orden que le corresponde a
esa subconsulta dentro de la lista de columnas del SELECT.
Comentarios:
En Visual FoxPro éstas son todas las posibilidades que
tenemos, pero en motores SQL más poderosos, como Firebird,
también podemos escribir subconsultas en la cláusula ORDER
BY.
A partir de ahora, usaremos intensivamente a
las subconsultas en los ejemplos de los artículos de la
serie: De DBF a SQL, porque gracias a ellas podremos obtener
resultados asombrosos, comparados con los que obtendríamos
si solamente usáramos los comandos tradicionales del VFP.
Conclusión:
Las subconsultas no solamente pueden usarse en la lista de
columnas del SELECT, pueden usarse en muchas cláusulas
también.
Cuando se usan a continuación de la cláusula FROM se las
llama tablas derivadas. Y las tablas derivadas son muy
útiles especialmente cuando se requiere el uso de la cláusula
GROUP BY y de las funciones agrupadas.
Las subconsultas son tan pero tan útiles, que las verás en la
mayoría de los SELECT complejos. No debes asustarte si aún no
las entiendes, mirando los ejemplos y sobre todo poniéndolos en
práctica con tus propias tablas, no tardarás en dominar su uso.
De DBF a SQL (17). Ejemplos
de subconsultas
19 AGOSTO, 2018 / WROV
Hay que dominar el uso de las subconsultas. Son tan
poderosas, que no usarlas sería un verdadero desperdicio. Para
facilitarte la tarea, en este artículo veremos varios ejemplos.
Captura 1. Las filas de la tabla CLIENTES
Captura 2. Las filas de la tabla VENTASCAB
C
Captura 3. Las filas de la tabla VENTASDET
Queremos ver a los clientes a los cuales no se les vendió
Captura 4. Los clientes a los cuales no se les vendió
Este es un caso muy común: datos que están en una tabla pero
que no están en otra tabla.
En la Captura 4. vemos los clientes que están en la tabla de
CLIENTES pero que no están en la tabla VENTASCAB.
Desde luego que la consulta puede ser mucho más compleja,
por ejemplo ahora queremos:
Ver a los clientes a los cuales no se les vendió entre el 1
de enero y el 10 de enero
Captura 5. Los clientes a los cuales no se les vendió
entre el 1 de enero y el 10 de enero
Si observas la Captura 2., verás que ninguno de estos
identificadores de clientes se encuentra en las ventas realizadas
hasta el 10 de enero.
Hagamos otra pregunta interesante, ahora queremos saber:
La última fecha en la cual se le vendió a cada cliente
Captura 6. La fecha de la última venta realizada a cada
cliente
En la cuarta columna hay muchos valores .NULL., eso es debido
a que a esos clientes no se les realizó ni una sola venta. Como
recordarás, si no quieres ver .NULL. puedes usar la función
NVL().
Y ahora lo que nos interesa conocer es:
El total vendido a cada cliente
Captura 7. El total que se le vendió a cada cliente
Como de costumbre, esos valores .NULL. significa que no existen
datos. En otras palabras, nada se les vendió a esos clientes. Y si
prefieres ver el número cero antes que la palabra .NULL.
entonces con la función NVL() lo conseguirás.
Habrás observado también que esta subconsulta es más
compleja que las anteriores. Aquí hemos usado la cláusula JOIN
y también la cláusula WHERE. Desde luego que se pueden usar
otras cláusulas también, el requisito como ya lo sabes, es que
la subconsulta solamente devuelva un valor.
Ahora lo que nos interesa es:
La última fecha en la cual se vendió a los clientes activos
Captura 8. La última fecha en la cual se vendió a los
clientes activos
La Captura 8. es similar a la Captura 6., pero con una
diferencia: en la Captura 8. solamente aparecen los clientes
que están en la tabla VENTASCAB, mientras que en la Captura
6. aparecían todos los clientes, se les haya vendido o no.
En la Captura 8. se usó una tabla derivada para obtener los
resultados. La tabla principal se llama MISVENTAS y está basada
en la tabla VENTASCAB y en la tabla CLIENTES, pero no es la
tabla VENTASCAB ni tampoco es la tabla CLIENTES. Cuidado, no
te confundas. MISVENTAS es una tabla virtual, no existe en la
vida real, y esa tabla virtual proviene tanto de la tabla
VENTASCAB como de la tabla CLIENTES, porque usa columnas
de ambas tablas.
En general, usar tablas derivadas es más rápido que no
usarlas. ¿Por qué? Porque las filas ya se encuentran filtradas y
cualquier proceso que se deba hacer sobre los datos solamente
ocupa a algunas filas, no a todas. De todas maneras, cada caso
es un caso y si necesitas mucha velocidad en la respuesta
deberías probar las otras alternativas también. Todo lo que
puedes hacer usando tablas derivadas también lo puedes
hacer sin usarlas pero usar tablas derivadas es, en general,
más rápido. Usualmente, la ganancia en velocidad estará entre
un 10% y un 20%. Ya tú decidirás si las usas o no.
Nuestra tabla virtual MISVENTAS puede ser utilizada
exactamente igual a como se utilizaría una tabla real. Eso
significa que puede tener las cláusulas TOP, DISTINCT, JOIN,
WHERE, GROUP BY, HAVING, UNION, ORDER BY. Puedes usar las
que necesites.
Por ejemplo, para ver en la Captura 8. a los clientes ordenados
alfabéticamente, se podría escribir:
Captura 9. Las fechas de las últimas ventas, ordenadas
por nombres de clientes
Así como en la Captura 9. hemos usado a la cláusula ORDER
BY para ordenar a la tabla virtual MISVENTAS, también
podríamos haber usado cualquier otra cláusula que
necesitemos. Por ejemplo, se puede hacer un JOIN de una tabla
virtual con otra tabla, sea ésta virtual o real.
A todos los efectos, una tabla virtual funciona exactamente igual
que una tabla real.
Y es que el lenguaje SQL es realmente muy, muy poderoso.
NOTA: ¿Has notado que las tablas derivadas pueden devolver
varias filas y varias columnas? Y es que las tablas
derivadas son un caso especial de subconsultas, y
justamente para diferenciarlas es que tienen un nombre distinto.
Las subconsultas deben devolver una sola fila y una sola
columna, menos cuando se usan como tablas derivadas, en
ese caso lo normal es que devuelvan varias filas y varias
columnas. Esto es importante, no lo olvides.
Conclusión:
Con las subconsultas se puede muy fácilmente obtener casi
cualquier dato que se necesite, en este artículo hemos mostrado
algunos ejemplos y en los sucesivos artículos se verán muchos
ejemplos más.
Un caso especial de subconsulta es cuando se la usa a
continuación de la cláusula FROM y en ese caso recibe el
nombre de tabla derivada. A diferencia de las
demás subconsultas, una tabla derivada puede devolver
varias filas y varias columnas.
Las tablas derivadas son especialmente útiles cuando se
requiere usar la cláusula GROUP BY o las funciones
agrupadas. En estos casos usualmente se obtiene una mejora
en velocidad de entre el 10% y el 20%.
De DBF a SQL (18). Paginación
20 AGOSTO, 2018 / WROV
Hay ocasiones en las cuales se requiere obtener de las tablas
una cantidad fija de filas, el caso más común es para mostrar
esas filas en una grilla. Si por ejemplo una tabla tiene 450.000
filas, escribir un SELECT que te traiga esas 450.000 filas sería un
gran error, y por varios motivos:
1. Ningún usuario mirará tantas filas
2. Habrá muchísimo tráfico en la red, innecesariamente. Eso
la volverá lenta
3. Si varios usuarios están ejecutando ese SELECT, puedes
llegar a saturar la red
4. El usuario tardará mucho en obtener las filas que quiere
ver
Para solucionar esos problemas es usual utilizar una técnica
llamada paginación.
Por ejemplo, en la grilla muestras las primeras 100 filas, si el
usuario quiere ver las siguientes 100 filas, entonces debe hacer
clic en un botón. Cada vez que hace clic en ese botón se le
muestran 100 filas más.
¿Cuáles son las ventajas de la paginación?
1. La cantidad de filas que se obtienen del SELECT es
relativamente pequeña (en nuestro ejemplo, serían 100
filas)
2. La velocidad es muy alta (no es lo mismo obtener 100 filas
que obtener 450.000 filas)
3. No perjudicas a los demás usuarios
Casi todos los motores SQL ofrecen una forma sencilla de
hacer paginación. Por ejemplo, en Firebird se escribiría:
Listado 1. Haciendo paginación en Firebird. Obteniendo las
primeras 100 filas
1 SELECT
2 *
3 FROM
4 MiTabla
ROWS
5
1 TO 100
6
Listado 2. Haciendo paginación en Firebird. Obteniendo las
siguientes 100 filas
1 SELECT
2 *
3 FROM
4 MiTabla
ROWS
5
101 TO 200
6
En el Listado 1. y en el Listado 2. vemos como se pueden
obtener la cantidad de filas que se desean cuando
usamos Firebird. Es muy fácil.
Lamentablemente, el comando SELECT del Visual FoxPro no
cuenta con esta posibilidad. Sin embargo podemos
hacer paginación usando un truco. Y para ello haremos uso de
una subconsulta.
La tabla de CLIENTES
Captura 1. Las filas de la tabla CLIENTES
Haciendo paginación con Visual FoxPro
Como nativamente el Visual FoxPro no nos permite
hacer paginación, crearemos un procedimiento que se
encargará de esa tarea.
Este PROCEDURE (salvo algunas pequeñas modificaciones
estéticas realizadas por el autor de este blog) ha sido
compartido por Víctor Hugo Espínola de Paraguay, en el
grupo RinconFox del Whatsapp. Muchas gracias Víctor Hugo,
muy buen aporte.
Listado 3. Un PROCEDURE para poder hacer paginación
1 PROCEDURE PAGINA_SELECT
LPARAMETERS tcTabla, tcIdentificador, tcOrdenarPor, tnTamanoPagina, tnPaginaNro
2
LOCAL lnFilasExcluidas, lcCondicionWhere, lcConsulta, llConsultaOK
3
4 *--- tcTabla : La tabla que contiene los datos
5 *--- tcIdentificador: La Primary Key o Unique Key de la tabla
6 *--- tcOrdenarPor : La columna (o columnas) por las cuales se desea ordenar a
7 *--- tnTamanoPagina : El tamaño de cada página, o sea, la cantidad de filas por
*--- tnPaginaNro : El número de la página que se desea extraer
8
9 tnTamanoPagina = Max(tnTamanoPagina, 1) && Para asegurar que la Página tenga al
10 tnPaginaNro = Max(tnPaginaNro, 1) && Para asegurar que el Número de la Pá
11
12
13
14
15 *--- lnFilasExcluidas: La cantidad de filas que estarán excluidas
16 *--- lcCondicionWhere: La condición para mostrar o no la primera página
17
18 lnFilasExcluidas = (tnPaginaNro - 1) * tnTamanoPagina
19 lcCondicionWhere = Iif(lnFilasExcluidas = 0, "WHERE 1=0", "")
20
TEXT TO [Link] NOSHOW TEXTMERGE PRETEXT 15
21 SELECT
22 TOP <>
23 *
24 FROM
<>
25 WHERE
26 <> NOT IN (SELECT
27 TOP <>
28 <>
29 FROM
<>
30 <>
31 ORDER BY
32 <>)
33 ORDER BY
<>
34
ENDTEXT
35
36 =MessageBox([Link])
37
38 =ExecScript([Link])
39
40 ENDPROC
41 *
*
42
43
44
45
Ejemplos de uso
Para hacerlo sencillo, en los siguientes ejemplos cada página
tendrá 3 filas. Eso significa que en la primera página estarán las
filas del 1 al 3, en la segunda página las filas del 4 al 6, en la
tercera página las filas del 7 al 9, etcétera.
Ejemplo 1. Para ver la primera página
DO PAGINA_SELECT WITH “CLIENTES”, “CLI_IDENTI”,
“CLI_NOMBRE”, 3, 1
Captura 2. La consulta que ejecutará el Visual FoxPro
La condición de la subconsulta siempre dará falso como
resultado, porque 1 siempre será diferente que 0. Eso significa
que ninguna fila se obtendrá de la subconsulta, porque
siempre estará totalmente vacía. Por lo tanto, la columna
CLI_IDENTI nunca se encontrará en esa subconsulta y en
consecuencia se mostrarán las 3 primeras filas de la tabla
CLIENTES.
¿Entiendes la lógica empleada allí?
NOTA: En lugar de escribir 1 = 0 también se podría haber
escrito .F., pero hay una diferencia y es que 1 = 0 funcionará en
cualquier motor SQL cuando queremos establecer una condición
como falsa, en cambio .F. solamente funcionará en Visual
FoxPro.
Captura 3. La primera página de la tabla CLIENTES
Puedes comparar la Captura 1. con la Captura 3., para
comprobar que efectivamente se están mostrando los tres
primeros clientes.
Ejemplo 2. Para ver la segunda página
DO PAGINA_SELECT WITH “CLIENTES”, “CLI_IDENTI”,
“CLI_NOMBRE”, 3, 2
Captura 4. La consulta que ejecutará el Visual FoxPro
En este caso, la subconsulta devuelve las 3 primeras filas de la
tabla CLIENTES. Y la condición en la cláusula WHERE es que el
valor de la columna CLI_IDENTI no se encuentre ahí. En otras
palabras, se excluirán de la tabla CLIENTES sus tres primeras
filas. Eso implica que el SELECT principal tomará las 3 primeras
filas, empezando a contar desde la cuarta fila de la tabla
CLIENTES. Ingenioso ¿verdad?
Captura 5. La segunda página de la tabla CLIENTES
Ejemplo 3. Para ver la tercera página
DO PAGINA_SELECT WITH “CLIENTES”, “CLI_IDENTI”,
“CLI_NOMBRE”, 3, 3
Captura 6. La consulta que ejecutará el Visual FoxPro
Aquí, la subconsulta devuelve las 6 primeras filas de la tabla
CLIENTES. Y la condición en la cláusula WHERE es que el valor
de la columna CLI_IDENTI no debe encontrarse en ninguna de
esas 6 filas. Son excluidas. Por lo tanto el TOP 3 del SELECT
principal mostrará las filas 7, 8, y 9.
Captura 7. La tercera página de la tabla CLIENTES
Y así podríamos continuar con las siguientes páginas. Para
hacerlo corto, veamos la última página que tiene datos, que en
nuestro ejemplo es la página número 6.
Ejemplo 4. Para ver la sexta página
DO PAGINA_SELECT WITH “CLIENTES”, “CLI_IDENTI”,
“CLI_NOMBRE”, 3, 6
Captura 8. La consulta que ejecutará el Visual FoxPro
Captura 9. La sexta página de la tabla CLIENTES
Nuestra tabla de CLIENTES tiene 16 filas (como puedes
comprobar volviendo a mirar la Captura 1.). En la Captura
8. las primeras 15 filas son excluidas por la condición que se ha
puesto en la cláusula WHERE. Por lo tanto se deberían mostrar
las siguientes 3 filas. Pero como solamente queda una fila, ya
que la tabla tiene 16 filas y 15 filas fueron excluidas, se
mostrará una sola fila, tal y como puedes ver en la Captura 9.
Con estos ejemplos, hemos verificado que el PROCEDURE que se
escribió en el Listado 3. funciona perfectamente bien.
Conclusión:
Traer todas las filas de una tabla muchas veces no es práctico ni
recomendable, sobre todo cuando esas filas deben ser
mostradas a los usuarios, porque ningún usuario estará mirando
miles y miles de filas.
Una técnica para mostrar solamente unas cuantas filas, y
siempre la misma cantidad de filas, es la paginación.
Mediante esta técnica se divide imaginariamente a la tabla en
partes iguales, cada una de esas partes recibe el nombre
de página.
La cantidad de filas de cada página la determina el
programador, según lo que sea más adecuado a sus
necesidades.
Por ejemplo, cada página podría tener 50 filas. En la grilla se le
muestran las 50 primeras filas de la tabla. Si quiere ver las
siguientes 50 filas entonces debe hacer clic en un botón y allí
vería desde la fila 51 hasta la fila 100. Si quiere ver las
siguientes 50 filas entonces debe hacer clic en el botón y allí
vería desde la fila 101 hasta la fila 150. Y así sucesivamente.
Desde luego que también podría tener un botón para ver la
página anterior.
De esta manera, nunca se extraerían de la tabla más de 50 filas
y en la grilla por lo tanto, nunca se verían más de 50 filas.
Los motores SQL tienen nativamente una cláusula que permite
hacer paginación. Lamentablemente, ese no es el caso
con Visual FoxPro. Pero se puede escribir un PROCEDURE que
permita hacer paginación, tal y como se mostró en el Listado 3.
La paginación es muy, pero muy, importante. Deberías
emplearla en cada ocasión posible.
De DBF a SQL (19). Hallando los
porcentajes sobre un total
21 AGOSTO, 2018 / WROV
A las subconsultas podemos darles muchos usos, depende en
gran medida de nuestra imaginación y de lo acostumbrados que
estemos a usarlas. Si las usamos mucho, enseguida se nos
ocurren soluciones a los problemas planteados.
Por ejemplo, nos preguntan: “del total vendido en el mes, ¿cuál
es el porcentaje que le corresponde a cada cliente?”
O también: “del total vendido en esta Factura, ¿cuál es el
porcentaje que le corresponde a cada producto?”
C
Captura 1. Las filas de la tabla CLIENTES
Captura 2. Las filas de la tabla VENTASCAB
Captura 3. Las filas de la tabla VENTASDET
Entonces, si el Total vendido en el mes es el 100%, ¿qué
porcentaje se le ha vendido a cada cliente?
Captura 4. El porcentaje vendido a cada cliente
En la Captura 4. estamos empleando mucho de lo aprendido
en esta serie de artículos: hay subconsultas, cláusulas JOIN,
GROUP BY, ORDER BY, funciones agrupadas. Y es que a
medida que avancemos los ejemplos se irán haciendo más
complejos. Y más útiles también, en muchos casos.
En la columna TOTAL_CLIENTE se encuentra el total de lo
vendido a cada cliente. En la columna TOTAL_VENTAS se
encuentra el total de lo vendido a todos los clientes (en realidad,
no era necesario crear esta columna pero se hizo así para que
sea más fácil entender el ejemplo). En la columna
PORCENTAJE_CLIENTE se encuentra el porcentaje que sobre el
total de las ventas le corresponde a cada cliente. Y si ahora
sumas todos los números que están en la columna
PORCENTAJE_CLIENTE verás que el resultado es exactamente
100. O sea, es el 100%.
En otras palabras, poco más del 27% del dinero que ingresó por
las ventas fue gracias a MARCELA, las ventas a RAQUEL
representaron algo más que el 12% del total, etcétera.
Este SELECT es de mucha utilidad para los administradores de
las Empresas porque no solamente muestra el total bruto
vendido a cada cliente sino también el porcentaje vendido a
cada uno de ellos.
Veamos ahora otro ejemplo, para entender bien la técnica
empleada.
Captura 5. El porcentaje vendido de cada producto en
una Factura
Ahora en la columna PORCENTAJE_PRODUCTO tenemos el
porcentaje que sobre el Total de la Factura le corresponde a
cada producto. Podríamos haber puesto el Nombre del producto
en cada una de las filas, haciendo un JOIN a la tabla
PRODUCTOS. Eso ya queda a tu cargo.
Conclusión:
Mediante el uso de subconsultas, hallar los porcentajes que le
corresponden a cada fila de la tabla es muy fácil de conseguir.
De DBF a SQL (20). Los operadores
especiales de comparación
22 AGOSTO, 2018 / WROV
Como ya sabes, para filtrar las filas que aparecerán en el
resultado de un SELECT se usan las cláusulas WHERE y HAVING.
En ellas puedes usar los operadores de comparación
tradicionales: =, <>, >, >=, <, <=
Pero además, si en la cláusula WHERE has escrito
una subconsulta, puedes usar allí unos operadores de
comparación especiales. Ellos son:
ALL. Todas las filas obtenidas de la subconsulta deben
cumplir con la comparación (=, <>, >, >=, <, <=)
ANY. Cualquiera de las filas de las obtenidas de
la subconsulta debe cumplir con la comparación (=, <>,
>, >=, <, <=)
EXISTS. La subconsulta devolvió al menos una fila.
Muchas veces se lo usa con el operador lógico NOT
IN. En alguna de las filas de la subconsulta debe
encontrarse el valor buscado. Muchas veces se lo usa con
el operador lógico NOT. Es el operador de comparación
especial más ampliamente utilizado
SOME. Es idéntico a ANY, funciona exactamente igual
Los operadores especiales de comparación: ALL, ANY, SOME,
son raramente utilizados, muy pocas veces los verás en los
SELECT. El que más se usa es IN frecuentemente con su
negativo, es decir: NOT IN. También se usa bastante EXISTS y
muchas veces con su negativo, es decir: NOT EXISTS.
En general (no siempre, pero muchísimas veces sí) se puede
cambiar una comparación escrita con IN por una comparación
escrita con EXISTS y se obtiene exactamente el mismo
resultado, no hay diferencia. ¿Cuál es preferible utilizar
entonces, IN o EXISTS? Depende de tu gusto y de las
circunstancias, aunque EXISTS suele ser un poco más rápido.
La sintaxis para usar los operadores de comparación
especiales: ALL, ANY, SOME es la siguiente:
MiColumna [operador_de_comparacion]
[operador_de_comparacion_especial] MiSubconsulta
Por ejemplo:
MiColumna > ALL (SELECT ColumnaSubconsulta FROM
MiOtraTabla)
La subconsulta devolverá varias filas que contendrán un valor
y MiColumna deberá ser mayor que todos esos valores (porque
en este ejemplo usamos el operador de comparación mayor
que).
NOTA: Habíamos dicho en artículos anteriores que
las subconsultas deben devolver solamente un valor, o sea
una sola fila y una sola columna. Eso no se aplica cuando se
utilizan los operadores especiales de comparación, ya que en
este caso las subconsultas pueden (y frecuentemente lo
hacen) devolver varias filas.
¿Cuándo se pueden usar los operadores especiales de
comparación?
Cuando en la pregunta que se debe responder se encuentran las
palabras: todos, ninguno, alguno, siempre, nunca, a veces, al
menos una vez.
Ejemplos:
¿Quiénes son los alumnos que aprobaron todos los
exámenes?
¿Quiénes son los alumnos que se aplazaron en al menos un
examen?
¿Quiénes son los alumnos que nunca faltaron a clases?
¿Cuáles son los productos que se venden todos los días?
¿Cuáles son los productos que durante el mes
pasado nunca se vendieron?
¿Cuáles son los productos que se vendieron al menos una
vez durante la semana pasada?
¿Quiénes son los clientes que siempre abonaron sus cuotas
en las fechas fijadas?
¿Quiénes son los clientes que nunca abonaron sus cuotas
en las fechas fijadas?
¿Quiénes son los clientes que al menos una vez se
atrasaron con sus cuotas?
¿Quiénes son los proveedores que nos proveen el producto
XXX?
¿Quiénes son los proveedores que nos proveen el producto
XXX a un precio menor que 80 dólares?
Estos son solamente algunos pocos ejemplos de lo que se puede
responder cuando se usan los operadores de comparación
especiales. Hay que aclarar que todas estas preguntas también
se podrían responder sin utilizarlos, pero en ese caso se
escribiría más y probablemente el código no sea tan fácil de
entender. Por algo es que existen estos operadores ¿verdad?
Ejemplos de uso:
Para entender mejor todo esto veremos algunos ejemplos.
Captura 1. Las filas de la tabla PRODUCTOS
Captura 2. Las filas de la tabla VENTASDET
Captura 3. Las filas de la tabla VENTASCAB
Ejemplo 1. ¿Cuáles productos se vendieron con un precio
de venta igual a 12?
Los precios de venta de los productos pueden variar, el precio
de venta que tienen hoy puede ser distinto al precio de venta
que tenían hace unos meses. Si nos interesan los productos que
alguna vez se vendieron con un precio de venta igual a 12,
entonces …
Captura 4. Los productos que alguna vez se vendieron
con un precio unitario de 12
El operador de comparación especial ANY indica que el precio
de venta 12 puede encontrarse en cualquier fila de la tabla
VENTASDET.
Ejemplo 2. ¿Cuáles productos alguna vez se vendieron
con una cantidad mayor o igual que 10?
Captura 5. Todos los productos cuya cantidad vendida
fue alguna vez mayor o igual que 10
Todos los productos que se encuentran en la Captura 5. en al
menos una venta su cantidad fue mayor o igual que 10.
Ejemplo 3. ¿Cuáles son las Facturas en las cuales se
vendieron uvas?
Captura 6. Todas las Facturas en las cuales se vendió
uvas
Aquí usamos la función EXISTS() para hallar todas las Facturas
de ventas en las cuales se hayan vendido uvas.
Ejemplo 4. ¿Cuáles son las Facturas en las cuales no se
vendieron ciruelas?
Captura 7. Todas las Facturas en las cuales no se
vendieron ciruelas
En la Captura 7. la condición es negativa. O sea, se escribió:
NOT EXISTS. Aquí buscamos las Facturas en las cuales no
existen ciruelas.
Comentario:
Desde luego que hay muchísimos usos más que se les puede
dar a los operadores de comparación especiales, estos fueron
unos sencillos ejemplos para que te vayas haciendo una idea de
las posibilidades que dispones. En sucesivos artículos de esta
serie irás viendo otros ejemplos.
Conclusión:
Cuando necesitamos filtrar a nuestra tabla según condiciones en
las cuales se encuentren las
palabras: todos, ninguno, alguno, siempre, nunca, a veces,
podemos usar los llamados operadores de comparación
especiales.
Sus nombres son: ALL, ANY, EXISTS, IN, SOME y se usan en la
cláusula WHERE.
El más usado es IN y luego EXISTS. Los demás, aunque pueden
ser muy útiles, son raramente utilizados, quizás por falta de
costumbre de los programadores.
De DBF a SQL (21). La técnica de los
dos cursores
23 AGOSTO, 2018 / WROV
Hasta ahora, en todos los artículos de esta serie hemos visto
como escribir un SELECT que nos devuelva las filas que
necesitamos. Eso está muy bien y funciona perfectamente, pero
tiene un problema, y es que si nuestro SELECT es muy complejo
entonces se nos dificulta entender lo que hace. Para subsanar
este inconveniente podemos utilizar dos (o más)
comandos SELECT.
El primer SELECT hace gran parte del trabajo. El
segundo SELECT completa el trabajo.
Veamos un ejemplo:
Captura 1. Enviando el SELECT a un cursor con nombre
propio
En la Captura 1. el resultado del SELECT fue enviado a un
cursor llamado MiCursor. Y como fue enviado a un cursor, nada
vemos en la “Ventana de comandos” del Visual FoxPro. Para
ver el contenido de ese cursor podemos escribir:
Captura 2. El contenido del cursor llamado MiCursor
Fíjate que lo único que escribimos es: SELECT * FROM MiCursor,
tal y como te lo indica la flecha roja en la Captura 2. Lo
interesante de esto es que hemos reemplazado
un SELECT complejo como el mostrado en la Captura 1. por
un SELECT extremadamente simple, como el que te muestra la
flecha roja.
Pero además, y aquí viene lo realmente importante: podemos
tratar a nuestro cursor como si de una tabla con todas esas
columnas y todas esas filas se tratara. Por ejemplo, para ver
todas las ventas cuyos totales sean superiores a 200 podríamos
escribir:
Captura 3. Las ventas cuyos totales son mayores que 200
Desde luego que este mismo resultado podrías haber obtenido
escribiendo una cláusula WHERE en la Captura 1. pero
tu SELECT sería aún más complejo de lo que ya es.
El SELECT de la Captura 3. es mucho más sencillo y por lo
tanto mucho más fácil de entender.
Esta técnica de los dos cursores (o tres cursores, o cuatro
cursores, los que necesites) nos permite obtener resultados de
procesamientos muy complejos escribiendo poco y entendiendo
mucho.
¿Cuándo debemos emplear la técnica de los dos
cursores?
Cuando nuestro SELECT ya está muy largo y eso nos dificulta
entender lo que hace. Y con mucha mayor razón si no estamos
pudiendo obtener el resultado que queremos obtener. En tales
casos, escribir dos (o más) cursores para dividir el problema en
varias partes suele ser lo más inteligente que se puede hacer.
¿El SELECT ya está largo y te cuesta entender lo que hace y por
qué no estás obteniendo el resultado que deseas obtener?
Emplea la técnica de los dos cursores: divídelo en partes.
En el primer cursor pones todas las columnas que necesitarás
procesar, incluyendo las subconsultas que deben estar en la
lista de columnas del SELECT.
En el segundo cursor pones las cláusulas WHERE, GROUP BY,
HAVING y quizás algunos JOIN que no pusiste en el primer
cursor.
Si tu segundo cursor ya está largo y complejo y aún no
consigues obtener el resultado que quieres, utiliza un tercer
cursor. Y así sucesivamente.
Al final, el último cursor que utilices debe ser muy sencillo de
entender, algo como lo que vemos en la Captura 3.
Con la técnica de los dos cursores ahorrarás mucho tiempo y
quizás algún dolor de cabeza.
El defecto de esta técnica
Esta técnica no es perfecta, tiene un gran defecto.
¿Cuál es?
Que las filas se procesan más veces que las estrictamente
necesarias. Por ejemplo, si la tabla tiene 1.000.000 de filas y se
necesitaron 4 SELECT para obtener el resultado deseado eso
significa que quizás se procesaron 4.000.000 de filas.
¿Cuál es la solución?
Una vez que se obtuvo el resultado deseado realizar el proceso
inverso. Es decir lo que se agregó en el cuarto SELECT copiarlo
en el tercer SELECT. Lo que se agregó en el cuarto SELECT más
lo que se agregó en el tercer SELECT, agregarlo al
segundo SELECT. Y lo que se agregó en el cuarto SELECT, en el
tercer SELECT, y en el segundo SELECT, agregarlo al
primer SELECT.
Se tendrá así un SELECT grande, feo, muy complejo, pero que
funcionará perfectamente y que además será muy rápido
porque procesará a todas esas filas una sola vez.
Conclusión:
Cuando el SELECT ya se está tornando muy complejo, es difícil
de entender lo que hace, y no se está consiguiendo obtener el
resultado deseado, lo más inteligente que se puede hacer es
dividir el problema en partes.
O sea, no usar un solo SELECT sino dos o más SELECT.
Para ello, lo usual es que en el primer SELECT se escriban todas
las columnas que se necesitan, esto suele implicar a la cláusula
JOIN para traer a las columnas de otras tablas. El cursor así
obtenido se utiliza en un segundo SELECT y es en este segundo
SELECT donde se escriben las cláusulas WHERE, GROUP BY, y
HAVING.
Si este segundo SELECT también se está tornando muy
complejo y aún no se está consiguiendo obtener el resultado
deseado, entonces se crea un cursor con nombre propio que
será utilizado por un tercer SELECT. Y así sucesivamente.
El último SELECT que se escriba debe ser muy simple, muy
sencillo, muy fácil de entender. Desde luego que eso de “muy
fácil de entender” depende de cada quien, porque
un SELECT que para una persona resulta “muy fácil de
entender” para otra persona puede ser muy difícil de entender.
Lo importante es que sea muy fácil de entender para la persona
que escribe el SELECT.
Empleando la técnica de los dos cursores se puede muy
rápidamente resolver problemas muy complicados. Trata de
utilizarla en cada ocasión que se te dificulte obtener el resultado
deseado.
De DBF a SQL (22). Optimizando el uso
del operador de comparación especial IN
25 AGOSTO, 2018 / WROV
Como seguramente recordarás, el operador de comparación
especial IN es muy útil para reemplazar al operador de
comparación OR y para escribir mucho menos.
Por ejemplo, se puede reemplazar:
Listado 1. Usando el operador OR
1 WHERE
2 MICOLOR = 'AZUL' OR MICOLOR = 'BLANCO' OR MICOLOR = 'ROJO'
por esta forma simplificada:
Listado 2. Usando el operador IN
1 WHERE
2 MICOLOR IN ('AZUL', 'BLANCO', 'ROJO')
Evidentemente se escribe mucho menos cuando se usa IN y
cuantas más sean las opciones mayor será el ahorro que
obtendremos, pero de todas maneras si las opciones posibles
son muchas, la condición en la cláusula WHERE puede llegar a
ser extremadamente larga, y por lo tanto tediosa de leer.
Un ejemplo sería verificar si en una columna se escribió la sigla
de alguno de los Estados que forman parte de los Estados
Unidos de América. Hay 50 siglas, cada una de ellas consta de 2
letras, entre ellas: AZ = Arizona, NC = North Caroline, NY = New
York, etc.
En casos así, sería engorroso estar verificando si se escribieron
correctamente las 50 siglas, con mucha mayor razón si deben
ser utilizadas por varios SELECT.
¿Cuál es la solución?
Crear un cursor, o una tabla, con todos los valores posibles y
luego crear una subconsulta de ese cursor o tabla para
verificar que se tenga un valor correcto.
En nuestro ejemplo de los colores, escribiríamos:
Listado 3. Creando un cursor e insertándole filas
1 CREATE CURSOR MISCOLORES cNombre(C (20))
3 INSERT INTO MISCOLORES VALUES ('AZUL')
4 INSERT INTO MISCOLORES VALUES ('BLANCO')
5 INSERT INTO MISCOLORES VALUES ('ROJO')
Y ahora, para verificar si el color que está en una columna de la
tabla es un color válido, podríamos escribir:
Listado 4. Usando una subconsulta
1 WHERE
2 MICOLOR IN (SELECT cNombre FROM MISCOLORES)
Y así, hemos simplificado grandemente la escritura. Es cierto,
tuvimos que crear un cursor o una tabla e insertarle un montón
de datos, pero nuestro SELECT se simplificó grandemente y eso
es lo realmente importante.
Desde luego que si las opciones son pocas, no vale la pena crear
un cursor o una tabla para conseguir los mismos resultados,
pero cuando las opciones son muchas entonces sí puede llegar a
ser muy conveniente. ¿Y cuánto es “muchas”? En general, si las
opciones serán hasta 12 es mejor hacerlo como se mostró en
el Listado 2. pero si son más de 12 entonces hacerlo como se
mostró en el Listado 4.
Conclusión:
Cuando aún usando el operador de comparación especial IN la
condición se vuelve muy larga suele ser muy conveniente crear
un cursor o una tabla que contenga los datos y luego con
una subconsulta hacer la verificación correspondiente.
De DBF a SQL (23). Usando la
función CAST()
26 AGOSTO, 2018 / WROV
En SQL existe una función muy útil llamada CAST(). Se utiliza
para cambiar el tipo de una variable o columna por otro tipo.
Por ejemplo, en Visual FoxPro para cambiar a una variable que
es de tipo carácter por otra que es de tipo numérico podemos
usar la función VAL().
Listado 1. Convirtiendo una variable de carácter a numérica en
Visual FoxPro
1 ? VAL("9")
Y el resultado, como ya sabes, será el número 9.
Todo está muy bien, pero con CAST() podemos hacer mucho
más que eso:
Listado 2. Convirtiendo de carácter a numérico con CAST()
1 ? CAST("9" AS INTEGER)
En el Listado 2. convertimos de carácter a numérico, sería el
equivalente a usar la función VAL(), pero también podríamos
escribir:
Listado 3. Convirtiendo de carácter a numérico con decimales
1 ? CAST("9" AS NUMERIC(10, 2))
Aquí, el resultado no será 9 sino que será 9.00, o sea un 9 con
dos decimales.
También podrías escribir algo como:
Listado 4. Convirtiendo de carácter a numérico de punto flotante
1 ? CAST("9.123" AS FLOAT(10,2))
Devolverá 9.12 porque se le pidieron 2 decimales. También
puedes hacer a la inversa, por ejemplo:
Listado 5. Convirtiendo de numérico a carácter
1 ? CAST(123 AS CHARACTER(10))
Convertirá el número 123 al string “123”. Fíjate que es
importante la cantidad de caracteres que especifiques en la
función CAST(). En este ejemplo pusimos 10, siempre debe ser
igual o mayor a la cantidad de dígitos para que no se trunque el
resultado.
También podemos convertir un carácter a fecha, por ejemplo:
Listado 6. Convirtiendo de carácter a fecha
1 SET DATE BRITISH
2 SET CENTURY ON
3
4 ? CAST("21/04/2018" AS DATE)
Y desde luego, también puedes convertir de fecha a carácter,
por ejemplo:
Listado 7. Convirtiendo de fecha a carácter
1 ? CAST(Date() AS CHARACTER(10))
Nuevamente, hay que asegurarse de que la cantidad de
caracteres sea suficiente para recibir a la fecha o se obtendrá un
resultado truncado.
Conclusión:
En Visual FoxPro usamos varias funciones para convertir de un
tipo de datos a otro tipo de datos, en SQL solamente usamos la
función CAST(), eso nos facilita recordar cual es la función que
debemos usar y también nos facilita la lectura de nuestro código
fuente porque es más fácil que te olvides lo que hace:
? VAL(“123456”)
a que te olvides lo que hace:
? CAST(“123” AS INTEGER)
“CAST” significa “moldear” y “AS INTEGER” se traduce: “como
entero”. Si lees inglés, entonces es muy fácil saber que
escribiste: “moldear a 123 como entero”. Y por lo tanto,
entender tu código fuente es más sencillo que escribir VAL().
NOTA: En nuestros ejemplos usamos solamente algunos tipos
de datos, hay más tipos de datos que puedes usar: LOGICAL,
DATETIME, CURRENCY, etc. En la ayuda del Visual FoxPro los
encontrarás a todos.
De DBF a SQL (24). Buscando texto
26 AGOSTO, 2018 / WROV
Muchas veces necesitamos realizar búsquedas en un texto, por
ejemplo para tener una lista de todas las personas cuyos
apellidos empiezan con la letra “V”. Para ello en SQL tenemos el
operador LIKE.
LIKE puede usar dos “comodines”:
_ Significa que allí hay un carácter desconocido
% Significa que a continuación puede existir una cantidad
desconocida de caracteres
Veamos algunos ejemplos:
Captura 1. La tabla de CLIENTES ordenada por los
nombres de los clientes
Ejemplo 1. Ver a todos los clientes cuyos nombres
empiezan con la letra “M”
Captura 2. Todos los clientes cuyos nombres empiezan
con la letra “M”
Lo que hicimos en la Captura 2. fue pedirle que nos muestre a
todos los clientes cuya primera letra del nombre es una “M” y a
continuación puede existir una cantidad desconocida de
caracteres.
Ejemplo 2. Ver a todos los clientes cuyos nombres
empiezan con “EL”
Captura 3. Todos los clientes cuyos nombres empiezan
con las letras “EL”
En este caso escribimos dos letras antes del símbolo de
porcentaje, por lo tanto nos mostró solamente a aquellos
clientes cuyos nombres empiezan con esas dos letras.
Ejemplo 3. Ver a todos los clientes que tienen una letra
“S” en sus nombres
Captura 4. Todos los clientes que tienen una letra “S” en
sus nombres
En la Captura 4. usamos dos veces al comodín “%”, una vez
antes de la letra “S” y otra vez después de la letra “S”. Con eso
le estamos diciendo al VFP que la letra “S” puede estar en
cualquier lugar: al principio (como en “Silvia”), en el medio
(como en “Isabel”), o al final (como en “Mercedes”).
Ejemplo 4. Ver a todos los clientes cuya segunda letra del
nombre es una “E”
Captura 5. Todos los clientes cuya segunda letra del
nombre es una “E”
Cuando queremos que el texto conocido se encuentre en una
posición exacta usamos el guión bajo (_). En ese caso, como hay
un guión bajo al principio, eso significa que el primer carácter
puede ser cualquiera, luego debe haber una letra “E” y los
siguientes caracteres pueden ser cualesquiera.
Ejemplo 5. Ver a todos los clientes cuya tercera letra sea
una “R”
Captura 6. Todos los clientes cuya tercera letra del
nombre es una “R”
En la Captura 6. hemos escrito dos guiones bajos y luego una
letra “R” y luego el símbolo de porcentaje. Eso significa que el
primer carácter puede ser cualquiera, el segundo carácter
puede ser cualquiera, el tercer carácter debe ser una letra “R”, y
los restantes caracteres pueden ser cualesquiera.
Ejemplo 6. Ver a todos los clientes cuya segunda letra
sea una “A” y cuya quinta letra sea una “E”
Captura 7. Los clientes cuya segunda letra es una “A” y
la quinta letra es una “E”
En la Captura 7. mediante el uso de los guiones bajos le hemos
indicado que la segunda letra debe ser una “A” y que la quinta
letra debe ser una “E”. Los demás caracteres pueden ser
cualesquiera.
Ejemplo 7. Ver a todos los clientes cuya última letra del
nombre es una “A”
Captura 8. Todos los clientes cuya última letra del
nombre es una “A”
En la Captura 8. hemos obtenido los clientes que en la última
letra de su nombre tienen una “A”. También podríamos haber
obtenido los que terminan con “NA” o con “ENA” o con lo que
deseáramos.
Conclusión:
Para buscar caracteres o inclusive palabras enteras dentro de un
texto podemos usar el operador LIKE. Éste acepta dos
comodines: a) el guión bajo que reemplaza a un carácter y, b) el
símbolo de porcentaje que reemplaza a cualquier cantidad de
caracteres.
En los ejemplos de arriba hemos visto como ambos pueden ser
usados. Desde luego que hay muchísimas más combinaciones
posibles, puedes inclusive buscar palabras enteras, frases
enteras, que pueden estar al principio, en el medio, al final, etc.
De DBF a SQL (25). Creando tablas
y cursores
28 AGOSTO, 2018 / WROV
La creación de tablas es absolutamente necesaria cuando se
trabaja con datos que deben ser guardados y recuperados.
En Visual FoxPro podemos crearlos de la forma tradicional con
el comando CREATE o de la forma orientada hacia SQL con el
comando CREATE TABLE.
Los cursores son muy similares a las tablas pero se diferencian
en que luego de ser cerrados ya no pueden ser accedidos,
desaparecen, son eliminados. Son por lo tanto útiles para
guardar datos temporarios, pero no para guardar datos
permanentes.
Captura 1. Creando la tabla ALUMNOS
En la Captura 1. vemos como se puede crear una tabla usando
el comando CREATE TABLE. Este comando se agregó al Visual
FoxPro cuando se lo hizo compatible con SQL. Como ves, es
muy fácil de entender. A continuación de los nombres de las
columnas se escribe el tipo y si es necesario la cantidad de
lugares que se deben reservar. En ese ejemplo hemos
usado N (numérico), C (carácter), y L (lógico). Hay más tipos, los
puedes encontrar en la ayuda del Visual FoxPro.
Captura 2. Insertándole filas a la tabla de ALUMNOS
(Si haces clic sobre la imagen, la verás más grande)
Agregarle filas a una tabla también es muy fácil. Para ello, se
usa el comando INSERT INTO, a continuación la lista de
columnas que se desean agregar (no es necesario que se
coloquen todas las columnas de la tabla, pueden faltar
columnas, no hay problema con eso) y finalmente la palabra
VALUES y los valores que se colocarán en las columnas.
Si ahora escribimos el comando USE, la tabla se cerrará pero
continuará en el disco y cuando lo necesitemos podremos ver
nuevamente el contenido de sus filas.
Captura 3. Creando un cursor e insertándole una fila
(Si haces clic sobre la imagen, la verás más grande)
Como puedes ver en la Captura 3., crear un cursor e insertarle
filas es prácticamente lo mismo que crear una tabla e insertarle
filas. La única diferencia es que en lugar de escribir CREATE
TABLE debes escribir CREATE CURSOR, pero nada más, es la
única diferencia.
Pero si escribes CLOSE TABLES o CLOSE ALL o sales del Visual
FoxPro, y luego en la ventana de comandos escribes:
Listado 1. Abriendo una tabla
1 SELECT * FROM ALUMNOS
Verás todas las filas de la tabla ALUMNOS, sin embargo si
escribes:
Listado 2. Intentando abrir un cursor que ya había sido cerrado
1 SELECT * FROM MICURSOR
Ninguna de las filas que habías guardado en tu cursor te serán
mostradas y en cambio verás el mensaje:
Captura 4. Después de cerrado, un cursor es eliminado,
ya no existe
¿Por qué? Porque el cursor es algo temporal, después de
cerrado, es eliminado, deja de existir. Por lo tanto en los
cursores solamente debemos guardar datos temporales, si
necesitamos guardar datos persistentes, deberemos usar una
tabla, nunca un cursor.
Conclusión:
Para guardar datos y luego poder recuperarlos deberemos crear
tablas o cursores. La diferencia es que los datos guardados en
las tablas pueden permanecer allí durante mucho tiempo, en
cambio los datos guardados en los cursores desaparecen
cuando se cierra el cursor, porque el mismo cursor es eliminado,
deja de existir.
De DBF a SQL (26). INSERT, UPDATE, y
DELETE, con subconsultas
30 AGOSTO, 2018 / WROV
Ya hemos vistos varios ejemplos de subconsultas y ya tienes
una buena idea de lo que puedes conseguir con ellas. Ahora
daremos un paso más: las utilizaremos con los otros comandos
SQL.
Subconsultas con INSERT
Cuando queremos insertar datos en una tabla tenemos dos
formas de hacerlo:
1. Con la cláusula VALUES
2. Con una subconsulta
Listado 1. Insertando filas con la cláusula VALUES
1 INSERT INTO CLIENTES ;
2 (CLI_SERVID, CLI_IDENTI, CLI_CODIGO, CLI_NOMBRE) ;
3 VALUES(1 , 1 , 'PJ001' , 'PEREZ, JUAN')
En el Listado 1. vemos la primera forma. Especificamos cuales
son las columnas que queremos usar, luego los valores que
deseamos que tengan esas columnas. Muy sencillo, nada del
otro mundo.
NOTA: Al autor de este blog le gusta ver su código fuente bien
ordenado, pero si prefieres puedes escribir el Listado 1. en una
sola línea, en lugar de escribirla en 3 líneas, como en ese
ejemplo.
Listado 2. Insertando una fila con una subconsulta
1 INSERT INTO CLIENTES ;
2 (CLI_SERVID, CLI_IDENTI, CLI_CODIGO, CLI_NOMBRE) ;
3 SELECT 1, 1, Right(ALU_MATRIC, 5), ALU_NOMBRE FROM ALUMNOS WHERE ALU_IDENTI = 5
En este caso, con la subconsulta se insertó dentro de la tabla
CLIENTES al alumno cuyo Identificador es 5.
Pero aquí viene lo realmente interesante…
Listado 3. Insertando múltiples filas con una subconsulta
1 INSERT INTO CLIENTES ;
2 (CLI_SERVID, CLI_IDENTI, CLI_CODIGO, CLI_NOMBRE) ;
3 SELECT 1, 1, Right(ALU_MATRIC, 5), ALU_NOMBRE FROM ALUMNOS
En el Listado 3. estaríamos insertando a todos los
alumnos dentro de la tabla de CLIENTES. Y desde luego que
puedes usar las cláusulas que ya conoces: TOP, DISTINCT,
WHERE, JOIN, GROUP BY, HAVING, para insertar solamente a
unas cuantas filas, las que cumplan con las condiciones que
impongas. Y si usas la cláusula ORDER BY entonces también
podrías determinar en que orden se insertarán esas filas.
Como habrás notado, esta posibilidad es extremadamente
poderosa porque nos permite insertar en una tabla el resultado
obtenido de un SELECT. Y ese SELECT puede ser todo lo
complejo que quieras, las filas que se obtengan como resultado
de su ejecución serán las filas que serán insertadas. Muy útil,
realmente.
Subconsultas con UPDATE
Listado 4. Usando una subconsulta en la columna que se
actualizará
1 UPDATE ;
2 CLIENTES ;
3 SET ;
4 CLI_NOMBRE = (SELECT ALU_NOMBRE FROM ALUMNOS WHERE ALU_MATRIC = '012003') ;
WHERE ;
5
CLI_IDENTI = 12
6
En este caso quisimos actualizar a la columna CLI_NOMBRE y
escribimos una subconsulta a continuación. El resultado que se
obtenga de esa subconsulta será el nuevo valor de la columna
CLI_NOMBRE.
Listado 5. Usando una subconsulta en la condición
1 UPDATE ;
2 CLIENTES ;
3 SET ;
4 CLI_NOMBRE = 'JOSEFINA' ;
WHERE ;
5
CLI_IDENTI = (SELECT VTC_IDECLI FROM VENTASCAB WHERE VTC_IDENTI = 12345)
6
En este caso, la subconsulta no fue escrita a continuación de la
columna que deseamos actualizar sino en la cláusula WHERE.
Y por supuesto, también podríamos poner subconsultas en
ambos lugares.
MUY IMPORTANTE: Cuando ejecutamos el comando UPDATE
una precaución a tener muy en cuenta es usar siempre una
cláusula WHERE, porque de no hacerlo se actualizarán todas
las filas de la tabla. Alguna vez eso será justamente lo que
quieres hacer, pero esos casos son muy raros, lo usual es que se
quiera actualizar solamente una fila o unas pocas filas con cada
UPDATE. Mucho cuidado con esto.
Subconsultas con DELETE
Listado 6. Estableciendo una condición mediante una
subconsulta
1
DELETE FROM ;
2 CLIENTES ;
3 WHERE ;
4 CLI_NOMBRE = (SELECT ;
5 ALU_NOMBRE ;
6 FROM ;
ALUMNOS ;
7 WHERE ;
8 ALU_IDENTI = 218)
9
En el Listado 6. usamos una subconsulta para establecer la
condición que deberá cumplir la fila que será borrada.
CUIDADO: Si son varias las filas que cumplen la condición
entonces todas ellas serán borradas. No ejecutes el comando
DELETE sin antes estar muy pero muy seguro de cuales serán
las filas borradas, porque podrías terminar dándote cuenta que
borraste filas que no debías borrar.
Conclusión:
Las subconsultas no solamente pueden usarse con el comando
SELECT, también pueden usarse con los comandos INSERT,
UPDATE, y DELETE, tal y como vimos en este artículo.
De DBF a SQL (27). Entendiendo
Cliente/Servidor
31 AGOSTO, 2018 / WROV
Hasta ahora, todo lo que hemos hecho en esta serie de artículos
podíamos realizarlos con las tablas .DBF, nativas del Visual
FoxPro. Eso está muy bien y podríamos quedarnos allí porque
como habrás comprobado si pusiste en práctica los ejemplos
con tus propias tablas, hay una muy notoria ganancia en
velocidad. Escribes menos y obtienes los resultados mucho más
rápidamente. Está muy bueno eso ¿verdad?. Pero no nos
quedaremos allí sino que daremos un gran salto hacia adelante
y nos adentraremos en una tecnología muy distinta a la
tradicionalmente usada en Visual FoxPro pero que a su vez
es extremadamente poderosa. Su nombre
es Cliente/Servidor y es más o menos el equivalente a que un
jugador de fútbol de un club sudamericano de mediano porte
vaya a jugar al Barcelona de España o al Real Madrid o algo así.
Es totalmente otro nivel de juego.
Entonces, ¿de qué se trata Cliente/Servidor?
Lo primero que debes saber es que esta es una metodología
para acceder a los datos desde varias computadoras, en otras
palabras, desde una red de computadoras. Aunque puedes
hacer aplicaciones Cliente/Servidor que sean monousuario no
fue pensado para eso sino para aplicaciones multiusuario.
En las aplicaciones antiguas (o sea, las tradicionales del VFP)
los programas “tocaban” físicamente a los datos, o sea que por
un lado estaba el programa, por el otro lado estaban los datos
que se guardaban dentro de los archivos y no había algo entre
ellos que los separara.
Programa <——–> Archivo de datos
Esto funcionaba, pero con muuuchos problemas potenciales, por
ejemplo un usuario estaba modificando un registro que otro
usuario también estaba modificando. O un usuario estaba
borrando un registro que otro usuario necesitaba en su informe.
Eso causaba muchos inconvenientes. Mientras no había algo
mejor los usuarios debían conformarse con eso … pero ahora
tenemos Cliente/Servidor.
¿Cómo funciona Cliente/Servidor?
En esta metodología los programas jamás “tocan” físicamente a
los datos, aunque quieran hacerlo no podrán, es totalmente
imposible. En lugar de eso, los programas se comunican con
el Cliente, el Cliente le envía una petición al Servidor, y es
el Servidor quien procesa esa petición y le envía la respuesta
al Cliente y el Cliente se la envía de regreso al programa.
Programa —–> Cliente —–>Servidor
Servidor —–> Cliente —–> Programa
Como ves, el camino es mucho más largo pero también es
muchísimo más seguro y confiable. El Cliente es el
intermediario. Nada puede hacerse sin él.
Entonces, el Servidor se instala en una computadora. ¿En cuál
computadora? En la computadora donde estarán alojadas todas
las bases de datos.
El Cliente se instala en todas las demás computadoras, o sea
en las computadoras que usarán los usuarios.
¿Y cómo se comunica el Cliente con el Servidor, y viceversa?
Para que la comunicación entre ellos sea posible se necesita de
una canal de comunicación. Hay muchos canales de
comunicación, entre ellos: ADO, JDBC, ODBC, .NET, .PYTHON,
etc. El más usado es el ODBC, pero tú puedes optar por
cualquier otro, si así lo prefieres.
Cuando un programa necesita algo se lo pide al Cliente y éste
se lo pide al Servidor. ¿Qué puede necesitar un programa?
Veamos algunos ejemplos:
Grabar la venta que corresponde a la Factura Nº 12345,
con fecha de hoy, le vendimos a Juan Pérez y el monto de
la venta fue de 1.250 dólares
Grabar la cobranza que hoy le hicimos a María Benítez,
según el Recibo Nº 785 y por un monto de 420 dólares
Cambiar el precio de venta de los monitores Toshiba de
21″, el nuevo precio es 100 dólares
Ver todas las ventas que hicimos el mes pasado
Ver los nombres de todos los vendedores que hoy hicieron
ventas por más de 500 dólares
En todos estos casos el programa se lo pide al Cliente,
el Cliente se lo pide al Servidor y el Servidor trata de cumplir
con el pedido. Si pudo cumplir se lo indica al Cliente enviándole
un número 1. Si no pudo cumplir le envía el número -1. Y si aún
no terminó de procesar el pedido le envía un 0 (cero) y así
el Cliente sabe que el Servidor todavía está trabajando.
Por lo tanto, cuando recibe un 1 el Cliente sabe que su petición
tuvo éxito. Y si recibe un -1 el Cliente sabe que su petición falló.
Súper fácil ¿verdad?
Si la petición tuvo éxito entonces el Cliente recibe un “cursor”.
¿Y qué es un cursor? ya lo sabes, porque ya lo hemos descrito
en artículos anteriores de esta serie. Un cursor es una tabla de
memoria, una tabla virtual, una copia del contenido que hay en
el Servidor.
Eso significa que si modificas el cursor la Base de Datos ni se
entera, su contenido queda exactamente igual a como estaba
antes. ¿Por qué? porque el cursor es una copia de lo que está
dentro de la Base de Datos (es como fotocopiar un libro, si
escribes sobre la fotocopia el libro original continúa igual, allí no
verás lo que escribiste en la fotocopia.
En Cliente/Servidor siempre trabajas con fotocopias y jamás
puedes tocar al libro original).
Resumiendo:
El Servidor se instala en una sola computadora (en la que
se guardarán todas las bases de datos)
El Cliente se instala en muchas computadoras (en las que
trabajarán los usuarios)
Para que el Cliente y el Servidor puedan comunicarse
entre sí se necesita de un canal de comunicación entre
ellos (por ejemplo el driver ODBC)
El Cliente envía peticiones al Servidor,
el Servidor procesa esas peticiones y luego le envía el
resultado al Cliente
El Cliente sabe que su petición tuvo éxito si recibe como
respuesta el número 1. Si la respuesta es -1 entonces la
petición falló. Si la respuesta es 0 entonces
el Servidor aún continúa procesando los datos
Si el Cliente recibió un 1 porque la petición tuvo éxito
entonces también recibe un “cursor”, o sea una tabla de
memoria, donde se encuentra la respuesta enviada por
el Servidor (por ejemplo, los nombres de todos los
vendedores que hoy vendieron por un monto superior a
500 dólares)
Ni la aplicación (el sistema de contabilidad, de ventas, de
tesorería, de cobranzas, etc.) ni el Cliente jamás tocan a
los datos, solamente el Servidor puede hacer eso.
El Servidor sólo toca a los datos si recibe un pedido
del Cliente para hacerlo. Si el Cliente no se lo pide,
el Servidor jamás toca a los datos.
Ventajas de usar Cliente/Servidor
Control centralizado: Para poder acceder a una Base de Datos
se necesita el nombre de un usuario y su contraseña y los
permisos (también llamados derechos o privilegios)
correspondientes. Una tabla .DBF puede abrirla cualquiera que
conozca aunque sea un poquito de Visual FoxPro. Una
tabla SQL no. Solamente podrá abrirla si tiene autorización para
hacerlo.
Ocultamiento: Para abrir una tabla .DBF se requiere conocer
en cual computadora se encuentra, en cual carpeta de esa
computadora, y cual es el nombre de esa tabla .DBF. En buenos
motores como Firebird SQL, no es necesario tener toda esa
información. Podrías pedir conectarte a
MISERVIDOR/MIBASEDATOSCOLEGIO y si previamente el
Administrador de la Base de Datos definió a cual computadora
se la conoce como MISERVIDOR y a cual Base de Datos de esa
computadora se la conoce como MIBASEDATOSCOLEGIO,
podrías realizar la conexión sin tener la menor idea del nombre
real de la Base de Datos (podría ser [Link], por ejemplo)
ni cual es la computadora (podría ser una computadora local, la
que tiene el IP [Link], por ejemplo; o podría ser una
computadora remota, la que tiene el IP [Link], por
ejemplo).
Seguridad: Como consecuencia directa de los dos puntos
anteriores, la seguridad que se puede obtener es muy grande. Si
por ejemplo a un usuario solamente se le otorgó el derecho de
hacer SELECT a la tabla ALUMNOS, él no podrá causarle un daño
a la Base de Datos aunque lo intente y lo reintente. Primero,
porque con un SELECT jamás podría causar daño, y segundo
porque como ya vimos quizás ni siquiera sabe en cual
computadora se encuentra la Base de Datos, ni en cual carpeta,
ni cual es el nombre real de esa Base de Datos.
Velocidad: Los Servidores SQL están optimizados para
conseguir una muy buena velocidad de respuesta. ¿Visual
FoxPro te parece rápido? Si crees eso entonces es seguro que
no has probado un buen motor SQL, tal como Firebird. El autor
de este blog aún recuerda cuando un proceso que con
tablas .DBF se demoraba más de 20 minutos en realizar,
con Firebird finalizaba en 5 ó 6 segundos, es otro mundo.
Escalabilidad: Al mejorar el hardware de la computadora
donde se encuentra el Servidor se puede conseguir un gran
incremento en la velocidad de las respuestas.
Menos fallas: En las tablas .DBF un problema eterno es que
pueden corromperse. Un corte de la energía eléctrica o de la
conexión con el Servidor pueden dañar a las tablas de datos o
a los archivos de índice. En un buen motor SQL (ya lo sabes:
como Firebird) tal cosa no puede ocurrir. Aunque se corte la
energía eléctrica 100 veces en un día, ninguna tabla de la Base
de Datos será dañada.
Backups en cualquier momento: las tablas .DBF no se
pueden copiar mientras están siendo usadas, primero debes
cerrarlas y luego copiarlas. Con un buen motor SQL (y sí…
Firebird) puedes realizar los backups cuando se te ocurra,
ningún problema.
Mejor desarrollo de aplicaciones: como ya no debes escribir
tanto código y además no debes perder tanto tiempo pensando
en los algoritmos correctos, y haciendo un larguísimo proceso de
prueba/error entonces puedes concentrarte en lo realmente
importante: que tu aplicación se vea mejor, sea más fácil de
usar, sea más completa. Esto te posibilitará aumentar las ventas
y por lo tanto, ganar más dinero.
Conclusión:
Cambiar de la forma tradicional de programar
a Cliente/Servidor no es algo que se puede conseguir de la
noche a la mañana pero las ventajas de realizar ese cambio son
muy grandes y deberían muy seriamente ser analizadas.
Porque en resumidas cuentas lo que se conseguirá será trabajar
menos y obtener mejores resultados.
De DBF a SQL (28). Conectándose a
un Servidor
1 SEPTIEMBRE, 2018 / WROV
En el artículo:
De DBF a SQL (27). Entendiendo Cliente/Servidor
ya hemos visto las grandes ventajas que tendremos si usamos
esa tecnología. Ahora veremos un ejemplo práctico, para
entender mejor.
¿Que se necesita para conectarse a un Servidor?
Que el Servidor esté instalado en una
computadora. Puede ser la propia computadora o alguna
otra computadora, tanto en una red local como en una red
remota. Pero debe estar instalado, sí o sí.
Que el Cliente esté instalado en la computadora
desde la cual se realizará la conexión. Si en una
computadora no se instaló el Cliente, entonces desde esa
computadora no se podrá conectar con el Servidor.
Que un driver de comunicación esté instalado en la
computadora del Cliente. Para que el Cliente pueda
comunicarse con el Servidor y para que
el Servidor pueda comunicarse con el Cliente, se
necesita de un canal que los enlace. Hay muchos, el más
usado es el ODBC.
Si usamos ODBC, escribir una cadena de
conexión. Hay de dos tipos: a) con DSN y, b) sin DSN.
La cadena de conexión de ODBC
La cadena de conexión depende del motor SQL que utilices. Las
diferencias en las sintaxis no son grandes, pero existen y deben
tomarse en cuenta porque cualquier diferencia entre lo que
deberías haber escrito y lo que realmente escribiste hará que la
conexión sea rechazada.
Cadena de conexión sin DSN
Si te conectas a una Base de Datos con DSN, tu cadena de
conexión será más corta pero previamente debiste haberte
tomado el trabajo de crear el DSN en esa computadora. En
general, no se justifica hacerlo y por ello el autor de este blog
siempre se conecta sin DSN.
Listado 1. Conectándose a un Servidor mediante ODBC y sin
usar DSN
1 lcCadenaConexion = "DRIVER={Firebird/Interbase(r) driver};" ;
2 + "USER=SYSDBA;" ;
3 + "PASSWORD=masterkey;" ;
4 + "ROLE=;" ;
+ "DATABASE=D:\CONTABILIDAD\BASESDATOS\[Link];"
5
En el Listado 1. vemos un ejemplo típico de cadena de conexión
sin usar DSN. Allí especificamos:
1. Cual es el driver que usaremos para conectar al
Cliente con el Servidor. Eso también implica cual es el
motor SQL que usaremos. En este ejemplo
es Firebird pero podría ser cualquier otro: MySql, MariaDb,
Postgre, SQL Server, Oracle, etc.
2. El nombre del usuario. Para conectarse a una Base de
Datos siempre se debe escribir el nombre del usuario. Es
imposible realizar la conexión si no se escribe ese dato.
3. La contraseña del usuario. Todo usuario debe tener una
contraseña o password, sí o sí. No existe la posibilidad de
conectarse sin escribir una contraseña.
4. El rol del usuario. Un rol es un grupo de usuarios que
tienen los mismos derechos (también llamados permisos o
privilegios) en una Base de Datos. Este dato es opcional, si
no se lo escribe entonces el usuario solamente tendrá sus
propios derechos (permisos/privilegios) pero no los
derechos (permisos/privilegios) que tiene algún rol.
5. La Base de Datos. Dentro de un Servidor se pueden
tener muchas bases de datos y se debe escribir a cual de
esas bases de datos el usuario se quiere conectar.
NOTA: Los espacios en blanco son muy importantes. No debes
colocarlos ni antes ni después del signo igual porque ese
pequeño detalle en algunos casos hará que la conexión sea
rechazada.
Realizando la conexión
Para intentar la conexión a la Base de Datos que especificamos
en el Listado 1. simplemente debemos escribir:
Listado 2. Intentando conectar a la Base de Datos
1 lnHandle = SQLStringConnect(lcCadenaConexion)
La función SQLStringConnect() del Visual FoxPro es la que
deberemos usar cuando nuestra cadena de conexión es sin DSN.
Fíjate que guardamos el valor que devuelve esa función en una
variable, llamada en este caso lnHandle (ese nombre es un
ejemplo, tú puedes darle el nombre que prefieras). ¿Y por qué
guardamos el resultado de la función en una variable? Porque a
partir de este momento todas las acciones que realicemos en la
Base de Datos se harán usando esa variable.
La primera acción debe ser, evidentemente, verificar si el
intento de conexión tuvo éxito o no lo tuvo.
¿Y cómo sabemos si la conexión se realizó exitosamente?
Verificando el valor de la variable. Si ese valor es 1 (uno), fue
exitosa, si fue -1 (menos uno), ocurrió algún error. ¿Cuál error?
Eso lo podremos averiguar usando la función AERROR().
Si la conexión fue exitosa, entonces ya podremos realizar
cualquier operación que deseemos en la Base de Datos. Desde
luego que si no tenemos permiso (derecho/privilegio) para
realizar esa operación, el resultado será un fracaso.
Un pequeño programita para realizar una consulta a una
Base de Datos
Captura 1. Conectándose a la Base de Datos y realizando
una consulta
¿Qué hicimos en la Captura 1.?
1. Intentamos la conexión a la Base de Datos
2. Si la conexión tuvo éxito, intentamos consultar a la tabla
BANCOS
3. Si la consulta tuvo éxito, el contenido de la tabla BANCOS
se guardará en el cursor cuyo nombre es MICURSOR.
Mostramos el contenido de ese cursor.
4. Si la consulta no tuvo éxito, el problema pudo estar en el
comando que quisimos ejecutar (el comando SELECT, en
este ejemplo). Los datos de ese error lo guardamos en el
array laError y le mostramos al usuario el problema que
ocurrió.
5. Nos desconectamos de la Base de Datos. Eso siempre
debemos hacer cuando ya no necesitemos estar
conectados. La función SQLDisconnect() es la que se
encarga de esa tarea.
6. Si el intento de conexión a la Base de Datos no tuvo éxito,
le mostramos un mensaje al usuario.
Fíjate que la función que usamos para ejecutar un comando en
la Base de Datos se llama SQLEXEC(). El primer parámetro que
recibe debe ser la variable que usamos al conectarnos a la Base
de Datos. ¿Por qué? porque podríamos estar conectados a varias
bases de datos al mismo tiempo y el Visual FoxPro necesita
saber a cual de ellas nos estamos refiriendo. Por ejemplo,
podrías tener lnHandle1, lnHandle2, lnHandle3, para conectarte
a 3 bases de datos distintas y al mismo tiempo.
Análogamente, la función SQLDISCONNECT() también recibe
como parámetro a la variable que se obtuvo al conectarse a la
Base de Datos. ¿Por qué? porque si te conectaste a varias bases
de datos, debe saber de cual de ellas quieres
desconectarte. NOTA: Si escribes SQLDISCONNECT(0) te
desconectarás de todas las bases de datos. Ese número cero
significa que quieres desconectarte de todas.
Captura 2. El resultado de la consulta a la tabla BANCOS
Si la conexión a la Base de Datos tuvo éxito y la consulta a la
tabla de BANCOS también tuvo éxito, entonces obtendremos el
resultado buscado, tal y como se muestra en la Captura 2.
Conclusión:
Conectarse a una Base de Datos usando la
tecnología Cliente/Servidor no es algo difícil de realizar, tal y
como pudiste ver en este artículo.
Como habrás notado, al realizar una operación (un SELECT, en
este caso), el resultado se guarda en un cursor. Ese cursor es
local, lo que hagas en él no afecta a la tabla original, la tabla de
la cual proviene. A ese cursor le puedes agregar filas, modificar
filas, o borrar filas, que la tabla original no se verá afectada.
Es como hacer la fotocopia de un libro. En la fotocopia puedes
escribir lo que se te antoje que el libro no tendrá lo que
escribiste en la fotocopia.
El trabajar con cursores es una gran ventaja, porque puedes
hacer lo que quieras con ellos sin afectar a las tablas originales.
Solamente cuando necesites actualizar a las tablas originales es
que los contenidos de los cursores se trasladarán a ellas.
De DBF a SQL (29). Las cadenas de
conexión ODBC a los motores SQL
más populares
3 SEPTIEMBRE, 2018 / WROV
En el artículo:
De DBF a SQL (28). Conectándose a un Servidor
ya hemos visto como conectarse a un Servidor. En esos
ejemplos usamos Firebird, pero hay muchos otros
motores SQL. Para conectarse a cada uno de ellos la cadena de
conexión es un poco diferente, porque depende de cada
motor SQL.
IMPORTANTE: En los ejemplos mostrados a continuación
solamente se ve una de las muchas formas de conectarse
mediante ODBC a esos motores SQL. En este artículo no
queremos mostrar exhaustivamente todas las formas de
conexión sino solamente mostrar que hay pequeñas diferencias
en las respectivas cadenas de conexión.
Listado 1. Para conectarse a DB2
1 lcCadenaConexion = "Driver={IBM DB2 ODBC DRIVER};" ;
+ "Database=MiBaseDeDatos;" ;
2
+ "Hostname=DireccionDeMiServidor;" ;
3 + "Port=1234;" ;
4
+ "Protocol=TCPIP;" ;
5 + "Uid=NombreUsuario;" ;
6 + Pwd=Contrasena;"
7
Listado 2. Para conectarse a Excel
1 lcCadenaConexion = "Driver={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}
2 + "DBQ=D:\MisPlanillas\[Link];"
Listado 3. Para conectarse Firebird
1 lcCadenaConexion = "DRIVER=Firebird/InterBase(r) driver;" ;
2 + "UID=SYSDBA;" ;
3 + "PWD=masterkey;" ;
+ "DBNAME=MiDireccionIP/3050:D:\Contabilidad\Databases\[Link];"
4
Listado 4. Para conectarse a Ingres
1
2 lcCadenaConexion = 'Provider='MSDASQL;' ;
+ 'DRIVER=Ingres;' ;
3 + 'SRVR=xxxxx;' ;
4 + 'DB=xxxxx;'
5 + 'Persist Security Info=False;' ;
6 + 'Uid=NombreUsuario;' ;
+ 'Pwd=Contrasna;' ;
7
+ 'SELECTLOOPS=N;' ;
8 + 'Extended Properties="SERVER=xxxxx;' ;
9 + 'DATABASE=MiBaseDeDatos;SERVERTYPE=INGRES";'
10
Listado 5. Para conectarse a MySQL
1 lcCadenaConexion = "Driver={MySQL ODBC 5.2 ANSI Driver};" ;
2 + "Server=localhost;" ;
3 + "Database=MiBaseDeDatos;" ;
4 + "User=NombreUsuario;" ;
+ "Password=Contrasena;" ;
5
+ "Option=3;"
6
Listado 6. Para conectarse a PostgreSQL
1 lcCadenaConexion = "Driver={PostgreSQL};" ;
2 + "Server=IP address;" ;
3 + "Port=5432;" ;
4 + "Database=MiBaseDeDatos;" ;
+ "Uid=NombreUsuario;" ;
5
+ "Pwd=Contrasena;"
6
Listado 7. Para conectarse a SQL Server
1 lcCadenaConexion = "Driver={SQL Server};" ;
+ "Server=NombreDeMiServidor;" ;
2
+ "DataBase=MiBaseDeDatos;" ;
3 + "Uid=NombreUsuario;" ;
4 + "Pwd=Contrasena;";
5
Conclusión:
Cuando queremos conectarnos mediante ODBC y sin DSN a una
Base de Datos debemos escribir una cadena de conexión, la cual
contiene los datos que el ODBC necesita para que esa conexión
pueda ser efectuada.
La función del Visual FoxPro que necesitaremos se llama
SQLSTRINGCONNECT(), la cual nos devolverá un número entero
mayor que cero (1, 2, 3, 4, etc.) si la conexión tuvo éxito, o el
número -1 si la conexión no se pudo realizar.
¿Y por qué la conexión no se pudo realizar?
Puede haber varios motivos, por ejemplo: a) El Servidor está
apagado, b) No hay comunicación con el Servidor, c) No se
escribió correctamente la dirección IP, d) El Servidor no está
“escuchando” ese número de puerto, e) El nombre del usuario
es incorrecto, f) La contraseña es incorrecta, g) La ruta o el
nombre de la Base de Datos son incorrectos, etc.
Si la conexión tuvo éxito entonces ese número que recibimos de
la función SQLSTRINGCONNECT() es el que deberemos utilizar
en lo sucesivo para realizar cualquier operación con esa Base de
Datos.
Entonces, para conectarnos debemos escribir:
Listado 8. Para conectarse a una Base de Datos
1 lcCadenaConexion = "Mi cadena de conexión al motor SQL de mi elección"
2
3 lnHandle = SqlStringConnect(lcCadenaConexion)
Verificando el valor que se encuentre en la variable lnHandle
podremos saber si la conexión se realizó con éxito o si ocurrió
algún problema.
De DBF a SQL (30). Conectándose
mediante ADO a las bases de datos
8 SEPTIEMBRE, 2018 / WROV
El método más común para conectarse a las bases de
datos SQL se llama ODBC (Open Database Connectivity, o en
español: conectividad abierta a las bases de datos). Sin
embargo, no es el único método que se puede emplear, tal y
como vimos en artículos anteriores. Otro método que puede
utilizarse se llama ADO (ActiveX Data Objects, o en español:
objetos de datos ActiveX).
¿Qué es ADO?
Es un conjunto de objetos cuya finalidad es acceder a fuentes de
datos de una manera uniforme. No solamente a bases de datos,
también a planillas Excel, documentos de texto, archivos CSV,
archivos XML, etc. Cualquier archivo que contiene datos podría
eventualmente ser accedido usando ADO.
ADO surgió en 1996 con la idea de Microsoft de uniformizar el
acceso a los datos.
¿Qué se puede hacer con ADO?
Todas las operaciones normales en una Base de Datos: crear
tablas, borrar tablas, insertar filas, modificar filas, borrar filas,
consultar filas, etc.
¿Qué se necesita para usar ADO?
Lo primero, en conseguir un proveedor para el motor SQL que
estás usando. Si usas Firebird, encontrarás el que utiliza el
autor de este blog en:
[Link]
firebird_interbase_odbc_drivers.html
También podrías usar [Link], que es muy bueno, o el que
prefieras (porque hay varios más).
¿Cómo se utiliza ADO?
Primero, debes conectarte a la Base de Datos
Segundo, debes obtener un RecordSet, o sea un conjunto
resultado con los datos que te interesan
Tercero, puedes manipular esos datos como desees
Cuarto, debes cerrar el RecordSet
Quinto, debes cerrar la conexión
Ejemplo. Conectarse a la Base de Datos y mostrar los
nombres de los clientes
En nuestra Base de Datos tenemos una tabla llamada CLIENTES
y queremos obtener los nombres de los mismos.
Listado 1. Mostrar los nombres de los clientes
1 CLEAR
2
CLOSE ALL
3
4
CLEAR ALL
5
6 LOCAL loSQL, loRecordSet
7
8 *--- Primero, se conecta a la Base de Datos
9
10 loSQL = CreateObject("[Link]")
11
12 WITH loSQL
.Provider = "[Link]"
13 .ConnectionString = "User=SYSDBA;password=masterkey;Data Source=E:\SQL\DATABASES
14 .Open()
15 ENDWITH
16
17 *--- Segundo, se crea un RecordSet
18
19 loRecordSet = CreateObject("[Link]")
20
*--- Tercero, se obtienen los datos que nos interesan
21
22 [Link]("Select * From CLIENTES", loSQL)
23
24 *--- Cuarto, se procesan los datos obtenidos
25
26 DO WHILE ![Link]
27 ? [Link]("CLI_NOMBRE").Value
[Link]()
28 ENDDO
29
30 *--- Quinto, se cierran y se eliminan los objetos que se habían creado
31
32 [Link]()
33
34 [Link]()
35
36 RELEASE loRecordSet, loSQL
37
38
39
40
Captura 1. Los nombres de los clientes
Como puedes ver, en la pantalla se mostraron los nombres de
los 4 clientes cuyos datos estaban guardados en la tabla
respectiva.
A diferencia de ODBC aquí no obtendrás un cursor para trabajar
con él. Un cursor es una tabla .DBF temporal y desde mi punto
de vista es más útil porque puede ser indexada, mostrada en un
browse, etc. La forma de usar ADO me parece más antigua, más
manual, aunque sobre gustos…
Desde luego que siempre puedes enviar el contenido de
los recordsets a cursores o a tablas .DBF pero sería un trabajo
adicional y ¿vale la pena? eso ya no lo sé porque dependerá de
tus circunstancias.
Propiedades, métodos y eventos de ADO
ADO tiene muchísimas propiedades, métodos y eventos que
puedes usar, en el ejemplo anterior se mostró solamente una
pequeñísima parte de ellos. Si te interesa el tema hay varias
páginas con información útil en Internet, una de ellas es la
siguiente:
[Link]
Conclusión:
Si quieres, puedes usar ADO para conectarte a una Base de
Datos pero yo hasta ahora no le he encontrado alguna ventaja.
Quizás sea más rápido que ODBC y en ese caso sí podría
justificarse conectarse mediante ADO, pero eso es algo que aún
no he verificado.
Usar cursores es más rápido y más sencillo (para mí, claro) que
estar obteniendo los datos fila por fila. Esto lo notarás
principalmente cuando quieras imprimir informes porque
mientras un cursor o una tabla .DBF ya están preparados para
ser usados en informes, los recordsets no lo están y deberán
prepararse previamente y habría que ver si vale la pena el
esfuerzo.
De todas maneras, siempre es bueno tener varias alternativas
para hacer cualquier cosa y disponer de ADO aumenta nuestras
posibilidades de conexión a las bases de datos, así que
bienvenido.
De DBF a SQL (31). Optimizando la
escritura de los SELECT
9 SEPTIEMBRE, 2018 / WROV
Hay muchas maneras de escribir los SELECT y como en todas las
cosas, las hay mejores y las hay peores.
He visto que frecuentemente escriben algo similar a esto:
Listado 1. Una forma incorrecta de escribir un SELECT
1
SELECT
2 VENTASCAB.FECHA_VENTA,
3 VENTASCAB.NUMERO_FACTURA,
4 CLIENTES.NOMBRE_CLIENTE
5 FROM
6 VENTASCAB
JOIN
7
CLIENTES
8
ON [Link] = [Link]
9
¿Funciona ese SELECT?
Sí, sin dudas que funcionará.
¿Es la mejor manera de escribirlo?
Rotundamente, NO.
¿Por qué no es correcto escribir así, cuáles son los problemas?
1. Escribes demasiado texto. Delante de cada columna
estás colocando el alias de la tabla. Cuantas más columnas
tengas, mayor será la cantidad de texto innecesario que
estarás escribiendo.
2. Demoras más en escribir tu SELECT. Como estás
escribiendo mucho texto innecesario, eso hace que estés
perdiendo mucho tiempo en escribirlo.
3. Aumentas la probabilidad de cometer errores de
sintaxis. Como escribes mucho texto innecesario, la
probabilidad de que escribas algo mal también aumenta
mucho. Por ejemplo, en lugar de escribir el alias CLIENTES
escribiste el alias CLIEMTES (una M en lugar de una N),
luego tendrás que corregir ese error, la consecuencia…
pierdes tiempo.
4. Haces trabajar más al motor SQL. Cada vez que
encuentra un alias, tu motor SQL debe referenciarlo y
dirigirlo a la tabla correspondiente, como consecuencia …
5. Tu SELECT se ejecuta más lento de lo que
debería. Como el motor SQL trabaja más de la cuenta, eso
acarrea una demora. Es cierto, es una demora muy
pequeña…pero existe. Y cuanto mayor sea la cantidad de
alias que estés escribiendo innecesariamente, mayor será
también la demora que innecesariamente está causando tu
SELECT.
Listado 2. Un ejemplo de SELECT que funciona pero no está
optimizado
1 TEXT TO lcSQLcommand NOSHOW ADDITIVE TEXTMERGE PRETEXT 7
2 SELECT DISTINCT
[Link],
3
[Link],
4
[Link],
5
[Link],
6 [Link],
7 [Link],
ventas.MULTI_DIV,
8
[Link],
9
[Link],
10
[Link] ,
11
resolucion.NRO_CONTROL,
12 [Link],
13 [Link],
14 [Link],
15 [Link],
16 [Link],
[Link],
17
[Link],
18
[Link],
19
[Link],
20
[Link],
21 [Link],
22 [Link],
23 (iif(impu_prc=15,[Link] * optiimpumv.impu_prc / 100, 0)) as imp_15,
24 (iif(impu_prc=4, [Link] * optiimpumv.impu_prc / 100, 0)) as imp_4,
25 (iif(impu_prc=10, [Link] * optiimpumv.impu_prc / 100, 0)) as imp_10,
(iif(impu_prc=15,[Link] * optiimpumv.impu_prc / 100, 0)) + (iif(impu_prc=4,
26optiimpumv.impu_prc / 100, 0)) + totneto as total
27 FROM
28 movimientos
29 LEFT JOIN ventas ON ([Link] = [Link])
30 AND ([Link] = [Link])
AND ([Link] = [Link])
31
INNER JOIN (SELECT NRO_CONTROL, DOCUMENTO AS DO FROM RESOLMV)resolucion ON (ventas.D
32
LEFT JOIN grupos ON ([Link] = [Link])
33
LEFT JOIN optiImpumv on ([Link] = [Link] )
34
35
36 group by [Link]
37ENDTEXT
38
39
En el grupo de Whatsapp llamado RinconFox uno de los
miembros escribió lo que ves en el Listado 2., y fue el motivo
que me llevó a escribir este artículo del blog. Es cierto, podría
funcionar su SELECT, pero tiene los 5 problemas descritos más
arriba.
¿Cuál es la forma correcta de escribir un SELECT?
Escribir un SELECT optimizado, para que nosotros escribamos
menos y se ejecute lo más rápido posible, conlleva dos tareas:
1. Elegir correctamente los nombres de las columnas de
nuestras tablas
2. Evitar escribir alias, salvo cuando sea estrictamente
necesario hacerlo
Si volvemos a mirar el Listado 1. nos encontraremos con estas
líneas:
JOIN CLIENTES ON [Link] = [Link]
Y allí vemos que en la tabla VENTASCAB existe una columna
llamada IDCLIENTE y que en la tabla CLIENTES también existe
una columna llamada IDCLIENTE. Eso, nos obliga a usar un alias,
para poder distinguir a una columna de la otra. El problema
entonces no está en el SELECT (en casos así, escribir un alias es
obligatorio) sino en el diseño de las tablas.
¿Por qué?
Porque ambas columnas, aunque pertenecen a tablas diferentes
… tienen el mismo nombre.
¿Cuál es la forma correcta de nombrar a las columnas?
Asignándoles un prefijo que nos indique a cual tabla pertenecen.
En el ejemplo de arriba lo correcto sería llamarlas VTC_IDECLI y
CLI_IDENTI. Ambas sirven para guardar el Identificador del
Cliente pero sus nombres son muy distintos, no hay posibilidad
de que el motor SQL se confunda y entonces podríamos escribir:
JOIN CLIENTES ON VTC_IDECLI = CLI_IDENTI
¿Y qué se consigue con eso?
1. Escribir menos código fuente
2. Disminuir la probabilidad de cometer un error de sintaxis
3. Que el SELECT se ejecute más rápido
Escribiéndolo de manera correcta, el SELECT que escribimos en
el Listado 1. quedaría así:
Listado 3. La forma correcta de escribir un SELECT
1
SELECT
2 VTC_FECHAX,
3 VTC_NROFAC,
4 CLI_NOMBRE
5 FROM
6 VENTASCAB
JOIN
7
CLIENTES
8
ON VTC_IDECLI = CLI_IDENTI
9
Fíjate que no se ha usado ningún alias. ¿Por qué? Porque es
innecesario usarlos, y entonces se consiguió escribir menos
texto, disminuir la probabilidad de cometer errores de sintaxis,
hacer trabajar menos al motor SQL, y que el SELECT se ejecute
más rápido. Todas son ventajas.
Conclusión:
Hay muchas formas incorrectas y una sola forma correcta de
escribir un SELECT. Desde luego que tú puedes elegir la que
prefieras. En este artículo se mostró la forma correcta y se
explicó el por qué es esa la forma correcta. Nadie te puede
obligar a escribir los SELECT de forma correcta o de forma
incorrecta, esa será tu decisión.
De DBF a SQL (32). Mejorando la
estética de los SELECT
10 SEPTIEMBRE, 2018 / WROV
Hay muchas maneras de escribir un SELECT, y si solamente
serán para uso propio, no importa mucho como se los escriba,
siempre y cuando con ellos se obtenga el resultado que se
desea obtener.
Sin embargo, si el SELECT será leído (y eventualmente,
modificado) por otra persona allí ya el asunto cambia, ya que
cuanto más legible sea, mejor será para todos.
El autor de este blog hace varios años estuvo evaluando cual es
la mejor estética para los SELECT y encontró que es la siguiente:
Listado 1. La manera más estética de escribir un SELECT
1 SELECT
MiColumna1,
2
MiColumna2,
3 MiColumna3
4 FROM
5 MiTabla1
6 JOIN
MiTabla2
7 ON MiColumna1 = MiColumnaJoin1 AND
8 MiColumna2 = MiColumnaJoin2
9 WHERE
10 MiCondición1
11 GROUP BY
MiColumna1,
12 MiColumna2,
13 MiColumna3
14 HAVING
15 MiCondición2
ORDER BY
16 MiColumna1,
17 MiColumna2
18
19
20
21
Y en la mayoría de los artículos de este blog verás que los
SELECT están escritos de esa manera.
Como puedes ver, primero se escribe la cláusula en mayúsculas
(SELECT, FROM, JOIN, WHERE, GROUP BY, etc.) y luego con
identación, lo que corresponde a esa cláusula.
Escribir así, facilita la lectura.
Conclusión:
Si la lectura de nuestro SELECT es fácil, eso nos ayudará a
nosotros entenderlo rápido y también a cualquier eventual lector
a quien por ejemplo se le solicitó ayuda porque algo no está
saliendo como quisiéramos. Un SELECT ilegible (donde pones
todo el texto seguido, mezclas mayúsculas con minúsculas, etc.)
le quitará las ganas a más de uno de ayudarnos.
Si la estética mostrada en el Listado 1. no te gusta, al menos
elige una que sea consistente y fácil de leer, no solamente para
tí, también para los demás. Y úsala siempre, en todos tus
SELECT.
De DBF a SQL (33). Usando la
función SQLEXEC()
16 SEPTIEMBRE, 2018 / WROV
Ahora que ya sabemos como conectarnos a una Base de Datos
de un SGBD (Sistema Gerenciador de Bases de Datos) que usa
el lenguaje SQL (Firebird, Postgre, MySql, Oracle, SQL Server,
SQLite, etc.) el siguiente paso es enviar comandos al Servidor y
recibir la respuesta del Servidor.
Para ello, el Visual FoxPro nos provee la función SQLEXEC().
Esta función puede recibir dos, tres, o cuatro argumentos:
El primero sirve para indicarle a cual Base de Datos
queremos enviar el comando, porque podríamos tener
varias bases de datos abiertas al mismo tiempo y como es
lógico el VFP necesitará conocer cual es la correcta.
El segundo es el comando que deseamos enviar.
El tercero es opcional, es el nombre que queremos darle al
cursor que obtendremos si la función SQLEXEC() termina
con éxito. Si no le asignamos un nombre al cursor entonces
el VFP le asignará el nombre SQLRESULT a ese cursor.
Porque todo cursor debe tener un nombre, sí o sí.
El cuarto también es opcional, es el nombre del array que
recibirá los datos sobre la cantidad de filas obtenidas.
A su vez, la función SQLEXEC() devolverá un número, el cual
puede ser:
Más que 1. Significa que la función SQLEXEC() finalizó con
éxito y este número indica la cantidad de conjuntos
resultados que devolvió (normalmente devuelve un
conjunto de resultados, pero podría devolver más).
1. Significa que el comando finalizó con éxito.
0. Significa que aún se está procesando el comando.
-1. Significa que ocurrió un error
Listado 1. Un ejemplo del uso de la función SQLEXEC()
1 _Screen.AddProperty("nHandle", 0)
3 _Screen.nHandle = CONECTAR_BASE_DATOS()
4
IF _Screen.nHandle > 0 THEN
5
DO EJECUTAR_COMANDO
6
=SQLDisconnect(_Screen.nHandle)
7
ELSE
8
=MessageBox("Falló la conexión a la Base de Datos. Verifícala")
9 ENDIF
10
11 RETURN
12 *
13 *
14 FUNCTION CONECTAR_BASE_DATOS
15 LOCAL lcCadenaConexion, lnHandle, lcConsulta, llConsultaOK, lnNumElementos
16
17 lcCadenaConexion = "DRIVER={Firebird/Interbase(r) driver};" ;
+ "USER=SYSDBA;" ;
18
+ "PASSWORD=masterkey;" ;
19
+ "ROLE=;" ;
20
+ "DATABASE=[Link]:D:\MISDATOS\[Link];"
21
22
lnHandle = SQLStringConnect(lcCadenaConexion)
23
24 RETURN(lnHandle)
25
26 ENDFUNC
27 *
28 *
29 PROCEDURE EJECUTAR_COMANDO
LOCAL lcComando, llComandoOK, lnElementos
30
31
lcComando = "SELECT * FROM BANCOS"
32
33
llComandoOK = SQLExec(_Screen.nHandle, lcComando) = 1
34
35
IF llComandoOK THEN
36
SELECT * FROM SQLRESULT
37 ELSE
38 lnElementos = AError(laError)
39 IF lnElementos > 0 THEN
40 =MessageBox("Ocurrió el error " + Transform(laError[1]) + " " + laError[2])
41 ENDIF
ENDIF
42
43
44
45 ENDPROC
46 *
47 *
48
49
En el Listado 1., en el procedure llamado EJECUTAR_COMANDO
hemos usado la función SQLEXEC() y le hemos enviado dos
argumentos:
1. El número que identifica a la Base de Datos que contiene la
tabla que nos interesa
2. El comando que deseamos ejecutar
¿Y qué hace ese = 1 en la línea:
1 llComandoOK = SQLExec(_Screen.nHandle, lcComando) = 1
El Visual FoxPro usa el mismo símbolo para representar una
asignación de valor a una variable o una comparación. En otros
lenguajes se usan símbolos distintos. Por ejemplo, en el
Lenguaje C el símbolo = significa asignación y el
símbolo == significa comparación. En Pascal se usa := para
asignación y = para comparación.
La línea de arriba pide que primero el Visual
FoxPro compare el valor devuelto por la función SQLEXEC() con
el número 1. Una comparación solamente puede devolver uno
de estos dos valores: .T., .F. y segundo que el resultado de esa
comparación lo guarde en la variable llComandoOK.
Entonces, como en la variable llComandoOK guardamos el
resultado de una comparación, en esa variable solamente
podemos tener o .T. o .F.
No existe otra posibilidad.
Por otro lado, la línea:
1 IF llComandoOK THEN
Es una forma simplificada de escribir:
1 IF llComandoOK = .T. THEN
Ambas líneas son equivalentes, se puede escribir de cualquiera
de las dos formas. El autor de este blog prefiere de la primera
forma, pero sobre gustos…
¿Y qué hace la línea:
1 SELECT * FROM SQLRESULT
Como no le hemos dicho al Visual FoxPro como queremos que
se llame el cursor entonces el Visual FoxPro de forma
automática le asigna el nombre SQLRESULT al cursor que
obtuvimos de la función SQLEXEC(). ¿Por qué? Porque todo
cursor debe tener un nombre, sí o sí, y si ese nombre no lo
asignamos nosotros entonces el Visual FoxPro se encarga de
asignarle un nombre.
Listado 2. Otro ejemplo de la función SQLEXEC()
1 PROCEDURE EJECUTAR_COMANDO
2 LOCAL lcComando, llComandoOK, lnElementos
3
lcComando = "SELECT * FROM BANCOS"
4
5
llComandoOK = SQLExec(_Screen.nHandle, lcComando, "MICURSOR") = 1
6
7
IF llComandoOK THEN
8
SELECT * FROM MICURSOR
9
ELSE
10 lnElementos = AError(laError)
11 IF lnElementos > 0 THEN
12 =MessageBox("Ocurrió el error " + Transform(laError[1]) + " " + laError[2])
13 ENDIF
14 ENDIF
15
ENDPROC
16
*
17
18 *
19
En el Listado 2. le hemos pedido a la función SQLEXEC() que al
cursor le asigne el nombre de MICURSOR (desde luego que ese
nombre es solamente un ejemplo, tú puedes asignarle cualquier
otro nombre a tu cursor).
Y más abajo hemos escrito:
1 SELECT * FROM MICURSOR
Porque MICURSOR es el nombre del cursor, y de esa forma
debemos referirnos a él cada vez que deseemos utilizarlo.
Listado 3. Y otro ejemplo de la función SQLEXEC()
1 PROCEDURE EJECUTAR_COMANDO
2 LOCAL lcComando, llComandoOK, lnElementos
3
lcComando = "SELECT * FROM BANCOS"
4
5
llComandoOK = SQLExec(_Screen.nHandle, lcComando, "MICURSOR", aINFO) = 1
6
7
IF llComandoOK THEN
8
=MessageBox(aINFO[1] + " " + Transform(aINFO[2]))
9
SELECT * FROM MICURSOR
10 ELSE
11 lnElementos = AError(laError)
12 IF lnElementos > 0 THEN
13 =MessageBox("Ocurrió el error " + Transform(laError[1]) + " " + laError[2])
14 ENDIF
ENDIF
15
16
ENDPROC
17
*
18
19 *
20
En el Listado 3. hemos pedido que guarde en el array aINFO[] los
datos sobre el comando ejecutado. Veremos algo similar a:
Captura 1. El nombre del cursor y la cantidad de filas
retornadas
Conclusión:
Para pedirle al Visual FoxPro que envíe un comando
al Servidor y que si ese comando se ejecutó con éxito guarde el
resultado en un cursor, usamos a la función SQLEXEC().
La función SQLEXEC() puede recibir hasta 4 parámetros. Los dos
primeros son obligatorios porque evidentemente debemos
indicarle cual es la Base de Datos que contiene los datos
(porque podríamos estar conectados a varias bases de datos) y
cual es el comando que queremos enviar al Servidor. El tercer
parámetro es opcional, y le indica cual queremos que sea el
nombre de nuestro cursor. Si no le indicamos un nombre, la
función SQLEXEC() de forma automática le asignará el nombre
de SQLRESULT a ese cursor. El cuarto parámetro también es
opcional y sirve para guardar en un array el nombre del cursor y
la cantidad de filas que se obtuvieron al ejecutar con éxito el
comando enviado al Servidor.
A su vez, la función SQLEXEC() nos devolverá un número, el cual
puede ser: mayor que 1 (si obtuvimos varios conjuntos resultado
como veremos en otro artículo de este blog), 1 (si el comando se
ejecutó con éxito), 0 (si el comando aún está ejecutándose), -1
(si el comando finalizó con algún error).
Cuando la función SQLEXEC() devuelve 1 también devuelve un
cursor. Ese cursor (como todos los cursores) es una tabla de
memoria, una tabla temporal, y a todos los efectos puede usarse
exactamente igual a como se usaría una tabla .DBF normal. Pero
lo que hagamos en el cursor no se reflejará en la tabla de la
Base de Datos. Son totalmente independientes. Es como escribir
en la fotocopia de un libro, lo que escribas en la fotocopia no se
escribirá en el libro.
Si la función SQLEXEC() nos devuelve -1 eso significa que
el Servidor encontró un error. ¿Qué clase de error? Puede ser
una palabra mal escrita (escribiste SEKECT en lugar de SELECT),
el nombre de una tabla inexistente (escribiste SELECT * FROM
ARTICULOS y no existe una tabla llamada ARTICULOS), etc. Lo
importante a saber es que el Servidor te avisará que ocurrió un
error y para saber cual es ese error puedes usar la función
AERROR() tal y como se puede ver en los Listados mostrados
más arriba.
De DBF a SQL (34). Enviándole
parámetros a la función SQLEXEC()
22 SEPTIEMBRE, 2018 / WROV
En el artículo anterior ya hemos visto que para enviarle algún
comando al Servidor deberemos usar la función SQLEXEC()
del Visual FoxPro.
El segundo parámetro de la función SQLEXEC() es el comando
que deseamos enviar. Pero aquí hay varias alternativas, algunas
peores y algunas mejores, tal y como veremos a continuación.
Listado 1. Una forma (incorrecta) de enviarle parámetros a la
función SQLEXEC()
1 llComandoOK = SQLEXEC(_Screen.nHandle, "SELECT * FROM BANCOS") = 1
Hay programadores que envían comandos al Servidor de forma
similar a la mostrada en el Listado 1. Eso está mal. ¿Por qué?
Porque si la función SQLEXEC() falló y queremos verificar cual
fue el problema, deberíamos escribir nuevamente todo lo que
está entre comillas, por ejemplo:
Listado 2. Para verificar cual comando se envió al Servidor
1 llComandoOK = SQLEXEC(_Screen.nHandle, "SELECT * FROM BANCOS") = 1
2
3 =MESSAGEBOX("SELECT * FROM BANCOS")
En un comando tan simple y tan sencillo como el mostrado en
los listados 1 y 2 no sería problema pero algunos SELECT
pueden ser muy largos (como ya has visto en otros artículos de
este blog) y por lo tanto escribir nuevamente todo el segundo
parámetro sería muy engorroso.
Una mejor alternativa sería escribir:
Listado 3. Guardando el comando en una variable
1 lcComando = "SELECT * FROM BANCOS"
2
3 llComandoOK = SQLEXEC(_Screen.nHandle, lcComando) = 1
4
5 =MESSAGEBOX(lcComando)
Ahora, el comando que se envía al Servidor previamente se
guarda en una variable y es esa variable la que se usa en todos
los casos.
Si el comando que deseamos enviar al Servidor es constante,
como en los casos hasta aquí mostrados, ya está bien con lo que
hicimos, no necesitamos más. Pero en muchos casos
necesitamos enviarle al Servidor datos variables y allí ya se
complica el asunto.
Listado 4. Enviando un comando con datos variables al Servidor
1 lnIdenti = 5
2
3 lcComando = "SELECT * FROM BANCOS WHERE BAN_IDENTI = " + TRANSFORM(lnIdenti)
4
5 llComandoOK = SQLEXEC(_Screen.nHandle, lcComando) = 1
6
7 =MESSAGEBOX(lcComando)
Al ejecutar el Listado 4. veremos algo similar a:
Captura 1. Viendo el comando que se envió al Servidor
En la Captura 1. vemos el comando que se envió al Servidor,
cuando ese comando es muy largo hacerlo de esta forma nos
resultará muy útil para buscar y encontrar errores en el texto
enviado.
Y hablando de ese tema, cuando el texto a enviar es largo, es
preferible utilizar la construcción TEXT…ENDTEXT.
Listado 5. Enviando comandos largos al Servidor
1 TEXT TO lcComando NOSHOW
2 SELECT * FROM BANCOS WHERE BAN_IDENTI = 5
3 ENDTEXT
4
5 llComandoOK = SQLEXEC(_Screen.nHandle, lcComando) = 1
6
=MESSAGEBOX(lcComando)
7
Escribir lcComando = “algún texto aquí” tiene la desventaja de
que ese texto no puede superar los 254 caracteres, en cambio si
se usa la construcción TEXT…ENDTEXT se puede sobrepasar ese
límite, sin ningún problema. Por lo tanto, es mejor
acostumbrarse a usar siempre TEXT…ENDTEXT y así el texto
siempre será aceptado, sin importar cuantos caracteres
escribamos.
¿Y cómo haríamos para enviarle datos variables al Servidor?
Listado 6. Una forma para enviarle datos variables al Servidor
1 [Link] = 5
2
3 TEXT TO lcComando NOSHOW
4 SELECT * FROM BANCOS WHERE BAN_IDENTI = ?[Link]
5 ENDTEXT
6
llComandoOK = SQLExec(_Screen.nHandle, lcComando) = 1
7
8
=MessageBox(lcComando)
9
Fíjate que dentro de la construcción TEXT…ENDTEXT usamos un
símbolo de pregunta antes del nombre de la variable. Para que
esto funcione, esa variable debe ser PRIVATE o debe ser PUBLIC.
Como seguramente ya sabes, no se recomienda usar PUBLIC,
por lo tanto lo mejor sería que usaras PRIVATE.
Al ejecutar el Listado 6. veremos algo similar a esto:
Captura 2. El comando que se envió al Servidor usando
una variable
¿Cuál es el problema con esta forma de enviar comandos
al Servidor? Que no se ve cual es el valor que se envió. En este
caso vemos ?[Link] pero no sabemos cual es el valor que
está guardado en [Link]
Listado 7. Viendo los valores de las variables enviadas al
Servidor
1 [Link] = 5
2
3 TEXT TO lcComando TEXTMERGE NOSHOW
4 SELECT * FROM BANCOS WHERE BAN_IDENTI = <<[Link]>>
5 ENDTEXT
6
llComandoOK = SQLExec(_Screen.nHandle, lcComando) = 1
7
8
=MessageBox(lcComando)
9
Al ejecutar el Listado 7. lo que veremos será:
Captura 3. Viendo las variables enviadas al Servidor
Fíjate que en el Listado 7. después del TEXT TO se escribió
TEXTMERGE. ¿Por qué? Porque esa palabra TEXTMERGE es la
que le indica al Visual FoxPro que debe evaluar el contenido
de la variable. Pero además, se usaron los símbolos << y >>
para indicarle cual es la variable.
Desde luego que podríamos tener muchas variables, por
ejemplo:
Listado 8. Enviando varias variables al Servidor
1
2 [Link] = "BANCOS"
3 [Link] = "BAN_NOMBRE"
[Link] = 5
4
5 TEXT TO lcComando TEXTMERGE NOSHOW
6 SELECT
7 <<[Link]>>
8 FROM
9 <<[Link]>>
WHERE
10 BAN_IDENTI = <<[Link]>>
11 ENDTEXT
12
13 llComandoOK = SQLExec(_Screen.nHandle, lcComando) = 1
14
15 =MessageBox(lcComando)
16
Al ejecutar el Listado 8. lo que veríamos sería:
Captura 4. Enviando varias variables al Servidor y viendo
sus contenidos
Y si preferimos que el SELECT no esté en varias líneas sino todo
seguido, podríamos escribir:
Listado 9. Para que las cláusulas del SELECT se muestren una a
continuación de la otra
1 [Link] = "BANCOS"
[Link] = "BAN_NOMBRE"
2
[Link] = 5
3
4 TEXT TO lcComando TEXTMERGE NOSHOW PRETEXT 15
5 SELECT
6 <<[Link]>>
7 FROM
<<[Link]>>
8
9
10 WHERE
BAN_IDENTI = <<[Link]>>
11 ENDTEXT
12
13 llComandoOK = SQLExec(_Screen.nHandle, lcComando) = 1
14
15 =MessageBox(lcComando)
16
Al ejecutar el Listado 9. lo que veremos sería:
Captura 5. Las cláusulas del SELECT una a continuación
de la otra
Para que se muestre en una sola línea en la construcción TEXT…
ENDTEXT se escribió PRETEXT 15.
El autor de este blog prefiere ver los comandos como se
muestra en la Captura 4. pero hay gente que prefiere verlos
como se muestra en la Captura 5. así que para satisfacer
ambos gustos se mostraron las dos formas.
Conclusión:
La función SQLEXEC() nos permite enviar solicitudes
al Servidor y nos devuelve el resultado de la solicitud. Tenemos
varias maneras de enviar esas solicitudes, tal y como pudimos
ver en este artículo.
Menos de la manera mostrada en el Listado 1., la cual jamás
deberíamos usar, dependiendo de las circunstancias podríamos
usar alguna de las otras alternativas.