Home > Enterprise >  Dynamic self join
Dynamic self join

Time:09-08

Consider the following model, consisting of one table cars with 3 columns:

class Car(Model):
    owner_id = IntegerField()
    name = CharField()
    power = IntegerField()

The input can be from 1 up to 5 car names (no duplicates). How do you get the owners of all cars (partial match not allowed), up to 5, and sort them by the total horse power?

Here's a graphical representation of what I'd like to achieve based on the data below, also available in this SQL fiddle:

create table cars(owner_id integer, name varchar(21), power integer);
insert into cars values (101,'bmw',300);
insert into cars values (101,'audi',200);
insert into cars values (101,'mercedes',100);
insert into cars values (102,'bmw',250);
insert into cars values (102,'mercedes',400);
insert into cars values (103,'bmw',200);
insert into cars values (103,'audi',100);
insert into cars values (103,'mercedes',190);
               ┌──────────┐
'bmw'          │          │       owner_id|total_power
'mercedes' ───►│  query   ├───►   --------|-----------
               │          │            102|        650
               └──────────┘            101|        400
                                       103|        390
               ┌──────────┐
'bmw'          │          │       owner_id|total_power
'mercedes' ───►│  query   ├───►   --------|-----------
'audi'         │          │            101|        600
               └──────────┘            103|        490

With 2 cars, I tried this (it doesn't work):

select owner_id, sum(power) as total_power
from cars c1
inner join cars c2 on c1.owner_id = c2.owner_id
where c1.name = 'bmw' and c2.name = 'audi'
group by owner_id
order by total_power desc

I have no idea:

  • if there's a better way to write this for 5 cars or if 5 joins are the only way
  • how to make the sum of all power
  • how to write this in peewee

For non-peewee answers, I'm using SQLite.

Thanks

CodePudding user response:

Use a CTE that returns the input car names to filter the table and use aggregation to get the total power for each owner_id.

Place the final filter in the HAVING clause:

WITH cte AS (VALUES ('bmw'), ('mercedes'))
SELECT owner_id, SUM(power) power
FROM cars
WHERE name IN cte
GROUP BY owner_id
HAVING COUNT(DISTINCT name) = (SELECT COUNT(*) FROM cte)
ORDER BY SUM(power) DESC;

See the demo.

CodePudding user response:

For SQLITE, how about something like following -

with data as 
  (select *,
  'name'||dense_rank() over (order by name) name_cnt,
  'power'||dense_rank() over (order by name) power_cnt
  from cars where name in ('bmw','audi')
 )
 select 
 owner_id, 
 max(case when name_cnt = 'name1' then name end) name1,
 max(case when power_cnt = 'power1' then power end)  power1,
 max(case when name_cnt = 'name2' then name end)  name2,
 max(case when power_cnt = 'power2' then power end)  power2,
 sum(power) total_power
 from data
group by owner_id
order by owner_id;

SQL fiddle here.

However, this does not distinct if a owner has two cars of same model/name - total though given is correct.

Updated solution to exclude owners that do not have ALL cars in the list.

with data as 
(select *,
'name'||dense_rank() over (order by name) name_cnt,
'power'||dense_rank() over (order by name) power_cnt
from cars where name in ('bmw','audi','mercedes','porsche','honda')
 ), d1 as (
 select 
 owner_id, count(distinct name) n1, count(distinct name_cnt) n2
 from data
 group by owner_id
), d2 as (
  select * from data where owner_id in
  (select owner_id from d1 
  where n1 = (select max(n2) from d1))
 ) 
select 
 owner_id, 
 max(case when name_cnt = 'name1' then name end) name1,
 max(case when power_cnt = 'power1' then power end)  power1,
 max(case when name_cnt = 'name2' then name end)  name2,
 max(case when power_cnt = 'power2' then power end)  power2,
 max(case when name_cnt = 'name3' then name end)  name3,
 max(case when power_cnt = 'power3' then power end)  power3,
 max(case when name_cnt = 'name4' then name end)  name4,
 max(case when power_cnt = 'power4' then power end)  power4,
 max(case when name_cnt = 'name5' then name end)  name5,
 max(case when power_cnt = 'power5' then power end)  power5,
 sum(power) total_power
 from d2
group by owner_id
order by owner_id;

SQL fiddle.

  • Related