You are on page 1of 81

Mejoras a SQL Server "Yukon" Beta 1 Transact-

SQL



Autor: Itzik Ben-Gan
Solid Quality Learning
Octubre 2003
Aplica a: Microsoft SQL Server "Yukon" Beta 1

http://www.microsoft.com/spanish/msdn/articulos/archivo/100904/voices/09SQLServerYukonBeta1TransactSQL.asp
Resumen: Este documento presenta varias de las nuevas mejoras en Transact-SQL en Microsoft SQL
Server "Yukon Beta 1. Estas nuevas caractersticas pueden aumentar su poder de expresividad, la
performance de sus consultas, y su capacidad de manejo de errores. Este documento se enfoca
principalmente en las mejoras relacionales que son conceptualmente nuevas y las demuestra por medio
de ejemplos prcticos. El mismo no cubre todas las nuevas caractersticas de Transact-SQL. (78 pginas
impresas)
Contenido
Introduccin y Perspectiva
Aumentando el Poder de Expresividad de Consultas y Soporte DRI
Mejoras de Performance y Manejo de Errores
Otras Capacidades de SQL Server "Yukon" Beta 1 que Afectan a Transact-SQL
Conclusin
Introduccin y Perspectiva
Este documento presenta varias de las nuevas mejoras a Transact-SQL en Microsoft SQL Server TM
"Yukon Beta 1. Estas nuevas caractersticas pueden aumentar su poder de expresividad, la performance
de sus consultas, y su capacidad de manejo de errores. Este documento se enfoca principalmente en las
mejoras relacionales que son conceptualmente nuevas y las demuestra por medio de ejemplos prcticos.
Este documento no cubre todas las nuevas caractersticas de Transact-SQL.
Conocimiento necesario: Se espera que la audiencia tenga experiencia utilizando Transact-SQL para
consultas ad hoc y como componentes de aplicaciones en Microsoft SQL Server 2000.
Aumentando el Poder de Expresividad de Consultas y Soporte
DRI
Esta seccin presenta las siguientes mejoras y nuevas caractersticas relacionales:
Nuevas consultas recursivas basadas en expresiones comunes de tablas (CTE por sus siglas en
ingls)
Nuevos operadores relacionales PIVOT y APPLY
Mejoras en Declarative referential integrity (DRI)
Consultas Recursivas y Expresiones Comunes de Tablas
Este artculo explora las sutilezas de las expresiones recursivas de las CTEs y las aplica como una solucin
a problemas comunes de una manera que simplifica los enfoques tradicionales.
Expresiones comunes de tablas
Una expresin comn de tabla (CTE) es un grupo de resultados named temporarios que puede ser referido
por una declaracin que define. En su manera ms simple, puede pensar en las CTEs como una versin
mejorada de las tablas derivadas que se asimilan a un tipo de vista no persistente. Uno se refiere a una
CTE desde una clusula FROM en una consulta de manera similar a la manera en que se refiere a las
tablas derivadas y vistas. La CTE se define solo una vez, y puede referirse a ella varias veces en la
consulta. En la definicin de CTE, puede referirse a variables definidas en el mismo batch. Hasta se
pueden utilizar CTEs en INSERT, UPDATE, DELETE y CREATE VIEW, de manera similar a la manera en que
utiliza las vistas. Sin embargo, el verdadero poder de las CTEs esta en sus capacidades recursivas, en las
cuales las CTEs contienen referencias a ellas mismas. En este documento, las CTEs se describen primero
en su forma simple y despus en su forma recursiva. Este artculo cubre consultas SELECT con CTEs.
Las tablas derivadas se utilizan cuando desea referirse al resultado de una consulta como si fuera una
tabla, pero no quiere crear una vista persistente en la base de datos. Sin embargo, las tablas derivadas
tienen una limitacin que esta relajada por las CTEs: no puede definir una tabla derivada una vez en su
consulta y utilizarla varias veces. En su lugar, debe definir varias tablas derivadas con la misma consulta.
Si no, puede definir una CTE una vez y utilizarla varias veces durante la misma consulta sin que persista
en la base de datos.
Antes de brindar un ejemplo prctico de CTEs, la sintaxis bsica de CTE es comparada a las tablas
derivadas y vistas. La siguiente es una forma general de una consulta dentro de una vista, tabla derivada
y CTE:
Vista
CREATE VIEW ()
AS

GO

SELECT *
FROM
Tabla Derivada
SELECT *
FROM (<derived_table_query>) AS <derived_table_alias>(<column_aliases>)
CTE
WITH <cte_alias>(<column_aliases>)
AS
(
<cte_query>
)
SELECT *
FROM <cte_alias>
Debe brindar un alias a la CTE y un listado opcional de aliases para sus columnas de resultado siguiendo la
palabra clave WITH, escribir este cuerpo, y hacer referencia al mismo desde la consulta externa.
Tenga en cuenta que si la clusula WITH de una CTE no es la primera declaracin en el batch, debe
delimitarla desde la declaracin precedente utilizando un punto y coma (;) delante de la misma. Este
punto y coma se utiliza para evitar la ambigedad con otros usos de la clusula WITH (por ejemplo, pistas
de tablas). Si bien puede encontrar que especificar un punto y coma no es necesario en todos los casos,
se recomienda su uso consistente.
Como ejemplo prctico, considere las tablas Employees y Orders en la base de datos Northwind. Cada
empleado reporta a un gerente especificado en la columna ReportTo. Cada empleado en la tabla
Employees puede tener rdenes relacionadas en la tabla Orders. Suponga que desea obtener el conteo
de rdenes y la fecha de la ltima orden por cada empleado y, en la misma fila, detalles similares del
gerente. El siguiente ejemplo muestra como puede implementar una solucin utilizando vistas, tablas
derivadas y CTEs:
Vista
CREATE VIEW VEmpOrders(EmployeeID, NumOrders, MaxDate)
AS

SELECT EmployeeID, COUNT(*), MAX(OrderDate)
FROM Orders
GROUP BY EmployeeID
GO

SELECT E.EmployeeID, OE.NumOrders, OE.MaxDate,
E.ReportsTo, OM.NumOrders, OM.MaxDate
FROM Employees AS E
JOIN VEmpOrders AS OE
ON E.EmployeeID = OE.EmployeeID
LEFT OUTER JOIN VEmpOrders AS OM
ON E.ReportsTo = OM.EmployeeID

Tablas Derivadas
SELECT E.EmployeeID, OE.NumOrders, OE.MaxDate,
E.ReportsTo, OM.NumOrders, OM.MaxDate
FROM Employees AS E
JOIN (SELECT EmployeeID, COUNT(*), MAX(OrderDate)
FROM Orders
GROUP BY EmployeeID) AS OE(EmployeeID, NumOrders, MaxDate)
ON E.EmployeeID = OE.EmployeeID
LEFT OUTER JOIN
(SELECT EmployeeID, COUNT(*), MAX(OrderDate)
FROM Orders
GROUP BY EmployeeID) AS OM(EmployeeID, NumOrders, MaxDate)
ON E.ReportsTo = OM.EmployeeID

CTE
WITH EmpOrdersCTE(EmployeeID, NumOrders, MaxDate)
AS
(
SELECT EmployeeID, COUNT(*), MAX(OrderDate)
FROM Orders
GROUP BY EmployeeID
)
SELECT E.EmployeeID, OE.NumOrders, OE.MaxDate,
E.ReportsTo, OM.NumOrders, OM.MaxDate
FROM Employees AS E
JOIN EmpOrdersCTE AS OE
ON E.EmployeeID = OE.EmployeeID
LEFT OUTER JOIN EmpOrdersCTE AS OM
ON E.ReportsTo = OM.EmployeeID

La definicin de la CTE debe estar seguida por una consulta externa, que puede o no hacer referencia a
ella. Usted no puede referirse a la CTE ms tarde en el batch despus de otras declaraciones
intervinientes.
Puede definir varias CTEs con la misma clusula WITH, cada una referida a una definida previamente. Las
comas se utilizan para delimitar las CTEs. Por ejemplo, suponga que deseaba calcular el mnimo, mximo
y la diferencia en la cantidad de rdenes de empleados:
WITH
EmpOrdersCTE(EmployeeID, Cnt)
AS
(
SELECT EmployeeID, COUNT(*)
FROM Orders
GROUP BY EmployeeID
),
MinMaxCTE(MN, MX, Diff)
AS
(
SELECT MIN(Cnt), MAX(Cnt), MAX(Cnt)-MIN(Cnt)
FROM EmpOrdersCTE
)
SELECT * FROM MinMaxCTE

El resultado de este programa es:
MN MX Diff
----------- ----------- -----------
42 156 114
En EmpOrdersCTE, calcula la cantidad de rdenes de cada empleado; y en MinMaxCTE, se refiere a
EmpOrdersCTE para calcular el mnimo, mximo y la diferencia.
Nota Dentro de una CTE, usted no esta limitado a referirse solo a la CTE definida directamente antes de
esta, sino que puede referirse a todas las CTEs previamente definidas. Note que no se permiten las
referencias hacia delante; una CTE puede referirse a CTEs definidas antes de ella o a ella misma (vea
"Consultas Recursivas ms adelante), pero no a CTEs definidas con anterioridad. Si define a las CTEs C1,
C2 y C3 con la misma declaracin WITH, C2 puede referirse a C1 y C2, pero no a C3.
A modo de ejemplo, el siguiente cdigo genera un histograma que calcula el nmero de empleados que
cae dentro de los cuatro rangos de conteo de rdenes entre el mnimo y el mximo. Si el clculo parece
un poco complicado, no pierda tiempo en intentar descifrarlo. El propsito de este ejemplo es que utilice
un escenario prctico para demostrar la declaracin de mltiples CTEs en la misma instruccin WITH,
donde cada una puede referirse a un CTE previo.
WITH
EmpOrdersCTE(EmployeeID, Cnt)
AS
(
SELECT EmployeeID, COUNT(*)
FROM Orders
GROUP BY EmployeeID
),
MinMaxCTE(MN, MX, Diff)
AS
(
SELECT MIN(Cnt), MAX(Cnt), MAX(Cnt)-MIN(Cnt)
FROM EmpOrdersCTE
),
NumsCTE(Num)
AS
(
SELECT 1 AS Num
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
),
StepsCTE(Step, Fromval, Toval)
AS
(
SELECT
Num,
MN + ROUND((Num-1)*((Diff+1)/4.), 0),
MN + ROUND((Num)*((Diff+1)/4.), 0) - 1
FROM MinMaxCTE CROSS JOIN NumsCTE
),
HistogramCTE(Step, Fromval, Toval, Samples)
AS
(
SELECT S.Step, S.Fromval, S.Toval, COUNT(*)
FROM StepsCTE AS S
JOIN EmpOrdersCTE AS OE
ON OE.Cnt BETWEEN S.Fromval AND S.Toval
GROUP BY S.Step, S.Fromval, S.Toval
)
SELECT * FROM HistogramCTE
El resultado de este programa es:
Step Fromval Toval Samples
----- -------- ------ ---------
1 42 70 3
2 71 99 2
3 100 127 3
4 128 156 1
Note como el Segundo CTE (MinMaxCTE) se refiere al primero (EmpOrdersCTE); el tercero (NumsCTE) no
se refiere a ningn CTE. El cuarto (StepsCTE) se refiere al segundo y tercer CTE, y el quinto
(HistogramCTE) se refiere al primero y al cuarto CTE.
Consultas Recursivas
Las CTEs no recursivas aumentan su poder y expresividad. Pero por cada trozo de cdigo que utiliza CTEs
no recursivas, usted suele poder escribir cdigo ms corto que obtenga el mismo resultado utilizando otra
construccin Transact-SQL, como por ejemplo tablas derivadas. El caso es diferente con CTEs recursivas.
Esta seccin describe la semntica de las consultas recursivas y brinda implementaciones prcticas para
una jerarqua de empleados en un organigrama, y para un escenario de facturacin de materiales (BOM
por sus siglas en ingles).
Semntica
Cuando una CTE hace referencia a ella misma es considerada recursiva. Las CTEs recursivas se construyen
de por lo menos dos consultas (o miembros en idioma de consulta recursiva). Una es una consulta no
recursiva, tambien conocida como miembro Anchor (AM por sus siglas en ingles). El otro es una consulta
recursiva, tambien conocida como miembro recursivo (RM por sus siglas en ingles). Estas consultas estn
separadas por un operador UNION ALL. El ejemplo a continuacin muestra una forma genrica
simplificada de CTE recursiva:
WITH RecursiveCTE(<column_list>)
AS
(
-- Anchor Member:
-- SELECT query that does not refer to RecursiveCTE
SELECT ...
FROM <some_table(s)>
...

UNION ALL

-- Recursive Member
-- SELECT query that refers to RecursiveCTE
SELECT ...
FROM <some_table(s)>
JOIN RecursiveCTE
...
)
-- Outer Query
SELECT ...
FROM RecursiveCTE
...
Lgicamente usted puede pensar en el algoritmo implementando una CTE recursiva como:
1. El miembro ancla esta activado. Se genera el grupo R0 (R de resultados).
2. El miembro recursivo esta activado, obteniendo el grupo Ri (i = numero de paso) como entrada
al referirse a RecursiveCTE. Se genera el grupo Ri + 1.
3. La lgica del Paso 2 se corre repetidamente (aumentando el nmero de paso en cada iteracin)
hasta que devuelve un grupo vaco.
4. Se ejecuta la consulta externa, obteniendo el resultado acumulativo (UNION ALL) de todos los
pases anteriores al referirse a RecursiveCTE.
Puede tener ms de dos miembros en la CTE, pero se permite solo un operador UNION ALL entre un
miembro recursivo y otro miembro (recursivo o no recursivo). Otros operadores, como por ejemplo
UNION, solo son permitidos entre miembros no recursivos. A diferencia de los operadores UNION y UNION
ALL que soportan conversin implcita, las CTEs recursivas requieren igualar exactamente las columnas en
todos los miembros, incluyendo el mismo tipo de datos, longitud y precisin.
Si desea realizar una analoga entre CTEs recursivas y desarrollo clsico de rutinas recursivas (no
necesariamente especfico a SQL Server), puede encontrar similitudes. Las rutinas recursivas usualmente
consiste de tres elementos importantes: primera invocacin de la rutina, evaluacin de terminacin
recursiva, y una llamada recursiva a la misma rutina. El miembro ancla corresponde a la primera
invocacin de la rutina; el miembro recursivo corresponde a la invocacin recursiva de la rutina; y la
evaluacin de terminacin, la cual suele ser explicita en rutinas recursivas (por ejemplo, por medio de una
declaracin IF), esta implcita en una CTE recursiva - la recursin se detiene cuando no devuelve ninguna
fila de la invocacin previa.
Las secciones siguientes de este artculo presentan ejemplos prcticos y usos de CTEs recursivas en
ambientes de uno o varios padres.
Ambiente de un solo padre: organigrama de empleados
Para un escenario de jerarqua de un solo padre se utiliza un organigrama de empleados.
Nota Los ejemplos de esta seccin crean y utilizan una tabla denominada Employees que tiene una
estructura diferente de la tabla Employees en Northwind. Debe correr su cdigo en su propia base de
datos de evaluacin o en tempdb, no en Northwind.
El siguiente cdigo genera la tabla Employees y la puebla con datos de muestra:
USE tempdb -- or your own test database

CREATE TABLE Employees
(
empid int NOT NULL,
mgrid int NULL,
empname varchar(25) NOT NULL,
salary money NOT NULL,
CONSTRAINT PK_Employees PRIMARY KEY(empid),
CONSTRAINT FK_Employees_mgrid_empid
FOREIGN KEY(mgrid)
REFERENCES Employees(empid)
)

CREATE INDEX idx_nci_mgrid ON Employees(mgrid)

SET NOCOUNT ON
INSERT INTO Employees VALUES(1 , NULL, 'Nancy' , $10000.00)
INSERT INTO Employees VALUES(2 , 1 , 'Andrew' , $5000.00)
INSERT INTO Employees VALUES(3 , 1 , 'Janet' , $5000.00)
INSERT INTO Employees VALUES(4 , 1 , 'Margaret', $5000.00)
INSERT INTO Employees VALUES(5 , 2 , 'Steven' , $2500.00)
INSERT INTO Employees VALUES(6 , 2 , 'Michael' , $2500.00)
INSERT INTO Employees VALUES(7 , 3 , 'Robert' , $2500.00)
INSERT INTO Employees VALUES(8 , 3 , 'Laura' , $2500.00)
INSERT INTO Employees VALUES(9 , 3 , 'Ann' , $2500.00)
INSERT INTO Employees VALUES(10, 4 , 'Ina' , $2500.00)
INSERT INTO Employees VALUES(11, 7 , 'David' , $2000.00)
INSERT INTO Employees VALUES(12, 7 , 'Ron' , $2000.00)
INSERT INTO Employees VALUES(13, 7 , 'Dan' , $2000.00)
INSERT INTO Employees VALUES(14, 11 , 'James' , $1500.00)

Cada empleado reporta a un gerente cuyo ID esta almacenado en la columna mgrid. Un key extranjero es
definido en la columna mgrid haciendo referencia a la columna empid, lo cual significa que el ID de
gerente debe o corresponder a un ID de empleado valido dentro de la tabla o ser NULL. Nancy, el jefe,
tiene NULL en la columna mgrid. Las relaciones gerente-empleado se muestran en la Figura 1.

Figura 1. Organigrama de empleados
Los siguientes son algunos pedidos comunes realizados contra la tabla Employees:
Ver detalles de Robert (empid=7) y todos sus subordinados en todos los niveles.
Ver detalles sobre todos los empleados dos niveles debajo de Janet (empid=3)
Ver la cadena de gerentes que lleva a James (empid=14)
Cuantos empleados reportan a cada gerente directamente o indirectamente?
Ver todos los empleados de una manera que sea sencilla para ver sus dependencias jerrquicas.
Las CTEs recursivas brindan los medios para lidiar con estos pedidos, los cuales son por naturaleza
recursivos, sin la necesidad de mantener informacin adicional acerca de la jerarqua en la base de datos.
Probablemente el primer pedido sea el ms comn de todos: ver un empleado (por ejemplo, Robert, cuyo
empid=7) y sus subordinados en todos los niveles. La siguiente CTE brinda una solucin a este pedido:
WITH EmpCTE(empid, empname, mgrid, lvl)
AS
(
-- Anchor Member (AM)
SELECT empid, empname, mgrid, 0
FROM Employees
WHERE empid = 7

UNION ALL

-- Recursive Member (RM)
SELECT E.empid, E.empname, E.mgrid, M.lvl+1
FROM Employees AS E
JOIN EmpCTE AS M
ON E.mgrid = M.empid
)
SELECT * FROM EmpCTE
El resultado de este programa es:
empid empname mgrid lvl
----------- ------------------------- ----------- -----------
7 Robert 3 0
11 David 7 1
12 Ron 7 1
13 Dan 7 1
14 James 11 2
Siguiendo la lgica de la CTE recursiva descripta previamente, esta CTE se procesa de la siguiente
manera:
1. El miembro ancla es activado devolviendo la fila de Robert de Employees. Note la constante 0
que es devuelta en la columna de resultados lvl.
2. El miembro recursivo es activado repetitivamente, devolviendo subordinados directos de los
resultados previos por medio de una operacin conjunta entre Employees y EmpCTE. Employees
representa los subordinados, y EmpCTE (el cual contiene los resultados de la invocacin previa)
representa los gerentes:
o Primero, devuelve los subordinados de Robert: David, Ron y James.
o Luego, devuelve los subordinados de David, Ron y Dan: solo James.
o Finalmente, devuelve los subordinados de James: ninguno, en cuyo caso la recursin es
finalizada.
3. La consulta externa devuelve todas filas de EmpCTE.
Note que el valor lvl es incrementado repetitivamente en cada invocacin recursiva.
Utilizando este nivel de contador usted puede limitar el nmero de iteraciones en la recursin. Por
ejemplo, la siguiente CTE se utiliza para devolver todos los empleados que se encuentran dos niveles
debajo de Janet:
WITH EmpCTEJanet(empid, empname, mgrid, lvl)
AS
(
SELECT empid, empname, mgrid, 0
FROM Employees
WHERE empid = 3

UNION ALL

SELECT E.empid, E.empname, E.mgrid, M.lvl+1
FROM Employees as E
JOIN EmpCTEJanet as M
ON E.mgrid = M.empid
WHERE lvl <2 ) SELECT empid, empname FROM EmpCTEJanet WHERE lvl="2"
El resultado de este programa es:
empid empname
----------- -------------------------
11 David
12 Ron
13 Dan
Los agregados en este ejemplo de cdigo comparado con los previos se muestran en negrita. El filtro
WHERE lvl < 2 en el miembro recursivo es utilizado como una evaluacin de terminacin - no devuelve
filas cuando lvl = 2, por lo tanto se detiene la recursin. El filtro WHERE lvl = 2 en la consulta externa se
utiliza para remover los niveles hasta el nivel 2. Note que lgicamente el filtro en la consulta externa (lvl
= 2) es suficiente por si mismo para retornar solo las filas deseadas. El filtro en el miembro recursivo (lvl
< 2) se agrega por razones de performance - para detener la recursin rpidamente, tan pronto como
devuelve los dos niveles debajo de Janet.
Como mencionamos con anterioridad, las CTEs pueden referirse a variables locales definidas dentro del
mismo batch. Por ejemplo, para hacer una consulta ms genrica, puede utilizar variables por ID de
empleado y nivel en lugar de constantes:
DECLARE @empid AS INT, @lvl AS INT
SET @empid = 3 -- Janet
SET @lvl = 2 -- two levels

WITH EmpCTE(empid, empname, mgrid, lvl)
AS
(
SELECT empid, empname, mgrid, 0
FROM Employees
WHERE empid = @empid

UNION ALL

SELECT E.empid, E.empname, E.mgrid, M.lvl+1
FROM Employees as E
JOIN EmpCTE as M
ON E.mgrid = M.empid
WHERE lvl <@lvl ) SELECT empid, empname FROM EmpCTE WHERE lvl="@lvl"
Puede utilizar una pista para forzar la terminacin de la consulta luego de que un cierto numero de
iteraciones recursivas hayan sido invocadas. Esto se logra agregando OPTION(MAXRECURSION value) al
final de la consulta externa, como se muestra en el siguiente ejemplo:
WITH EmpCTE(empid, empname, mgrid, lvl)
AS
(
SELECT empid, empname, mgrid, 0
FROM Employees
WHERE empid = 1

UNION ALL

SELECT E.empid, E.empname, E.mgrid, M.lvl+1
FROM Employees as E
JOIN EmpCTE as M
ON E.mgrid = M.empid
)
SELECT * FROM EmpCTE
OPTION (MAXRECURSION 2)
El resultado de este programa es:
empid empname mgrid lvl
----------- ------------------------- ----------- -----------
1 Nancy NULL 0
2 Andrew 1 1
3 Janet 1 1
4 Margaret 1 1
10 Ina 4 2
7 Robert 3 2
8 Laura 3 2
9 Ann 3 2

.Net SqlClient Data Provider: Msg 530, Level 16, State 1, Line 1
Statement terminated. Maximum recursion 2 has been exhausted before
statement completion
Los resultados generados hasta el momento son retornados, y se genera el error 530. Puede implementar
el pedido de retornar empleados que estn dos niveles debajo de Janet utilizando MAXRECURSION en
lugar del filtro en el miembro recursivo:
WITH EmpCTE(empid, empname, mgrid, lvl)
AS
(
SELECT empid, empname, mgrid, 0
FROM Employees
WHERE empid = 3

UNION ALL

SELECT E.empid, E.empname, E.mgrid, M.lvl+1
FROM Employees as E
JOIN EmpCTE as M
ON E.mgrid = M.empid
)
SELECT empid, empname
FROM EmpCTE
WHERE lvl = 2
OPTION (MAXRECURSION 2)
El resultado de este programa es:
empid empname
----------- -------------------------
11 David
12 Ron
13 Dan
.Net SqlClient Data Provider: Msg 530, Level 16, State 1, Line 1
Statement terminated. Maximum recursion 2 has been exhausted before
statement completion
Tenga en cuenta que su cliente obtendr un error adems de los resultados. No es una buena prctica de
programacin utilizar cdigo que devuelve errores en situaciones validas. Se recomienda utilizar el filtro
presentado con anterioridad y, si lo desea, la funcin MAXRECURSION como una garanta contra bucles
infinitos.
Cuando esta funcin no esta especificada, SQL Server predetermina a un valor de 100. Este valor puede
ser utilizado como una garanta cuando sospecha de llamadas recursivas cclicas. Si no desea limitar el
numero de llamadas recursivas, coloque la funcin MAXRECURSION en valor 0.
Los ejemplos de recursividad brindados hasta este punto tienen un miembro ancla que es el gerente y un
miembro recursivo que recupera los subordinados. Algunos pedidos requieren lo opuesto, por ejemplo,
devolver el path de gerentes de James (James y todos sus gerentes en todos los niveles). El siguiente
cdigo brinda una respuesta a este pedido:
WITH EmpCTE(empid, empname, mgrid, lvl)
AS
(
SELECT empid, empname, mgrid, 0
FROM Employees
WHERE empid = 14

UNION ALL

SELECT M.empid, M.empname, M.mgrid, E.lvl+1
FROM Employees as M
JOIN EmpCTE as E
ON M.empid = E.mgrid
)
SELECT * FROM EmpCTE
El resultado de este programa es:
empid empname mgrid lvl
----------- ------------------------- ----------- -----------
14 James 11 0
11 David 7 1
7 Robert 3 2
3 Janet 1 3
1 Nancy NULL 4
El miembro ancla devuelve la fila de James. El miembro recursivo devuelve los gerentes de los empleados
devueltos anteriormente o el gerente en particular, porque aqu se utiliza una jerarqua de un solo padre y
el pedido comienza con un solo empleado.
Tambien puede utilizar consultar recursivas para calcular agregados, como por ejemplo el nmero de
subordinados que reportan a cada gerente directa o indirectamente:
WITH MgrCTE(mgrid, lvl)
AS
(
SELECT mgrid, 0
FROM Employees
WHERE mgrid IS NOT NULL

UNION ALL

SELECT M.mgrid, lvl + 1
FROM Employees AS M
JOIN MgrCTE AS E
ON E.mgrid = M.empid
WHERE M.mgrid IS NOT NULL
)
SELECT mgrid, COUNT(*) AS cnt
FROM MgrCTE
GROUP BY mgrid
El resultado de este programa es:

mgrid cnt
----------- -----------
1 13
2 2
3 7
4 1
7 4
11 1
El miembro ancla devuelve una fila con el ID de gerente por cada empleado. NULL en la columna de ID de
gerente es excluido porque no representa a ningn gerente especifico. El miembro recursivo devuelve el
ID de gerente de los gerentes que fueron devueltos anteriormente, nuevamente, los NULLs son excluidos.
Eventualmente, la CTE contiene por cada gerente la misma cantidad de ocurrencias que su nmero directo
o indirecto de subordinados. La consulta externa queda a agrupar el resultado por ID de gerente y
devolver el conteo de ocurrencias.
Como el ultimo ejemplo de un pedido contra una jerarqua de un solo padre, suponga que desea devolver
los subordinados de Nancy (el jefe) ordenados y con la sangra correspondiente de acuerdo a las
dependencias jerrquicas. El siguiente cdigo realiza esta accin, ordenndolos de acuerdo a sus IDs de
empleado:
WITH EmpCTE(empid, empname, mgrid, lvl, sortcol)
AS
(
SELECT empid, empname, mgrid, 0,
CAST(empid AS VARBINARY(900))
FROM Employees
WHERE empid = 1

UNION ALL

SELECT E.empid, E.empname, E.mgrid, M.lvl+1,
CAST(sortcol + CAST(E.empid AS BINARY(4)) AS VARBINARY(900))
FROM Employees AS E
JOIN EmpCTE AS M
ON E.mgrid = M.empid
)
SELECT
REPLICATE(' | ', lvl)
+ '(' + (CAST(empid AS VARCHAR(10))) + ') '
+ empname AS empname
FROM EmpCTE
ORDER BY sortcol

(1) Nancy
| (2) Andrew
| | (5) Steven
| | (6) Michael
| (3) Janet
| | (7) Robert
| | | (11) David
| | | | (14) James
| | | (12) Ron
| | | (13) Dan
| | (8) Laura
| | (9) Ann
| (4) Margaret
| | (10) Ina
Para ordenarlos de acuerdo al valor empid, cree un string binario llamado sortcol por cada empleado. El
string esta compuesto de IDs de empleados concatenados en la cadena de gerenciamiento llevando a cada
empleado, convertido a valores binarios. El miembro ancla es el punto de partida. El mismo genera un
valor binario con el empid del empleado raz. En cada iteracin, el miembro recursivo aade el ID de
empleado actual convertido a un valor binario al sortcol del gerente. Luego la consulta externa ordena el
resultado por sortcol. Note que las columnas correspondientes tanto en el miembro ancla como en el
recursivo deben tener el mismo tipo de datos, longitud y precisin. Esta es la razn por la cual la
expresin generando el valor sortcol es convertida a varbinary(900), a pesar de que una variable integer
requiere 4 bytes en su representacin binaria: 900 bytes cubren 225 niveles, lo cual es una limitacin ms
que razonable. Si desea soportar ms niveles, puede aumentar la longitud pero asegurese de hacerlo en
los dos miembros, de lo contrario obtendr un error.
La sangra jerrquica es lograda al replicar un string de caracteres (en este caso ' | ') la misma cantidad
de veces que el numero de niveles del empleado. Para ello, el ID del empleado debe estar ubicado entre
parntesis, y finalmente el nombre del empleado tambien es aadido.
Ambientes de mltiples padres: facturacin de materiales
En la seccin anterior, las CTEs son utilizadas para manejar jerarquas en las cuales cada nodo en el rbol
solo tena un padre. Un escenario ms complejo de relaciones es un grafico en el cual cada nodo puede
tener ms de un padre. Esta seccin describe el uso de CTEs en un escenario de facturacin de materiales
(BOM). Un BOM es un Acyclic Directed Graph, lo que significa que cada nodo puede tener ms de un
padre; un nodo no puede ser un padre por si mismo, directa o indirectamente; la relacin entre dos nodos
no es dual (por ejemplo, A contiene a C, pero C no contiene a A). La Figura 2 muestra las relaciones entre
tems en un escenario BOM.

Figura 2. Ambiente multi padre
El tem A contiene los tems D, B y C; el tem C contiene a B y a E; el tem B esta contenido en los tems A
y C, y as sucesivamente. El siguiente cdigo crea las tablas tems y BOM y las puebla con los datos de
muestra:
CREATE TABLE Items
(
itemid VARCHAR(5) NOT NULL PRIMARY KEY,
itemname VARCHAR(25) NOT NULL,
/* other columns, e.g., unit_price, measurement_unit */
)

CREATE TABLE BOM
(
itemid VARCHAR(5) NOT NULL REFERENCES Items,
containsid VARCHAR(5) NOT NULL REFERENCES Items,
qty INT NOT NULL
/* other columns, e.g., quantity */
PRIMARY KEY(itemid, containsid),
CHECK (itemid <> containsid)
)

SET NOCOUNT ON
INSERT INTO Items(itemid, itemname) VALUES('A', 'Item A')
INSERT INTO Items(itemid, itemname) VALUES('B', 'Item B')
INSERT INTO Items(itemid, itemname) VALUES('C', 'Item C')
INSERT INTO Items(itemid, itemname) VALUES('D', 'Item D')
INSERT INTO Items(itemid, itemname) VALUES('E', 'Item E')
INSERT INTO Items(itemid, itemname) VALUES('F', 'Item F')
INSERT INTO Items(itemid, itemname) VALUES('G', 'Item G')
INSERT INTO Items(itemid, itemname) VALUES('H', 'Item H')
INSERT INTO Items(itemid, itemname) VALUES('I', 'Item I')
INSERT INTO Items(itemid, itemname) VALUES('J', 'Item J')
INSERT INTO Items(itemid, itemname) VALUES('K', 'Item K')

INSERT INTO BOM(itemid, containsid, qty) VALUES('E', 'J', 1)
INSERT INTO BOM(itemid, containsid, qty) VALUES('C', 'E', 3)
INSERT INTO BOM(itemid, containsid, qty) VALUES('A', 'C', 2)
INSERT INTO BOM(itemid, containsid, qty) VALUES('H', 'C', 4)
INSERT INTO BOM(itemid, containsid, qty) VALUES('C', 'B', 2)
INSERT INTO BOM(itemid, containsid, qty) VALUES('B', 'F', 1)
INSERT INTO BOM(itemid, containsid, qty) VALUES('B', 'G', 3)
INSERT INTO BOM(itemid, containsid, qty) VALUES('A', 'B', 2)
INSERT INTO BOM(itemid, containsid, qty) VALUES('A', 'D', 2)
INSERT INTO BOM(itemid, containsid, qty) VALUES('H', 'I', 1)
La tabla Items contiene una fila por cada tem. La tabla BOM contiene las relaciones entre los nodos en el
grafico. Cada relacin esta compuesta por el tem ID del padre (itemid), el tem ID del hijo (containsid) y
la cantidad de containsid dentro de itemid(qty).
Un pedido comn en un escenario BOM es el "explotar un tem: esto significa girar el grafico,
comenzando con el tem dado y devolviendo todos los tems que contiene, directa o indirectamente. Esto
puede sonar familiar ya que es similar a retornar un subrbol fuera de un rbol como en el Organigrama
de empleados. Sin embargo, en un grafico dirigido el pedido es un poco ms complejo conceptualmente,
porque un tem contenido puede ser alcanzado desde varios tems contenidos diferentes mediante
diferentes path. Por ejemplo, suponga que desea explotar el tem A. Note que hay dos paths diferentes
que llevan de tem a tem: A?B, A?C?B. Esto significa que el tem B ser alcanzado dos veces, lo que
significa que todos los tems que contenga B (F y G) sern alcanzados dos veces. Afortunadamente, con
las CTEs, la implementacin de dicho pedido es tan simple como implementar un pedido de extraer un
subrbol de un rbol.
WITH BOMCTE
AS
(
SELECT *
FROM BOM
WHERE itemid = 'A'

UNION ALL

SELECT BOM.*
FROM BOM
JOIN BOMCTE
ON BOM.itemid = BOMCTE.containsid
)
SELECT * FROM BOMCTE
El resultado de este programa es:

itemid containsid qty
------ ---------- -----------
A B 2
A C 2
A D 2
C B 2
C E 3
E J 1
B F 1
B G 3
B F 1
B G 3
El miembro ancla devuelve todos los tems directos que contiene A de BOM. Por cada tem contenido que
es devuelto por la iteracin anterior del CTE, el miembro recursivo devuelve los tems que contiene al unir
BOM con BOMCTE. Lgicamente, (no necesariamente en el orden que fueron devueltos) (A, B), (A, C), (A,
D) son devueltos primero, luego (B, F), (B, G), (C, B), (C, E); y finalmente (B, F), (B, G), (E, J). Note que
muchos de los pedidos de un BOM no requieren que usted muestre un tem ms de una vez en los
resultados finales. Puede utilizar la clusula DISTINCT para eliminar duplicados si desea mostrar solo
"cual de los tems estuvo involucrado en la explosin.
WITH BOMCTE
AS
(
SELECT *
FROM BOM
WHERE itemid = 'A'

UNION ALL

SELECT BOM.*
FROM BOM
JOIN BOMCTE
ON BOM.itemid = BOMCTE.containsid
)
SELECT DISTINCT containsid FROM BOMCTE
El resultado de este programa es:
containsid
----------
B
C
D
E
F
G
J
Para ayudarlo a entender el proceso de explosin de partes, visualice sus resultados intermedios como un
rbol en el cual todos los tems estn expandidos a sus tems contenidos. La Figura 3 muestra los rboles
formados por las partes de A y H que explotaron junto con las cantidades de tems.

Figura 3. Explosin de partes
Llevando el pedido original un poco ms all, generalmente usted esta ms interesado en obtener las
cantidades acumulativas de cada tem antes que los tems en si mismos. Por ejemplo, A contiene 2
unidades de C. C contiene 3 unidades de E. E contiene una unidad de J. El nmero total de unidades de J
requeridas por A es un producto de las cantidades a lo largo del path que lleva de A a J: 2*3*1 = 6. La
Figura 4 muestra las cantidades acumulativas de cada tem que forman A antes de agregar los tems.

Figura 4. Explosin de partes - cantidades calculadas
La siguiente CTE calcula el producto acumulativo de cantidades:
WITH BOMCTE(itemid, containsid, qty, cumulativeqty)
AS
(
SELECT *, qty
FROM BOM
WHERE itemid = 'A'

UNION ALL

SELECT BOM.*, BOM.qty * BOMCTE.cumulativeqty
FROM BOM
JOIN BOMCTE
ON BOM.itemid = BOMCTE.containsid
)
SELECT * FROM BOMCTE
El resultado de este programa es:
itemid containsid qty cumulativeqty
------ ---------- ----------- -------------
A B 2 2
A C 2 2
A D 2 2
C B 2 4
C E 3 6
E J 1 6
B F 1 4
B G 3 12
B F 1 2
B G 3 6
Esta CTE agrega la columna cumulativeqty a la CTE previa. El miembro anchor devuelve las cantidades de
los tems contenidos como cumulativeqty. Para cada uno de los siguientes niveles conteniendo tems, el
miembro recursivo multiplica sus cantidades por la cantidad de sus tems acumulativos contenidos. Note
que los tems que fueron alcanzados por mltiples paths aparecen varias veces en los resultados, cada
uno con las cantidades acumulativas por cada path. Dicho resultado no es muy significativo por si mismo,
pero ayuda a entender el paso intermedio hacia los resultados finales en el cual cada tem aparecer solo
una vez. Para obtener las cantidades totales de cada tem en A, haga que la consulta externa agrupe el
resultado por containsid:
WITH BOMCTE(itemid, containsid, qty, cumulativeqty)
AS
(
SELECT *, qty
FROM BOM
WHERE itemid = 'A'

UNION ALL

SELECT BOM.*, BOM.qty * BOMCTE.cumulativeqty
FROM BOM
JOIN BOMCTE
ON BOM.itemid = BOMCTE.containsid
)
SELECT containsid AS itemid, SUM(cumulativeqty) AS totalqty
FROM BOMCTE
GROUP BY containsid
El resultado de este programa es:

itemid totalqty
------ -----------
B 6
C 2
D 2
E 6
F 6
G 18
J 6
PIVOT y UNPIVOT
PIVOT y UNPIVOT son dos operadores relacionales nuevos que se especifican en la clusula FROM de una
consulta. Los mismos realizan alguna manipulacin en una expresin de tabla de entrada de valores y
devuelve como resultado una tabla. El operador PIVOT rota filas en columnas, realizando posibles
agregados a lo largo del camino. Ensancha la expresin de tabla de entrada basada en una columna pivot
dada generando una tabla de resultados con una columna para cada valor nico en la columna pvot. El
operador UNPIVOT realiza una operacin opuesta a la PIVOT, rotando columnas en filas. Estrecha la
expresin de la tabla de entrada basada en una columna pvot.
PIVOT
El operador PIVOT es til para manejar escenarios de esquema abierto y para generar reportes a travs
de tabs.
En un escenario de esquema abierto, usted mantiene entidades en grupos de atributos que no son
conocidos o son diferentes para cada tipo de entidad. Los usuarios de su aplicacin definen los atributos
de manera dinmica. En lugar de pre definir muchas columnas y almacenar muchos valores nulos en sus
tablas, divide los atributos en diferentes filas y almacena solo los atributos relevantes para cada instancia
de cada entidad.
PIVOT le permite generar reportes a travs de tabs para escenarios de esquema abiertos y otros en los
cuales rota filas en columnas, posiblemente calculando agregados a lo largo del camino y presentando los
datos en una forma til.
Un ejemplo de un escenario de esquema abierto es una base de datos que realiza seguimiento de tems
que estn disponibles en una subasta. Algunos atributos son relevantes para todos los tems subastados,
como por ejemplo el tipo de tem, cuando fue realizado, y su precio inicial. Solo los atributos que son
relevantes para todos los tems se almacenan en la tabla AuctionItems.
CREATE TABLE AuctionItems
(
itemid INT NOT NULL PRIMARY KEY NONCLUSTERED,
itemtype NVARCHAR(30) NOT NULL,
whenmade INT NOT NULL,
initialprice MONEY NOT NULL,
/* other columns */
)

CREATE UNIQUE CLUSTERED INDEX idx_uc_itemtype_itemid
ON AuctionItems(itemtype, itemid)

INSERT INTO AuctionItems VALUES(1, N'Wine', 1822, 3000)
INSERT INTO AuctionItems VALUES(2, N'Wine', 1807, 500)
INSERT INTO AuctionItems VALUES(3, N'Chair', 1753, 800000)
INSERT INTO AuctionItems VALUES(4, N'Ring', -501, 1000000)
INSERT INTO AuctionItems VALUES(5, N'Painting', 1873, 8000000)
INSERT INTO AuctionItems VALUES(6, N'Painting', 1889, 8000000)
Otros atributos son especficos al tipo de tem, y nuevos tems de diferentes tipos estn siendo agregados
continuamente. Dichos atributos pueden ser almacenados en una tabla diferente (ItemAttributes) en la
cual cada atributo de tem es almacenado en una fila diferente. Cada fila contiene un ID de tem, nombre
del atributo, y valor del atributo.
CREATE TABLE ItemAttributes
(
itemid INT NOT NULL REFERENCES AuctionItems,
attribute NVARCHAR(30) NOT NULL,
value SQL_VARIANT NOT NULL,
PRIMARY KEY (itemid, attribute)
)

INSERT INTO ItemAttributes
VALUES(1, N'manufacturer', CAST(N'ABC' AS NVARCHAR(30)))
INSERT INTO ItemAttributes
VALUES(1, N'type', CAST(N'Pinot Noir' AS NVARCHAR(15)))
INSERT INTO ItemAttributes
VALUES(1, N'color', CAST(N'Red' AS NVARCHAR(15)))
INSERT INTO ItemAttributes
VALUES(2, N'manufacturer', CAST(N'XYZ' AS NVARCHAR(30)))
INSERT INTO ItemAttributes
VALUES(2, N'type', CAST(N'Porto' AS NVARCHAR(15)))
INSERT INTO ItemAttributes
VALUES(2, N'color', CAST(N'Red' AS NVARCHAR(15)))
INSERT INTO ItemAttributes
VALUES(3, N'material', CAST(N'Wood' AS NVARCHAR(15)))
INSERT INTO ItemAttributes
VALUES(3, N'padding', CAST(N'Silk' AS NVARCHAR(15)))
INSERT INTO ItemAttributes
VALUES(4, N'material', CAST(N'Gold' AS NVARCHAR(15)))
INSERT INTO ItemAttributes
VALUES(4, N'inscription', CAST(N'One ring ...' AS NVARCHAR(50)))
INSERT INTO ItemAttributes
VALUES(4, N'size', CAST(10 AS INT))
INSERT INTO ItemAttributes
VALUES(5, N'artist', CAST(N'Claude Monet' AS NVARCHAR(30)))
INSERT INTO ItemAttributes
VALUES(5, N'name', CAST(N'Field of Poppies' AS NVARCHAR(30)))
INSERT INTO ItemAttributes
VALUES(5, N'type', CAST(N'Oil' AS NVARCHAR(30)))
INSERT INTO ItemAttributes
VALUES(5, N'height', CAST(19.625 AS NUMERIC(9,3)))
INSERT INTO ItemAttributes
VALUES(5, N'width', CAST(25.625 AS NUMERIC(9,3)))
INSERT INTO ItemAttributes
VALUES(6, N'artist', CAST(N'Vincent Van Gogh' AS NVARCHAR(30)))
INSERT INTO ItemAttributes
VALUES(6, N'name', CAST(N'The Starry Night' AS NVARCHAR(30)))
INSERT INTO ItemAttributes
VALUES(6, N'type', CAST(N'Oil' AS NVARCHAR(30)))
INSERT INTO ItemAttributes
VALUES(6, N'height', CAST(28.75 AS NUMERIC(9,3)))
INSERT INTO ItemAttributes
VALUES(6, N'width', CAST(36.25 AS NUMERIC(9,3)))
Note que el tipo de dato sql_variant es utilizada para la columna de valor porque diferentes valores de
atributos pueden ser de diferentes tipos de datos. Por ejemplo, el atributo size almacena un valor de
atributo integer, y el atributo name almacena un valor de atributo de caracteres string.
Suponga que desea presentar los datos en la tabla ItemAttributes con una fila por cada tem que es una
pintura (tems 5 y 6) y una columna por cada atributo. Sin el operador PIVOT, debera escribir lo
siguiente:
SELECT
itemid,
MAX(CASE WHEN attribute = 'artist' THEN value END) AS [artist],
MAX(CASE WHEN attribute = 'name' THEN value END) AS [name],
MAX(CASE WHEN attribute = 'type' THEN value END) AS [type],
MAX(CASE WHEN attribute = 'height' THEN value END) AS [height],
MAX(CASE WHEN attribute = 'width' THEN value END) AS [width]
FROM ItemAttributes AS ATR
WHERE itemid IN(5,6)
GROUP BY itemid
El resultado de este programa es:
itemid artist name type height width
------ ---------------- ---------------- ---------- ------ ------
5 Claude Monet Field of Poppies Oil 19.625 25.625
6 Vincent Van Gogh The Starry Night Oil 28.750 36.250
El operador PIVOT le permite mantener cdigo ms corto y sencillo de leer para obtener los mismos
resultados:
SELECT *
FROM ItemAttributes AS ATR
PIVOT
(
MAX(value)
FOR attribute IN([artist], [name], [type], [height], [width])
) AS PVT
WHERE itemid IN(5,6)
Como con la mayora de las caractersticas, entender el operador PIVOT requiere experiencia y uso.
Algunos de los elementos en la sintaxis PIVOT son evidentes y solo necesitan que usted deduzca la
relacin de estos elementos con la consulta que no utiliza el nuevo operador, y otros estn ocultos.
Puede encontrar tiles los siguientes trminos para entender la semntica del operador PIVOT:
table_expression
Es la tabla virtual sobre la que trabaja el operador PIVOT (la parte en la consulta entre la clusula FROM y
el operador PIVOT): ItemAttributes AS ATR en este caso.
pivot_column
Es la columna de table_expression cuyos valores usted desea rotar entre columnas de resultados:
attribute en este caso.
column_list
Es el listado de valores de pivot_column que usted desea presentar como columnas de resultados (en los
parntesis que siguen a la clusula IN). Estos deben estar expresados como identificadores legales:
[artist], [name], [type], [height], [width] en este caso.
aggregate_function
Es la funcin agregada que utiliza para generar los valores de datos o de columna en el resultado: MAX()
en este caso.
value_column
Es la columna de table_expression que usted utiliza como el argumento para aggregate_function: value en
este caso.
group_by_list
Is the hidden part-ALL columns from table_expression excluding pivot_column and value_column that
are used to group the result: itemid in this case.
select_list
Es el listado de columnas siguiendo a la clusula SELECT que puede incluir cualquier columna(s) de
group_by_list y column_list. Los aliases puede ser utilizados para cambiar el nombre de las columnas de
resultados: * en este caso devuelve todas las columnas de group_by_list y column_list.
El operador PIVOT devuelve una fila por cada valor nico en group_by_list como si tuviera una consulta
con una clusula GROUP BY y especificara esas columnas. Note que group_by_list no esta especificado
explcitamente en ninguna parte de la columna; esta implcito. El mismo contiene todas las columnas de
table_expression excluyendo pivot_column y value_column. Entender esto es probablemente la clave para
entender porque las consultas que escribe con el operador PIVOT funcionan como lo hacen, y porque
puede devolver errores en algunos casos.
Posibles columnas de resultados incluyen valores de group_by_list y <column_list>. Si especifica un
asterisco (*), la consulta devolver ambas listas. La parte de datos de la columna de resultados o los
valores de las columnas de resultados son calculados por aggregate_function con value_column como
argumento.
El siguiente cdigo ilustra los diferentes elementos en la consulta utilizando el operador PIVOT.
SELECT * -- itemid, [artist], [name], [type], [height], [width]
FROM ItemAttributes AS ATR
PIVOT
(
MAX(value)
FOR attribute IN([artist], [name], [type], [height], [width])
) AS PVT
WHERE itemid IN(5,6)
Y lo siguiente relaciona los diferentes elementos a la consulta que no utiliza el operador PIVOT.
SELECT
itemid,
MAX(CASE WHEN attribute = 'artist' THEN value END) AS [artist],
MAX(CASE WHEN attribute = 'name' THEN value END) AS [name],
MAX(CASE WHEN attribute = 'type' THEN value END) AS [type],
MAX(CASE WHEN attribute = 'height' THEN value END) AS [height],
MAX(CASE WHEN attribute = 'width' THEN value END) AS [width]
FROM ItemAttributes AS ATR
WHERE itemid IN(5,6)
GROUP BY itemid
Note que debe especificar de manera explicita los valores en <column_list>. El operador PIVOT no provee
una opcin para derivarlos dinmicamente de pivot_column en una consulta esttica. Puede utilizar SQL
dinmico para construir la consulta usted mismo y obtener este resultado.
Llevando la consulta PIVOT anterior un paso ms all, suponga que desea devolver todos los atributos
relevantes a pinturas para cada tem de la subasta, y desea incluir aquellas que aparezcan en
AuctionItems y aquellas que aparezcan en ItemAttributes. Pruebe con la siguiente consulta, que le
devolver un error:
SELECT *
FROM AuctionItems AS ITM
JOIN ItemAttributes AS ATR
ON ITM.itemid = ATR.itemid
PIVOT
(
MAX(value)
FOR attribute IN([artist], [name], [type], [height], [width])
) AS PVT
WHERE itemtype = 'Painting'
El siguiente es el mensaje de error:
.Net SqlClient Data Provider: Msg 8156, Level 16, State 1, Line 1
The column 'itemid' was specified multiple times for 'PVT'.
Recuerde que PIVOT trabaja sobre table_expression, lo cual es la tabla virtual devuelta por la seccin en
la consulta entre la clusula FROM y la clusula PIVOT. En esta consulta, la tabla virtual contiene dos
instancias de la columna itemid - una originada de AuctionItems y la otra de ItemAttributes. Estar
tentado de utilizar la consulta como sigue, pero tambien obtendr un error.
SELECT ITM.itemid, itemtype, whenmade, initialprice,
[artist], [name], [type], [height], [width]
FROM AuctionItems AS ITM
JOIN ItemAttributes AS ATR
ON ITM.itemid = ATR.itemid
PIVOT
(
MAX(value)
FOR attribute IN([artist], [name], [type], [height], [width])
) AS PVT
WHERE itemtype = 'Painting'
El siguiente es el mensaje de error:
.Net SqlClient Data Provider: Msg 8156, Level 16, State 1, Line 1
The column 'itemid' was specified multiple times for 'PVT'.
.Net SqlClient Data Provider: Msg 107, Level 15, State 1, Line 1
The column prefix 'ITM' does not match with a table name or alias name
used in the query.
Como mencionamos ms adelante, el operador PIVOT trabaja sobre la tabla virtual devuelta por
table_expression y no sobre las columnas en select_list. Select_list es evaluada despus de que el
operador PIVOT realiza su manipulacin y solo puede referirse a group_by_list y column_list. Esta es la
razn por la cual el alias ITM no es ms reconocido en select_list. Si entiende esto, se dar cuenta que
debe proporcionar a PIVOT con una table_expression que contenga solo las columnas sobre las que quiere
trabajar. Esto incluye las columnas de agrupamiento (solo una ocurrencia de itemid ms itemtype,
whenmade y initialprice), la columna pivot (attribute), y la columna de valor (value). Puede lograr esto
utilizando CTEs o tablas derivadas. A continuacin puede encontrar un ejemplo utilizando una CTE:
WITH PNT
AS
(
SELECT ITM.*, ATR.attribute, ATR.value
FROM AuctionItems AS ITM
JOIN ItemAttributes AS ATR
ON ITM.itemid = ATR.itemid
WHERE ITM.itemtype = 'Painting'
)
SELECT * FROM PNT
PIVOT
(
MAX(value)
FOR attribute IN([artist], [name], [type], [height], [width])
) AS PVT
El resultado de este programa es:
itemid itemtype whenmade initialprice artist name
type height width
------ -------- -------- ------------ ---------------- ----------------
---- ------ -----
5 Painting 1873 8000000.0000 Claude Monet Field of Poppies
Oil 19.62 25.62
6 Painting 1889 8000000.0000 Vincent Van Gogh The Starry Night
Oil 28.75 36.25
El siguiente es un ejemplo utilizando una tabla derivada:
SELECT *
FROM (SELECT ITM.*, ATR.attribute, ATR.value
FROM AuctionItems AS ITM
JOIN ItemAttributes AS ATR
ON ITM.itemid = ATR.itemid
WHERE ITM.itemtype = 'Painting') AS PNT
PIVOT
(
MAX(value)
FOR attribute IN([artist], [name], [type], [height], [width])
) AS PVT
Tambien puede utilizar PIVOT cuando desee generar un reporte a travs de tabs para resumir los datos.
Por ejemplo, utilizando la tabla Orders en la base de datos Northwind, suponga que desea devolver el
numero de ordenes que cada empleado realizo por cada cliente, con los IDs de empleado en columnas.
Teniendo en cuenta que debe proveer al operador PIVOT solo la informacin relevante, usa una tabla
derivada y escribe la siguiente consulta:
SELECT CustomerID,
[1] AS Emp1, [2] AS Emp2, [3] AS Emp3, [4] AS Emp4, [5] AS Emp5,
[6] AS Emp6, [7] AS Emp7, [8] AS Emp8, [9] AS Emp9
FROM (SELECT OrderID, CustomerID, EmployeeID
FROM Orders) ORD
PIVOT
(
COUNT(OrderID)
FOR EmployeeID IN([1], [2], [3], [4], [5], [6], [7], [8], [9])
) AS PVT
El resultado de este programa es:
CustomerID Emp1 Emp2 Emp3 Emp4 Emp5 Emp6 Emp7 Emp8 Emp9
---------- ---- ---- ---- ---- ---- ---- ---- ---- ----
ALFKI 2 0 1 2 0 1 0 0 0
ANATR 0 0 2 1 0 0 1 0 0
ANTON 1 0 3 1 0 0 2 0 0
AROUT 3 0 2 4 0 1 0 1 2
BERGS 4 1 6 1 2 0 0 2 2
BLAUS 0 0 1 1 0 1 0 1 3
...
La funcin COUNT(OrderID) cuenta el nmero de filas por cada empleado en la lista. Note que PIVOT
rechaza el uso de COUNT(*). Los aliases de columnas se utilizan para brindar nombres ms descriptivos a
las columnas de resultados. Utilizar PIVOT para mostrar un orden de conteo para cada empleado en una
columna diferente es razonable cuando tiene un numero pequeo de empleados, cuyos IDs se saben de
antemano.
Tambin puede utilizar esta funcin con valores derivados de expresiones. Por ejemplo, suponga que
desea devolver el valor total de transporte de cada empleado en cada ao, pivotando los aos en
columnas. El orden de ao es derivado de la columna OrderDate:
SELECT EmployeeID, [1996] AS Y1996, [1997] AS Y1997, [1998] AS Y1998
FROM (SELECT EmployeeID, YEAR(OrderDate) AS OrderYear, Freight
FROM Orders) AS ORD
PIVOT
(
SUM(Freight)
FOR OrderYear IN([1996], [1997], [1998])
) AS PVT
El resultado de este programa es:
EmployeeID Y1996 Y1997 Y1998
----------- ------------------------- ------------------------- -------
--
8 1258.4000 2935.2800
3294.2000
4 1968.5900 6648.6000
2728.9500
3 880.0300 6918.3400
3086.3700
7 664.3200 3240.6600
2760.4600
6 766.1000 2114.1700
900.2000
2 973.1800 3796.4100
3926.8200
9 532.8400 1046.0900
1747.3300
5 1365.3700 1184.7500
1368.5900
1 1871.0400 4584.4700
2381.1300
Los reportes a travs de tabs son comunes en escenarios de data warehouse. Considere la siguiente tabla
OrdersFact, la cual puede poblar con rdenes y detalles de rdenes de Northwind:
CREATE TABLE OrdersFact
(
OrderID INT NOT NULL,
ProductID INT NOT NULL,
CustomerID NCHAR(5) NOT NULL,
EmployeeID INT NOT NULL,
OrderYear INT NOT NULL,
OrderMonth INT NOT NULL,
OrderDay INT NOT NULL,
Quantity INT NOT NULL,
PRIMARY KEY(OrderID, ProductID)
)

INSERT INTO OrdersFact
SELECT O.OrderID, OD.ProductID, O.CustomerID, O.EmployeeID,
YEAR(O.OrderDate) AS OrderYear, MONTH(O.OrderDate) AS OrderMonth,
DAY(O.OrderDate) AS OrderDay, OD.Quantity
FROM Northwind.dbo.Orders AS O
JOIN Northwind.dbo.[Order Details] AS OD
ON O.OrderID = OD.OrderID
Para obtener las cantidades totales por cada ao y mes, devolviendo aos en filas y meses en columnas,
debe utilizar la siguiente consulta:
SELECT *
FROM (SELECT OrderYear, OrderMonth, Quantity
FROM OrdersFact) AS ORD
PIVOT
(
SUM(Quantity)
FOR OrderMonth IN([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12])
) AS PVT
El resultado de este programa es:
OrderYear 1 2 3 4 5 6 7 8 9 10
11 12
--------- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -
---- -----
1997 2401 2132 1770 1912 2164 1635 2054 1861 2343 2679
1856 2682
1996 NULL NULL NULL NULL NULL NULL 1462 1322 1124 1738
1735 2200
1998 3466 3115 4065 4680 921 NULL NULL NULL NULL NULL
NULL NULL
PIVOT devuelve valores nulos para intersecciones no existentes entre ao y mes. Un ao aparece en el
resultado si aparece en la expresin de la tabla de entrada (la tabla derivada ORD), sin importar si tiene
una interseccin con cualquiera de los meses especificados. Esto significa que puede obtener una fila con
NULL en todas las columnas si no especifica los meses existentes. Sin embargo, los valores nulos en el
resultado no representan necesariamente intersecciones no existentes. Los mismos pueden resultar de
valores base nulos en la columna de cantidades, a menos que la columna rechace los valores nulos. Si
desea sobrescribir NULL y ver otro valor en su lugar, por ejemplo 0, puede lograrlo utilizando la funcin
ISNULL():
SELECT OrderYear,
ISNULL([1], 0) AS M01,
ISNULL([2], 0) AS M02,
ISNULL([3], 0) AS M03,
ISNULL([4], 0) AS M04,
ISNULL([5], 0) AS M05,
ISNULL([6], 0) AS M06,
ISNULL([7], 0) AS M07,
ISNULL([8], 0) AS M08,
ISNULL([9], 0) AS M09,
ISNULL([10], 0) AS M10,
ISNULL([11], 0) AS M11,
ISNULL([12], 0) AS M12
FROM (SELECT OrderYear, OrderMonth, Quantity
FROM OrdersFact) AS ORD
PIVOT
(
SUM(Quantity)
FOR OrderMonth IN([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12])
) AS PVT
El resultado de este programa es:
OrderYear M01 M02 M03 M04 M05 M06 M07 M08 M09 M10
M11 M12
--------- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -
---- -----
1997 2401 2132 1770 1912 2164 1635 2054 1861 2343 2679
1856 2682
1996 0 0 0 0 0 0 1462 1322 1124 1738
1735 2200
1998 3466 3115 4065 4680 921 0 0 0 0 0 0
0
Utilizando ISNULL (Quantity, 0) dentro de la tabla derivada solo se encargar de los valores base nulos en
la columna Quantity (si es que esta existe) y no de los valores nulos que PIVOT genero por intersecciones
no existentes.
Suponga que usted desea devolver las cantidades totales por cada empleado contra una combinacin de
valores de ao y mes durante el primer trimestre de cada ao. Para obtener valores de ao y mes en filas
y IDs de empleados en columnas, utilice la siguiente consulta:
SELECT *
FROM (SELECT EmployeeID, OrderYear, OrderMonth, Quantity
FROM OrdersFact
WHERE OrderMonth IN(1,2,3)) AS ORD
PIVOT
(
SUM(Quantity)
FOR EmployeeID IN([1],[2],[3],[4],[5],[6],[7],[8],[9])
) AS PVT
El resultado de este programa es:
OrderYear OrderMonth 1 2 3 4 5 6 7 8 9
--------- ----------- ---- ---- ---- ---- ---- ---- ---- ---- ----
1997 1 304 230 364 812 NULL 64 248 305 74
1998 1 397 252 745 586 344 97 265 543 237
1997 2 168 36 574 675 NULL 117 145 417 NULL
1998 2 566 290 662 554 276 129 328 13 297
1997 3 275 151 198 267 188 75 191 288 137
1998 3 467 558 578 481 157 288 307 912 317
El listado de agrupar implcito en este caso es OrderYear y OrderMonth, porque EmployeeID y Quantity
estn siendo utilizadas como las columnas pivot y valor, respectivamente.
Sin embargo, si desea que la combinacin de valores de ao y mes aparezca como columnas, debe
concatenarlas usted mismo antes de pasarlas al operador PIVOT, ya que solo puede haber una columna
pivot:
SELECT *
FROM (SELECT EmployeeID, OrderYear*100+OrderMonth AS YM, Quantity
FROM OrdersFact
WHERE OrderMonth IN(1,2,3)) AS ORD
PIVOT
(
SUM(Quantity)
FOR YM IN([199701],[199702],[199703],[199801],[199802],[199803])
) AS PVT
El resultado de este programa es:
EmployeeID 199701 199702 199703 199801 199802
199803
----------- ---------- ---------- ---------- ---------- ---------- ----
-
1 304 168 275 397 566 467
2 230 36 151 252 290 558
3 364 574 198 745 662 578
4 812 675 267 586 554 481
5 NULL NULL 188 344 276 157
6 64 117 75 97 129 288
7 248 145 191 265 328 307
8 305 417 288 543 13 912
9 74 NULL 137 237 297 317
UNPIVOT
El operador UNPIVOT le permite normalizar los datos pre pivot. La sintaxis y elementos del operador
UNPIVOT son similares a los del operador PIVOT.
Como ejemplo, considere la tabla AuctionItems en la seccin anterior:
itemid itemtype whenmade initialprice
----------- ------------------------ ----------- --------------
1 Wine 1822 3000.0000
2 Wine 1807 500.0000
3 Chair 1753 800000.0000
4 Ring -501 1000000.0000
5 Painting 1873 8000000.0000
6 Painting 1889 8000000.0000
Suponga que usted desea que cada atributo aparezca en una fila diferente de manera similar a la manera
en que se mantienen los atributos en la tabla ItemAttributes:
itemid attribute value
----------- --------------- -------
1 itemtype Wine
1 whenmade 1822
1 initialprice 3000.00
2 itemtype Wine
2 whenmade 1807
2 initialprice 500.00
3 itemtype Chair
3 whenmade 1753
3 initialprice 800000.00
4 itemtype Ring
4 whenmade -501
4 initialprice 1000000.00
5 itemtype Painting
5 whenmade 1873
5 initialprice 8000000.00
6 itemtype Painting
6 whenmade 1889
6 initialprice 8000000.00
En la consulta UNPIVOT, desea rotar las columnas itemtype, whenmade, e initialprice a filas. Cada fila
debe tener el ID, atributo y valor de tem. Los nuevos nombres de columnas que usted debe brindar son
attribute y value. Los mismos corresponden a pivot_column y value_column en el operador PIVOT. La
columna attribute debe tener la actual columna de nombres que usted desea rotar (itemtype, whenmade,
e initialprice) como valores. La columna value debe tener los valores de las tres diferentes columnas de
fuente en una sola columna de destino. Para ayudarlo a clarificar la situacin, primero se presenta una
versin de consulta UNPIVOT que no es valida, seguida de una valida en la cual se aplican algunas
restricciones:
SELECT itemid, attribute, value
FROM AuctionItems
UNPIVOT
(
value FOR attribute IN([itemtype], [whenmade], [initialprice])
) AS UPV
Como argumentos para el operador PIVOT, usted brinda el nombre para value_column (en este caso
value) seguido de la clusula FOR. Siguiendo a la clusula FOR, provea un nombre para pivot_column (en
este caso attribute) y luego una clusula IN con el listado de nombres de columna fuente que desea
obtener como valores en pivot_column. En el operador PIVOT, este listado de columna es referido a como
<column_list>. Esta consulta genera el siguiente error:
.Net SqlClient Data Provider: Msg 8167, Level 16, State 1, Line 1
Type of column 'whenmade' conflicts with the type of other columns
specified in the UNPIVOT list.
La columna de valor destino contiene valores originados de columnas con distintas Fuentes (aquellas que
aparecen en <column_list>). Debido a que el objetivo de todas las columnas de valores es una sola
columna, UNPIVOT necesita que todas las columnas en <column_list> tengan datos con el mismo tipo,
largo y precisin. Para cumplir con esta restriccin, puede brindar al operador UNPIVOT una expresin de
tabla que convierta las tres columnas al mismo tipo de datos. El tipo de dato sql_variant es un buen
candidato ya que puede convertir columnas de fuentes diferentes al mismo tipo de datos y aun as
mantener los tipos de datos originales. Al aplicar esta restriccin, ingrese la consulta anterior como
indicamos aqu y obtenga el resultado que desea:
SELECT itemid, attribute, value
FROM (SELECT itemid,
CAST(itemtype AS SQL_VARIANT) AS itemtype,
CAST(whenmade AS SQL_VARIANT) AS whenmade,
CAST(initialprice AS SQL_VARIANT) AS initialprice
FROM AuctionItems) AS ITM
UNPIVOT
(
value FOR attribute IN([itemtype], [whenmade], [initialprice])
) AS UPV
El tipo de dato en la columna de resultado attribute es sysname. Este es el tipo de datos que SQL Server
utiliza para almacenar nombres de objetos.
Note que el operador UNPIVOT elimina los valores nulos en la columna value del resultado; por lo tanto,
no puede ser considerada una operacin reversa exacta por el operador PIVOT.
Habiendo rotado las columnas en AuctionItems a filas, ahora puede unir el resultado de la operacin
UNPIVOT con las filas de ItemAttributes para proveer un resultado unificado:
SELECT itemid, attribute, value
FROM (SELECT itemid,
CAST(itemtype AS SQL_VARIANT) AS itemtype,
CAST(whenmade AS SQL_VARIANT) AS whenmade,
CAST(initialprice AS SQL_VARIANT) AS initialprice
FROM AuctionItems) AS ITM
UNPIVOT
(
value FOR attribute IN([itemtype], [whenmade], [initialprice])
) AS UPV

UNION ALL

SELECT *
FROM ItemAttributes

ORDER BY itemid, attribute
El resultado de este programa es:
itemid attribute value
----------- --------------- -------------
1 color Red
1 initialprice 3000.00
1 itemtype Wine
1 manufacturer ABC
1 type Pinot Noir
1 whenmade 1822
2 color Red
2 initialprice 500.00
2 itemtype Wine
2 manufacturer XYZ
2 type Porto
2 whenmade 1807
3 initialprice 800000.00
3 itemtype Chair
3 material Wood
3 padding Silk
3 whenmade 1753
4 initialprice 1000000.00
4 inscription One ring
4 itemtype Ring
4 material Gold
4 size 10
4 whenmade -501
5 height 19.625
5 initialprice 8000000.00
5 itemtype Painting
5 name Field of Poppies
5 artist Claude Monet
5 type Oil
5 whenmade 1873
5 width 25.625
6 height 28.750
6 initialprice 8000000.00
6 itemtype Painting
6 name The Starry Night
6 artist Vincent Van Gogh
6 type Oil
6 whenmade 1889
6 width 36.250
APPLY
El operador relacional APPLY le permite invocar una funcin de tabla de tipo de valor especfica una por
cada fila de una expresin de tabla externa. APPLY debe ser especificado en la clusula FROM de una
consulta, de manera similar a la manera en que se utiliza el operador relacional JOIN. APPLY esta
disponible en dos formatos: CROSS APPLY y OUTER APPLY. Con el operador APPLY, SQL Server "Yukon
Beta 1 le permite referirse a una funcin de tabla de tipo de valor en una sub consulta correlacionada.
CROSS APPLY
CROSS APPLY invoca una funcin de tabla de tipo de valor por cada fila en la expresin de tabla externa.
Puede referirse a columnas en la tabla externa como argumentos a la funcin de la tabla de tipo de valor.
CROSS APPLY devuelve un grupo de resultados unificados de todos los resultados devueltos por las
invocaciones individuales de la funcin de tabla de tipo de valor. Si la funcin devuelve un grupo vaco
para una fila externa, la fila externa no es devuelta en el resultado. Por ejemplo, la siguiente funcin de
tabla de tipo de valor acepta dos enteros como argumentos y devuelve una tabla con una fila, con los
valores mnimos y mximos como columnas:
CREATE FUNCTION dbo.fn_scalar_min_max(@p1 AS INT, @p2 AS INT) RETURNS
TABLE
AS
RETURN
SELECT
CASE
WHEN @p1 <@p2 THEN @p1 WHEN @p2 < @p1 THEN @p2 ELSE COALESCE(@p1,
@p2)
END AS mn, CASE WHEN @p1> @p2 THEN @p1
WHEN @p2 > @p1 THEN @p2
ELSE COALESCE(@p1, @p2)
END AS mx
GO

SELECT * FROM fn_scalar_min_max(10, 20)
El resultado de este programa es:
mn mx
----------- -----------
10 20
Dada la siguiente Tabla T1:
CREATE TABLE T1
(
col1 INT NULL,
col2 INT NULL
)

INSERT INTO T1 VALUES(10, 20)
INSERT INTO T1 VALUES(20, 10)
INSERT INTO T1 VALUES(NULL, 30)
INSERT INTO T1 VALUES(40, NULL)
INSERT INTO T1 VALUES(50, 50)
Usted desea invocar fn_scalar_min_max por cada fila en T1. Escriba la consulta CROSS APPLY de la
siguiente manera:
SELECT *
FROM T1 CROSS APPLY fn_scalar_min_max(col1, col2) AS M
El resultado de este programa es:
col1 col2 mn mx
----------- ----------- ----------- -----------
10 20 10 20
20 10 10 20
NULL 30 30 30
40 NULL 40 40
50 50 50 50
Si la funcin de tabla de tipo de valor devuelve mltiples filas por una cierta fila externa, la fila externa es
devuelta varias veces. Considere la tabla Employees utilizada con anterioridad en la seccin de consultas
recursivas (escenario de Organigrama de Empleados). En la misma base de datos, usted tambien crea la
siguiente tabla, Departments:
CREATE TABLE Departments
(
deptid INT NOT NULL PRIMARY KEY,
deptname VARCHAR(25) NOT NULL,
deptmgrid INT NULL REFERENCES Employees
)

SET NOCOUNT ON
INSERT INTO Departments VALUES(1, 'HR', 2)
INSERT INTO Departments VALUES(2, 'Marketing', 7)
INSERT INTO Departments VALUES(3, 'Finance', 8)
INSERT INTO Departments VALUES(4, 'R&D', 9)
INSERT INTO Departments VALUES(5, 'Training', 4)
INSERT INTO Departments VALUES(6, 'Gardening', NULL)
Muchos departamentos tienen un ID de gerente que corresponde a un empleado en la tabla Employees,
pero como en el caso del departamento de Gardening (Jardinera), es posible que un departamento no
tenga gerente. Note que un gerente en la tabla Employees necesariamente maneja un departamento. La
siguiente funcin de tabla de tipo de valor acepta un ID de empleado como argumento y devuelve a este
empleado y todos sus subordinados en todos los niveles:
CREATE FUNCTION dbo.fn_getsubtree(@empid AS INT) RETURNS @TREE TABLE
(
empid INT NOT NULL,
empname VARCHAR(25) NOT NULL,
mgrid INT NULL,
lvl INT NOT NULL
)
AS
BEGIN
WITH Employees_Subtree(empid, empname, mgrid, lvl)
AS
(
-- Anchor Member (AM)
SELECT empid, empname, mgrid, 0
FROM employees
WHERE empid = @empid

UNION all

-- Recursive Member (RM)
SELECT e.empid, e.empname, e.mgrid, es.lvl+1
FROM employees AS e
JOIN employees_subtree AS es
ON e.mgrid = es.empid
)
INSERT INTO @TREE
SELECT * FROM Employees_Subtree

RETURN
END
GO
Para devolver todos los subordinados en todos los niveles por el gerente de cada departamento, utilice la
siguiente consulta:
SELECT *
FROM Departments AS D
CROSS APPLY fn_getsubtree(D.deptmgrid) AS ST
El resultado de este programa es:
deptid deptname deptmgrid empid empname mgrid
lvl
----------- ---------- ----------- ----------- ---------- ----------- -
--
1 HR 2 2 Andrew 1 0
1 HR 2 5 Steven 2 1
1 HR 2 6 Michael 2 1
2 Marketing 7 7 Robert 3 0
2 Marketing 7 11 David 7 1
2 Marketing 7 12 Ron 7 1
2 Marketing 7 13 Dan 7 1
2 Marketing 7 14 James 11 2
3 Finance 8 8 Laura 3 0
4 R&D 9 9 Ann 3 0
5 Training 4 4 Margaret 1 0
5 Training 4 10 Ina 4 1
Existen dos cosas a tener en cuenta aqu. Primero, cada fila de Departments esta duplicada por la misma
cantidad de filas devueltas por fn_getsubtree en el departamento del gerente. Segundo, el departamento
Gardening no aparece en el resultado por fn_getsubtree devolvi un grupo vaco.
Otro uso prctico del operador CROSS APPLY responde a un pedido comn: querer devolver n filas por
cada grupo. Como ejemplo, la siguiente funcin devuelve el nmero solicitado de las rdenes ms
recientes de un cliente en particular:
USE Northwind
GO

CREATE FUNCTION fn_getnorders(@custid AS NCHAR(5), @n AS INT)
RETURNS TABLE
AS
RETURN
SELECT TOP(@n) *
FROM Orders
WHERE CustomerID = @custid
ORDER BY OrderDate DESC
GO
Utilizando el operador CROSS APPLY, puede obtener las dos rdenes ms recientes de cada cliente
utilizando la siguiente consulta sencilla:
SELECT O.*
FROM Customers AS C
CROSS APPLY fn_getnorders(C.CustomerID, 2) AS O
Para ms informacin acerca de la mejora a TOP, vea "Mejoras a TOP en este documento.
OUTER APPLY
OUTER APPLY es muy parecido a CROSS APPLY, con el agregado que tambin devuelve filas de la tabla
externa por las cuales la funcin de tabla de tipo de valor devolvi un grupo vaco. Los valores nulos son
devueltos como los valores de columnas que corresponden a las columnas de la funcin de tabla de tipo
de valor. Como ejemplo, revise la consulta contra la tabla Departments de la seccin anterior para utilizar
OUTER APPLY en lugar de CROSS APPLY y vea la ltima fila del resultado:
SELECT *
FROM Departments AS D
OUTER APPLY fn_getsubtree(D.deptmgrid) AS ST
El resultado de este programa es:
deptid deptname deptmgrid empid empname mgrid
lvl
----------- ---------- ----------- ----------- ---------- ----------- -
--
1 HR 2 2 Andrew 1 0
1 HR 2 5 Steven 2 1
1 HR 2 6 Michael 2 1
2 Marketing 7 7 Robert 3 0
2 Marketing 7 11 David 7 1
2 Marketing 7 12 Ron 7 1
2 Marketing 7 13 Dan 7 1
2 Marketing 7 14 James 11 2
3 Finance 8 8 Laura 3 0
4 R&D 9 9 Ann 3 0
5 Training 4 4 Margaret 1 0
5 Training 4 10 Ina 4 1
6 Gardening NULL NULL NULL NULL NULL
Utilizando funciones de tablas de tipo de valor en sub consultas correlacionadas
En SQL Server 2000, no puede referirse a funciones de tablas de tipo de valor dentro de una sub consulta
correlacionada. Junto con brindar el operador relacional APPLY, esta restriccin es removida. Ahora,
dentro de una sub consulta, usted puede brindar una funcin de tabla de tipo de valor con columnas de la
consulta externa como argumentos. Por ejemplo, si desea devolver solo aquellos departamentos cuyo
gerente tiene por lo menos tres empleados, puede escribir la siguiente consulta:
SELECT *
FROM Departments AS D
WHERE (SELECT COUNT(*)
FROM fn_getsubtree(D.deptmgrid)) >= 3

deptid deptname deptmgrid
----------- ------------------------- -----------
1 HR 2
2 Marketing 7
Soporte para Acciones New Declarative Referential Integrity (DRI) SET
DEFAULT y SET NULL
ANSI SQL define cuatro posibles acciones referenciales en soporte para una restriccin FOREIGN KEY.
Dichas acciones se especifican para indicar como desea que su sistema reaccione en respuesta a las
operaciones DELETE o UPDATE contra una tabla que esta referenciada por una clave externa. SQL Server
2000 soporta dos de estas acciones: NO ACTION y CASCADE. SQL Server "Yukon Beta 1 agrega el
soporte para las acciones referenciales SET DEFAULT y SET NULL.
Las acciones referenciales SET DEFAULT y SET NULL extienden las capacidades DRI. Puede utilizar estas
opciones en conjunto con las clusulas ON UPDATE y ON DELETE en una declaracin de clave externa. SET
DEFAULT significa que, en una tabla referenciada, elimina filas (ON DELETE) o actualiza la clave
referenciada (ON UPDATE), SQL Server establece los valores de referencia de columna de las filas
relacionadas en la tabla de referencia al valor predeterminado de la columna. De manera similar, SQL
Server puede reaccionar estableciendo los valores a NULL si utiliza la opcin SET NULL, siempre que la
columna de referencia permita valores nulos.
Como ejemplo, la siguiente tabla Customers tiene tres clientes reales y uno ficticio:
CREATE TABLE Customers
(
customerid CHAR(5) NOT NULL,
/* other columns */
CONSTRAINT PK_Customers PRIMARY KEY(customerid)
)

INSERT INTO Customers VALUES('DUMMY')
INSERT INTO Customers VALUES('FRIDA')
INSERT INTO Customers VALUES('GNDLF')
INSERT INTO Customers VALUES('BILLY')
La tabla Orders realiza el seguimiento de las rdenes. Una orden no necesariamente tiene que estar
asignada a un cliente real. Si ingresa una orden y no especifica un ID de cliente, a la orden se le asigna de
manera predeterminada el ID de cliente DUMMY. Al eliminar alguna variable de la tabla Customer, usted
desea que SQL Server establezca NULL en la columna customerid de las filas relacionadas a esta en
Orders. Las rdenes con valores NULL en la columna customerid se vuelven "hurfanas, esto significa que
no pertenecen a ningn cliente. Suponga que tambien desea permitir actualizaciones a la columna
customerid en Customers. Querr acomodar la actualizacin a las filas relacionadas en Orders, pero
suponga que una regla de negocios de su compaa dicte lo contrario: las ordenes correspondientes a un
cliente cuyo ID fue cambiado deben estar relacionadas con el cliente predeterminado (DUMMY). Despus
de una actualizacin de la columna customerid en Customers, usted desea que SQL establezca el valor
predeterminado "DUMMY a los IDs de clientes relaciones (customerid) en Orders. Usted crea la tabla
Orders con una clave externa de la siguiente manera y la puebla con algunas ordenes:
CREATE TABLE Orders
(
orderid INT NOT NULL,
customerid CHAR(5) NULL DEFAULT('DUMMY'),
orderdate DATETIME NOT NULL,
CONSTRAINT PK_Orders PRIMARY KEY(orderid),
CONSTRAINT FK_Orders_Customers
FOREIGN KEY(customerid)
REFERENCES Customers(customerid)
ON DELETE SET NULL
ON UPDATE SET DEFAULT
)

INSERT INTO Orders VALUES(10001, 'FRIDA', '20040101')
INSERT INTO Orders VALUES(10002, 'FRIDA', '20040102')
INSERT INTO Orders VALUES(10003, 'BILLY', '20040101')
INSERT INTO Orders VALUES(10004, 'BILLY', '20040103')
INSERT INTO Orders VALUES(10005, 'GNDLF', '20040104')
INSERT INTO Orders VALUES(10006, 'GNDLF', '20040105')
Para evaluar las opciones SET NULL y SET DEFAULT, establezca las siguientes declaraciones DELETE y
UPDATE.
DELETE FROM Customers
WHERE customerid = 'FRIDA'

UPDATE Customers
SET customerid = 'DOLLY'
WHERE customerid = 'BILLY'
Como resultado, las rdenes de FRIDA son asignadas con valores nulos en la columna customerid y las
ordenes de BILLY con DUMMY:
orderid customerid orderdate
----------- ---------- ----------------------
10001 NULL 1/1/2004 12:00:00 AM
10002 NULL 1/2/2004 12:00:00 AM
10003 DUMMY 1/1/2004 12:00:00 AM
10004 DUMMY 1/3/2004 12:00:00 AM
10005 GNDLF 1/4/2004 12:00:00 AM
10006 GNDLF 1/5/2004 12:00:00 AM
Note que si utiliza la opcin SET DEFAULT y la columna de referencia tiene un valor predeterminado no
nulo que no tiene un valor correspondiente en la tabla referenciada, obtendr un error al correr la accin.
Por ejemplo, si elimina el cliente DUMMY de Customers y luego actualiza el customerid de GNDLF a
GLDRL, obtendr un error. La accin UPDATE dispara una accin SET DEFAULT que intenta asignar las
rdenes originales de GNDLF con el ID de cliente DUMMY que no tiene una fila correspondiente en
Customers:
DELETE FROM Customers
WHERE customerid = 'DUMMY'

UPDATE Customers
SET customerid = 'GLDRL'
WHERE customerid = 'GNDLF'

.Net SqlClient Data Provider: Msg 547, Level 16, State 0, Line 1
UPDATE statement conflicted with COLUMN FOREIGN KEY constraint
'FK_Orders_Customers'.
The conflict occurred in database 'tempdb', table 'Customers', column
'customerid'.
The statement has been terminated.
Puede encontrar ms informacin acerca de claves externas, incluyendo sus acciones referenciales
definidas, viendo sys.foreign_keys.
Mejoras de Performance y Manejo de Errores
Esta seccin cubre las mejoras enfocadas a problemas de performance en versiones previas de SQL
Server, aumentar sus capacidades de carga de informacin, y mejorar dramticamente las capacidades de
manejo de errores. Estas mejoras incluyen el proveedor rowset BULK y bloque de manejo de errores
TRY/CATCH.
Manejo de Excepciones en Transacciones
SQL Server "Yukon" Beta 1 presenta un mecanismo de manejo de excepciones simple pero muy poderoso
en el formato de un bloque TRY/CATCH Transact-SQL.
Versiones anteriores de SQL Server requeran que usted incluyera cdigo de manejo de errores despus
de cada declaracin que fuera sospechada de error. Para centralizar el cdigo de chequeo de errores, debe
utilizar etiquetas y la declaracin GOTO. Adems, ejemplos de errores como errores de conversin de tipo
de datos causaban que el batch fuera finalizado, por lo tanto, no poda identificar dichos errores con
Transact-SQL. SQL Server "Yukon Beta 1 resuelve muchos de estos problemas.
Errores de cancelacin de transacciones que solan causar la terminacin de un batch ahora pueden ser
identificados y resueltos, siempre que estos errores no causen un corte en la conexin (errores tpicos con
severidad 21 o ms; por ejemplo, errores de hardware, etc.).
Escriba el cdigo que desea ejecutar comenzando con un bloque BEGIN TRY/END TRY y contine con el
cdigo de manejo de error en un bloque BEGIN CATCH TRAN_ABORT/END CATCH. Note que un bloque
TRY debe tener su correspondiente bloque CATCH; de otra manera obtendr un error de sintaxis. Como
ejemplo, considere la siguiente tabla T1:
CREATE TABLE T1(col1 INT NOT NULL PRIMARY KEY CHECK (col1 > 0))
Usted desea escribir una transaccin que inserte una fila en una tabla y tambien realizar otras actividades.
Asimismo, tambien desea responder a una situacin de falla con alguna actividad correctiva. Debido a que
el nuevo bloque de manejo de errores se encarga de los errores de aborto de transaccin, debe comenzar
la sesin estableciendo XACT_ABORT en ON, para que cualquier error sea tratado como un error de aborto
de transaccin. Utilice el siguiente bloque TRY/CATCH de esta manera:
SET XACT_ABORT ON

BEGIN TRY
BEGIN TRAN
INSERT INTO T1 VALUES(1)
/* perform other activity */
PRINT 'After INSERT.'
COMMIT
END TRY
BEGIN CATCH TRAN_ABORT
ROLLBACK
/* perform corrective activity */
PRINT 'INSERT failed.'
END CATCH
Cuando corra este cdigo por primera vez, debera obtener un mensaje indicando que el punto 'After
INSERT' ha sido alcanzado. Cuando lo corra por segunda vez, debera obtener lo siguiente:
.Net SqlClient Data Provider: Msg 2627, Level 14, State 1, Line 1
Violation of PRIMARY KEY constraint 'PK__T1__45BE5BA9'.
Cannot insert duplicate key in object 'T1'.
INSERT failed.
Si el cdigo dentro del bloque TRY se completa sin errores, se pasa el control a la primera declaracin
siguiendo el correspondiente bloque CATCH. Cuando una declaracin dentro de la transaccin en el bloque
TRY falla, se pasa el control a la primera declaracin dentro del bloque CATCH correspondiente. Despus
de esta falla, la transaccin abierta ingresa un nuevo estado "condenado, y la transaccin pasa a
llamarse "transaccin condenada. Una transaccin condenada no puede ser comprometida. Tampoco se
pueden realizar trabajos que causen escritura al log de la transaccin. Para ingresar una nueva
transaccin, primero debe emitir una declaracin ROLLBACK para terminar la transaccin condenada. Es
por eso que el cdigo anterior emite una declaracin ROLLBACK en el bloque CATCH.
Como ejemplo ms detallado, el siguiente cdigo reacciona de manera diferente dependiendo del tipo de
error que haya causado la falla, y tambien imprime mensajes indicando que partes del cdigo han sido
activadas:
SET XACT_ABORT ON

PRINT 'Before TRY/CATCH block.'

BEGIN TRY

PRINT ' Entering TRY block.'

BEGIN TRAN
DECLARE @val AS INT
SET @val = 10 -- also try with 0, NULL, 'a'
INSERT INTO T1 VALUES(@val)
PRINT ' After INSERT.'
COMMIT

PRINT ' Exiting TRY block.'

END TRY
BEGIN CATCH TRAN_ABORT

DECLARE @err AS INT
SET @err = @@error

PRINT ' Entering CATCH block.'
PRINT ' ' + CAST(@err AS VARCHAR(10)) + ' error occurred.'

ROLLBACK

IF @err = 2627
BEGIN
PRINT ' Handling PK violation...'
END
ELSE IF @err = 547
BEGIN
PRINT ' Handling CHECK constraint violation...'
END
ELSE IF @err = 515
BEGIN
PRINT ' Handling NULL violation...'
END
ELSE IF @err = 245
BEGIN
PRINT ' Handling conversion error...'
END

PRINT ' Exiting CATCH block.'

END CATCH

PRINT 'After TRY/CATCH block.'
Si desea examinar @@error en el bloque CATCH, debe asignar su valor a una variable antes de ejecutar
otra declaracin excepto DECLARE, ya que todas las otras declaraciones cambian su valor. Este cdigo
debera completar sin errores al correrlo por primera vez, generando el siguiente resultado:
Before TRY/CATCH block.
Entering TRY block.
After INSERT.
Exiting TRY block.
After TRY/CATCH block.
Note que se salto el bloque CATCH. Correr este cdigo por segunda vez debera generar el siguiente
resultado:
Before TRY/CATCH block.
Entering TRY block.
.Net SqlClient Data Provider: Msg 2627, Level 14, State 1, Line 28
Violation of PRIMARY KEY constraint 'PK__T1__45BE5BA9'.
Cannot insert duplicate key in object 'T1'.
Entering CATCH block.
2627 error occurred.
Handling PK violation...
Exiting CATCH block.
After TRY/CATCH block.
El mensaje generado por SQL Server aparece en negrita para distinguirlo de los mensajes generados por
el comando PRINT. Note que el bloque TRY fue ingresado pero no completado. Como resultado del primer
error de violacin de clave, el control fue pasado al bloque CATCH, el cual identifico y resolvi el error. De
manera similar, si asigna otros valores que no son validos a la variable @val, como por ejemplo 0, el cual
viola la restriccin CHECK; NULL, que no est permitido en col1; y 'a', que no puede ser convertido en
INT; obtendr el error apropiado, y el cdigo de manejo apropiado ser activado.
Note que si desea que todos los errores (tanto severos como no severos) sean tratados como errores de
cancelacin de transacciones que pasen el control al bloque CATCH, la opcin de sesin XACT_ABORT
debe ser encendida como mencionamos con anterioridad. De otra manera, los errores no severos como
por ejemplo la violacin de clave primaria no pasarn el control al bloque CATCH. Por ejemplo, si corre el
cdigo de arriba poniendo en OFF la opcin XACT_ABORT, obtendr el siguiente resultado:
Before TRY/CATCH block.
Entering TRY block.
.Net SqlClient Data Provider: Msg 2627, Level 14, State 1, Line 42
Violation of PRIMARY KEY constraint 'PK__T1__45BE5BA9'.
Cannot insert duplicate key in object 'T1'.
The statement has been terminated.
After INSERT.
Exiting TRY block.
After TRY/CATCH block.
Debido a que una violacin de clave primaria no es considerado un error de cancelacin de transaccin y
XACT_ABORT esta determinado en OFF, el control no es pasado al bloque CATCH, y el cdigo en el bloque
TRY continua corriendo ms all del punto de error. Por otro lado, si intenta asignar el valor 'a' la variable
@val , esto causara un error de cancelacin de transaccin sin importar si XACT_ABORT esta en ON u
OFF; y el control seria pasado al bloque CATCH despus del error:
Before TRY/CATCH block.
Entering TRY block.
.Net SqlClient Data Provider: Msg 245, Level 16, State 1, Line 43
Syntax error converting the varchar value 'a' to a column of data type
int.
Entering CATCH block.
245 error occurred.
Handling conversion error...
Exiting CATCH block.
After TRY/CATCH block.
Los bloques TRY/CATCH pueden ser anidados. Un error en un bloque TRY pasa el control al bloque CATCH
siguiente ms cercano. Un error en un bloque CATCH es tratado de la misma manera que cualquier otro
error; esto significa que si se encuentra dentro de un bloque TRY y es considerado un error de cancelacin
de transaccin, el control ser pasado al bloque CATCH ms cercano. Sin embargo, los bloques
TRY/CATCH no pueden abarcar batches ni declaraciones compuestas. Por ejemplo, usted puede definir un
bloque TRY/CATCH completo dentro del mismo bloque ELSE o IF, pero no de manera dinmica a travs de
varios de ellos.
Nota El comando RAISERROR tiene una nueva opcin llamada TRAN_ABORT, la cual permite elevar los
errores de cancelacin de transaccin.
Otras Capacidades de SQL Server "Yukon" Beta 1 que Afectan a
Transact-SQL.
Esta seccin describe brevemente otras mejoras en SQL Server "Yukon Beta1 que afectan Transact-SQL.
Las mismas incluyen mejoras a XML/XQuery, Queuing y SQL Server Service Broker, disparadores DDL,
Eventos y Notificaciones DML.
Mejoras TOP
En la versin SQL Server 7.0 y SQL Server 2000, la opcin TOP le permita limitar el nmero de
porcentajes de lneas devueltas por una consulta SELECT; sin embargo, debe proveer una constante como
un argumento. En SQL Server "Yukon Beta 1, TOP es mejorada de la siguiente manera:
Puede especificar una expresin numrica que devuelva el nmero o porcentaje de lneas a ser
afectadas por su consulta, usando variables y sub consultas de manera opcional.
Puede utilizar la opcin TOP en consultas DELETE, UPDATE e INSERT.
La nueva sintaxis para consultas utilizando TOP es:
SELECT [TOP (< expression>) [PERCENT] [WITH TIES]]
FROM ...[ORDER BY...]

DELETE [TOP (< expression>) [PERCENT]] FROM ...

UPDATE [TOP (< expression>) [PERCENT]] SET ...

INSERT [TOP (< expression>) [PERCENT]] INTO ...
La expresin numrica debe ser especificada entre parntesis. Especificar constantes sin parntesis esta
soportado en consultas SELECT solo por compatibilidad hacia atrs. La expresin debe estar contenida en
si misma - si utiliza una sub consulta no puede referirse a columnas en la tabla en la consulta externa. Si
no especifica la opcin PERCENT, la expresin debe ser convertible implcitamente al tipo de datos bigint.
Si especifica la opcin PERCENT; la expresin debe ser convertible implcitamente a float y caer dentro
del rango de 0 a 100. La opcin WITH TIES y la clusula ORDER BY son soportadas solo en consultas
SELECT.
El siguiente cdigo, por ejemplo, utiliza una variable como argumento para la opcin TOP, devolviendo el
nmero especificado de rdenes ms recientes:
USE Northwind

DECLARE @n AS BIGINT
SET @n = 2

SELECT TOP(@n) *
FROM Orders
ORDER BY OrderDate DESC
Esta mejora es til especialmente cuando obtiene el nmero de filas solicitadas como un argumento a un
procedimiento almacenado o a una funcin definida por el usuario. Al utilizar una sub consulta auto
contenida, puede responder pedidos dinmicos como "calcular el nmero promedio de rdenes mensuales
y devolver las ordenes ms recientes.
USE Northwind

SELECT TOP(SELECT
COUNT(*)/DATEDIFF(month, MIN(OrderDate), MAX(OrderDate))
FROM Orders) *
FROM Orders
ORDER BY OrderDate DESC
La opcin SET ROWCOUNT en versiones anteriores de SQL Server le permite limitar el nmero de filas
afectadas por una consulta. Por ejemplo, SET ROWCOUNT se usa comnmente para purgar
peridicamente grandes cantidades de datos en varias transacciones pequeas en lugar de una
transaccin grande.
SET ROWCOUNT 1000

DELETE FROM BigTable WHERE datetimecol <'20000101' WHILE @@rowcount> 0
DELETE FROM BigTable WHERE datetimecol <'20000101' SET ROWCOUNT 0
Utilizando SET ROWCOUNT de esta manera permite reciclar y realizar una copia de seguridad del log de la
transaccin durante el proceso de purga, y tambien puede prevenir bloqueo de escalacin. En lugar de
utilizar SET ROWCOUNT, ahora puede utilizar TOP de la siguiente manera:
DELETE TOP(1000) FROM BigTable WHERE datetimecol <'20000101' WHILE
@@rowcount> 0
DELETE TOP(1000) FROM BigTable WHERE datetimecol <'20000101'
Cuando utiliza la opcin TOP, el optimizador puede decirle cual es el "objetivo de filas y si TOP es
utilizado, permitindole al optimizador producir planes ms eficientes.
A pesar de que pueda pensar que utilizar TOP en declaraciones INSERT no es necesario porque siempre
puede especificarlo en la consulta SELECT, puede encontrar til el insertarlo en el resultado de un
comando EXEC o el resultado de una operacin UNION, por ejemplo:
INSERT TOP ... INTO ...
EXEC ...

INSERT TOP ... INTO ...
SELECT ... FROM T1
UNION ALL
SELECT ... FROM T2
ORDER BY ...
XML y XQuery
SQL Server "Yukon" Beta 1 presenta varias mejoras relacionadas con XML que le permiten almacenar,
consultar y actualizar datos estructurados XML de manera nativa. Usted puede almacenar datos XML y
relacionales en la misma base de datos, aprovechando el motor de base de datos existente para
almacenamiento y procesamiento de consultas.
Presentamos un nuevo tipo de dato xml. El tipo de datos xml puede ser utilizado para columnas de tablas
y puede ser indexado. Xml tambien puede ser utilizado en variables, vistas, funciones y procedimientos
almacenados. Xml puede ser generado por consultas relacionales FOR XML o accedido como un rowset
relacional utilizando OPENXML. Puede importar esquemas a su base de datos o exportar esquemas de la
misma. Puede utilizar esquemas para validar y restringir sus datos XML. Puede consultar y modificar datos
XML tipeados utilizando XQuery. El tipo de dato xml es soportado en disparadores, replicacin, copia
bulk, DBCC, y bsqueda de texto completo. Sin embargo, xml no es comparable, lo que significa que no
puede definir una restriccin PRIMARY KEY, UNIQUE o FOREIGN KEY en una columna xml.
Los siguientes ejemplos utilizan el tipo de datos xml. El siguiente cdigo define una variable XML llamada
@x y carga datos de rdenes de clientes:
USE Northwind

DECLARE @x AS XML
SET @x = (SELECT C.CustomerID, O.OrderID
FROM Customers C
JOIN Orders O
ON C.CustomerID=O.CustomerID
ORDER BY C.CustomerID
FOR XML AUTO, TYPE)
SELECT @x
El siguiente cdigo crea una tabla con una columna xml y cargas bulk un archivo XML en ella utilizando la
funcin OPENROWSET:
CREATE TABLE T1
(
keycol INT NOT NULL PRIMARY KEY,
xmldoc XML NULL
)

INSERT INTO T1(keycol, xmldoc)
SELECT 1 AS keycol, xmldoc
FROM OPENROWSET(BULK 'C:\documents\mydoc.xml', SINGLE_NCLOB)
AS X(xmldoc)
SQL Server "Yukon" Beta 1 tambin presenta soporte para XQuery, el cual es un lenguaje para consultas
de acuerdo con los estndares de W3C. Microsoft brinda extensiones al estndar en SQL Server que
permiten utilizar XQuery para encartes, actualizaciones y eliminaciones. XQuery esta embebido con
Transact-SQL por medio de mtodos de estilo de tipo definidos por el usuario (UDT por sus siglas en
ingles).
XQuery brinda los siguientes mtodos para consultas:
Manipulando datos XML: @x::query (xquery string) devolviendo XML
Verificando existencia: @x::exist (xquery string) devolviendo bit
Devolviendo un valor escalar: @x::value (xquery string, sql_type string) devolviendo
sql_type
XQuery brinda el siguiente mtodo de modificacin: @x::modify (xDML string).
Como ejemplo, existe una tabla llamada Jobs que contiene informacin de trabajos formateada en XML en
una columna llamada jobinfo. La siguiente consulta devuelve el ID y datos XML luego de alguna
manipulacin en cada columna que cumpla con el criterio. El mtodo jobinfo::exist() es invocado en la
clusula WHERE para filtrar las filas que desea. Devuelve 1 solo para filas cuya columna jobinfo contiene
el elemento editado con un atributo date que es mayor que la variable Transact-SQL @date. Por cada fila
devuelta, se genera un resultado XML al invocar el mtodo jobinfo::query(). Para cada elemento de
trabajo encontrado en jobinfo, el mtodo query() genera un elemento <jobschedule>, con un atributo de
id basado en el atributo de id del trabajo, y los sub elementos <begin> y <end> con datos basados en
los atributos start y end de jobinfo:
SELECT id, jobinfo::query(
'for $j in //job
return
< jobschedule id="{$j/@id}">
< begin>{data($j/@start)}< /begin>
< end>{data($j/@end)}< /end>
< /jobschedule>')
FROM Jobs
WHERE 1 = jobinfo::exist(
'//edited[@date > sql:variable("@date")]')
La siguiente invocacin del mtodo value() devuelve el atributo start del primer trabajo en jobinfo en el
formato datetime en Transact-SQL:
SELECT id, jobinfo::value('(//job)[1]/@start', 'DATETIME') AS startdt
FROM Jobs
WHERE id = 1
XQuery tambien puede ser utilizado para modificar datos. Por ejemplo, usted utiliza el siguiente cdigo
para actualizar la columna XML empinfo en la tabla Employees para el empleado 1. Actualiza el atributo
date del sub elemento editado del elemento resume a un nuevo valor:
UPDATE Employees SET empinfo::modify(
'update /resume/edited/@date
to xs:date("2000-6-20")')
WHERE empid = 1
Disparadores DDL
En versiones anteriores de SQL Server, usted poda definir disparadores AFTER solo para declaraciones
DML (INSERT, UPDATE y DELETE) emitidas contra una tabla. SQL Server "Yukon Beta 1 le permite definir
disparadores para eventos DDL con un panorama completo del servidor o base de datos. Puede definir un
disparador DDL para una declaracin como CREATE_TABLE, o para un grupo de declaraciones como
DDL_DATABASE_LEVEL_EVENTS. Dentro del disparador, puede obtener datos relacionados con el evento
que lo disparo accediendo a la funcin eventdata(). Esta funcin devuelve datos XML sobre el evento, y
el esquema por cada evento hereda el esquema base de eventos del Servidor.
La informacin de eventos incluye:
Cuando sucedi el evento
EL SPID desde el cual se emiti el evento
El tipo de evento
El objeto afectado
Opciones SET
La declaracin de Transact-SQL que lo disparo
Similar a los disparadores en versiones anteriores de SQL Server, los disparadores DDL corren en el
contexto de la transaccin que los dispara. Si decide deshacer el evento que dispar el disparador, puede
emitir una declaracin ROLLBACK. Por ejemplo, el siguiente disparador previene la creacin de tablas
nuevas en la base de datos actual:
CREATE TRIGGER trg_capture_create_table ON DATABASE FOR CREATE_TABLE
AS

-- PRINT event information For DEBUG
PRINT 'CREATE TABLE Issued'
PRINT EventData()

-- Can investigate data returned by EventData() and react accordingly.

RAISERROR('New tables cannot be created in this database.', 16, 1)
ROLLBACK
GO
Si emite una declaracin CREATE TABLE dentro de la base de datos en la cual el disparador fue creado,
debera obtener el siguiente resultado:
CREATE TABLE T1(col1 INT)

CREATE TABLE Issued

<EVENT_INSTANCE>
<PostTime>2003-04-17T13:55:47.093</PostTime>
<SPID>53</SPID>
<EventType>CREATE_TABLE</EventType>
<Database>testdb</Database>
<Schema>dbo</Schema>
<Object>T1</Object>
<ObjectType>TABLE</ObjectType>
<TSQLCommand>
<SetOptions ANSI_NULLS="ON" ANSI_NULL_DEFAULT="ON"
ANSI_PADDING="ON"
QUOTED_IDENTIFIER="ON" ENCRYPTED="FALSE" />
<CommandText>CREATE TABLE T1(col1 INT)</CommandText>
</TSQLCommand>
</EVENT_INSTANCE>

.Net SqlClient Data Provider: Msg 50000, Level 16, State 1, Procedure
trg_capture_create_table, Line 10
New tables cannot be created in this database.

.Net SqlClient Data Provider: Msg 3609, Level 16, State 1, Line 1
Transaction ended in trigger. Batch has been aborted.
Note que el resultado XML ms arriba fue formateado de manera manual con el propsito de una lectura
ms sencilla. Al correr este cdigo, usted obtendr un resultado XML sin formato.
Para cancelar el disparador, emita la siguiente declaracin:
DROP TRIGGER trg_capture_create_table ON DATABASE
Eventos y Notificaciones DML
SQL Server "Yukon Beta 1 le permite capturar DDL, DML, y eventos de sistema y enviar una Notificacin
de Eventos a un Service Broker Deployment. Contrariamente a lo que sucede con los disparadores que
son procesados de manera sincrnica, las notificaciones de eventos son un mecanismo de entrega de
eventos que permite la consumicin asincrnica. Una Notificacin de Evento enva datos XML a un servicio
Service Broker especfico, y los consumidores del evento lo consumen de manera asincrnica. Un
consumidor de evento puede esperar la llegada de nuevos datos utilizando extensiones a la clusula
WAITFOR que se describe con anterioridad.
Una Notificacin de Evento es definida por:
Panorama (SERVIDOR, BASE DE DATOS, objeto individual)
Listado de eventos o grupos de eventos (por ejemplo, INSERT, DML_EVENTS, CREATE_TABLE,
DDL_EVENTS, y as sucesivamente)
Nombre de instalacin que implementa el tipo de mensaje y contrato de SQL Server Events.
Los datos de un evento son enviados en formato XML utilizando el esquema de eventos de SQL Server. La
sintaxis general para crear una notificacin de eventos es la siguiente:
CREATE EVENT NOTIFICATION <name>
ON <scope>
FOR <list_of_event_or_event_groups>
TO SERVICE <deployment_name>
Cuando se crea una Notificacin de Evento, se establece una Service Broker Conversation entre una
instalacin de sistema y la instalacin especificada por el usuario. El <deployment_name> especifica el
Service Broker con el cual SQL Server inicia una conversacin para brindar informacin acerca de un
evento. La instalacin especificada debe implementar el tipo de mensaje y contrato de SQL Server Events.
Cuando ocurre un evento para el cual existe una Notificacin de Evento, se construye un mensaje XML con
los datos pertinentes del evento y se enva por medio de una conversacin de Notificacin de Evento a la
instalacin especificada.
Como ejemplo, el siguiente cdigo define una notificacin de evento que enva una notificacin a cierta
instalacin cada vez que se emite un INSERT contra la tabla denominada T1:
CREATE QUEUE Data_Change_Queue

CREATE SERVICE Data_Changes
ON QUEUE Data_Change_Queue
( [//s.ms.net/SQL/Notifications/PostEventNotification/v1.0] )

CREATE EVENT NOTIFICATION notify_INSERT_T1
ON TABLE T1
FOR INSERT
TO SERVICE Data_Changes
El siguiente INSERT causara que se enve un mensaje XML al servicio Data_Changes, el cual esta
construido sobre el Data_Change_Queue.
INSERT_T1 VALUES (1)
El mensaje XML podra ser extrado de la fila con la siguiente declaracin:
RECEIVE TOP (1) CAST(message_body AS nvarchar(MAX))
FROM Data_Change_Queue
El resultado sera el siguiente (menos el formato):
<EVENT_INSTANCE>
<PostTime>2003-05-15T19:36:40.900</PostTime>
<SPID>57</SPID>
<EventType>INSERT</EventType>
<Database>tempdb</Database>
<Schema>dbo</Schema>
<Object>T1</Object>
<ObjectType>TABLE</ObjectType>
<Rows>
<inserted col1="1"/>
</Rows>
</EVENT_INSTANCE>
Los eventos disparados para declaraciones INSERT que se ejecutan con xito producen datos XML que
incluyen un elemento <Inserted> por cada fila insertada. De manera similar, los datos XML para
declaraciones DELETE incluyen un elemento <Deleted> por cada fila eliminada. Los datos XML para
declaraciones UPDATE incluyen elementos <Deleted> e <Inserted> con las imgenes "antes de la
actualizacin y "despus de la actualizacin de las filas afectadas.
La declaracin WAITFOR mencionada con anterioridad puede ser utilizada para recibir notificaciones en
modo bloqueo de la siguiente manera:
WAITFOR (RECEIVE * FROM myQueue)
Conclusin
Las mejoras de Transact-SQL en SQL Server "Yukon Beta 1 aumentan su poder de expresividad en
escritura de consultas, le permite mejorar la performance de su cdigo y extender sus capacidades de
manejo de errores. El esfuerzo continuo que se esta poniendo en mejorar Transact-SQL muestra una firme
creencia sobre su rol significativo en SQL Server, su poder y su futuro.
Condiciones de Uso http://www.microsoft.com/spain/misc/avisolegal.htm
Desacargado desde www.WillyDev.Net
Ultima actualizacin: Lunes, 13 de Septiembre de 2004

You might also like