Search code examples
sqlpostgresqlrecursionrecursive-query

Recursive PostgreSQL Query for Updating Column


Hello I'm trying to update database column recursively with SQL query.

I have 2 tables module and module_dependency.

First table module has columns (with example data):

id, name, state,
1, "website", "installed"
2, "purchase", "installed"
3, "crm", "installed"
4, "sale", "uninstalled"
5, "account", "installed"
6, "website_sale", "installed"
7, "purchase_bonus", "installed"
8, "website_blog", "installed"
9, "sale_discount", "installed"
10, "website_online", "installed"

Second table module_dependency has columns (with example data):

 id | dependency_name | module_id 
----+-----------------+-----------
  1 | website_sale    |         1
  2 | sale_bonus      |         4
  3 | website_blog    |         1
  4 | sale_discount   |         4
  5 | website_online  |         1
  6 | crm             |         10
  7 | account         |         3

I need to change state of website module to "to upgrade" with all of it's depended modules.

I can check current module's state:

SELECT * FROM module WHERE name = 'website' AND state = 'installed';

 id |  name   |   state   
----+---------+-----------
  1 | website | installed

To get all dependencies of website module I use query:

SELECT m.name, md.dependency_name , md.module_id
FROM module m JOIN module_dependency md ON (m.id = md.module_id)
WHERE m.name = 'website' AND m.state = 'installed';

  name   | dependency_name | module_id 
---------+-----------------+-----------
 website | website_sale    |         1
 website | website_blog    |         1
 website | website_online  |         1

That means website_sale, website_blog, and website_online is depended on module_id 1 which is website.

The main problem is that website_online is depended on crm and crm on account module.

SELECT m.name, md.dependency_name , md.module_id
FROM module m JOIN module_dependency md ON (m.id = md.module_id)
WHERE m.name = 'website_online' AND m.state = 'installed';

      name      | dependency_name | module_id 
----------------+-----------------+-----------
 website_online | crm             |        10

SELECT m.name, md.dependency_name , md.module_id
FROM module m JOIN module_dependency md ON (m.id = md.module_id)
WHERE m.name = 'crm' AND m.state = 'installed';

 name | dependency_name | module_id 
------+-----------------+-----------
 crm  | account         |         3

I need recursively change state of all website dependencies to "to upgrade". Updated rows should look like this:

id, name, state,
1, "website", "to upgrade"
3, "crm", "to upgrade"
5, "account", "to upgrade"
6, "website_sale", "to upgrade"
8, "website_blog", "to upgrade"
10, "website_online", "to upgrade"

You can create tables with test data:

CREATE TABLE module(id integer, name text, state text);
INSERT INTO module VALUES
(1, 'website', 'installed'),
(2, 'purchase', 'installed'),
(3, 'crm', 'installed'),
(4, 'sale', 'uninstalled'),
(5, 'account', 'installed'),
(6, 'website_sale', 'installed'),
(7, 'purchase_bonus', 'installed'),
(8, 'website_blog', 'installed'),
(9, 'sale_discount', 'installed'),
(10, 'website_online', 'installed');


CREATE TABLE module_dependency(id integer, dependency_name text, module_id integer);
INSERT INTO module_dependency VALUES
(1, 'website_sale', 1),
(2, 'sale_bonus', 4),
(3, 'website_blog', 1),
(4, 'sale_discount', 4),
(5, 'website_online', 1),
(6, 'crm', 10),
(7, 'account', 3);

I have tried to write recursive query, but unsuccessfully. Looks like infinite loop. If I could get all module ids of module table, I could simply update selected entries.

WITH RECURSIVE modules_to_upgrade AS
(
    SELECT id
    FROM module
    WHERE name = 'website'

        UNION ALL

    SELECT md.module_id
    FROM module_dependency md JOIN modules_to_upgrade mtu on mtu.id = md.module_id
)
select * from modules_to_upgrade;

Solution

  • To link the recursive part back to modules it seems you can't use the dependency id.

    So it has to be on the names.

    WITH RECURSIVE rcte_modules_to_upgrade AS
    (
        SELECT 
          m.id as module_id
        , m.name as module_name
        , md.id as dependency_id
        , md.dependency_name
        , m.id as base_module_id
        , 1 as depth
        FROM module m
        JOIN module_dependency md 
          ON md.module_id = m.id
        WHERE m.name = 'website'
    
        UNION ALL
    
        SELECT 
          m.id
        , m.name
        , md.id
        , md.dependency_name
        , rcte.base_module_id
        , rcte.depth + 1
        FROM rcte_modules_to_upgrade rcte
        JOIN module m
          ON m.name = rcte.dependency_name
        LEFT JOIN module_dependency md 
          ON md.module_id = m.id
    )
    select * 
    from rcte_modules_to_upgrade;
    
    module_id | module_name    | dependency_id | dependency_name | base_module_id | depth
    --------: | :------------- | ------------: | :-------------- | -------------: | ----:
            1 | website        |             1 | website_sale    |              1 |     1
            1 | website        |             3 | website_blog    |              1 |     1
            1 | website        |             5 | website_online  |              1 |     1
            6 | website_sale   |          null | null            |              1 |     2
            8 | website_blog   |          null | null            |              1 |     2
           10 | website_online |             6 | crm             |              1 |     2
            3 | crm            |             7 | account         |              1 |     3
            5 | account        |          null | null            |              1 |     4
    
    WITH RECURSIVE rcte_modules_to_upgrade AS
    (
        SELECT 
          m.id as module_id
        , md.dependency_name
        FROM module m
        JOIN module_dependency md 
          ON md.module_id = m.id
        WHERE m.name = 'website'
    
        UNION ALL
    
        SELECT 
          m.id
        , md.dependency_name
        FROM rcte_modules_to_upgrade rcte
        JOIN module m
          ON m.name = rcte.dependency_name
        LEFT JOIN module_dependency md 
          ON md.module_id = m.id
    )
    update module
    set state = 'to upgrade'
    where id in (select distinct module_id 
                 from rcte_modules_to_upgrade);
    
    6 rows affected
    
    select * from module order by id;
    
    id | name           | state      
    -: | :------------- | :----------
     1 | website        | to upgrade 
     2 | purchase       | installed  
     3 | crm            | to upgrade 
     4 | sale           | uninstalled
     5 | account        | to upgrade 
     6 | website_sale   | to upgrade 
     7 | purchase_bonus | installed  
     8 | website_blog   | to upgrade 
     9 | sale_discount  | installed  
    10 | website_online | to upgrade 
    

    db<>fiddle here