Home > Software design >  How to exclude join based on another join in postgresql?
How to exclude join based on another join in postgresql?

Time:07-02

This is just simplified example - actual schema is much more complicated.

Each car is offered in "base" color (offer.model_id = null) or as models in different colors. I need to exclude cars with existing models NOT in BLUE or WHITE but keep cars in base color without extra models.

Schema (PostgreSQL v13)

CREATE TABLE color (
    id int4 NOT NULL,
    name varchar NOT NULL,
    CONSTRAINT color_pkey PRIMARY KEY (id)
);

INSERT INTO color (id, name) VALUES(1, 'WHITE');
INSERT INTO color (id, name) VALUES(2, 'BLUE');
INSERT INTO color (id, name) VALUES(3, 'RED');
INSERT INTO color (id, name) VALUES(4, 'BLACK');

CREATE TABLE car (
    id int4 NOT NULL,
    name varchar NOT NULL,
    CONSTRAINT car_pkey PRIMARY KEY (id)
);

INSERT INTO car (id, name) VALUES(1, 'Ford');
INSERT INTO car (id, name) VALUES(2, 'Skoda');
INSERT INTO car (id, name) VALUES(3, 'Toyota');

CREATE TABLE model (
    id int4 NOT NULL,
    car_id int4 NOT NULL,
    name varchar NOT NULL,
    CONSTRAINT model_pkey PRIMARY KEY (id)
);

INSERT INTO model (id, car_id, name) VALUES(1, 1, 'Escort');
INSERT INTO model (id, car_id, name) VALUES(2, 1, 'Puma');
INSERT INTO model (id, car_id, name) VALUES(3, 2, 'Octavia');
INSERT INTO model (id, car_id, name) VALUES(4, 3, 'Yaris');

CREATE TABLE offer (
    id int4 NOT NULL,
    car_id int4 NOT NULL,
    model_id int4,
    color_id int4 NOT NULL,
    CONSTRAINT offer_pkey PRIMARY KEY (id)
);
ALTER TABLE offer ADD CONSTRAINT "offer_car_id" FOREIGN KEY ("car_id") REFERENCES car(id);
ALTER TABLE offer ADD CONSTRAINT "offer_model_id" FOREIGN KEY ("model_id") REFERENCES model(id);
ALTER TABLE offer ADD CONSTRAINT "offer_color_id" FOREIGN KEY ("color_id") REFERENCES color(id);
CREATE UNIQUE INDEX "offer_car_color" ON offer USING btree (car_id) WHERE (model_id IS NULL);
CREATE UNIQUE INDEX "offer_model_color" ON offer USING btree (car_id, model_id) WHERE (model_id IS NOT NULL);

INSERT INTO offer (id, car_id, model_id, color_id) VALUES(1, 1, null, 1);
INSERT INTO offer (id, car_id, model_id, color_id) VALUES(2, 2, null, 2);
INSERT INTO offer (id, car_id, model_id, color_id) VALUES(3, 3, null, 4);
INSERT INTO offer (id, car_id, model_id, color_id) VALUES(4, 2, 3, 3);
INSERT INTO offer (id, car_id, model_id, color_id) VALUES(5, 3, 4, 2);

Query #1

FULL OFFER - CARS AND MODELS TOGETHER

select car.id, car.name as car_name, model.name as model_name, color.name as offered_color
from offer
left outer join model on model.id = offer.model_id
inner join color on color.id = offer.color_id
inner join car on car.id = offer.car_id
order by car.name, model.name NULLS first;
id car_name model_name offered_color
1 Ford WHITE
2 Skoda BLUE
2 Skoda Octavia RED
3 Toyota BLACK
3 Toyota Yaris BLUE

Query #2

LIST OF CARS IN WHITE OR BLUE (base color or model's color)

select car.id, car.name as car_name, model.name as model_name, color.name as offered_color
from car
inner join offer on offer.car_id = car.id
inner join color on color.id = offer.color_id
left outer join model on model.id = offer.model_id
where color.name in ('WHITE', 'BLUE')
order by car.name;
id car_name model_name offered_color
1 Ford WHITE
2 Skoda BLUE
3 Toyota Yaris BLUE

Query #3

REQUIRED QUERY: EXCLUDE CARS WITH EXISTING MODELS BUT NOT IN WHITE OR BLUE

select ...?
id car_name model_name offered_color
1 Ford WHITE
3 Toyota Yaris BLUE

Skoda is excluded even it is offered in base BLUE but it has models and non is in BLUE or WHITE.


View on DB Fiddle

CodePudding user response:

I think I've found the solution. The question is if it's the best one...?

https://www.db-fiddle.com/f/9Fh6SPxATLuHQCxG7iWktk/6

select
    car.id,
    car.name as car_name,
    model.name as model_name,
    color.name as color_name
from
    car
inner join offer on
    car.id = offer.car_id
inner join color on
    color.id = offer.color_id
left join model on
    model.id = offer.model_id
where
    color.name in ('WHITE', 'BLUE')
    and
    ((not exists (
    select
            1
    from
            offer o
    where
            o.car_id = car.id
        and o.model_id is not null))
    or (
  exists (
    select
        1
    from
        offer o
    inner join color c on
        c.id = o.color_id
    where
        o.car_id = car.id
        and o.model_id is not null
        and c.name in ('WHITE', 'BLUE'))
    ))
order by
    car.name;

CodePudding user response:

I can't follow the logic in your description, but I might be able to offer some help on how to write the SQL so you can figure it out.

I suggest adopting the style of using CTEs so you can make it more readable and built it with building blocks.

Once you have all the different conditions you want as CTEs, you can use EXISTS() to filter based on whether they exist in each of those various conditions.

Example: (this does not produce the correct result, though)

WITH FULL_OFFER AS (
  select 
    car.id, 
    car.name as car_name, 
    model.name as model_name, 
    color.name as offered_color 
  from offer 
    left outer join model on model.id = offer.model_id 
    inner join color on color.id = offer.color_id 
    inner join car on car.id = offer.car_id
), 
BASE_MODELS AS (
  SELECT * 
  FROM FULL_OFFER 
  WHERE model_name IS NULL
), 
BASE_WHITE_OR_BLUE AS (
  SELECT * 
  FROM BASE_MODELS 
  WHERE COALESCE(offered_color,'NONE') in('WHITE', 'BLUE')
),
NO_MODELS AS (
  SELECT *
  FROM FULL_OFFER
  WHERE NOT EXISTS (
    SELECT 1 
    FROM BASE_MODELS
    WHERE FULL_OFFER.id=BASE_MODELS.id
    AND FULL_OFFER.offered_color = BASE_MODELS.offered_color)
  ),
ANY_WHITE_BLUE AS (
  SELECT *
  FROM FULL_OFFER 
  WHERE COALESCE(offered_color,'NONE') in('WHITE', 'BLUE')
  )
SELECT *
FROM FULL_OFFER
WHERE 
  (EXISTS (SELECT 1 
           FROM BASE_WHITE_OR_BLUE 
           WHERE FULL_OFFER.id = BASE_WHITE_OR_BLUE.id) 
   AND EXISTS (SELECT 1 
               FROM NO_MODELS 
               WHERE FULL_OFFER.id = NO_MODELS.id)
  )
  OR EXISTS (SELECT 1 
             FROM ANY_WHITE_BLUE 
             WHERE FULL_OFFER.id = ANY_WHITE_BLUE.id)

This doesn't produce what you want, but only because I can't translate the conditions from your description.

Hopefully those 2 tips will help you out, if someone else doesn't come along and help you with the correct SQL.

  • Related