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:
- Se localizan todos los vuelos cuyo origen es ALC.
- De todos los resultados de 1), para todos los vuelos existentes (toda la tabla) se buscan vuelos cuyo origen es el destino de la lista 1), y además, el destino no esté en alguno de los tramos anteriores – en otras palabras, evitar pasar dos veces por el mismo aeropuerto
-
Así iterativamente, dejando de buscar cuando:
- se hallan procesado todos los vuelos existentes.
- O, se haya llegado al destino final que es ROM.
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:
- WHERE M.[route] NOT LIKE ‘%-‘ + t.destination + ‘-%’
AND M.[route] NOT LIKE t.destination + ‘-%’
AND M.[route] NOT LIKE ‘%-‘ + t.destination
- Es decir, cuando el trayecto (route), no tenga como destino, algún aeropuerto por el que ya haya pasado.
- Nota: con el predicado WHERE M.[route] NOT LIKE ‘%’ + t.destination + ‘%’ sin guiones en los comodines, debería funcionar, pero no estoy seguro que no haya códigos de aeropuertos que sean subconjunto de otro, por ejemplo AL, y ALC, MA, y MAD, etc.
- WHERE M.[route] NOT LIKE ‘%-‘ + t.destination + ‘-%’
-
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).
2 comments
Muchas gracias por el tiempo que dedicaste en la explicación, me has ayudado mucho. Un saludo
Más que EXCELENTE!!!