如何计算 table 中的对象数量,按 has_many 中的列分组,某些条件是否重要?

How to count number of objects in table, grouped by column in has_many, for which some conditions matter?

我有一个叫 cars (id, brand) 的 table。汽车可以有很多 adverts (id, car_id, state, dealer_name).

我想计算 独特 汽车的数量,以 dealer_name 为一组,该特定经销商是目前唯一拥有汽车的经销商 for sale

https://www.db-fiddle.com/f/cjstvXSZ2sWYNqfe6LRcTc/1(查询的预期输出也写在那里)

我已经设法在 ruby 应用程序中以迭代方式执行此操作,为每个 dealer_name 执行一个查询,但是我无法在单个分组查询中执行此操作。

数据:

cars
id    make
1     'BMW'
2     'Mercedes'
3     'Mercedes'
4     'Volvo'
5     'Volvo'
6     'Volvo'
7     'Alfa Romeo'

adverts
id    car_id  state       dealer_name
1     1       'sold'      'Dealer 1'
2     2       'for sale'  'Dealer 1'
4     3       'for sale'  'Dealer 2'
5     4       'for sale'  'Dealer 2'
6     5       'for sale'  'Dealer 3'
7     5       'for sale'  'Dealer 4'
8     7       'sold'      'Dealer 4'

expected output:
dealer_name   Number of cars with only this dealer having the car currently 'for sale'
Dealer 1      1
Dealer 2      2
Dealer 3      0
Dealer 4      0

您可以在子查询中进行 window 计数,然后在外部查询中使用条件聚合:

select 
    dealer_name, 
    sum(case when cnt = 1 and state = 'for sale' then 1 else 0 end) cnt
from (
    select 
        dealer_name, 
        state,
        car_id, 
        count(*) over(partition by car_id, state) cnt
    from adverts a
) x 
group by dealer_name
order by dealer_name;

Demo on DB Fiddle:

| dealer_name | cnt |
| ----------- | --- |
| Dealer 1    | 1   |
| Dealer 2    | 2   |
| Dealer 3    | 0   |
| Dealer 4    | 0   |

注意:这是一个有据可查的问题,SO 可以使用更多!

如果您可以为同一笔交易拥有重复的汽车(有点暗示您希望计算不同的汽车),那么这就比较棘手了。 Postgres 不支持 count(distinct) 作为聚合函数。

一种方法是:

select dealer_name,
       count(distinct car_id) filter (where state = 'for sale' and min_dn = max_dn) as cnt
from (select a.*,
             min(a.dealer_name) over (partition by a.car_id, a.state) as min_dn,
             max(a.dealer_name) over (partition by a.car_id, a.state) as max_dn
      from adverts a
     ) a 
group by dealer_name
order by dealer_name;

或者您可以使用两级聚合:

select dealer_name,
       count(*) filter (where for_sale and num_dealers = 1) as cnt
from (select dealer_name, car_id,
             bool_or(state = 'for sale') as for_sale,
             count(*) filter (where state = 'for sale' over (partition by car_id)) as num_dealers
      from adverts
      group by dealer_name, car_id
     ) dc
group by dealer_name;

这些也是 return 所有 经销商,即使他们没有汽车出售。