Home > Enterprise >  Postgres: Find missing items in a version table
Postgres: Find missing items in a version table

Time:12-06

I have a Table in Postgres 12 which tracks what Items i are use in which Versions v:

CREATE TABLE compare_test(v BIGINT, i BIGINT);

With example data:

INSERT INTO compare_test VALUES
 (1,21),
 (1,22),
 (1,23),
 (2,21),
 (2,22),
 (2,23),
 (3,21),
 (3,22);

I'm trying to create a View that returns:

source_v target_v source_i target_i
1 3 23 null
2 3 23 null

Queries used to compare missing values in two Tables like:

SELECT l.v as source_v, l.i as source_i,
           r.v as target_v, r.i as target_i
  FROM compare_test l
    LEFT JOIN
        compare_test r ON r.i = l.i
  WHERE r.i IS NULL;

and

SELECT l.v as source_v, l.i as source_i
  FROM compare_test l
  WHERE NOT EXISTS
      (
        SELECT i as target_i
          FROM compare_test r
          WHERE r.i = l.i
      )

do not seem to work if the joined Table is the same Table or if more than 2 Versions are in the Table.

I don't have the option to change the Database Structure but I can use plugins.

CodePudding user response:

The solution below gives those results.
It makes re-use of a CTE.

(but somehow I got a feeling that there should exist a more efficient way)

with cte1 as (
  SELECT i
  , count(*) cnt
  , min(v) min_v
  , max(v) max_v
  FROM compare_test
  GROUP BY i
)
, cte2 as
(
  select *
  from cte1 as c1
  where not exists (
    select 1
    from cte1 c2
    where c2.min_v = c1.min_v
      and c2.max_v < c1.max_v
   ) 
) 
select distinct
  t.v as source_v
, c1.max_v as target_v
, c2.i as source_i
, null as target_i
from cte2 c2
left join compare_test t
  on t.i = c2.i
left join cte1 c1
  on t.v between c1.min_v and c1.max_v
  and c1.i != t.i
order by source_v

But if it's not really required to follow the relations, then it becomes really simple.
Then it's just a left join of the existing to all possible combinations.

select distinct 
  src.v as source_v
, missing.v as target_v
, src.i as source_i
, missing.i as target_i
from
(
  select ver.v, itm.i
  from (select distinct v from compare_test) as ver
  cross join (select distinct i from compare_test) as itm
  left join compare_test t
    on t.v = ver.v and t.i = itm.i
  where t.v is null
) as missing
left join compare_test as src
  on src.i = missing.i and src.v != missing.v
order by target_i, target_v, source_v
source_v | target_v | source_i | target_i
-------: | -------: | -------: | -------:
       1 |        5 |       21 |       21
       2 |        5 |       21 |       21
       3 |        5 |       21 |       21
       1 |        5 |       22 |       22
       2 |        5 |       22 |       22
       3 |        5 |       22 |       22
       1 |        3 |       23 |       23
       2 |        3 |       23 |       23
       1 |        5 |       23 |       23
       2 |        5 |       23 |       23
       5 |        1 |       44 |       44
       5 |        2 |       44 |       44
       5 |        3 |       44 |       44

db<>fiddle here

  • Related