7

我无法使用 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]))
4

1 回答 1

4

我是 arel 的新手,但是在敲了几天并真正挖掘之后,我认为它无法完成。这是我所做工作的概述,如果有人有任何其他见解,将受到欢迎。

首先,这些脚本将创建测试表并用测试数据填充它们。我设置了 9 个费用帐户用户,每个用户都有一组不同的费用/已付债务,如下所示: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 JOIN,以避免将结果与同一构造中的两个连接相乘。

(注意使用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 值而不是零 - 我不确定如何从 arel 中影响 COALESCE 函数调用。

但是,更重要的是,将 LEFT OUTER JOIN 组合到一个没有内部子选择的语句中会导致 sum_paid 总数在示例 2、3 和 4 中成倍增加 - 任何时候只要有不止一个费用或付款,而且至少有一个的另一个。

根据一些在线阅读,我曾希望稍微改变 arel 可以解决问题:

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 语句中实现这一点所必需的。

于 2011-06-10T14:36:52.547 回答