我很难用 Rails ORM 为 generate_series 重写纯SQL查询。
首先,解释一下我要做什么。
我的任务是从用户每月选择的日期间隔中提取发票总数。容易。
因此,如果用户选择2019年,则结果将类似于:
Month | total_sum
2 500
3 600
5 700
问题是,结果仅包含发票中存在的月份。
要解决此问题,我想用generate_series
(SQL函数)填补几个月的空白。
我想出了类似这样的SQL:
WITH
range_values AS (
SELECT date_trunc('month', date_start) as minval,
date_trunc('month', date_end) as maxval
FROM transactions),
months_range AS (
SELECT generate_series(minval, maxval, '1 month'::interval) as month
FROM range_values
),
monthly_sum AS (
SELECT date_trunc('month', payment_date) AS month,
SUM(total) AS total
FROM transactions
GROUP BY month
)
SELECT date_part('month', months_range.month),
coalesce(monthly_sum.total, 0) AS total
FROM months_range
LEFT JOIN monthly_sum ON months_range.month = monthly_sum.month;
结果是我期望的,但是我想将此方法与现有关系链接起来。
我想到了:
@relation.joins("LEFT JOIN generate_series(TIMESTAMP '1-1-2018', TIMESTAMP '1-1-2021', interval '1 month') AS series ON transactions.payment_date = series")
.group("series")
.select("series, sum(transactions.total)"`)
跟踪生成的查询:
SELECT series, sum(transactions.total)
FROM transactions
LEFT JOIN generate_series(TIMESTAMP '1-1-2018', TIMESTAMP '1-1-2021', interval '1 month') AS series ON transactions.payment_date = series
GROUP BY series
rails的结果是:
#<ActiveRecord::Relation [#<Transaction id: nil>]>
注意:我期望的是类似[{“ series” =>'x',“ total” =>'y'},{#...},#的数组。 ..]不是Transaction
个对象
如果我在 pgadmin 中运行此生成的SQL,结果为:
series | sum
null 5881
使用:
我现在得到了什么,但是还没有得到结果:
def group_total_by_months
@relation.joins("RIGHT JOIN generate_series(TIMESTAMP '1-1-2018', TIMESTAMP '1-1-2020', interval '1 month') AS series
ON date_trunc('month', transactions.payment_date) = series")
.group("series")
.select("series, sum(transactions.total) AS total")
end
结果:
#<ActiveRecord::AssociationRelation [#<Transaction id: nil, total: 0.61173e3>, #<Transaction id: nil, total: 0.364446e4>, #<Transaction id: nil, total: 0.1625e4>]>
结果是正确的3个月,但缺少日期序列。 to_sql:
SELECT series AS payment_date, sum(transactions.total) AS total FROM
\"transactions\"
RIGHT JOIN generate_series(TIMESTAMP '1-1-2018', TIMESTAMP '1-1-2020',
interval '1 month') AS series ON date_trunc('month',
transactions.payment_date) = series
WHERE \"transactions\".\"account_id\" = 1
GROUP BY series
我想要实现的目标:
WITH filteret_transactions AS (
SELECT * FROM transactions WHERE transactions.account_id = 1
)
SELECT series AS payment_date, sum(filteret_transactions.total) AS total
FROM filteret_transactions
RIGHT JOIN generate_series(TIMESTAMP '1-1-2018', TIMESTAMP '1-1-2020', interval '1 month') AS series
ON date_trunc('month', filteret_transactions.payment_date) = series GROUP BY series
答案 0 :(得分:1)
我认为以下内容应该对您有用
def sums_by_month_range(date1,date2,relates_to, date_column: :payment_date, sum_column: :total)
relation_table = Arel::Table.new(:relation)
relation = Arel::Nodes::As.new(relation_table, relates_to.arel)
month_range_table = Arel::Table.new(:month_range)
month_range = Arel::Nodes::As.new(month_range_table,
Arel::SelectManager.new.project(
Arel::Nodes::NamedFunction.new('date_part',[Arel.sql("'month'"),Arel::Attribute.new(Arel::Table.new(:series),'month')]).as('month')
).distinct.from(
Arel::Nodes::NamedFunction.new('generate_series',[Arel.sql("date '#{date1.strftime('%Y-%m-%d')}'"),Arel.sql("date '#{date2.strftime('%Y-%m-%d')}'"),Arel.sql("'1 month'::interval")]).as('series(month)').to_sql
)
)
ActiveRecord::Base.connection.exec_query(
month_range_table.project(
month_range_table[:month],
relation_table[sum_column].sum.as('total')
).with(month_range, relation).join(relation_table, Arel::Nodes::OuterJoin).on(
Arel::Nodes::NamedFunction.new('date_trunc',[Arel.sql("'month'"),relation_table[date_column]]).eq(month_range_table[:month])
).group(month_range_table[:month]).to_sql
).to_hash
end
要重新创建第二个示例,可以将其称为:
sums_by_month(Date.new(2018,1,1),Date.new(2021,1,1), @relation)
这将返回Array
中的Hash
为[{'month' => x, 'total' => y}]
,您可以根据需要进行修改。查询将类似于
WITH month_range AS (
SELECT DISTINCT
date_part('month', [series].[month]) AS month
FROM
generate_series(date '2018-01-01', date '2019-01-01', '1 month'::interval) AS series(month)
), relation AS (
[WHATEVER AR QUERY OBJECT YOU PASS IN]
)
SELECT
month_range.month,
SUM(relation.total) AS total
FROM
month_range
LEFT OUTER JOIN relation ON date_trunc('month', relation.payment_date) = month_range.month
GROUP BY
month_range.month
TL; DR 这将重现您的确切查询(帖子中的示例1)
transaction_table = Transaction.arel_table
range_values_table = Arel::Table.new(:range_values)
range_values = Arel::Nodes::As.new(range_values_table,
transaction_table.project(
Arel::Nodes::NamedFunction.new('date_trunc',[Arel.sql("'month'"),transaction_table[:date_start]]).as('minval'),
Arel::Nodes::NamedFunction.new('date_trunc',[Arel.sql("'month'"),transaction_table[:date_end]]).as('maxval')
)
)
month_range_table = Arel::Table.new(:months_range)
month_range = Arel::Nodes::As.new(month_range_table,
range_values_table.project(
Arel::Nodes::NamedFunction.new('generate_series',[range_values_table[:minval],range_values_table[:maxval],Arel.sql("'1 month'::interval")]).as('month')
)
)
monthly_sum_table = Arel::Table.new(:monthly_sum)
monthly_sum = Arel::Nodes::As.new(monthly_sum_table,
transaction_table.project(
Arel::Nodes::NamedFunction.new('date_trunc',[Arel.sql("'month'"),transaction_table[:payment_date]]).as('month'),
transaction_table[:total].sum.as('total')
).group(Arel::Nodes::NamedFunction.new('date_trunc',[Arel.sql("'month'"),transaction_table[:payment_date]])))
query = month_range_table.project(
Arel::Nodes::NamedFunction.new('date_part',[Arel.sql("'month'"),month_range_table[:month]]).as('month'),
Arel::Nodes::NamedFunction.new('coalesce',[monthly_sum_table[:total],Arel.sql("0")]).as('total')
).with(
range_values,
month_range,
monthly_sum
).join(monthly_sum_table, Arel::Nodes::OuterJoin).on(monthly_sum_table[:month].eq(month_range_table[:month]))
ActiveRecord::Base.connection.exec_query(query.to_sql).to_hash
生成的SQL
WITH range_values AS (
SELECT
date_trunc('month', transactions.date_start) AS minval,
date_trunc('month', transactions.date_end) AS maxval
FROM
transactions),
months_range AS (
SELECT
generate_series(range_values.minval, range_values.maxval, '1 month'::interval) AS month
FROM
range_values),
monthly_sum AS (
SELECT
date_trunc('month', transactions.payment_date) AS month,
SUM(transactions.total) AS total
FROM
transactions
GROUP BY
date_trunc('month', transactions.payment_date))
SELECT
date_part('month', months_range.month) AS month,
coalesce(monthly_sum.total, 0) AS total
FROM
months_range
LEFT OUTER JOIN monthly_sum ON monthly_sum.month = months_range.month