使用子查询对所有父项的关联模型的列 returns 求和相同的数量
Using subqueries to sum an associated model's column returns the same amount for all parents
给定模型User
和Invoice
,一个用户有很多发票,一张发票属于一个用户。
发票有 status
和 amount_cents
列。
我需要编写一个查询来获取所有用户列,同时添加以下列:
- 一个
total_paid
别名列,汇总每个用户 paid
的所有 amount_cents
发票
- 一个
total_unpaid
别名列,汇总每个用户 unpaid
的所有 amount_cents
发票
当我使用分配了别名的多个子查询时,我有点不知道正确的结构是什么,但我已经为任务的第一部分想出了一些非常基本的东西:
select users.*, (SELECT SUM(amount_cents) FROM invoices) as total_paid from users
join invoices on users.id = invoices.user_id
where invoices.status = 'paid'
group by users.id
我不确定我是否应该从父端或子端编写查询(我想是从父(用户)端,因为我需要的所有数据都在用户列中)但上面的查询似乎在 total_paid
列中为所有不同的用户返回相同的金额,而不是为每个用户返回正确的金额。
如有任何帮助,我们将不胜感激。
这可以使用子查询完成,如下所示:
Select users.id,
(Select Sum(amount_cents)
From invoices Where status = 'paid' And user_id=users.id) As total_paid,
(Select Sum(amount_cents)
From invoices Where status = 'unpaid' And user_id=users.id) As total_unpaid
From users
Group by users.id
语句 (SELECT SUM(amount_cents) FROM invoices) returns 所有用户的总金额,这与您想要的每个用户的金额不同:
具有横向连接的解决方案:
select u.*
, paid.total as total_paid
, unpaid.total as total_unpaid
FROM users AS u
LEFT JOIN LATERAL
( SELECT sum(amount_cents) AS total
FROM invoices
WHERE user_id = u.id
AND status = 'paid'
) AS paid
ON True
LEFT JOIN LATERAL
( SELECT sum(amount_cents) AS total
FROM invoices
WHERE user_id = u.id
AND status = 'unpaid'
) AS unpaid
ON True
具有 JOIN 和 window 函数的解决方案:
SELECT u.*
, t.total_paid
, t.total_unpaid
FROM users AS u
INNER JOIN
(
SELECT DISTINCT ON (user_id)
, user_id
, sum(amount_cents) FILTER (WHERE status = 'paid') OVER (PARTITION BY user_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS total_paid
, sum(amount_cents) FILTER (WHERE status = 'unpaid') OVER (PARTITION BY user_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS total_unpaid
FROM invoices
ORDER BY u.user_id
) AS t
ON u.id = t.user_id
您可以使用标量子查询
select u.*,
(select sum(amount_cents) from invoices where user_id = u.id and status = 'paid') total_paid,
(select sum(amount_cents) from invoices where user_id = u.id and status = 'unpaid') total_unpaid
from users u;
或者横向连接可能会更有效一些。
select u.*, l.*
from users u
left join lateral
(
select sum(amount_cents) filter (where status = 'paid') total_paid,
sum(amount_cents) filter (where status = 'unpaid') total_unpaid
from invoices where user_id = u.id
) l on true;
但是如果 users.id
是主键(可能是这种情况),那么事情可以简化为
select u.*,
sum(i.amount_cents) filter (where i.status = 'paid') total_paid,
sum(i.amount_cents) filter (where i.status = 'unpaid') total_unpaid
from users u
left outer join invoices i on u.id = i.user_id
group by u.id;
另一种选择是使用外部连接
users_table = User.arel_table
paid_invoices_table = Arel::Table.new(Invoice.arel_table.name, as: 'paid_invoices')
unpaid_invoices_table = Arel::Table.new(Invoice.arel_table.name, as: 'unpaid_invoices')
paid_join = Arel::Nodes::OuterJoin.new(
paid_invoices_table,
Arel::Nodes::On.new(
users_table[:id].eq(paid_invoices_table[:user_id])
.and(paid_invoices_table[:status].eq('paid'))
)
)
unpaid_join = Arel::Nodes::OuterJoin.new(
unpaid_invoices_table,
Arel::Nodes::On.new(
users_table[:id].eq(unpaid_invoices_table[:user_id])
.and(unpaid_invoices_table[:status].not_eq('paid'))
)
)
User.joins(paid_join,unpaid_join)
.select(
User.arel_table[Arel.star],
paid_invoices_table[:amount_cents].sum.as('total_paid'),
unpaid_invoices_table[:amount_cents].sum.as('total_unpaid'))
.group(:id)
结果查询:
SELECT
users.*,
SUM(paid_invoices.amount_cents) AS total_paid,
SUM(unpaid_invoices.amount_cents) AS total_unpaid
FROM
users
LEFT OUTER JOIN invoices AS paid_invoices ON users.id = paid_invoices.user_id
AND paid_invoices.status = 'paid'
LEFT OUTER JOIN invoices AS unpaid_invoices ON users.id = unpaid_invoices.user_id
AND unpaid_invoices.status <> 'paid'
GROUP BY
users.id
给定模型User
和Invoice
,一个用户有很多发票,一张发票属于一个用户。
发票有 status
和 amount_cents
列。
我需要编写一个查询来获取所有用户列,同时添加以下列:
- 一个
total_paid
别名列,汇总每个用户paid
的所有amount_cents
发票 - 一个
total_unpaid
别名列,汇总每个用户unpaid
的所有amount_cents
发票
当我使用分配了别名的多个子查询时,我有点不知道正确的结构是什么,但我已经为任务的第一部分想出了一些非常基本的东西:
select users.*, (SELECT SUM(amount_cents) FROM invoices) as total_paid from users
join invoices on users.id = invoices.user_id
where invoices.status = 'paid'
group by users.id
我不确定我是否应该从父端或子端编写查询(我想是从父(用户)端,因为我需要的所有数据都在用户列中)但上面的查询似乎在 total_paid
列中为所有不同的用户返回相同的金额,而不是为每个用户返回正确的金额。
如有任何帮助,我们将不胜感激。
这可以使用子查询完成,如下所示:
Select users.id,
(Select Sum(amount_cents)
From invoices Where status = 'paid' And user_id=users.id) As total_paid,
(Select Sum(amount_cents)
From invoices Where status = 'unpaid' And user_id=users.id) As total_unpaid
From users
Group by users.id
语句 (SELECT SUM(amount_cents) FROM invoices) returns 所有用户的总金额,这与您想要的每个用户的金额不同:
具有横向连接的解决方案:
select u.*
, paid.total as total_paid
, unpaid.total as total_unpaid
FROM users AS u
LEFT JOIN LATERAL
( SELECT sum(amount_cents) AS total
FROM invoices
WHERE user_id = u.id
AND status = 'paid'
) AS paid
ON True
LEFT JOIN LATERAL
( SELECT sum(amount_cents) AS total
FROM invoices
WHERE user_id = u.id
AND status = 'unpaid'
) AS unpaid
ON True
具有 JOIN 和 window 函数的解决方案:
SELECT u.*
, t.total_paid
, t.total_unpaid
FROM users AS u
INNER JOIN
(
SELECT DISTINCT ON (user_id)
, user_id
, sum(amount_cents) FILTER (WHERE status = 'paid') OVER (PARTITION BY user_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS total_paid
, sum(amount_cents) FILTER (WHERE status = 'unpaid') OVER (PARTITION BY user_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS total_unpaid
FROM invoices
ORDER BY u.user_id
) AS t
ON u.id = t.user_id
您可以使用标量子查询
select u.*,
(select sum(amount_cents) from invoices where user_id = u.id and status = 'paid') total_paid,
(select sum(amount_cents) from invoices where user_id = u.id and status = 'unpaid') total_unpaid
from users u;
或者横向连接可能会更有效一些。
select u.*, l.*
from users u
left join lateral
(
select sum(amount_cents) filter (where status = 'paid') total_paid,
sum(amount_cents) filter (where status = 'unpaid') total_unpaid
from invoices where user_id = u.id
) l on true;
但是如果 users.id
是主键(可能是这种情况),那么事情可以简化为
select u.*,
sum(i.amount_cents) filter (where i.status = 'paid') total_paid,
sum(i.amount_cents) filter (where i.status = 'unpaid') total_unpaid
from users u
left outer join invoices i on u.id = i.user_id
group by u.id;
另一种选择是使用外部连接
users_table = User.arel_table
paid_invoices_table = Arel::Table.new(Invoice.arel_table.name, as: 'paid_invoices')
unpaid_invoices_table = Arel::Table.new(Invoice.arel_table.name, as: 'unpaid_invoices')
paid_join = Arel::Nodes::OuterJoin.new(
paid_invoices_table,
Arel::Nodes::On.new(
users_table[:id].eq(paid_invoices_table[:user_id])
.and(paid_invoices_table[:status].eq('paid'))
)
)
unpaid_join = Arel::Nodes::OuterJoin.new(
unpaid_invoices_table,
Arel::Nodes::On.new(
users_table[:id].eq(unpaid_invoices_table[:user_id])
.and(unpaid_invoices_table[:status].not_eq('paid'))
)
)
User.joins(paid_join,unpaid_join)
.select(
User.arel_table[Arel.star],
paid_invoices_table[:amount_cents].sum.as('total_paid'),
unpaid_invoices_table[:amount_cents].sum.as('total_unpaid'))
.group(:id)
结果查询:
SELECT
users.*,
SUM(paid_invoices.amount_cents) AS total_paid,
SUM(unpaid_invoices.amount_cents) AS total_unpaid
FROM
users
LEFT OUTER JOIN invoices AS paid_invoices ON users.id = paid_invoices.user_id
AND paid_invoices.status = 'paid'
LEFT OUTER JOIN invoices AS unpaid_invoices ON users.id = unpaid_invoices.user_id
AND unpaid_invoices.status <> 'paid'
GROUP BY
users.id