Arel在聚合上导致无限循环

我在使用Arel在同一查询中聚合2列时遇到问题。 当我运行它时,整个服务器冻结了一分钟,在rails dev-server崩溃之前。 我怀疑是无限循环:)。

也许我误解了Arel的整个概念,如果有人能看一下,我将不胜感激。

此查询的预期结果如下所示:[{:user_id => 1,:sum_account_charges => 300,:sum_paid_debts => 1000},…]

a_account_charges = Table(:account_charges) a_paid_debts = Table(:paid_debts) a_participants = Table(:expense_accounts_users) account_charge_sum = a_account_charges .where(a_account_charges[:expense_account_id].eq(id)) .group(a_account_charges[:user_id]) .project(a_account_charges[:user_id], a_account_charges[:cost].sum) paid_debts_sum = a_paid_debts .where(a_paid_debts[:expense_account_id].eq(id)) .group(a_paid_debts[:from_user_id]) .project(a_paid_debts[:from_user_id], a_paid_debts[:cost].sum) charges = a_participants .where(a_participants[:expense_account_id].eq(id)) .join(account_charge_sum) .on(a_participants[:user_id].eq(account_charge_sum[:user_id])) .join(paid_debts_sum) .on(a_participants[:user_id].eq(paid_debts_sum[:from_user_id])) 

我是新手,但是经过几天的敲打和真正的挖掘之后,我认为不能做到。 这里是我所做过的概述,如果有人有任何额外的见解,那将是受欢迎的。

首先,这些脚本将创建测试表并使用测试数据填充它们。 我已经设置了9个expense_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 

您必须先在用户之间进行LEFT OUTER JOIN并先收取费用,这样您才能为每个用户获取一行,然后您必须将结果 LEFT OUTER加入到债务中,以避免将结果与同一构造中的两个连接相乘。

(注意使用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 

经过多次尝试,我发现这个问题代码与我们之后的代码最接近:

 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加入到债务中。 这个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值而不是零 – 我不确定如何在arel中影响COALESCE函数调用。

更重要的是,将LEFT OUTER JOIN组合成一个没有内部子选择的语句会导致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')) 

但是当我在第二个arel构造中使用user_charges []时,我得到了一个未定义的方法错误,用于SelectManager#[] 。 这可能是一个错误,或者可能是对的 – 我实在说不出来。

我只是没有看到arel有一种方法可以利用第一个构造中的SQL作为第二个构造中的可查询对象以及必要的子查询别名,这是在一个SQL语句中实现这一点所需要的。