Я новичок в арле, но после нескольких дней стука и действительно копания я не думаю, что это можно сделать. Вот краткое изложение того, что я сделал, если у кого-то есть дополнительная информация, это будет приветствоваться.
Во-первых, эти сценарии будут создавать тестовые таблицы и заполнять их тестовыми данными. Я настроил 9 cost_account_users, каждый с разным набором сборов / paid_debts, следующим образом: 1 сбор / 1 платеж, 2 платежа / 2 платежа, 2 сбора / 1 платеж, 2 сбора / 0 платежей, 1 сбор / 2 платежа, 0 сборов / 2 платежа, 1 сбор / 0 платежей, 0 сборов / 1 платеж, 0 сборов, 0 платежей.
CREATE TABLE IF NOT EXISTS `expense_accounts_users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`expense_account_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=10 ;
INSERT INTO `expense_accounts_users` (`id`, `expense_account_id`) VALUES (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1), (8, 1), (9, 1);
CREATE TABLE IF NOT EXISTS `account_charges` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`expense_account_id` int(11) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
`cost` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=10 ;
INSERT INTO `account_charges` (`id`, `expense_account_id`, `user_id`, `cost`) VALUES (1, 1, 1, 1), (2, 1, 2, 1), (3, 1, 2, 2), (4, 1, 3, 1), (5, 1, 3, 2), (6, 1, 4, 1), (7, 1, 5, 1), (8, 1, 5, 2), (9, 1, 7, 1);
CREATE TABLE IF NOT EXISTS `paid_debts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`expense_account_id` int(11) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
`cost` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=10 ;
INSERT INTO `paid_debts` (`id`, `expense_account_id`, `user_id`, `cost`) VALUES (1, 1, 1, 1), (2, 1, 2, 1), (3, 1, 2, 2), (4, 1, 3, 1), (5, 1, 4, 1), (6, 1, 4, 2), (7, 1, 6, 1), (8, 1, 6, 2), (9, 1, 8, 1);
В конечном счете, чтобы получить данные, которые вам нужны, одним махом, вот оператор SQL, который вы будете использовать:
SELECT user_charges.user_id,
user_charges.sum_cost,
COALESCE(SUM(paid_debts.cost), 0) AS 'sum_paid'
FROM (
SELECT expense_accounts_users.id AS 'user_id',
COALESCE(sum(account_charges.cost), 0) AS 'sum_cost'
FROM expense_accounts_users
LEFT OUTER JOIN account_charges on expense_accounts_users.id = account_charges.user_id
GROUP BY expense_accounts_users.id)
AS user_charges
LEFT OUTER JOIN paid_debts ON user_charges.user_id = paid_debts.user_id
GROUP BY user_charges.user_id
Сначала необходимо выполнить СЛЕВОЕ ВНЕШНЕЕ СОЕДИНЕНИЕ между пользователями и начислениями так, чтобы вы получили строку для каждого пользователя, затем вам нужно ВЛЕВОЕ ВНЕШНЕЕ СОЕДИНЕНИЕ результат к долгам, чтобы избежать умножения результатов. с двумя соединениями внутри одной конструкции.
(обратите внимание на использование COALESCE для преобразования значений NULL из LEFT OUTER JOIN в нули - возможно, для удобства)
Результат этого утверждения таков:
user_id sum_cost sum_paid
1 1 1
2 3 3
3 3 1
4 1 3
5 3 0
6 0 3
7 1 0
8 0 1
9 0 0
После многих попыток я обнаружил, что этот код arel ближе всего подходит к тому, что мы ищем:
c = Arel::Table.new(:account_charges)
d = Arel::Table.new(:paid_debts)
p = Arel::Table.new(:expense_accounts_users)
user_charges = p
.where(p[:expense_account_id].eq(1))
.join(c, Arel::Nodes::OuterJoin)
.on(p[:id].eq(c[:user_id]))
.project(p[:id], c[:cost].sum.as('sum_cost'))
.group(p[:id])
charges = user_charges
.join(d, Arel::Nodes::OuterJoin)
.on(p[:id].eq(d[:user_id]))
.project(d[:cost].sum.as('sum_paid'))
По сути, я присоединяю пользователей к начислениям в первой конструкции с помощью LEFT OUTER JOIN, а затем пытаюсь взять результат этого и LEFT OUTER JOIN вернуть его в долги. Этот код arel производит следующую инструкцию SQL:
SELECT `expense_accounts_users`.`id`,
SUM(`account_charges`.`cost`) AS sum_cost,
SUM(`paid_debts`.`cost`) AS sum_paid
FROM `expense_accounts_users`
LEFT OUTER JOIN `account_charges` ON `expense_accounts_users`.`id` = `account_charges`.`user_id`
LEFT OUTER JOIN `paid_debts` ON `expense_accounts_users`.`id` = `paid_debts`.`user_id`
WHERE `expense_accounts_users`.`expense_account_id` = 1
GROUP BY `expense_accounts_users`.`id`
Который при запуске выдает такой вывод:
id sum_cost sum_paid
1 1 1
2 6 6
3 3 2
4 2 3
5 3 NULL
6 NULL 3
7 1 NULL
8 NULL 1
9 NULL NULL
Очень близко, но не совсем. Во-первых, отсутствие COALESCE дает нам значения NULL вместо нулей - я не уверен, как осуществить вызов функции COALESCE изнутри arel.
Что еще более важно, объединение ЛЕВЫХ ВНЕШНИХ СОЕДИНЕНИЙ в один оператор без внутреннего подвыбора приводит к тому, что итоговые суммы sum_paid умножаются в примерах 2, 3 и 4 - каждый раз, когда больше, чем на один либо заряда, либо платежа и как минимум одного другого.
Основываясь на некоторых онлайн-чтениях, я надеялся, что небольшое изменение арела решит проблему:
charges = user_charges
.join(d, Arel::Nodes::OuterJoin)
.on(user_charges[:id].eq(d[:user_id]))
.project(d[:cost].sum.as('sum_paid'))
Но каждый раз, когда я использовал user_charges [] во второй конструкции arel, я получал неопределенную ошибку метода для SelectManager # [] . Это может быть ошибкой, или может быть правильным - я действительно не могу сказать.
Я просто не вижу, чтобы у arel был способ использовать SQL из первой конструкции в качестве запрашиваемого объекта во второй конструкции с необходимым псевдонимом подзапроса, что необходимо для того, чтобы это произошло в одном операторе SQL.