Seguramente ya has oído hablar de las CTEs (o expresiones de tablas comunes) recursivas en SQL Server 2005.
El ejemplo típico del que habrás oído hablar será de la típica relación Empleado – Jefe, ¿verdad?
Vamos a ver un caso diferente, porque seguramente ya estarás aburrido de ese ejemplo
Acostumbrados a pasar demasiado tiempo en los aeropuertos, y además, de algún modo descontento con las aplicaciones web a través de las cuales compramos nuestros vuelos, me llegó a la idea de los grafos que me hicieron volver a los viejos tiempos en las clases de Matemática Discreta.
Bueno, al tema; veamos el siguiente ejemplo, dada la siguiente tabla:
if
exists
(select
*
from
sys.tables
where
name
=
'routes')
drop
table dbo.routes
go
create
table dbo.routes (
origin char(3)
, destination char(3)
, price money)
En la que:
- origin representa el origen del vuelo,
- destination, representa el destino del vuelo,
- y, price, el precio del vuelo.
Y dadas las siguientes combinaciones de vuelos:
insert dbo.routes select
'ALC',
'MAD', 100
insert dbo.routes select
'ALC',
'ROM', 1200
insert dbo.routes select
'ALC',
'BCN', 250
insert dbo.routes select
'MAD',
'ALC', 100
insert dbo.routes select
'MAD',
'BCN', 75
insert dbo.routes select
'BCN',
'MAD', 75
insert dbo.routes select
'MAD',
'ROM', 750
insert dbo.routes select
'ROM',
'ALC', 1200
insert dbo.routes select
'BCN',
'ROM', 400
insert dbo.routes select
'MAD',
'TEF', 1300
insert dbo.routes select
'TEF',
'ROM', 1500
Donde podemos ver por ejemplo, que el vuelo de ALC, a MAD vale 100€, el vuelo de ALC a ROM vale 1200, y así sucesivamente.
Si quisiéramos buscar todos los vuelos cuyo origen tienen ALC, y su destino es ROM, podríamos aplicar la siguiente consulta:
select
*
from dbo.routes
where
origin =
'ALC'
and destination =
'ROM'
Que nos devolvería:
origin destination price
------ ----------- ---------
ALC ROM 1200.00
En realidad, esta consulta devolverá vuelos "directos" entre Alicante (ALC) y Roma (ROM), pero ¿qué pasa con las escalas? Porque en la lista anterior, para ir de Alicante a Roma, podríamos elegir las siguientes combinaciones de vuelos:
ALC-ROM
ALC-BCN-ROM
ALC-MAD-ROM
ALC-MAD-BCN-ROM
ALC-BCN-MAD-ROM
ALC-MAD-TEF-ROM
ALC-BCN-MAD-TEF-ROM
Es decir, las combinaciones serían: un vuelo directo, dos vuelos con una escala (pasando por BCN, o MAD), tres vuelos con dos escalas, y un "estratosférico" Alicante – Barcelona – Madrid – Tenerife – Roma, con cuatro escalas
Pensemos un poco cómo debería funcionar una búsqueda de todos los trayectos cuyo origen es ALC, y su destino es ROM.
Para ello:
Y ¿cómo lo hacemos con TSQL? Para ello aparece el concepto de CTEs Recursivas de las que podéis ver una introducción aquí (http://technet.microsoft.com/es-es/library/ms186243.aspx) –si eres nuevo con las CTEs Recursiva, por favor, lee la URL anterior.
Vamos a empezar con la siguiente sentencia:
--
-- declaración de variables
--
DECLARE @origin CHAR(3)
DECLARE @destination CHAR(3)
DECLARE @stops INT
SET @origin =
'ALC'
SET @destination =
'ROM'
;WITH paths AS
(
--
-- punto de entrada
--
SELECT origin, destination, price
,
cast
(origin +
'-'
+ destination as
varchar(200))
as [route]
, 1 as stops
FROM dbo.routes
WHERE origin = @origin
UNION ALL
--
-- elemento de recursión
--
SELECT M.origin, t.destination, t.price + M.price
,
cast
(M.[route] +
'-'
+ t.destination as
varchar(200))
, M.stops + 1
FROM dbo.routes AS t
JOIN paths AS M
ON t.origin = M.destination
WHERE M.[route] NOT
LIKE
'%-'
+ t.destination +
'-%'
AND M.[route] NOT
LIKE t.destination +
'-%'
AND M.[route] NOT
LIKE
'%-'
+ t.destination
)
SELECT
*
FROM paths
WHERE destination = @destination
ORDER
BY price ASC
Que devolverá los siguientes resultados:
origin | destination | price | route | stops |
ALC | ROM | 575 | ALC-MAD-BCN-ROM | 3 |
ALC | ROM | 650 | ALC-BCN-ROM | 2 |
ALC | ROM | 850 | ALC-MAD-ROM | 2 |
ALC | ROM | 1075 | ALC-BCN-MAD-ROM | 3 |
ALC | ROM | 1200 | ALC-ROM | 1 |
ALC | ROM | 2900 | ALC-MAD-TEF-ROM | 3 |
ALC | ROM | 3125 | ALC-BCN-MAD-TEF-ROM | 4 |
Curiosidades:
Los cálculos se codifican en los elementos de recursión (en la parte SELECT):
- t.price + M.price, para incrementar en cada tramo el importe.
- M.stops + 1, para llevar "registro" del número de escalas.
La condición de ruptura de la recursión se implementa en el elemento de recursión:
Si quieres filtrar por número de escalas:
- Se puede establecer el filtro dentro del "elemento de recursión".
- O en la consulta externa (el alias paths).
Nota: juega con el predicado en ambos sitios, y fíjate que el predicado en el elemento de recursión debe ser una unidad mayor que en la consulta externa para que ambas opciones devuelvan los mismos resultados.