Mostrando entradas con la etiqueta transact-sql. Mostrar todas las entradas
Mostrando entradas con la etiqueta transact-sql. Mostrar todas las entradas

The Power of Cross Join

http://weblogs.sqlteam.com/jeffs/archive/2005/09/12/7755.aspx

Hugo Román Bernachea
Mail de contacto: SQLServer777@gmail.com

Microsoft Certified DBA
Microsoft Certified Trainer
Twitter: @bernachea

Read More...

Cual es la diferencia entre CROSS APPLY y CROSS JOIN

Traducido de:
http://weblogs.sqlteam.com/jeffs/archive/2007/10/18/sql-server-cross-apply.aspx
Pienso que la manera mas fácil de pensar la sentencia CROSS APPLY es que es semejante a hacer un CROSS JOIN con un sub-select correlativo en vez de una tabla derivada.

Digamos, una tabla derivada es "auto-contenida" de modo que las tablas y columnas que referencia no son accesibles por el select principal, aunque variables y parámetros pueden ser referenciados.
Por ejemplo, veamos:

select A.*, b.X
from A
cross join (select B.X from B where B.Val=A.Val) b

Esta sentencia no es válida, porque A.Val está fuera del alcance al estar dentro de la tabla derivada. Esto es porque la tabla derivada es evaluada de manera independiente de las otras tablas en el Select.
Para limitar los registros en Tabla B de manera de cumplir la condición B.Val = A.Val, nosotros tenemos que hacerlo "fuera" de la tabla derivada por medio de un join o en el criterio.

select A.*, b.X
from A
cross join (select * from B) b
where A.Val = b.Val

(por supuesto, lo de arriba es equivalente a hacer un inner join en la tabla derivada o simplemente uniendo a la tabla B).

También, tengamos en cuenta que el alcance de las tablas derivadas no rige solamente para los CROSS JOINS sino que también se aplica a todos los otros JOINS (CROSS, INNER, OUTER) e incluso para UNION. Todas estas sentencias usan tablas derivadas "auto-contenidas".

Esto es diferente a un sub-select correlativo en donde el SELECT principal está en el alcance para el sub-query. El sub-select es evaluado por cada registro en el query, de manera que otras tablas y columnas en el SELECT están disponibles.

select A.*, (select B.X from B where B.Val=A.Val) as X
from A

(obviamente el subquery tiene que regresar solo un registro).

Esta es una manera facil de pensar las diferencia entre CROSS JOIN y CROSS APPLY. CROSS JOIN, tal como vimos, une a una tabla derivada, sin embargo, CROSS APPLY, a pesar de lucir como un JOIN en realidad es aplicado al subselect correlativo. Esto impone las ventajas de subselects correlativos y además tiene implicaciones a nivel de performance.

Ahora si, podemos reescribir nuestro primer ejemplo usando CROSS APPLY y quedaría de la siguiente forma:

select A.*, b.X
from A
cross apply (select B.X from B where B.Val=A.Val) b

Como ahora aplicamos un APPLY y no un JOIN, A.Val está en alcance y esto funciona ok.

Table Valued User Defined Functions

Las mismas reglas aplican cuando usamos Table-Valued User-Defined Functions:

select A.*, B.X
from A
cross join dbo.UDF(A.Val) B

Esto es no válido nuevamente. A.Val no está en alcance para ser usado por la UDF.

Lo mejor que podíamos hacer antes de SQL 2005 era usar un subselect correlativo:

select A.*, (select X from dbo.UDF(A.Val)) X
from A

Sin embargo esto no es funcionalmente equivalente. La UDF no regresa mas de un registro o eso sería un error.

Con SQL 2005 podemos también en este caso usar CROSS APPLY y todo funcionará perfectamente:

select A.*, b.X
from A
cross apply dbo.UDF(A.Val) b

Esta es una manera de pensar las diferencias entre JOIN y APPLY, JOIN combina dos resultsets separados, pero APPLY es mas que un loop que evalua un resultset una y otra vez por cada registro.
Esto significa que en general APPLY será menos eficiente que un JOIN, del mismo modo que sub-selects correlativos son menos eficientes que tablas derivadas.

Entonces, cual es la ventaja de usar CROSS APPLY en vez de un sub-select correlacionado?
Bueno, muchas ventajas, por empezar...es mucho mas poderoso !.


CROSS APPLY puede regresar múltiples registros.

Esto nos permite hacer cosas como "joinear" una tabla a una función que parsear una columna csv en esa misma tabla en multiples registros.

select A.ID, b.Val
from A
cross apply dbo.ParseCSV(A.CSV) b

Cuando la funcion ParseCSV regresa multiples registros, simplemente actua como si hubiesemos joineado una tabla, duplicando los registros en tabla A por cada registro en la tabla de join.
Esto no se puede hacer con un sub-select correlativo, porque tira error.


CROSS APPLY puede retornar múltiples columnas.

De nuevo, en un sub-select correlativo podemos devolver un solo valor.
Si escribimos un script que regrese una sumatoria, podemos usar un subselect como el siguiente:

select o.*,
(select sum(Amount) from Order o
where p.OrderDate <= o.OrderDate) as RunningSum
from Order o

Sin embargo, que pasa si queremos regresar sumas adicionales de pedidos basados en algún otro criterio (pedidos con el mismo "ordercode")?.
Necesitaríamos otro subselect correlacionado, reduciendo la eficacia de nuestro select.

select o.*,
(select sum(Amount) from Order o
where p.OrderDate <= o.OrderDate) as RunningSum,
(select sum(Amount) from Order o
where p.OrderCode = o.OrderCode and p.OrderDate <= o.OrderDate) as SameCode
from Order o

Pero con un CROSS-APPLY seria mucho mas sencillo.

select o.*, rs.RunningSum, rs.SameCode
from Order o
cross apply
(
select
sum(Amount) as RunningSum,
sum(case when p.OrderCode = o.OrderCode then Amount else 0 end) as SameCode
from Order P
where P.OrderDate <= O.OrderDate
) rs


Entonces, tenemos el beneficio de regresar multiples columnas como si fuese una tabla derivada, y también tenemos la habilidad de referenciar valores en nuestro select.
Bastante poderoso.

Con CROSS APPLY podemos facilmente recuperar columnas del registro anterior en una tabla.

select o.*, prev.*
from Order o
cross apply
(
select top 1 *
from Order P where P.OrderDate < O.OrderDate
order by OrderDate DESC
) prev

Note que el script anterior no regresará pedidos que no tengan un pedido anterior, debemos usar OUTER APPLY para asegurarnos que todos los pedidos serán regresados, incluso si no existen pedidos previos.

select o.*, prev.*
from Order o
outer apply
(
select top 1 *
from Order P where P.OrderDate < O.OrderDate
order by OrderDate DESC
) prev


Hugo Román Bernachea
Mail de contacto: SQLServer777@gmail.com

Microsoft Certified DBA
Microsoft Certified Trainer
Twitter: @bernachea

Read More...

T-SQL - Gráfica de los distintos tipos de Joins

Yo pienso que esta imagen nos da la respuesta clara y exacta de como funciona cada uno de los joins disponibles en T-S
( click en la imagen para agrandarla)

















Hugo Román Bernachea
Mail de contacto: SQLServer777@gmail.com

Microsoft Certified DBA
Microsoft Certified Trainer
Twitter: @bernachea

Read More...

XQuery - Obtener default namespace de un campo o variable XML

Después de dar unas cuantas vueltas, apremiado para resolver un store procedure que tenía que procesar un campo xml (usando sp_preparedocument y OpenXML) que podía tener distintos namespaces en distintos registros, es que me puse a buscar la forma de recuperar el namespace predeterminado de un xml y encontré que la forma es la siguiente.

Select CampoXML.value('namespace-uri(/*[1])','VARCHAR(100)')
u obviamente
Select @variableXML.value('namespace-uri(/*[1])','VARCHAR(100)')
Esto por ejemplo puede regresar:
urn:proyecto-version-numero-1.1
Otro ejemplo:
SELECT CampoXML.value('local-name(/*[1])', 'varchar(100)'), CampoXML.value('namespace-uri(/*[1])','VARCHAR(100)') from tabla
Donde tenemos que:
local-name devuelve el nombre del elemento raiz
y
namespace-uri devuelve el namespace predeterminado.

Hugo Román Bernachea
Mail de contacto: SQLServer777@gmail.com

Microsoft Certified DBA
Microsoft Certified Trainer
Twitter: @bernachea

Read More...

Creando cursores en Transact-SQL

Un cursor es una variable que nos permite recorrer con un conjunto de resultados obtenido a través de una sentencia SELECT fila a fila. Los siguientes pasos nos permiten definir y utilizar cursores:
  • Declarar el cursor, utilizando DECLARE
  • Abrir el cursor, utilizando OPEN
  • Leer los datos del cursor, utilizando FETCH ... INTO
  • Cerrar el cursor, utilizando CLOSE
  • Liberar el cursor, utilizando DEALLOCATE

La sintaxis general para trabajar con un cursor es la siguiente.

-- Declaración del cursor
DECLARE nombre_cursor CURSOR FOR sentencia_sql;

-- apertura del cursor 
OPEN nombre_cursor; 

-- Lectura de la primera fila del cursor 
FETCH nombre_cursor; INTO lista_variables;
WHILE (@@FETCH_STATUS = 0)
BEGIN 
-- Lectura de la siguiente fila de un cursor
FETCH nombre_cursor; INTO lista_variables;
...
END -- Fin del bucle WHILE -- Cierra el cursor CLOSE nombre_cursor; -- Libera los recursos del cursorDEALLOCATE <nombre_cursor>
El siguente ejemplo muestra el uso de un cursor.

-- Declaracion de variables para el cursor
DECLARE @Id int,

@Nombre varchar(255),

@Apellido1 varchar(255),

@Apellido2 varchar(255),

@NifCif varchar(20),

@FxNacimiento datetime


-- Declaración del cursor

DECLARE cClientes CURSOR FOR

SELECT  Id, Nombre, Apellido1,
Apellido2, NifCif, FxNacimiento

FROM CLIENTES

-- Apertura del cursorOPEN cClientes

-- Lectura de la primera fila del cursor

FETCH cClientes INTO    @id, @Nombre, @Apellido1, @Apellido2, @NifCif, @FxNacimiento
WHILE (@@FETCH_STATUS = 0 )
BEGIN
PRINT @Nombre + ' ' + @Apellido1 + ' ' + @Apellido2
-- Lectura de la siguiente fila del cursor
FETCH cClientes INTO @id, @Nombre, @Apellido1, @Apellido2, @NifCif, @FxNacimiento
END -- Cierre del cursor
CLOSE cClientes

-- Liberar los recursos
DEALLOCATE cClientes

 
Cuando trabajamos con cursores, la funcion @@FETCH_STATUS nos indica el estado de la última instrucción FETCH emitida, los valores posibles son:
Valor devuelto Descripción
0 La instrucción FETCH se ejecutó correctamente.
-1 La instrucción FETCH no se ejecutó correctamente o la fila estaba más allá del conjunto de resultados.
-2 Falta la fila recuperada.
En la apertura del cursor, podemos especificar los siguientes parámetros:
DECLARE <nombre_cursor> CURSOR
[ LOCAL | GLOBAL ]
[ FORWARD_ONLY | SCROLL ]
[ STATIC | KEYSET | DYNAMIC | FAST_FORWARD ]
[ READ_ONLY | SCROLL_LOCKS | OPTIMISTIC ]
[ TYPE_WARNING ]
FOR >
El primer conjunto de parámetros que podemos especificar es [ LOCAL | GLOBAL ]. A continuación mostramos el significado de cada una de estas opciones.

  • LOCAL
    Especifica que el ámbito del cursor es local para el proceso por lotes, procedimiento almacenado o desencadenador en que se creó el cursor.

DECLARE cClientes CURSOR LOCAL FOR

SELECT  Id, Nombre, Apellido1, 

Apellido2, NifCif, FxNacimiento

FROM CLIENTES 

  • GLOBAL
    Especifica que el ámbito del cursor es global para la conexión. Puede hacerse referencia al nombre del cursor en cualquier procedimiento almacenado o proceso por lotes que se ejecute en la conexión.

DECLARE cClientes CURSOR GLOBAL FOR

SELECT  Id, Nombre, Apellido1, 

Apellido2, NifCif, FxNacimiento

FROM CLIENTES 
Si no se especifica GLOBAL ni LOCAL, el valor predeterminado se controla mediante la configuración de la opción de base de datos default to local cursor.
El siguiente conjunto de parámetros que podemos especificar es [ FORWARD_ONLY | SCROLL ]. A continuación mostramos el significado de cada una de estas opciones.
  • FORWARD_ONLY
    Especifica que el cursor sólo se puede desplazar de la primera a la última fila. FETCH NEXT es la única opción de recuperación admitida.
 

DECLARE cClientes CURSOR FORWARD_ONLY FOR

SELECT  Id, Nombre, Apellido1, 

Apellido2, NifCif, FxNacimiento

FROM CLIENTES

 
  • SCROLL
    Especifica que están disponibles todas las opciones de recuperación (FIRST, LAST, PRIOR, NEXT, RELATIVE, ABSOLUTE). Si no se especifica SCROLL en una instrucción DECLARE CURSOR la única opción de recuperación que se admite es NEXT. No es posible especificar SCROLL si se incluye también FAST_FORWARD.
    Si se incluye la opción SCROLL, la forma en la realizamos la lectura del cursor varia, debiendo utilizar la siguiente sintaxis: FETCH [ NEXT | PRIOR | FIRST | LAST | RELATIVE | ABSOLUTE ] FROM <>
-- Declaracion de variables para el cursorDECLARE @Id int,

@Nombre varchar(255),

@Apellido1 varchar(255),

@Apellido2 varchar(255),

@NifCif varchar(20),

@FxNacimiento datetime

 

-- Declaración del cursor

DECLARE cClientes CURSOR SCROLL FOR

SELECT  Id, Nombre, Apellido1, 

Apellido2, NifCif, FxNacimiento

FROM CLIENTES

-- Apertura del cursorOPEN cClientes

-- Lectura de la primera fila del cursorFETCH NEXT FROM cClientes 

INTO @id, @Nombre, @Apellido1, @Apellido2, @NifCif, @FxNacimiento

WHILE (@@FETCH_STATUS = 0 )

BEGIN

PRINT @Nombre + ' ' + @Apellido1 + ' ' + @Apellido2
-- Lectura de la siguiente fila del cursor
FETCH NEXT FROM cClientes
INTO @id,@Nombre,@Apellido1,@Apellido2,@NifCif,@FxNacimiento
END -- Lectura de la fila anterior FETCH PRIOR FROM cClientes INTO @id, @Nombre, @Apellido1, @Apellido2, @NifCif, @FxNacimiento PRINT @Nombre + ' ' + @Apellido1 + ' ' + @Apellido2 -- Cierre del cursorCLOSE cClientes -- Liberar los recursosDEALLOCATE cClientes
El siguiente conjunto de parámetros que podemos especificar es [ STATIC | KEYSET | DYNAMIC | FAST_FORWARD ]. A continuación mostramos el significado de cada una de estas opciones.
  • STATIC
    Define un cursor que hace una copia temporal de los datos que va a utilizar. Todas las solicitudes que se realizan al cursor se responden desde esta tabla temporal de tempdb; por tanto, las modificaciones realizadas en las tablas base no se reflejan en los datos devueltos por las operaciones de recuperación realizadas en el cursor y además este cursor no admite modificaciones.
 

DECLARE cClientes CURSOR STATIC FOR

SELECT  Id, Nombre, Apellido1, 

Apellido2, NifCif, FxNacimiento

FROM CLIENTES 
  • KEYSET
    Especifica que la pertenencia y el orden de las filas del cursor se fijan cuando se abre el cursor. El conjunto de claves que identifica las filas de forma única está integrado en la tabla denominada keyset de tempdb.
 

DECLARE cClientes CURSOR KEYSET FOR

SELECT  Id, Nombre, Apellido1, 

Apellido2, NifCif, FxNacimiento

FROM CLIENTES 
  • DYNAMIC
    Define un cursor que, al desplazarse por él, refleja en su conjunto de resultados todos los cambios realizados en los datos de las filas. Los valores de los datos, el orden y la pertenencia de las filas pueden cambiar en cada operación de recuperación. La opción de recuperación ABSOLUTE no se puede utilizar en los cursores dinámicos.
 

DECLARE cClientes CURSOR DYNAMIC FOR

SELECT  Id, Nombre, Apellido1, 

Apellido2, NifCif, FxNacimiento

FROM CLIENTES
  • FAST_FORWARD
    Especifica un cursor FORWARD_ONLY, READ_ONLY con las optimizaciones de rendimiento habilitadas. No se puede especificar FAST_FORWARD si se especifica también SCROLL o FOR_UPDATE.
 

DECLARE cClientes CURSOR FAST_FORWARD FOR

SELECT  Id, Nombre, Apellido1, 

Apellido2, NifCif, FxNacimiento

FROM CLIENTES 
En SQL Server 2000, las opciones de cursor FAST_FORWARD y FORWARD_ONLY se excluyen mutuamente. Si se especifican ambas, se genera un error. En SQL Server 2005, las dos palabras clave se pueden utilizar en la misma instrucción DECLARE CURSOR.
El siguiente conjunto de parámetros que podemos especificar es [ READ_ONLY | SCROLL_LOCKS | OPTIMISTIC ]. A continuación mostramos el significado de cada una de estas opciones.
  • READ_ONLY
    Evita que se efectúen actualizaciones a través de este cursor. No es posible hacer referencia al cursor en una cláusula WHERE CURRENT OF de una instrucción UPDATE o DELETE. Esta opción reemplaza la capacidad de actualizar el cursor.
 

DECLARE cClientes CURSOR READ_ONLY FOR

SELECT  Id, Nombre, Apellido1, 

Apellido2, NifCif, FxNacimiento

FROM CLIENTES
  • SCROLL_LOCKSEspecifica que se garantiza que las actualizaciones o eliminaciones posicionadas realizadas a través del cursor serán correctas. Microsoft SQL Server bloquea las filas cuando se leen en el cursor para garantizar que estarán disponibles para futuras modificaciones. No es posible especificar SCROLL_LOCKS si se especifica también FAST_FORWARD o STATIC.
 

DECLARE cClientes CURSOR SCROLL_LOCKS FOR

SELECT  Id, Nombre, Apellido1, 

Apellido2, NifCif, FxNacimiento

FROM CLIENTES
  • OPTIMISTIC
    Especifica que las actualizaciones o eliminaciones posicionadas realizadas a través del cursor no se realizarán correctamente si la fila se ha actualizado después de ser leída en el cursor. SQL Server no bloquea las filas al leerlas en el cursor. En su lugar, utiliza comparaciones de valores de columna timestamp o un valor de suma de comprobación si la tabla no tiene columnas timestamp, para determinar si la fila se ha modificado después de leerla en el cursor. Si la fila se ha modificado, el intento de actualización o eliminación posicionada genera un error. No es posible especificar OPTIMISTIC si se especifica también FAST_FORWARD.
 

DECLARE cClientes CURSOR OPTIMISTIC FOR

SELECT  Id, Nombre, Apellido1, 

Apellido2, NifCif, FxNacimiento

FROM CLIENTES 
Por último, queda la opción TYPE_WARNING
  • TYPE_WARNINGEspecifica que se envía un mensaje de advertencia al cliente si el cursor se convierte implícitamente del tipo solicitado a otro.
 

DECLARE cClientes CURSOR TYPE_WARNING FOR

SELECT  Id, Nombre, Apellido1, 

Apellido2, NifCif, FxNacimiento

FROM CLIENTES 
Podemos especificar multiples parámetros en la apertura de cursor, pero unicamente un parámetro de cada grupo. Por ejemplo:
 

DECLARE cClientes CURSOR LOCAL STATIC TYPE_WARNING FOR

SELECT  Id, Nombre, Apellido1, 

Apellido2, NifCif, FxNacimiento

FROM CLIENTES 
Para actualizar los datos de un cursor debemos especificar FOR UPDATE despues de la sentencia SELECT en la declaración del cursor, y WHERE CURRENT OF en la sentencia UPDATE tal y como muestra el siguiente ejemplo.
-- Declaracion de variables para el cursorDECLARE @Id int,

@Nombre varchar(255),

@Apellido1 varchar(255),

@Apellido2 varchar(255),

@NifCif varchar(20),

@FxNacimiento datetime

-- Declaración del cursorDECLARE cClientes CURSOR FOR

SELECT  Id, Nombre, Apellido1, 

Apellido2, NifCif, FxNacimiento

FROM CLIENTES

FOR UPDATE

 

-- Apertura del cursorOPEN cClientes

-- Lectura de la primera fila del cursorFETCH cClientes
INTO @id, @Nombre, @Apellido1, @Apellido2, @NifCif, @FxNacimiento


WHILE (@@FETCH_STATUS = 0 )

BEGIN

UPDATE Clientes
SET APELLIDO2 = isnull(@Apellido2,'') + ' - Modificado'
WHERE CURRENT OF cClientes
-- Lectura de la siguiente fila del cursor
FETCH cClientes INTO @id, @Nombre, @Apellido1, @Apellido2, @NifCif, @FxNacimiento
END -- Cierre del cursor

CLOSE cClientes

-- Liberar los recursos

DEALLOCATE cClientes
Hugo Román Bernachea
Mail de contacto: SQLServer777@gmail.com

Microsoft Certified DBA
Microsoft Certified Trainer
Twitter: @bernachea

Read More...