Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Introducción
Hasta ahora hemos visto consultas que obtienen los datos de una sola tabla, en
este tema veremos cómo obtener datos de diferentes tablas.
Para obtener datos de varias tablas tenemos que combinar estas tablas mediante
alguna operación basada en el álgebra relacional.
El álgebra relacional define una serie de operaciones cuyos operandos son tablas
y cuyo resultado es también una tabla.
La unión UNION
La diferencia EXCEPT
La intersección INTERSECT
El producto cartesiano CROSS JOIN
La composición interna INNER JOIN
La composición externa LEFT JOIN, RIGHT JOIN Y FULL JOIN
En todo el tema cuando hablemos de tablas nos referiremos tanto a las tablas que
físicamente están almacenadas en la base de datos como a las tablas temporales
y a las resultantes de una consulta o vista.
La unión de tablas consiste en coger dos tablas y obtener una tabla con las filas
de las dos tablas, en el resultado aparecerán las filas de una tabla y, a
continuación, las filas de la otra tabla.
Para poder realizar la operación, las dos tablas tienen que tener el mismo
esquema (mismo número de columnas y tipos compatibles) y la tabla resultante
hereda los encabezados de la primera tabla.
El resultado sería:
OFI ciudad
11 Valllarta
28 Valllarta
23 Michoacán
OFI ciudad
11 Vallarta
23 Michoacán
28 Vallarta
Ahora las filas aparecen ordenadas por el número de oficina y hemos utilizado el
nombre de columna de la primera consulta.
Otro ejemplo:
Obtener todos los productos cuyo precio exceda de 20 € o que se haya vendido
más de 300 euros del producto en algún pedido.
BD GestionA
T1 T2
Cod
Codigo
1
2
2
3
4
4
5
5
6
Devuelve:
Cod
1
6
Ejemplo:
productos
idfab idproducto
aci 41001
aci 41002
aci 41003 pedidos resultado
aci 41004 fab producto idfab idproducto
aci 4100x aci 41002 aci 41001
aci 4100y aci 41003 bic 41089
aci 4100z aci 41004 bic 41672
bic 41003 aci 4100x imm 775c
bic 41089 aci 4100y imm 887h
bic 41672 aci 4100z imm 887p
fea 112 bic 41003 imm 887x
fea 114 fea 112 qsa xk48
imm 773c fea 114 qsa xk48a
imm 775c imm 773c aci 41001
imm 779c imm 779c bic 41089
imm 887h qsa xk47 bic 41672
imm 887p rei 2a44g imm 775c
imm 887x rei 2a44l imm 887h
qsa xk47 rei 2a44r imm 887p
qsa xk48 rei 2a45c imm 887x
qsa xk48a aci 41002 qsa xk48
rei 2a44g qsa xk48a
rei 2a44l
rei 2a44r
rei 2a45c
La intersección INTERSECT
Devuelve:
Cod
2
4
5
Ejemplo: Obtener todos los productos que valen más de 20 euros y que además
se haya vendido en un pedido más de 300 euros de ese producto.
Resultado
Hasta ahora hemos operado con tablas que tenían el mismo esquema, pero
muchas veces lo que necesitamos es obtener una tabla que tenga en una misma
fila datos de varias tablas, por ejemplo, obtener las facturas y que en la misma fila
de factura aparezca el nombre y dirección del cliente. Pues en lo que queda del
tema estudiaremos este tipo de consultas basadas en la composición de tablas. La
composición de tablas consiste en obtener a partir de dos tablas cualesquiera una
nueva tabla fusionando las filas de una con las filas de la otra, concatenando los
esquemas de ambas tablas. Consiste en formar parejas de filas.
Una composición interna es aquella en la que los valores de las columnas que se
están combinando se comparan mediante un operador de comparación.
SELECT *
FROM empleados INNER JOIN oficinas
ON empleados.oficina=oficinas.oficina;
SELECT *
FROM pedidos INNER JOIN productos
ON producto = idproducto AND fab = idfab;
No aparecen los empleados que no tienen oficina, ni las oficinas que no tienen
empleados, porque para que salga la fila, debe de existir una fila de la otra tabla
que cumpla la condición.
Las palabras LEFT, RIGHT y FULL indican la tabla de la cual se van a añadir las
filas sin correspondencia.
Obtiene los empleados con su oficina y los empleados (tabla a la izquierda LEFT
del JOIN) que no tienen oficina aparecerán también en el resultado con los
campos de la tabla oficinas rellenados a NULL.
SELECT numemp,nombre,empleados.oficina, ciudad,
oficinas.oficina
FROM empleados RIGHT JOIN oficinas
ON empleados.oficina=oficinas.oficina;
Obtiene los empleados con su oficina y las oficinas (tabla a la derecha RIGHT del
JOIN) que no tienen empleados aparecerán también en el resultado con los
campos de la tabla empleados rellenados a NULL.
Aparecen tanto los empleados sin oficina como las oficinas sin empleados.
NOTA: Cuando necesitamos obtener filas con datos de dos tablas con una
condición de combinación utilizaremos un JOIN, aconsejo empezar por escribir el
JOIN con la condición que sea necesaria para combinar las filas, y luego
plantearos si la composición debe de ser interna o externa. Para este segundo
paso ésta sería la norma a seguir:
¿Pueden haber empleados que no tengan oficina y nos interesan?, si es que sí,
necesitamos un LEFT JOIN.
Seguiríamos preguntando:
¿Pueden haber oficinas que no tengan empleados y nos interesan?, si es que sí,
necesitamos un RIGHT JOIN.
Por ejemplo:
O bien:
Para poder llevarlo a cabo la sentencia SELECT consta de una serie de cláusulas
específicas (GROUP BY, HAVING), y Transact-SQL tiene definidas unas
funciones para poder realizar estos cálculos, las funciones de agregado (también
llamadas funciones de columna).
La diferencia entre una consulta de resumen y una consulta de las que hemos
visto hasta ahora es que en las consultas normales las filas del resultado se
obtienen directamente de las filas del origen de datos y cada dato que aparece en
el resultado tiene su dato correspondiente en el origen de la consulta mientras que
las filas generadas por las consultas de resumen no representan datos del origen
sino un total calculado sobre estos datos. Esta diferencia hará que las consultas
de resumen tengan algunas limitaciones que veremos a lo largo del tema.
Un ejemplo sería:
A la izquierda tenemos una consulta simple que nos saca las oficinas con sus
ventas ordenadas por región, y a la derecha una consulta de resumen que obtiene
la suma de las ventas de las oficinas de cada región
El mero hecho de utilizar una función de agregado en una consulta, convierte ésta
en una consulta de resumen.
La palabra ALL indica que se tiene que tomar en cuenta todos los valores de la
columna. Es el valor por defecto.
La palabra DISTINCT hace que se consideren todas las repeticiones del mismo
valor como uno sólo (considera valores distintos).
Todas las funciones de agregado se aplican a las filas del origen de datos una vez
ejecutada la cláusula WHERE (si la hubiera).
Expresion puede ser de cualquier tipo excepto text, image o ntext. No se permite
utilizar funciones de agregado ni subconsultas. El tipo de dato devuelto es int.
La función cuenta los valores distintos de NULL que hay en la columna. La palabra
ALL indica que se tienen que tomar todos los valores de la columna, mientras que
DISTINCT hace que se consideren todas las repeticiones del mismo valor como
uno solo. Estos parámetros son opcionales, por defecto se considera ALL.
Por ejemplo:
Si utilizamos * en vez de expresión, devuelve el número de filas del origen que nos
quedan después de ejecutar la cláusula WHERE.
Por ejemplo:
Es mejor que:
Las dos nos devuelven el número de empleados que tienen una oficina asignada
pero la primera es mejor porque se calcula más rápidamente.
Utilizar DISTINCT no tiene ningún sentido con MAX (el valor máximo será el
mismo si consideramos las repeticiones o no) y sólo se incluye para la
compatibilidad con SQL-92.
Por ejemplo:
El resultado será del mismo tipo aunque puede tener una precisión mayor.
Devuelve la suma de las ventas de todas las oficinas y de los objetivos de todas
las oficinas, el de mayor importe.
VAR sólo se puede utilizar con columnas numéricas. Los valores NULL se pasan
por alto.
Sólo se puede utilizar con columnas numéricas. Los valores NULL se pasan por
alto.
Sólo se puede utilizar con columnas numéricas. Los valores NULL se pasan por
alto.
Sólo se puede utilizar con columnas numéricas. Los valores NULL se pasan por
alto.
Es una función de agregado que genera como salida una columna adicional con el
valor 1 si la fila se agrega mediante el operador CUBE o ROLLUP, o el valor 0
cuando la fila no es el resultado de CUBE o ROLLUP.
Hasta ahora las consultas sumarias que hemos visto obtienen totales de todas las
filas del origen y producen una única fila de resultado.
Muchas veces cuando calculamos resúmenes nos interesan totales parciales, por
ejemplo saber de cada empleado cuánto ha vendido, y cuál ha sido su pedido
máximo, de cada cliente cuándo fue la última vez que nos compró, etc.
En todos estos casos en vez de obtener una fila única de resultados necesitamos
una fila por cada empleado, cliente, etc.
Una consulta con una cláusula GROUP BY agrupa los datos de la tabla origen y
produce una única fila resultado por cada grupo formado. Las columnas indicadas
en el GROUP BY se llaman columnas de agrupación o agrupamiento .
Cuando queremos realizar una agrupación múltiple, por varias columnas, éstas se
indican en la cláusula GROUP BY en el orden de mayor a menor agrupación igual
que con la cláusula ORDER BY.
Resultado:
Hay empleados sin oficinas (con oficina a nulo), estos forman un grupo con el
valor NULL en oficina, en este caso hay dos empleados así.
Ejemplo:
Resultado:
Número Importe
rep clie
de pedidos máximo
101 2113 1 225,00
102 2106 2 21,30
102 2120 1 37,50
103 2111 2 21,00
105 2103 4 275,00
105 2111 1 37,45
106 2101 1 14,58
107 2109 1 313,50
107 2124 2 24,30
108 2112 1 29,25
108 2114 1 71,00
108 2118 3 14,20
De cada representante obtenemos el número de pedidos y el importe máximo
vendido a cada cliente, de las ventas de 1997. La cláusula ORDER BY se ha
incluido para que las filas aparezcan ordenadas y quede más claro.
Hemos dicho que los resúmenes se calculan sobre todas las filas del origen
después de haber ejecutado el WHERE, pues ALL permite obtener un resumen de
las filas que no cumplen el WHERE.
ALL Incluye todos los grupos y conjuntos de resultados, incluso aquellos en los
que no hay filas que cumplan la condición de búsqueda especificada en la
cláusula WHERE. Cuando se especifica ALL, se devuelven valores NULL para las
columnas de resumen de los grupos que no cumplen la condición de búsqueda.
No se puede especificar ALL con los operadores CUBE y ROLLUP.
Resultado:
Número Importe
rep clie
de pedidos máximo
101 2102 0 NULL
101 2108 0 NULL
101 2113 1 225,00
102 2106 2 21,30
102 2120 1 37,50
103 2111 2 21,00
105 2103 4 275,00
105 2111 1 37,45
106 2101 1 14,58
106 2117 0 NULL
107 2109 1 313,50
107 2124 2 24,30
108 2112 1 29,25
108 2114 1 71,00
108 2118 3 14,20
Cuál ha sido el efecto de añadir ALL? Se han añadido filas para las filas del origen
que no cumplen la condición del WHERE pero sin que intervengan en el cálculo de
las funciones de agregado.
Por ejemplo el representante 101 tiene pedidos con el cliente 2102 pero estos
pedidos no son del año 1997, por eso aparece la primera fila (no estaba en el
resultado de la otra consulta) pero con 0 y NULL como resultados de las funciones
de agregado.
Por ejemplo:
Resultado:
Número Importe
rep clie
de pedidos máximo
101 2113 1 225,00
101 NULL 1 225,00
102 2106 1 21,30
102 2120 1 37,50
102 NULL 3 37,50
103 2111 2 21,00
103 NULL 2 21,00
105 2103 4 275,00
105 2111 1 37,45
105 NULL 5 275,00
106 2101 1 14,28
106 NULL 1 14,28
107 2109 1 313,50
107 2124 2 24,30
107 NULL 3 313,50
108 2112 1 29,25
108 2114 1 71,00
108 2118 3 14,20
108 NULL 5 71,00
... ... ... ...
NULL NULL 23 450,00
Por ejemplo:
Resultado:
Número Importe
rep clie
de pedidos máximo
101 2113 1 225,00
101 NULL 1 225,00
102 2106 1 21,30
102 2120 1 37,50
102 NULL 3 37,50
103 2111 2 21,00
103 NULL 2 21,00
105 2103 4 275,00
105 2111 1 37,45
105 NULL 5 275,00
106 2101 1 14,28
106 NULL 1 14,28
107 2109 1 313,50
107 2124 2 24,30
107 NULL 3 313,50
108 2112 1 29,25
108 2114 1 71,00
108 2118 3 14,20
108 NULL 5 71,00
... ... ... ...
NULL NULL 23 450,00
NULL 2101 1 14,58
NULL 2103 4 275,00
NULL 2106 2 21,30
NULL 2107 1 6,32
NULL 2108 1 56,25
NULL 2109 1 313,50
NULL 2111 3 37,45
NULL 2112 2 450,00
NULL 2113 1 225,00
NULL 2114 1 71,00
NULL 2118 3 14,20
NULL 2120 1 37,50
NULL 2124 2 24,30
Efecto: Obtenemos además de los resultados obtenidos con ROLLUP (los totales
por cada representante), los totales por el otro criterio (los totales por cada
cliente).
Tanto si utilizamos CUBE como ROLLUP, nos será útil la función de agregado
GROUPING.
Si cogemos por ejemplo la primera fila remarcada (101 NULL …) el valor NULL, no
sabemos si se refiere a una fila de subtotal o a que el representante 101 ha
realizado un pedido sin número de cliente. Para poder salvar este problema se
utiliza la función de agregado GROUPING.
Cuando queremos incluir una cláusula de selección sobre las filas del origen,
utilizamos la cláusula WHERE, pero cuando estamos definiendo una consulta de
resumen, no podemos utilizar esta cláusula para seleccionar filas del resultado ya
que cada una de éstas representa un grupo de filas de la tabla original. Para
seleccionar filas del resumen tenemos la cláusula HAVING.
HAVING funciona igual que la cláusula WHERE pero en vez de actuar sobre las
filas del origen de datos, actúa sobre las filas del resultado, selecciona grupos de
filas por lo que la condición de búsqueda sufrirá alguna limitación, la misma que
para la lista de selección:
Ejemplo:
Resultado:
Esta SELECT es la misma que la del primer ejemplo del apartado sobre la
cláusula GROUP BY, la diferencia es que le hemos añadido la cláusula HAVING,
que hace que del resultado sólo se visualicen los grupos que cumplan la
condición. Es decir sólo aparecen las oficinas que tienen menos de 2 empleados.
HAVING SUM(ventas)=10000
Resultado:
Vallartanas
4
2. Hallar cuántos pedidos hay de más de 250 euros.
Resultado:
Superiores a 250
4
3. ¿Cuántos títulos (cargos) de empleados se usan?
Resultado:
Cuántos títulos
3
4. ¿Entre qué cuotas se mueven los empleados?
Resultado:
Resultado:
Resultado:
Resultado:
Resultado:
5. En los resultados anteriores no se distinguen bien las líneas que corresponden a totales.
Modificar la consulta para obtener este resultado:
Agrupa Agrupa
rep clie Importe vendido
clie numemp
NULL NULL 2364,386 1 1
101 NULL 266,28 1 0
101 2102 39,78 0 0
101 2108 1,50 0 0
101 2113 225,00 0 0
102 NULL 77,76 1 0
102 2106 40,26 0 0
102 2120 37,50 0 0
103 NULL 21,00 1 0
103 2111 21,00 0 0
104 NULL NULL 0 0
104 NULL NULL 1 0
... .... ... (sigue) ... ...
6. Ahora modifica la consulta para que las filas de totales aparezcan más
claras. (Recuerda la función CASE)
Resultado:
Agrupa
rep clie Importe vendido Agrupa clie
numemp
NULL NULL 2364,386 Total empleado Total final
101 NULL 266,28 Total empleado
101 2102 39,78
101 2108 1,50
101 2113 225,00
102 NULL 77,76 Total empleado
102 2106 40,26
102 2120 37,50
103 NULL 21,00 Total empleado
103 2111 21,00
104 NULL NULL
104 NULL NULL Total empleado
... .... ... (sigue) ... ...
Resultado:
Agrupa
Agrupa clie rep clie Importe vendido
numemp
Total final Total empleado NULL NULL 2364,386
Total empleado 101 NULL 266,28
101 2102 39,78
101 2108 1,50
101 2113 225,00
Total empleado 102 NULL 77,76
102 2106 40,26
102 2120 37,50
Total empleado 103 NULL 21,00
103 2111 21,00
104 NULL NULL
Total empleado 104 NULL NULL
... (sigue) ... ... .... ...
Resultado:
Agrupa
numemp clie Importe vendido
numemp
Total final NULL Total empleado 2364,386
101 Total empleado 266,28
101 2102 39,78
101 2108 1,50
101 2113 225,00
102 Total empleado 77,76
102 2106 40,26
102 2120 37,50
103 Total empleado 21,00
103 2111 21,00
104 NULL NULL
104 Total empleado NULL
... (sigue) ... .... ...
Resultado:
Agrupa
numemp clie Importe vendido
numemp
Total final NULL Total empleado 2364,386
101 2102 39,78
101 2108 1,50
101 2113 225,00
101 Total empleado 266,28
102 2106 40,26
102 2120 37,50
102 Total empleado 77,76
103 2111 21,00
103 Total empleado 21,00
104 0,00
104 Total empleado 0,00
... (sigue) ... .... ...
6. Ahora modifica la consulta para que las filas de totales aparezcan más claras,
substituyendo el 1 de Agrupa clie por "Total empleado", el 1 de Agrupa numemp
por Total final y el valor 0 por espacio en blanco.
SELECT CASE GROUPING(numemp) WHEN 0 THEN ' ' ELSE 'Total Final' END AS
[Agrupa numemp],
CASE GROUPING(clie) WHEN 0 THEN ' ' WHEN 1 THEN 'Total empleado'
END AS [Agrupa clie],
numemp, clie, SUM(importe) AS [Importe vendido]
FROM empleados LEFT JOIN pedidos ON numemp = rep
GROUP BY numemp, clie WITH ROLLUP
ORDER BY numemp,clie;
SELECT CASE GROUPING(numemp) WHEN 0 THEN ' ' ELSE 'Total Final' END AS
[Agrupa numemp],
numemp,
CASE GROUPING(clie) WHEN 0 THEN CONVERT(CHAR(4),clie) WHEN 1
THEN 'Total empleado' END AS [Clie],
SUM(importe) AS [Importe vendido]
FROM empleados LEFT JOIN pedidos ON numemp = rep
GROUP BY numemp, clie WITH ROLLUP
ORDER BY numemp,clie;
9. El empleado 104 (y otros) no ha vendido a nadie y por eso sale en la columna
clie la palabra NULL, queremos que en estos casos no aparezca nada (se deje en
blanco), y el importe si es NULL que salga un cero.
SELECT CASE GROUPING(numemp) WHEN 0 THEN ' ' ELSE 'Total Final' END AS
[Agrupa numemp],
numemp,
CASE WHEN GROUPING(clie) = 1 THEN 'Total empleado' WHEN clie IS
NULL THEN ' ' ELSE CONVERT(CHAR(4),clie) END AS [Clie],
ISNULL(SUM(importe),0) AS [Importe vendido]
FROM empleados LEFT JOIN pedidos ON numemp = rep
GROUP BY numemp, clie WITH ROLLUP
ORDER BY numemp,clie;
10. Lo rematamos para que la fila del Total final no muestre los valores "NULL"
ni "Total empleado". En cambio, los cambiaremos por tres puntos. Tampoco
mostraremos el encabezado "Agrupa numemp".
SELECT CASE GROUPING(numemp) WHEN 0 THEN ' ' ELSE 'Total Final' END AS
[ ],
ISNULL(CONVERT(CHAR(3),numemp),'... ') AS [numemp],
CASE WHEN GROUPING(clie) = 1 AND GROUPING(numemp) = 0
THEN 'Total empleado' WHEN GROUPING(clie) = 1
AND GROUPING(numemp) = 1 THEN '...'
WHEN clie IS NULL THEN ' ' ELSE CONVERT(CHAR(4),clie) END AS
[Clie],
ISNULL(SUM(importe),0) AS [Importe vendido]
FROM empleados LEFT JOIN pedidos ON numemp = rep
GROUP BY numemp, clie WITH ROLLUP
ORDER BY numemp,clie;
Subconsultas
Introducción
Aparece siempre encerrada entre paréntesis y tiene la misma sintaxis que una
sentencia SELECT normal con alguna limitación:
No puede incluir una cláusula COMPUTE o FOR BROWSE y sólo puede incluir
una cláusula ORDER BY cuando se especifica también una cláusula TOP.
SELECT nombre
FROM empleados
WHERE cuota <= (SELECT SUM(importe)
FROM pedidos
WHERE rep = numemp);
Por ejemplo:
La columna oficina se encuentra en los dos orígenes (oficinas y empleados) pero
esta consulta no dará error (no se nos pedirá cualificar los nombres como pasaría
en una composición de tablas), dentro de la subconsulta se considera oficina el
campo de la tabla empleados. Con lo que compararía la oficina del empleado con
la misma oficina del empleado y eso no es lo que queremos, queremos comparar
la oficina del empleado con la oficina de oficinas, lo escribiremos pues así para
forzar a que busque la columna en la tabla oficinas.
Existen subconsultas que deben obligatoriamente devolver un único valor, son las
que aparecen en la lista de selección de la consulta externa o las que aparecen en
WHERE o HAVING combinadas con un operador de comparación sin modificar.
Sintaxis:
En este caso la segunda expresión será una subconsulta, con una sola columna
en la lista de selección y deberá devolver una única fila.
Ese valor único será el que se compare con el resultado de la primera expresión.
Ejemplo:
SELECT nombre
FROM empleados
WHERE cuota <= (SELECT SUM(importe)
FROM pedidos
WHERE rep = numemp);
La subconsulta devuelve una sola columna y como maximo una fila ya que es una
consulta de resumen sin cláusula GROUP BY.
Otro tipo de subconsultas son las que devuelven una lista de valores en forma de
una columna y cero, una o varias filas.
Ejemplo:
SELECT *
FROM empleados
WHERE oficina IN (SELECT oficina
FROM oficinas
WHERE region = 'Este');
Por cada empleado se calcula la lista de las oficinas del Este (nº de oficina) y se evalúa si la oficina
del empleado está en esta lista. Obtenemos pues los empleados de oficinas del Este.
SELECT *
FROM empleados
WHERE oficina IN (SELECT oficina
FROM oficinas
WHERE region = 'Otro');
La lista generada está vacía por lo que la condición IN devuelve FALSE y en este
caso no sale ningún empleado.
6.5. La comparación modificada (ANY,
ALL)
Los operadores de comparación que presentan una subconsulta se pueden
modificar mediante las palabras clave ALL, ANY o SOME. SOME es un
equivalente del estándar de SQL-92 de ANY.
El test ANY
<expresion> {=|<>|!=|>|>=|!>|<|<=|!<} {ANY|SOME} subconsulta
ANY significa que, para que una fila de la consulta externa satisfaga la condición
especificada, la comparación se debe cumplir para al menos un valor de los
devueltos por la subconsulta.
Por cada fila de la consulta externa se evalúa la comparación con cada uno de los
valores devueltos por la subconsulta y si la comparación es True para alguno de
los valores ANY es verdadero, si la comparación no se cumple con ninguno de los
valores de la consulta, ANY da False a no ser que todos los valores devueltos por
la subconsulta sean nulos en tal caso ANY dará NULL.
Ejemplo:
SELECT *
FROM empleados
WHERE cuota > ANY (SELECT cuota
FROM empleados empleados2
WHERE empleados.oficina = empleados2.oficina);
Obtenemos los empleados que tienen una cuota superior a la cuota de alguno de
sus compañeros de oficina, es decir los empleados que no tengan la menor cuota
de su oficina.
El test ALL
<expresion> {=|<>|!=|>|>=|!>|<|<=|!<} ALL subconsulta
Con el modificador ALL, para que se cumpla la condición, la comparación se debe
cumplir con cada uno de los valores devueltos por la subconsulta.
SELECT *
FROM empleados
WHERE cuota > ALL (SELECT cuota
FROM empleados empleados2
WHERE empleados.oficina = empleados2.oficina);
En el ejemplo anterior obtenemos los empleados que tengan una cuota superior a
todas las cuotas de la oficina del empleado. Podríamos pensar que obtenemos el
empleado de mayor cuota de su oficina pero no lo es, aquí tenemos un problema,
la cuota del empleado aparece en el resultado de subconsulta por lo tanto > no se
cumplirá para todos los valores y sólo saldrán los empleados que no tengan
oficina (para los que la subconsulta no devuelve filas).
De esta forma saldrían los empleados que tienen una cuota mayor que cualquier
otro empleado de su misma oficina.
O bien
Para no considerar los empleados que tengan la misma cuota que el empleado.
En este caso saldrían los empleados con la mayor cuota de sus oficina, pero si
dos empleados tienen la misma cuota superior, saldrían, hecho que no sucedería
con la otra versión.
1. Listar los clientes (numclie, nombre) asignados a Juan que no han remitido un pedido superior a
300 euros.
numclie nombre
2107 Julian López
2107 Julian López
2121 Vicente Ríos
2125 Pepito Grillo
2. Listar los empleados (numemp, nombre) mayores de 40 años que dirigen a un vendedor con
superávit (ha vendido más que su cuota).
numemp nombre
106 Luis Antonio
108 Ana Bustamante
110 Juan Victor
3. Listar los empleados (código de empleado) cuyo importe de pedido medio para productos
fabricados por ACI es superior al importe medio global (de todos los pedidos de todos los
empleados).
rep
105
4. Listar los empleados (numemp, nombre, ventas) cuyas ventas son iguales o
superiores al objetivo de las oficinas de una determinada ciudad (de todas las
oficinas de esa ciudad). Las oficinas con objetivo nulo no se deben de tener en
cuenta (como si no existiesen). Y si no hay oficinas en la ciudad no queremos que
salga ningún empleado. Intentar primero resolver la consulta utilizando >=ALL.
Ahora con Pachuca. Pachuca tiene una oficina con objetivo nulo, en este caso no
queremos que esa oficina cuente.
numemp nombre ventas
101 Antonio Viguer 30500,00
102 Alvaro Jaumes 47400,00
103 Juan Rovira 28600,00
105 Vicente Pantalla 36800,00
106 Luis Antonio 29900,00
108 Ana Bustamante 36100,00
109 María Sunta 39200,00
111 Juan Gris 60000,00
112 Julián Martorell 91000,00
114 Pablo Moreno 37000,00
Ahora para Michoacán. Como en Michoacán sólo hay una oficina y no tiene
objetivo no tiene que salir ningún empleado.
5. Listar las oficinas en donde todos los empleados tienen ventas que superan al 50% del objetivo
de la oficina.
oficina ciudad
11 Vallarta
13 Castellon
22 Alvarado
Ayuda ejercicios unidad 6: Las
subconsultas
1. Listar los clientes (numclie, nombre) asignados a Juan que no han remitido un
pedido superior a 300 euros.
3. Listar los empleados (código de empleado) cuyo importe de pedido medio para
productos fabricados por ACI es superior al importe medio global (de todos los
pedidos de todos los empleados).
SELECT rep
FROM pedidos
WHERE fab = 'ACI'
GROUP BY rep
HAVING AVG(importe) > (SELECT AVG(importe) FROM pedidos);
4. Listar los empleados (numemp, nombre, ventas) cuyas ventas son iguales o
superiores al objetivo de las oficinas de una determinada ciudad (de todas las
oficinas de esa ciudad). Las oficinas con objetivo nulo no se deben de tener en
cuenta (como si no existiesen). Y si no hay oficinas en la ciudad no queremos que
salga ningún empleado. Intentar primero resolver la consulta utilizando >=ALL.
Ahora para Michoacán. Como en Michoacán sólo hay una oficina y no tiene
objetivo no tiene que salir ningún empleado.
Esta sería la consulta definitiva que nos serviría para cualquier situación. Pero
como se ve el modificador ALL puede darnos problemas. Para solucionarlo,
realizamos la siguiente consulta.
5. Listar las oficinas en donde todos los empleados tienen ventas que superan al
50% del objetivo de la oficina.
SELECT oficina, ciudad
FROM oficinas
WHERE (objetivo * .5) < = (SELECT MIN(ventas)
FROM empleados WHERE empleados.oficina =
oficinas.oficina);
7. Actualización de datos (I)
7.1. Introducción
Hasta ahora hemos trabajado con tablas que tenían datos introducidos y cuando
nos ha hecho falta hemos añadido nuevos datos en las mismas y hemos
modificado algún dato directamente desde el entorno de SSMS, en este tema
veremos cómo hacerlo con instrucciones de Transact-SQL.
Seguimos en el DML porque las instrucciones que veremos actúan sobre los datos
de la base de datos no sobre su definición y tenemos tres tipos de operaciones
posibles:
Una forma de insertar datos es crear una tabla que incluya los datos de otra. Esta
es la sentencia SELECT... INTO.
SELECT ...
INTO nb_NuevaTabla
FROM ...
En la nueva tabla las columnas tendrán el mismo tipo y tamaño que las columnas
del resultado de la SELECT, se llamarán con el nombre de alias de la columna o
en su defecto con el nombre de la columna, pero no se transfiere ninguna otra
propiedad del campo o de la tabla como por ejemplo las claves e índices.
<destino> ::=
{
[nbBaseDatos.nbEsquema. | nbEsquema.]nbTablaVista
}
Si utilizamos una vista, y ésta tiene un origen basado en varias tablas, en su lista
de selección deberán aparecer columnas de una sola tabla (no podemos insertar
datos en varias tablas a la vez).
Cuando indicamos nombres de columnas, esas columnas serán las que reciban
los valores a insertar, la asignación de valores se realiza por posición, la primera
columna recibe el primer valor, la segunda columna el segundo, y así
sucesivamente.
Cuando no se indica una lista de columnas el sistema asume por defecto todas las
columnas de la tabla y en el mismo orden que aparecen en la definición de la
tabla, en este caso, los valores se tienen que especificar en el mismo orden que
las columnas en la definición de la tabla, y se tiene que especificar un valor por
cada columna ya que los valores se rellenan por posición, la primera columna
recibe el primer valor, la segunda columna el segundo, y así sucesivamente.
Cuando se insertan nuevas filas en una tabla, el sistema comprobará que la nueva
fila no infrinja ninguna regla de integridad, por ejemplo no podremos asignar a una
columna PRIMARY KEY un valor nulo o que ya exista en la tabla, a una columna
UNIQUE un valor que ya exista en la tabla, a una columna NOT NULL un valor
NULL, a una clave ajena (FOREIGN KEY) un valor que no exista en la tabla de
referencia.
Ejemplos.
En este caso hemos indicado sólo dos columnas y dos valores, las demás
columnas se rellenan con el valor por defecto si lo tiene (DEFAULT) o con NULL.
Si alguna columna no nombrada no admite nulos ni tiene cláusula DEFAULT
definida, la instrucción dará error.
INSERT INTO oficinas
VALUES (27,'Móstoles','Centro',default ,null, default)
Aquí no hemos indicado una lista de columnas luego los valores se tienen que
indicar en el mismo orden que las columnas dentro de la tabla, si nos
equivocamos de orden, el valor se guardará en una columna errónea (si los tipos
son compatibles) o generará un mensaje de error y la fila no se insertará (si los
tipos no son compatibles).
7.5. Inserción de varias filas
Si los valores que queremos insertar los tenemos en otras tablas, podemos
insertar varias filas a la vez indicando una consulta que genere las filas de valores
a insertar. En este caso utilizamos la sintaxis:
Tabla_derivada es cualquier instrucción SELECT válida que devuelva filas con los
datos que se van a cargar en el destino.
Cada fila devuelta por la SELECT es una lista de valores que se intentará insertar
como con la cláusula VALUES, por lo que las columnas devueltas por la SELECT
deberán cumplir las mismas reglas que los valores de la lista de valores
anteriores.
Ejemplo:
En este caso no hemos incluido una lista de columnas, por lo que en la SELECT
tenemos que generar los valores en el mismo orden que en trabajo.
Si hubiésemos escrito:
Hubiese dado error porque la columna col1 es INT y el valor a asignar es texto (el
nombre de la ciudad de la oficina).
TRANSACT-SQL nos permite insertar una fila de valores por defecto utilizando la
sintaxis:
Hace que la nueva fila contenga los valores predeterminados definidos para cada
columna.
Hay que tener en cuenta una serie de aspectos al utilizar esta instrucción:
Puede generar filas duplicadas en la tabla si los valores que se generan son
siempre los mismos.
Si la tabla tiene una clave principal, esta tendrá que estar basada en una columna
con la propiedad IDENTITY para que se generen valores diferentes
automáticamente.
Si una columna está definida como NOT NULL tendrá que incluir un DEFAULT o
ser una columna calculada con una expresión compatible.
7.7. Modificar datos almacenados - UPDATE
La sentencia UPDATE modifica los valores de una o más columnas en las filas
seleccionadas de una única tabla.
Para modificar los datos de una tabla es necesario disponer del privilegio UPDATE
sobre dicha tabla.
UPDATE
[ TOP ( expression ) [ PERCENT ] ]
<destino>
SET { nbcolumna = { expresion | DEFAULT | NULL }
} [ ,...n ]
[ FROM{ <origen> }]
[ WHERE <condicion> ]
[;]
<destino> ::=
{
[nbBaseDatos.[nbEsquema.]| nbEsquema.]nbTablaVista
}
La cláusula SET especifica qué columnas van a modificarse y con qué valor, el
valor se puede expresar mediante una expresión, la palabra DEFAULT que
equivale al valor predeterminado de la columna, o el valor nulo NULL.
Expresión en cada asignación debe generar un valor del tipo de dato apropiado
para la columna indicada. La expresión debe ser calculable basada en los valores
de la fila actualmente en actualización. Si para el cálculo se utiliza una columna
que también se modifica, el valor que se utilizará es el de antes de la modificación,
lo mismo para la condición del WHERE.
Expresión también puede ser una subconsulta siempre y cuanto devuelva un único
valor y cumpla las condiciones anteriormente expuestas.
Por ejemplo:
Actualiza todas las filas de la tabla oficinas dejando el campo ventas con el valor
cero.
En una misma sentencia podemos actualizar varias columnas, sólo tenemos que
indicar las distintas asignaciones separadas por comas:
Por ejemplo:
[ WHERE <condicion> ]
UPDATE oficinas
SET ventas = 0
WHERE region = 'Este';
La sentencia DELETE elimina filas de una tabla. Si se borran todas las filas, o se
borra la única fila de una tabla, la definición de la tabla no desaparece, sólo que la
tabla se queda vacía.
DELETE
[ TOP ( expression ) [ PERCENT ] ] [ FROM ] <destino>
[ FROM <origen>]
[ WHERE < condicion>]
[; ]
<destino> ::=
{
[nbBaseDatos. nbEsquema. | nbEsquema.]nbTablaVista
}
Con esta instrucción podemos eliminar una o varias filas de una tabla.
La segunda cláusula FROM sirve para indicar un origen que permita una condición
de WHERE sobre una tabla diferente de destino.
DELETE oficinas;
Equivalente a:
La cláusula WHERE permite eliminar determinadas filas, indica una condición que
deben cumplir las filas que se eliminan.
Por ejemplo:
DELETE oficinas
WHERE region = ’Este’;
Elimina las oficinas del Este.
Por ejemplo:
Originalmente sólo se podía indicar una tabla en la cláusula FROM, pero ahora
podemos indicar un origen basado en varias tablas.
Este caso se produce cuando las filas a eliminar dependen de un valor que está
en otra tabla. Por ejemplo queremos eliminar los empleados de las oficinas del
Este. Como la región de la oficina no está en empleados, habría que añadir al
origen la tabla oficinas para poder formular la condición del WHERE:
En el origen tenemos las dos tablas y en la primera FROM indicamos de qué tabla
queremos borrar.
Esto se podía haber resuelto, como toda la vida, mediante una subconsulta:
Si queremos eliminar todas las filas de una tabla podemos utilizar también la
instrucción TRUNCATE TABLE.
TRUNCATE TABLE
[nbBaseDatos.[nbEsquema.]| nbEsquema.]nbTabla [; ]
Esta sentencia quita todas las filas de una tabla sin registrar las eliminaciones
individuales de filas. Desde un punto de vista funcional, TRUNCATE TABLE es
equivalente a la instrucción DELETE sin una cláusula WHERE; no obstante,
TRUNCATE TABLE es más rápida y utiliza menos recursos de registros de
transacciones y de sistema.
3. Queremos actualizar el importe de los pedidos del mes actual con el precio
almacenado en la tabla productos.
Ayuda: En un primer paso obtener los pedidos del mes actual obteniendo también el precio unitario
dentro del pedido y el precio del producto de la tabla de productos.
Actualizar después la tabla de pedidos cambiando los importes para que el precio unitario
corresponda con el precio del producto. Los pedidos de los productos que no tienen precio se
quedarán como estaban. (3 filas afectadas)
Ayuda: Primero sacamos los productos que queremos actualizar con los pedidos correspondientes:
precio
idfab idproducto precio codigo numpedido fechapedido importe
pedido
aci 41001 NULL NULL NULL NULL NULL NULL
aci 41002 NULL 10 112992 1990-04-15 7,60 0,76
20:00:00.000
2008-02-05
aci 41002 NULL 18 113027 450,00 8,3333
00:00:00.000
2008-05-05
aci 41003 NULL 15 113012 37,45 1,07
00:00:00.000
2008-05-10
aci 41004 NULL 3 112963 3,276 0,117
00:00:00.000
1990-01-11
aci 41004 NULL 4 112968 39,78 1,17
00:00:00.000
2008-05-10
aci 41004 NULL 7 112983 7,02 1,17
00:00:00.000
2009-04-01
aci 4100x NULL 25 113055 1,50 0,25
00:00:00.000
2008-11-01
aci 4100x NULL 26 113057 NULL NULL
00:00:00.000
2008-01-01
aci 4100y NULL 8 112987 275,00 25,00
00:00:00.000
2008-12-12
aci 4100z NULL 1 110036 22,50 2,50
00:00:00.000
2008-12-12
aci 4100z NULL 6 112979 150,00 25,00
00:00:00.000
Vemos que el producto ACI 41001 no se podrá actualizar porque no tiene pedidos. Pero los demás
se actualizarán con el precio medio de sus pedidos, deberán quedar así (7 filas afectadas):
* aci 4100x tiene 2 pedidos pero uno sin precio por lo que no cuenta
3. Queremos actualizar el importe de los pedidos del mes actual con el precio
almacenado en la tabla productos.
Actualizar después la tabla de pedidos cambiando los importes para que el precio
unitario corresponda con el precio del producto. Los pedidos de los productos que
no tienen precio se quedarán como estaban. (3 filas afectadas)
Una vista es una tabla virtual que representa los datos de una o más tablas de una
forma alternativa. Para crear una nueva vista se emplea la sentencia CREATE
VIEW, debe ser la primera instrucción en un lote de consultas.
Sintaxis:
nbVista Es el nombre de la nueva vista. Los nombres de vistas deben seguir las
reglas de los identificadores.
Una vista no tiene por qué ser un simple subconjunto de filas y de columnas de
una tabla determinada. Es posible crear una vista que utilice más de una tabla u
otras vistas mediante una cláusula SELECT de cualquier complejidad.
Crea una vista con los datos de todos los empleados y de sus oficinas.
En este caso hemos tenido que definir alias de campo porque en el origen de la
sentencia SELECT existe duplicidad de nombres.
Por defecto las columnas de la vista heredan los nombres de las columnas de la
sentencia SELECT asociada, pero podemos cambiar estos nombres indicando
una lista de columnas después del nombre de la vista.
Las columnas que se vayan a modificar en la vista deben hacer referencia directa
a los datos subyacentes de las columnas de la tabla, es decir que las columnas no
se pueden obtener de otra forma, como con una función de agregado: AVG,
COUNT, SUM, MIN, MAX, GROUPING, STDEV, STDEVP, VAR y VARP, o un
cálculo.
Las columnas que se van a modificar no se ven afectadas por las cláusulas
GROUP BY, HAVING o DISTINCT.
Para eliminar una vista de una base de datos tenemos la sentencia DROP TABLE.
Sintaxis:
Se eliminan las vista de la base de datos actual. Cuando eliminamos una vista
eliminamos su definición y los permisos asociados a ella.
Se pueden quitar varias vistas en una misma sentencia DROP VIEW escribiendo
los nombres de las vistas a eliminar separados por comas.
Ejemplo:
Las sentencias que ya conocemos son las que en principio forman parte de
cualquier lenguaje SQL. Ahora veremos que TRANSACT-SQL va más allá de un
lenguaje SQL cualquiera ya que aunque no permita:
Tipos de datos.
Definición de variables.
Estructuras de control de flujo.
Gestión de excepciones.
Funciones predefinidas.
Elementos para la visualización, que permiten mostrar mensajes definidos
por el usuario gracias a la cláusula PRINT.
En SQL Server 2005 podemos definir tres tipos de bloques de código, los
procedimientos almacenados, los desencadenadores (o triggers) y funciones
definidas por el usuario.
También están los procedimientos de usuario, los crea cualquier usuario que
tenga los permisos oportunos.
Ejemplos:
CREATE PROCEDURE
Es equivalente a
VARYING Sólo se aplica a los parámetros de tipo cursor por lo que se explicará
cuando se expliquen los cursores.
Procedimiento básico
CREATE PROCEDURE Dice_Hola
AS
PRINT ‘Hola’;
GO –- Indicamos GO para cerrar el lote que crea el
procedimiento y empezar otro lote.
EXEC Dice_Hola; -- De esta forma llamamos al procedimiento (se
ejecuta).
En este caso, como la llamada es la primera del lote (va detrás del GO) podíamos
haber obviado la palabra EXEC y haber escrito directamente:
Dice_Hola
Con un parámetro de entrada (la palabra que queremos que escriba)
CREATE PROCEDURE Dice_Palabra @palabra CHAR(30)
AS
PRINT @palabra;
GO
EXEC Dice_Palabra ‘Lo que quiera’;
EXEC Dice_Palabra ‘Otra cosa’;
Aquí hemos hecho dos llamadas, una con el valor ‘Lo que quiera’ y otra con el
valor ‘Otra cosa’.
USE Biblio;
--DROP PROC VerUsuariosPoblacion; --La comentamos la primera vez
GO
CREATE PROCEDURE VerUsuariosPoblacion @pob CHAR(30),@pro CHAR(30)
AS
SELECT * FROM usuarios WHERE poblacion=@pob AND provincia = @pro;
GO
EXEC VerUsuariosPoblacion Michoacán, Vallarta
indicando sólo el valor de los parámetros (en este caso los tenemos que indicar
en el mismo orden en que están definidos) como en el ejemplo anterior, o bien
indicando el nombre del parámetro:
Indicar el nombre del parámetro en la llamada también nos permite indicar los
valores en cualquier orden, la siguiente llamada es equivalente a la anterior,
hemos invertido el orden y se ejecuta igual:
En este procedimiento todos los parámetros son obligatorios, no deja llamar con
un solo parámetro.
Da error.
Con parámetros opcionales
Para definir un parámetro opcional tenemos que asignarle un valor por defecto en
la definición del procedimiento.
Da error.
Ejemplo:
USE Gestion
GO
CREATE PROC ultimo_contrato @ofi INT, @fecha DATETIME OUTPUT
AS
SELECT @fecha=(SELECT MAX(contrato) FROM empleados WHERE
oficina=@ofi)
GO
RETURN
RETURN [expresion_entera]
USE Gestion
GO
CREATE PROC trabajadores @ofi INT, @num INT OUTPUT
AS
SELECT @num=(SELECT COUNT(*) FROM empleados WHERE oficina=@ofi)
GO
IF condicion
{ sentencia_sql | bloque_sql }
[ ELSE
{ sentencia_sql | bloque_sql } ]
Ejemplo: Si nos queremos guardar en una consulta todos los ejemplos para
probarlos en cualquier momento, es conveniente antes de los CREATE
PROCEDURE colocar un DROP PROCEDURE para que la instrucción CREATE
no dé error si el procedimiento ya existe, pero la primera vez la instrucción DROP
PROC nos dará error porque el procedimiento todavía no existe, así que lo mejor
es ejecutar el DROP sólo si el procedimiento existe, utilizando la función
object_id(‘nombre_de_objeto’,’tipo de objeto’) que nos devuelve el id del objeto y
NULL si el objeto no existe.
Esta instrucción permite definir un bucle que repite una sentencia o bloque de
sentencias mientras se cumpla una determinada condición.
WHILE condicion
{ sentencia_sql | bloque_sql }
| BREAK
| CONTINUE
BREAK Produce la salida del bucle WHILE más interno. La instrucción BREAK
interna sale al siguiente bucle más externo. Todas las instrucciones que se
encuentren después del final del bucle interno se ejecutan primero y después se
reinicia el siguiente bucle más externo.
CONTINUE Hace que se reinicie el bucle WHILE y omite las instrucciones que
haya después de la palabra clave CONTINUE.
Si condicion1 se cumple:
Si condicion1 no se cumple:
9.8. WAITFOR
Ejemplo:
PRINT CONVERT(CHAR(8),Getdate(),108);
WAITFOR DELAY '00:00:03'
PRINT CONVERT(CHAR(8),Getdate(), 108);
WAITFOR DELAY '00:03'
PRINT CONVERT(CHAR(8),Getdate(), 108);
9.9. GOTO
GOTO NombreEtiqueta
Ejemplo:
Definición
BEGIN TRY
{sentencia_sql|bloque_sql}
END TRY
BEGIN CATCH
[{sentencia_sql|bloque_sql}]
END CATCH [ ; ]
Estas funciones devuelven NULL si se las llama desde fuera del ámbito del bloque
CATCH. Con ellas se puede recuperar información sobre los errores desde
cualquier lugar dentro del ámbito del bloque CATCH. Por ejemplo, en la siguiente
secuencia de comandos se muestra un procedimiento almacenado que contiene
funciones de control de errores. Se llama al procedimiento almacenado en el
bloque CATCH de una construcción TRY…CATCH y se devuelve información
sobre el error.
TRY…CATCH detecta todos los errores de ejecución que tienen una gravedad
mayor de 10 y que no cierran la conexión de la base de datos.
TRY…CATCH no detecta:
USE Gestion;
GO
BEGIN TRY
SELECT * FROM TablaQueNoExiste;
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() as ErrorNumero,ERROR_MESSAGE() as
MensajeDeError;
END CATCH
Intentamos ejecutar una SELECT de una tabla que no existe en la base de datos.
El error no se captura y el control se transfiere fuera de la construcción TRY…
CATCH, al siguiente nivel superior, en este caso salta el mensaje de error del
sistema.
En este caso como el error se produce dentro del procedimiento, el control del
error se devuelve al nivel superior (el que ha realizado el EXECUTE) por lo que es
capturado por el TRY y entra en el bloque CATCH, en vez de que salte el mensaje
de error del sistema saldrá el nuestro.
Ejercicio paso a paso: Procedimientos
Objetivo
Para ello vamos a definir tres procedimientos, CreaBase que permita crear una
base de datos con el nombre que le indiquemos en la llamada, otro, BorrarBase
que borre una determinada base de datos, y por último RellenaBase para rellenar
con las tablas de Gestion8 la base de datos que indiquemos en la llamada.
GO
Empezar:
USE Gestion8
EXEC BorraBase 'Gestion10'
GO
EXEC CreaBase 'Gestion10'
GO
EXEC RellenaGestion 'Gestion10'
9.11. Desencadenadores o TRIGGERS
Desencadenadores DML
Desencadenadores DDL
Desencadenadores LOGON
AFTER indica que el desencadenador sólo se activa cuando todas las operaciones
especificadas en la instrucción SQL desencadenadora se han ejecutado
correctamente. Además, todas las acciones referenciales en cascada y las
comprobaciones de restricciones deben ser correctas para que este
desencadenador se ejecute.
Los desencadenadores DML usan las tablas lógicas deleted e inserted. Son de
estructura similar a la tabla en que se define el desencadenador, es decir, la tabla
en que se intenta la acción del usuario. Las tablas deleted e inserted guardan los
valores antiguos o nuevos de las filas que la acción del usuario puede cambiar.
CREATE INDEX ALTER INDEX DROP INDEX DROP TABLE
Ejemplo:
USE Gestion8
GO
CREATE TRIGGER ActualizaVentasEmpleados
ON pedidos FOR INSERT
AS
UPDATE empleados SET ventas=ventas+inserted.importe
FROM empleados, inserted
WHERE numemp=inserted.rep;
GO
Cuando se INSERTe un pedido, entrará en funcionamiento el trigger
ActualizaVentasEmpleado y se ejecutarán las instrucciones que aparecen
después de AS, en este caso actualizará (UPDATE) la tabla empleados sumará a
las ventas del empleado (ventas) el importe del pedido insertado
(inserted.importe), y sólo actualizará el empleado cuyo numemp coincida con el
campo rep del pedido insertado (WHERE numemp=inserted.rep).
Vemos que al insertar un pedido de 100 € del empleado 108, sus ventas han
aumentado en 100€.
ALTER TRIGGER
Ejemplo:
Ejemplo:
Ejemplo:
5. Crea un procedimiento que muestre los n productos más caros, n es un valor
que se indicará en la llamada.
6. Crea otro procedimiento que muestre los n productos más caros con empates y
nos devuelva cuántos hay.