2013-07-09 1 views
0

поставки заказа has_manyИспользовать подсчет отношения has_many для выбора - используя наличие?

Орден имеет столбец number_of_deliveries_paid_for

Как выбрать все заказы, где число поставок (COUNT (deliveries.id)) меньше, чем number_of_deliveries_paid_for

На данный момент, Я делаю:

Order.joins('LEFT OUTER JOIN deliveries ON deliveries.order_id = orders.id') 
    .select('orders.*, COUNT(deliveries.id) AS delivery_count') 
    .select { |o| o if o.number_of_deliveries_paid_for > o.delivery_count } 

Но это возвращает массив, было бы неплохо вернуть ActiveRecord :: Relation, и я думаю, было бы более эффективным, чтобы сделать это в SQL.

Update _ __ _ __ _ __ _ __ _ __

Это похоже на работу

Order.joins('LEFT OUTER JOIN deliveries ON deliveries.order_id = orders.id') 
    .group('orders.id') 
    .having('COUNT(deliveries.id) < orders.number_of_deliveries_paid_for') 

я могу цепи других областей на конце из этого и т. д. Но если я добавлю .count до конца, я получу ошибку

ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 
'orders.number_of_deliveries_paid_for' in 'having clause': 
SELECT COUNT(*) AS count_all, orders.id AS orders_id FROM `orders` 
LEFT OUTER JOIN deliveries ON deliveries.order_id = orders.id GROUP BY orders.id 
HAVING COUNT(deliveries.id) < orders.number_of_deliveries_paid_for 

, что заставляет меня беспокоиться!

ответ

1

Я не знаю, как написать это в Ruby, но с точки зрения SQL я чувствую, что вам нужен суб-запрос для агрегирования количества поставок по заказу.

Нечто подобное (я типа непосредственно SO - пожалуйста, простите опечатки или глупые ошибки синтаксиса):

SELECT orders.* FROM orders 
       LEFT OUTER JOIN (SELECT COUNT(*) AS C, deliveries.order_id AS ID 
             FROM deliveries 
             GROUP BY deliveries.order_id) AS S 
       ON orders.id = S.ID 
       WHERE S.C < orders.number_of_deliveries_paid_for 
        OR (deliveries.id IS NULL AND orders.number_of_deliveries_paid_for > 0); 

EDIT: В самом деле, это гораздо проще, используя ORDER BY ... HAVING:

SELECT *, count(*) AS C FROM orders 
         LEFT OUTER JOIN deliveries 
         ON deliveries.order_id = orders.id 
     GROUP BY orders.id 
     HAVING C < orders.number_of_deliveries_paid_for 
      OR (deliveries.id IS NULL AND orders.number_of_deliveries_paid_for > 0); 

А для любопытных, вот мой тест:

mysql> select * from orders; 
+------+---------+-------------------------------+ 
| id | name | number_of_deliveries_paid_for | 
+------+---------+-------------------------------+ 
| 1 | Order 1 |        0 | 
| 2 | Order 2 |        2 | 
| 3 | Order 3 |        3 | 
+------+---------+-------------------------------+ 
3 rows in set (0.00 sec) 

mysql> select * from deliveries; 
+----+----------+ 
| id | order_id | 
+----+----------+ 
| 1 |  2 | 
| 2 |  2 | 
| 3 |  3 | 
| 4 |  3 | 
+----+----------+ 
4 rows in set (0.00 sec) 

mysql> select * from orders JOIN deliveries ON deliveries.order_id = orders.id -> ; 
+------+---------+-------------------------------+----+----------+ 
| id | name | number_of_deliveries_paid_for | id | order_id | 
+------+---------+-------------------------------+----+----------+ 
| 2 | Order 2 |        2 | 1 |  2 | 
| 2 | Order 2 |        2 | 2 |  2 | 
| 3 | Order 3 |        3 | 3 |  3 | 
| 3 | Order 3 |        3 | 4 |  3 | 
+------+---------+-------------------------------+----+----------+ 
4 rows in set (0.00 sec) 

Как примечание стороны, увидеть разницу между JOIN и LEFT JOIN:

mysql> select *, COUNT(*) from orders LEFT JOIN deliveries ON deliveries.order_id = orders.id group by orders.id; 
+------+---------+-------------------------------+------+----------+----------+ 
| id | name | number_of_deliveries_paid_for | id | order_id | COUNT(*) | 
+------+---------+-------------------------------+------+----------+----------+ 
| 1 | Order 1 |        0 | NULL |  NULL |  1 | 
| 2 | Order 2 |        2 | 1 |  2 |  2 | 
| 3 | Order 3 |        3 | 3 |  3 |  2 | 
+------+---------+-------------------------------+------+----------+----------+ 
3 rows in set (0.00 sec) 

mysql> select *, COUNT(*) from orders JOIN deliveries ON deliveries.order_id = orders.id group by orders.id; 
+------+---------+-------------------------------+----+----------+----------+ 
| id | name | number_of_deliveries_paid_for | id | order_id | COUNT(*) | 
+------+---------+-------------------------------+----+----------+----------+ 
| 2 | Order 2 |        2 | 1 |  2 |  2 | 
| 3 | Order 3 |        3 | 3 |  3 |  2 | 
+------+---------+-------------------------------+----+----------+----------+ 
2 rows in set (0.01 sec) 
+0

вы говорите, что группа по ... имея хороший способ сделать это? – Edward

+0

Но мне нужно иметь все заказы, где нет поставок, поэтому мне нужно сделать левое соединение! – Edward

+0

ОК, много изменений после ... По-видимому, работают два варианта (подзапрос и предложение 'having'). В обоих случаях я продлил тест, чтобы учесть случай отсутствия доставки заказа. По этой причине я снова представил «LEFT JOIN». –

Смежные вопросы