Funciones recursivas en PosgreSQL

por | Jun 5, 2017 | Bases de Datos | 3 Comentarios

-- Tabla donde define cada una de las distancias entre dos localidades
DROP TABLE IF EXISTS public.etapas CASCADE; 
CREATE TABLE public.etapas
(
    from_city_name            CHARACTER VARYING(120) NOT NULL ,
    to_city_name              CHARACTER VARYING(120) NOT NULL ,
    km_distance_num           NUMERIC(5,2) NOT NULL ,
    CONSTRAINT pk_route PRIMARY KEY (from_city_name, to_city_name)
);

-- Carga de datos
INSERT INTO public.etapas VALUES
  ('Saint Jean Pied de Port','Roncesvalles',25.7),
  ('Somport','Jaca',30.5),
  ('Roncesvalles','Zubiri',21.5),
  ('Jaca','Arrés',25),
  ('Zubiri','Pamplona/Iruña',20.4),
  ('Arrés','Ruesta',28.7),
  ('Pamplona/Iruña','Puente la Reina/Gares',24),
  ('Ruesta','Sangüesa',21.8),
  ('Puente la Reina/Gares','Estella/Lizarra',22),
  ('Sangüesa','Monreal',27.25),
  ('Estella/Lizarra','Torres del Río',29),
  ('Monreal','Puente la Reina/Gares',31.1),
  ('Torres del Río','Logroño',20),
  ('Logroño','Nájera',29.6),
  ('Nájera','Santo Domingo de la Calzada',21),
  ('Santo Domingo de la Calzada','Belorado',22.7),
  ('Belorado','Agés',27.4),
  ('Agés','Burgos',23),
  ('Burgos','Hontanas',31.1),
  ('Hontanas','Boadilla del Camino',28.5),
  ('Boadilla del Camino','Carrión de los Condes',24.6),
  ('Carrión de los Condes','Terradillos de los Templarios',26.6),
  ('Terradillos de los Templarios','El Burgo Ranero',30.6),
  ('El Burgo Ranero','León',37.1),
  ('León','San Martín del Camino',25.9),
  ('San Martín del Camino','Astorga',24.2),
  ('Astorga','Foncebadón',25.9),
  ('Foncebadón','Ponferrada',27.3),
  ('Ponferrada','Villafranca del Bierzo',24.1),
  ('Villafranca del Bierzo','O Cebreiro',28.4),
  ('O Cebreiro','Triacastela',21.1),
  ('Triacastela','Sarria',18.3),
  ('Sarria','Portomarín',22.4),
  ('Portomarín','Palas de Rei',25),
  ('Palas de Rei','Arzúa',28.8),
  ('Arzúa','Pedrouzo',19.1),
  ('Pedrouzo','Santiago de Compostela',20),
  ('Bayona','Ustaritz',14.3),
  ('Ustaritz','Urdax',21.2),
  ('Urdax','Elizondo',18.8),
  ('Elizondo','Berroeta',9.7),
  ('Berroeta','Olagüe',20.4),
  ('Olagüe','Pamplona/Iruña',25),
  ('Irún','Hernani',26.6),
  ('Hernani','Tolosa',18.9),
  ('Tolosa','Zerain',33),
  ('Zerain','Salvatierra/Agurain',28),
  ('Salvatierra/Agurain','Vitoria/Gasteiz',27.4),
  ('Vitoria/Gasteiz','La Puebla de Arganzón',18.5),
  ('La Puebla de Arganzón','Haro',31),
  ('Haro','Santo Domingo de la Calzada',20),
  ('Bayona','Irún',33.8),
  ('Tolosa','Zegama',37.9),
  ('Zegama','Salvatierra/Agurain',20.1),
  ('La Puebla de Arganzón','Miranda del Ebro',22.3),
  ('Miranda del Ebro','Pancorbo',16.7),
  ('Pancorbo','Briviesca',23.4),
  ('Briviesca','Monasterio de Rodilla',19.8),
  ('Monasterio de Rodilla','Burgos',28.5);

-- Se define la función recursiva que ira recorriendo las localidades
WITH RECURSIVE f_recursiva AS (
    
    -- FASE No iterativa define el escenario de partida
    SELECT 
        to_city_name, 
        from_city_name, 
        0.0 + km_distance_num AS distancia,
        1 AS etapas,
        CAST(from_city_name || '->' || to_city_name As varchar(1000)) As resultado
    FROM public.etapas
    WHERE from_city_name = 'Bayona'
    
    -- FASES Iterativas, define las modificaciones que suceden en cada uno de las iteraciones.
    UNION ALL
    SELECT 
        tab.to_city_name,
        recur.from_city_name,
        recur.distancia + tab.km_distance_num AS distancia,
        recur.etapas + 1 AS etapas,
        CAST( recur.resultado || '->' || tab.to_city_name As varchar(1000)) As resultado
    FROM 
        public.etapas As tab
    INNER JOIN f_recursiva AS recur
        ON (tab.from_city_name = recur.to_city_name)
)

SELECT DISTINCT 
    from_city_name AS "Desde", 
    to_city_name AS "Hasta", 
    resultado, 
    etapas, 
    distancia
FROM 
    f_recursiva
WHERE 
    to_city_name = 'Santiago de Compostela'
ORDER BY 
    distancia ASC;

3 Comentarios

  1. Luis Barrios (Paraguay)

    Lindo ejemplo, muchas gracias.

    Responder
  2. Fredd

    Espectacular el ejemplo …. una pregunta, cómo se podría generar «resultado» con el orden inverso , sin modificar tablas, solo mostrar las etapas desde la última hacia atrás, en la select final, alguna sugerencia?
    Gracias!!!!

    Responder
    • Diego Calvo

      Buenas Fredd, entiendo que te saldría más a cuenta hacer una consulta que cambiase la tabla base de orden de origen a destino y aplicaría el mismo algoritmo.

      Responder

Enviar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *