8-Programacion Transact-Sql
8-Programacion Transact-Sql
1. VARIABLES
2. CONTROL DE FLUJO
3. CURSOR
4. FUNCIONES DEFINIDAS POR EL USUARIO
5. PROCEDIMIENTOS ALMACENADOS
6. TRIGGERS
TRANSACT SQL
- Variables
- Estructuras de control de flujo
- Bucles
Una variable presenta objetos de un dominio y su valor puede cambiar dentro del
ámbito de un programa.
Se definen dos tipos de variables: locales y globales. Las variables locales están
definidas por el usuario, mientras que las variables globales las suministra el sistema
y están predefinidas.
Variables locales:
Ej2.
-- declaramos dos variables y le asignamos el máximo y mínimo
-- precio desde la tabla Producto
DECLARE @Maximo DECIMAL, @Minimo DECIMAL
Variables Globales
Ej3.
-- Uso de variables globales
PRINT 'Version: ' + @@VERSION
PRINT 'Lenguaje : ' + @@LANGUAGE
PRINT 'Servidor: ' + @@SERVERNAME
PRINT 'Conexiones: ' + STR(@@MAX_CONNECTIONS)
Salida: Version: Microsoft SQL Server 2008 R2 (RTM) - 10.50.1600.1 (Intel X86)
Apr 2 2010 15:53:02
Copyright (c) Microsoft Corporation
Express Edition with Advanced Services on Windows NT 6.1 <X86> (Build
7600: )
Lenguaje: Español
Servidor: JULIOC-PC\SQLEXPRESS
Conexiones: 32767
2. CONTROL DE FLUJO
Cosntrucciones:
Ej4.
--Mostrar la cantidad de facturas hechas por el empleado
-- de código 6
DECLARE @CodVende INT, @CantFacturas INT
SET @CodVende = 6
SELECT @CantFacturas = COUNT(*)
FROM Factura
WHERE CodVende = @CodVende
---------
Salida: No ha hecho ninguna factura
Ej5.
--Evaluar la existencia de un registro de Producto;
--si existe actualizamos –precio a $10000 si no existe
--lo insertamos
DECLARE @CodProducto INT, @Descripcion VARCHAR(50), @Precio DECIMAL
SET @CodProducto = 1
SET @Descripcion = 'Aspirina'
SET @Precio = 10000
--------
Salida si existe registro: (1 filas afectadas)
Registro actualizado
CASE <expresión>
WHEN <valor_expresion> THEN <valor_devuelto>
WHEN <valor_expresion1> THEN <valor_devuelto1>
ELSE <valor_devuelto2> -- Valor por defecto
END
Ej6.
--Declarar una variable donde se asigne el número del mes, evalúe el
--valor de la variable y retorne el mes en letras.
DECLARE @M INT, @MES VARCHAR(20)
SET @M=4
SET @MES =
(CASE @M WHEN 1 THEN 'Enero'
WHEN 2 THEN 'Febrero'
WHEN 3 THEN 'Marzo'
WHEN 4 THEN 'Abril'
WHEN 5 THEN 'Mayo'
WHEN 6 THEN 'Junio'
WHEN 7 THEN 'Julio'
WHEN 8 THEN 'Agosto'
WHEN 9 THEN 'Septiembre'
WHEN 10 THEN 'Octubre'
WHEN 11 THEN 'Noviembre'
WHEN 12 THEN 'Diciembre'
ELSE 'no es un mes válido'
END)
PRINT @MES
--------
Salida: Abril
Ej7.
--Hallar estado del saldo de KardexVtas
DECLARE @Stock INT, @Mes INT
SET @Stock=100
SET @MES=11
SELECT Producto.CodProducto, Producto.Descripcion,
KardexVtas.Mes, KardexVtas.SaldoCant,
'ESTADO'=(CASE WHEN SaldoCant>@Stock THEN 'Cantidad OK'
WHEN SaldoCant=@Stock THEN 'Cantidad en
límite'
WHEN SaldoCant<@Stock THEN 'Hacer pedido'
END)
FROM Producto INNER JOIN KardexVtas
ON Producto.CodProducto = KardexVtas.CodProducto
GO
-------
Salida (Con Resultados a Cuadrícula)
(5 filas afectadas)
Estructura WHILE
Sintaxis:
WHILE <expresion>
BEGIN
...
END
Ej8.
--Listar los 5 primeros registros de la tabla productos
DECLARE @Contador INT = 1;
WHILE @Contador < 5
BEGIN
SELECT CodProducto, Descripcion, Precio
FROM Producto
WHERE CodProducto=@Contador
SET @Contador += 1;
END;
--------
Salida:
(1 filas afectadas)
(1 filas afectadas)
(1 filas afectadas)
(1 filas afectadas)
BREAK y CONTINUE
Sintaxis
WHILE BOOLEAN_EXPRESION
BEGIN
EXPRESION_SQL
[BREAK]
[EXPRESION_SQL]
[CONTINUE]
[EXPRESION_SQL]
END
Ej9.
--Actualizar las unidades de existencia de los productos asignándoles el
--valor de 1000 de aquellos productos cuyo stock sea cero
DECLARE @ID INT, @Descripcion VARCHAR(50)
WHILE EXISTS(
SELECT CodProducto, Mes, SaldoCant FROM KardexVtas
WHERE (SaldoCant=0)
)
BEGIN
END
---------
Salida: 1 filas afectadas)
Producto :crema actualizado
(1 filas afectadas)
Producto :Prod5 actualizado
(1 filas afectadas)
Producto :Prod7 actualizado
Ej10.
--Actualizar las unidades de existencia de los productos asignándoles el
--valor de 1000 de aquellos productos cuyo stock sea cero y codigo < 6
DECLARE @ID INT, @Descripcion VARCHAR(50)
WHILE EXISTS(
SELECT CodProducto, Mes, SaldoCant FROM KardexVtas
WHERE (SaldoCant=0)
)
BEGIN
SELECT TOP 1 @ID=CodProducto FROM KardexVtas
WHERE (SaldoCant=0)
IF @ID > 6 BREAK
UPDATE KardexVtas SET SaldoCant=1000
WHERE @ID=CodProducto
3. CURSOR
Un cursor es una especie de variable apuntador que nos permite recorrer con un
conjunto de resultados obtenidos a través de una sentencia SELECT fila por fila y
hacer referencia a estos uno por uno.
Declaración de cursor
Sintaxis:
Donde:
o FOWARD_ONLY los datos se extraen del cursor por orden de aparición (del
primero al último).
o OPTIMISTIC con esta opción, puede que una operación de UPDATE o DLETE
realizada en el cursor no pueda ejecutarse correctamente, porque otra
transacción haya modificado los datos en paralelo.
Apertura de un Cursor
Sintaxis
Lectura de un registro
Sintaxis
Donde:
o NEXT lee la fila siguiente (única opción posible para los INSENSITIVE
CURSOR).
Es la instrucción que permite extraer una fila del cursor y asignar valores a variables
con su contenido.
Sintaxis
CLOSE <nombre_cursor>
Sintaxis
DEALLOCATE <nombre_cursor>
Sintaxis básica
Ej11.
--Con un cursor, mostrar el primer registro de productos
--
DECLARE cursorAProducto CURSOR FOR
SELECT TOP 1 CodProducto, Descripcion, Precio FROM Producto
---------
Salida:
(1 filas afectadas)
Ej12.
--Mostrar el primer registro, el registro en la posición 6 y
--el último registro con un cursor
OPEN cursor_1_Producto
CLOSE cursor_1_Producto
DEALLOCATE cursor_1_Producto
--------
Salida:
Registro: 1
CodProducto Descripcion Precio
----------- --------------------------------------------------
-------------------------------------
1 Aspirina 10000
(1 filas afectadas)
Registro: 6
CodProducto Descripcion Precio
----------- --------------------------------------------------
-------------------------------------
6 Prod6 7050
(1 filas afectadas)
Registro: ultimo
CodProducto Descripcion Precio
----------- --------------------------------------------------
-------------------------------------
17 Prod17 36000
(1 filas afectadas)
Ej13.
--Con un cursor, listar los Productos registrados
DECLARE @CodProd INT, @NombreProd VARCHAR(50), @Precio
DECIMAL
OPEN cursor2Prod
FETCH cursor2Prod INTO @CodProd, @NombreProd, @Precio
WHILE @@FETCH_STATUS=0
BEGIN
PRINT STR(@CodProd) +','+@NombreProd+','+STR(@Precio)
FETCH cursor2Prod INTO @CodProd, @NombreProd, @Precio
END
CLOSE cursor2Prod
DEALLOCATE cursor2Prod
GO
---------
Salida: 1 Aspirina 10000
2 papel1 3000
3 crema 4150
Ej14.
--Con un cursor, listar los Productos registrados y su saldo
DECLARE @CodProd INT, @NombreProd VARCHAR(50), @Saldo INT
DECLARE cursor3Prod CURSOR FOR
OPEN cursor3Prod
FETCH cursor3Prod INTO @CodProd, @NombreProd, @Saldo
WHILE @@FETCH_STATUS=0
BEGIN
PRINT STR(@CodProd)+' '+@NombreProd+' '+STR(@Saldo)
FETCH cursor3Prod INTO @CodProd, @NombreProd, @Saldo
END
CLOSE cursor3Prod
DEALLOCATE cursor3Prod
GO
--------
Salida: 1 Aspirina 200
2 papel1 60
3 crema 1000
4 Maquina de Afeitar 350
5 Prod5 0
6 Prod6 100
7 Prod7 0
SELECT
FOR UPDATE después de la sentencia en la declaración del cursor, y
WHERE CURRENT OF <nombre_cursor> en la sentencia UPDATE.
DECLARE <nombre_cursor> …
FOR <instruccion_select>
Análisis:
Declarar variables DECLARE @varStock, @varPrecio, @Var2, ..
Bucle
WHILE(Condición) WHILE (@@FETCH_STATUS = 0 )
Inicio Bloque BEGIN
Evaluar stock IF @varStock >= 100
SET @varPrecio = @varPrecio*0.50
ELSE
SET @varPrecio = @varPrecio*0.80
OPEN cursorcito
FETCH cursorcito INTO @varCodProducto, @varDescripcion,
@varPrecio, @varStock
SET @precioAux=@varPrecio
WHILE (@@FETCH_STATUS = 0 )
BEGIN
IF @varStock>=100 SET @varPrecio = @varPrecio*0.5
ELSE SET @varPrecio = @varPrecio*0.8
-------
Salida:
(1 filas afectadas)
Precio PROD1 De 1500 a 1200
(1 filas afectadas)
Precio PROD2 De 3000 a 2400
(1 filas afectadas)
Precio PROD3 De 4150 a 3320
(1 filas afectadas)
Precio PROD4 De 2560 a 1280
(1 filas afectadas)
Precio PROD5 De 1200 a 600
(1 filas afectadas)
Precio PROD6 De 3500 a 1750
(1 filas afectadas)
Precio PROD7 De 2350 a 1880
Se modificaron 7 registros
Al igual que las funciones en los lenguajes de programación, en SQL Server las
funciones definidas por el usuario son rutinas:
o Aceptan parámetros
o Realizan una acción, como un cálculo complejo,
o Devuelven el resultado de esa acción como un valor.
Los beneficios del uso de funciones definidas por el usuario en SQL Server son:
Funciones escalares
Ej15.
-- Funcion que retorna el valor del saldo de un producto
-- en el kardex
--
CREATE FUNCTION valorPK(@CodProd int)
RETURNS MONEY
AS
BEGIN
DECLARE @valorProd int
-----------------
Para Ejecución:
PRINT dbo.valorPK(1)
GO
Salida:
135000.00
Ej16.
-- Funcion que retorna la cantidad de saldo de un producto
-- en el kardex
--
CREATE FUNCTION cantPK(@CodProd int)
RETURNS INT
AS
BEGIN
DECLARE @varCantProd int
--------
Para ejecución:
Ej17.
-- Funcion que retorna el precio de un producto
-- en el catalogo
--
CREATE FUNCTION [dbo].[precioProd](@CodProd int)
RETURNS MONEY
AS
BEGIN
DECLARE @valorProd int
-----------------
Para ejecución:
PRINT dbo.precioProd(1)
go
1500.00
-1.00
Ej18
-- Reportar el precio promedio de los productos registrados
--
CREATE FUNCTION PRECIOPROMEDIO]()
RETURNS MONEY
AS
BEGIN
DECLARE @PROM MONEY
SELECT @PROM=AVG(Precio)
FROM Producto
RETURN @PROM
END
Sintaxis:
Ej19.
--Función que liste el kardex incluyendo los nombres de los
--productos y el precio
CREATE FUNCTION muestraKardex()
RETURNS TABLE
AS
RETURN
(SELECT Producto.CodProducto, Producto.Descripcion,
Producto.Precio, KardexVtas.SaldoCant
FROM KardexVtas INNER JOIN
Producto ON KardexVtas.CodProducto =
Producto.CodProducto
)
GO
----------
Para ejecución
SELECT * FROM muestraKardex()
Salida:
(7 filas afectadas)
----------------
Para ejecución:
DECLARE @N INT
SELECT @N=COUNT(*) FROM muestraKardex()
PRINT 'FILAS RETORNADAS '+STR(@N)
GO
Salida:
FILAS RETORNADAS 7
5. PROCEDIMIENTOS ALMACENADOS
o Incluir parámetros
Ej20.
--procedimiento almacenado que liste todos los clientes
CREATE PROCEDURE listaProds
AS
BEGIN
SELECT CodProducto, Descripcion, Precio, PorcIva
FROM Producto
END
GO
----------
Ejecución
Ej.21
--Buscar un producto.
CREATE PROCEDURE buscaProd
@VarCodProd INT
AS
BEGIN
SELECT CodProducto, Descripcion, Precio, PorcIva
FROM Producto
WHERE CodProducto=@VarCodProd
END
GO
-----------
Ejecución: buscaProd 1
GO
-----------
----------
Ejecución: buscaProd 1,3
GO
Salida:
(3 filas afectadas)
Ej23.
–ingresar un producto
CREATE PROCEDURE addRegProd
@CodProd int,
@PorcIva float,
@Descrip varchar(50),
@Precio decimal
AS
INSERT INTO Producto
VALUES(@CodProd,@PorcIva, @Descrip,@Precio,0,1)
IF @@ERROR=0
PRINT 'Cliente Registrado'
ELSE
PRINT 'Cliente Ya Existe'
---------
Ejecución: addRegProd 18,16,'ProdX', 13250
GO
Salida
Si no está: (1 filas afectadas)
Cliente Registrado
TRANSACCIONES EN TRANSACT-SQL
Transacciones implícitas:
AS
BEGIN TRAN TProd
INSERT INTO Producto
VALUES(@CodProducto,@PorcIva,@Descripcion,@Precio,
@PorcDescto,@CodTipo)
GO
IF @@ERROR=0
BEGIN
COMMIT TRAN TProd
PRINT 'Cliente Registrado'
END
ELSE
BEGIN
PRINT 'Error-Codigo registro ya existe'
ROLLBACK TRAN TProd
END
-----------
Para ejecución:
addRegProdT 50,15,'PROD50',15250,15,1
GO
Salida (Si ya existe el registro con el valor de clave 50):
Sintaxis Try-Catch
BEGIN TRY
/*Bloque de instrucciones a validar*/
END TRY
BEGIN CATCH
/*Bloque de instrucciones que se ejecutan si ocurre un ERROR*/
END CATCH
Para un mejor control de los errores se pueden ejecutar los procesos dentro un
bloque TRY-CATCH donde:
Ej25.
-- Ingresar un producto como una transacción
-- con instrucciones try-catch
CREATE PROCEDURE addRegProdT_ConTC
@CodProducto INT, @PorcIva FLOAT, @Descripcion VARCHAR(50),
@Precio DECIMAL, @PorcDescto FLOAT, @CodTipo SMALLINT
AS
BEGIN TRAN TProd
BEGIN TRY
INSERT INTO Producto
VALUES(@CodProducto,@PorcIva,@Descripcion,@Precio,
@PorcDescto,@CodTipo)
COMMIT TRAN TProd
PRINT 'Cliente Registrado'
END TRY
BEGIN CATCH
PRINT 'Error-Codigo registro ya existe'
ROLLBACK TRAN TProd
END CATCH
GO
---------
Salida (Si ya existe el registro con el valor de clave 50):
(0 filas afectadas)
Error-Codigo registro ya existe
BEGIN TRY
IF (@SaldoCant-@Cantidad)<0
PRINT 'Transaccion de venta genera Saldo Kardex < 0'
UPDATE KardexVtas
SET SaldoCant-=@Cantidad, SaldoVr-=@Valor,
VentasCant+=@Cantidad, VentasVr+=@Precio
WHERE CodProducto=@CodProd
END TRY
BEGIN CATCH
ROLLBACK TRAN TElemFra
PRINT 'Venta NO registrada'
END CATCH
-----------
Ejecución: addElemFra 1,3,1,10
GO
(0 filas afectadas)
Venta NO registrada
(1 filas afectadas)
Venta registrada OK
Nota. Para que el DBMS reconozca este error la columna SaldoCant del Kárdex debe
ser restringida a contener valores >= 0.
6. TRIGGERS
Ej27.
--imprimir un mensaje cada vez que alguien trata de
-- insertar, eliminar o actualizar datos de la tabla Producto
CREATE TRIGGER tAct1Producto
ON Producto
FOR INSERT, UPDATE, DELETE
AS
PRINT 'Actualizacion de los registros de Productos'
Modificar un trigger
Ej28.
--imprimir un mensaje cada vez que alguien trata de
-- insertar, eliminar o actualizar datos de la tabla Producto
ALTER TRIGGER tAct1Producto
ON Producto
FOR INSERT, UPDATE, DELETE
AS
PRINT 'Tabla productos actualizada'
-----------------
Para ejecución:
UPDATE Producto
SET Precio = 12500
WHERE CodProducto=1
GO
(1 filas afectadas)
Disparador de Inserción.
Está tabla toma la misma estructura de la cual se originó el TRIGGER, de tal manera
que se pueda verificar los datos y ante un error podría revertirse los cambios.
Ej29.
--insertar los datos de un Producto siempre y cuando la
--descripción o nombre del producto sea único.
CREATE TRIGGER tIngresarProd
ON Producto
FOR INSERT
AS
IF @NomProdsIguales > 1
BEGIN
ROLLBACK TRANSACTION
PRINT 'Descripcion de producto ya registrada'
END
ELSE PRINT 'Producto ingresado OK'
GO
-----------------
Para ejecución:
INSERT INTO Producto
VALUES(55,16,'Prod55',8520,10,1)
GO
(1 filas afectadas)
-----------------
Para ejecución:
INSERT INTO Producto
VALUES(55,16,'Prod55',8520,10,1)
GO
UPDATE Factura
SET FechaFra=GETDATE()
WHERE Factura.NroFactura=(
SELECT INSERTED.NroFactura
FROM INSERTED
WHERE Factura.NroFactura=INSERTED.NroFactura
)
Disparador de Eliminación.
Está tabla toma la misma estructura del cual se origino el TRIGGER, de tal manera que se
pueda verificar los datos y ante un error podría revertirse los cambios.
En este caso, la reversión de los cambios significará restaurar los datos eliminados.
Ej31.
--eliminar Cliente del cuales no han registrado algún
--factura. De eliminarse algún Cliente que no cumpla con dicha
--condición la operación
--no deberá ejecutarse.
--
CREATE TRIGGER t1BorraCliente
ON Cliente
FOR DELETE
AS
IF EXISTS (SELECT * FROM Factura
WHERE Factura.CodCliente =
(SELECT CodCliente FROM DELETED) )
BEGIN
ROLLBACK TRANSACTION
PRINT 'ERROR-Cliente tiene registradas facturas'
END
-----------------
Para ejecución:
(1 filas afectadas)
Nota. Las tablas involucradas en este proceso NO deben tener relación alguna.
En ekl anterior ejemplo, la tabla Cliente es libre.
Disparador de Actualización
Usando estas dos tablas se podrá verificar los datos y ante un error podrían revertirse los
cambios.
Ej32.
--validar que el precio unitario de un producto
--no pueda ser actualizado como <=0
--
CREATE TRIGGER tValidaProdK
ON Producto
FOR UPDATE
AS
IF (SELECT Precio FROM INSERTED)<=0
BEGIN
PRINT 'ERROR- el precio no puede ser actualizado a <=0'
ROLLBACK TRANSACTION
END
ELSE PRINT 'Precio actualizado-OK'
-----------------
Para ejecución:
UPDATE Producto
SET Precio = 11111
WHERE CodProducto=1
GO
Salida:
Precio actualizado-OK
Tabla productos actualizada
(1 filas afectadas)
-----------------
Para ejecución:
UPDATE Producto
SET Precio = -3250
WHERE CodProducto=1
GO
Salida:
ERROR- el precio no puede ser actualizado a <=0
Mens. 3609, Nivel 16, Estado 1, Línea 1
La transacción terminó en el desencadenador. Se anuló el lote.
INSTEAD OF INSERT
END
ANEXO
Lenguaje DML
Operadores aritméticos: + - * / %
Operador de asignación: =
Algunos Operadores lógicos: ALL, AND, ANY, BETWEEN, EXISTS, IN, NOT, OR, SOME
Ej. 1
--mostrar los productos com precio entre 2000 y 8000
--
SELECT Elemfra.CodProducto, Producto.Descripcion, Producto.Precio,
Producto.PorcIva, Producto.PorcDescto
FROM Elemfra INNER JOIN
Producto ON Elemfra.CodProducto = Producto.CodProducto
WHERE (Producto.Precio > 2000 AND Producto.Precio < 8000)
Ej2.
--mostrar los productos com precio entre 2000 y 8000
--
SELECT Elemfra.CodProducto, Producto.Descripcion, Producto.Precio,
Producto.PorcIva, Producto.PorcDescto
FROM Elemfra INNER JOIN
Producto ON Elemfra.CodProducto = Producto.CodProducto
WHERE Producto.Precio BETWEEN 2000 AND 8000
PRINT DATEPART(year,GETDATE())
GO
PRINT DATEPART(MONTH,GETDATE())
GO
PRINT DATEPART(DAY,GETDATE())
GO
PRINT DATEPART(HOUR,GETDATE())
GO
PRINT DATEPART(MINUTE,GETDATE())
GO
PRINT DATEPART(SECOND,GETDATE())
GO
PRINT DATEPART(WEEK,GETDATE())
GO
Comandos DML
Ej. 4
--Insertar un único registro especificando todos los campos a ingresar
--
INSERT INTO Cliente (CodCliente, NOmbre, Dir, Telefono)
VALUES (100, 'juanita banana','calle 10','123456789')
GO
Ej5.
--Insertar un único registro especificando únicamente los
--valores de los campos
--
INSERT INTO Cliente
VALUES (101, 'pepita perez','calle 10','123456789')
GO
Ej6.
Sintaxis:
UPDATE Nombre_tabla
SET nombre_columna1 = expr1, nombre_columna2 = expr2,…...
[WHERE {condición}]
Ej8.
GO
Sintaxis
Sintaxis:
SELECT [ALL|DISTINCT] [TOP (expresión) [PERCENT] [WITH TIES] ]
< lista de selección >
[INTO nombre de la nueva tabla]
FROM <nombre de tabla>
WHERE <condición>
GROUP BY <nombre de campos>
HAVING <condición> [AND | OR <condición>]
ORDER BY
Ej9.
--mostrar los productos com precio entre 2000 y 8000
--
SELECT Elemfra.CodProducto, Producto.Descripcion, Producto.Precio,
Producto.PorcIva, Producto.PorcDescto
FROM Elemfra INNER JOIN
Producto ON Elemfra.CodProducto = Producto.CodProducto
WHERE (Producto.Precio > 2000 AND Producto.Precio < 8000)
Ej10.
--Mostrar los productos vendidos al cliente 1 que sean
--mayores a 3000
SELECT Producto.Descripcion, Factura.CodCliente, Producto.Precio
FROM Elemfra INNER JOIN
Producto ON Elemfra.CodProducto = Producto.CodProducto
INNER JOIN
Factura ON Elemfra.NroFactura = Factura.NroFactura
WHERE (Factura.CodCliente = 1) AND (Producto.Precio > 3000)
Ej11.
--Mostrar la suma de las ventas
--
SELECT SUM(Elemfra.Cantidad * Producto.Precio) AS [Valor venta]
FROM Elemfra INNER JOIN
Producto ON Elemfra.CodProducto = Producto.CodProducto
-------
Salida:
Valor venta
---------------------------------------
511740
(1 filas afectadas)
Ej12.
--Mostrar la suma de las ventas al cliente 1
--
SELECT SUM(Elemfra.Cantidad * Producto.Precio) AS [Valor venticas],
Factura.CodCliente AS Clientecito
FROM Elemfra INNER JOIN
Producto ON Elemfra.CodProducto = Producto.CodProducto
INNER JOIN
Factura ON Elemfra.NroFactura = Factura.NroFactura
GROUP BY Factura.CodCliente
HAVING (Factura.CodCliente = 1)
------
Salida:
(1 filas afectadas)