我有以下查询:
SELECT
SUM("balance_transactions"."fee") AS sum_id
FROM "balance_transactions"
JOIN charges ON balance_transactions.source = charges.balance_id
WHERE "balance_transactions"."account_id" = 6
AND (balance_transactions.type = 'charge'
AND charges.refunded = false
AND charges.invoice IS NOT NULL)
AND ("balance_transactions"."created" BETWEEN '2013-12-20' AND '2014-01-19');
这样做会增加这两个日期之间发生的所有“费用”。大。工作正常。
问题是我几乎总是一次需要数百个日期范围的费用,这相当于我运行相同的查询数百次。效率不高。
但是有没有办法将其压缩为所有日期范围的单个查询?
例如,我会为SUM
调用一系列范围,如下所示:
2013-12-20 to 2014-01-19
2013-12-21 to 2014-01-20
2013-12-22 to 2014-01-21
2013-12-23 to 2014-01-22
2013-12-24 to 2014-01-23
...so on and so on
我需要输出在每个日期范围内收集的费用总和(最终需要在数组中)。
那么,有关处理这种情况和减少数据库事务的方法的任何想法吗?
FWIW,这是在Rails应用程序内的Postgres上。
答案 0 :(得分:8)
假设我正确理解了您的要求,我认为您需要的是这样的:
SELECT "periods"."start_date",
"periods"."end_date",
SUM(CASE WHEN "balance_transactions"."created" BETWEEN "periods"."start_date" AND "periods"."end_date" THEN "balance_transactions"."fee" ELSE 0.00 END) AS period_sum
FROM "balance_transactions"
JOIN charges ON balance_transactions.source = charges.balance_id
JOIN ( SELECT '2013-12-20'::date as start_date, '2014-01-19'::date as end_date UNION ALL
SELECT '2013-12-21'::date as start_date, '2014-01-20'::date as end_date UNION ALL
SELECT '2013-12-22'::date as start_date, '2014-01-21'::date as end_date UNION ALL
SELECT '2013-12-23'::date as start_date, '2014-01-22'::date as end_date UNION ALL
SELECT '2013-12-24'::date as start_date, '2014-01-23'::date as end_date
) as periods
ON "balance_transactions"."created" BETWEEN "periods"."start_date" AND "periods"."end_date"
WHERE "balance_transactions"."account_id" = 6
AND "balance_transactions"."type" = 'charge'
AND "charges"."refunded" = false
AND "charges"."invoice" IS NOT NULL
GROUP BY "periods"."start_date", "periods"."end_date"
这应该会在一个结果集中返回您感兴趣的所有句点。 由于查询是在前端即时“生成”的,因此您可以根据需要向句点部分添加任意数量的行。
编辑:经过一些试验和错误,我设法让它在[sqlFiddle] [1]中运行并相应地更新了上面的语法。
答案 1 :(得分:0)
从SQL Server背景来看,我会将你的where子句更改为
...
AND (
"balance_transactions"."created" BETWEEN '2013-12-20' AND '2014-01-19'
OR
"balance_transactions"."created" BETWEEN '2013-12-21' AND '2014-01-20'
OR
"balance_transactions"."created" BETWEEN '2013-12-23' AND '2014-01-22'
OR
"balance_transactions"."created" BETWEEN '2013-12-24' AND '2014-01-23'
);
请确保您在这些日期有一个很好的索引! :)
答案 2 :(得分:0)
这是您可以使用的未经测试的程序。
CREATE OR REPLACE PROCEDURE sum_fees(v_start IN Date, v_end in Date) IS
BEGIN
SELECT
SUM("balance_transactions"."fee") AS sum_id
FROM "balance_transactions"
JOIN charges ON balance_transactions.source = charges.balance_id
WHERE "balance_transactions"."account_id" = 6
AND (balance_transactions.type = 'charge'
AND charges.refunded = false
AND charges.invoice IS NOT NULL)
AND ("balance_transactions"."created" BETWEEN v_start AND v_end);
END;
然后使用您的范围日期调用该程序。
答案 3 :(得分:0)
它尚未被提及,并且不如单个选择BUT那样有效,因为您可以使用UNION
和您自己的静态标识符(日期范围组)组合,例如:
SELECT
'a' AS DateRangeIdentifier,
SUM("balance_transactions"."fee") AS sum_id
FROM "balance_transactions"
JOIN charges ON balance_transactions.source = charges.balance_id
WHERE "balance_transactions"."account_id" = 6
AND (balance_transactions.type = 'charge'
AND charges.refunded = false
AND charges.invoice IS NOT NULL)
AND ("balance_transactions"."created" BETWEEN '2013-01-01' AND '2013-01-31')
UNION
SELECT
'b' AS DateRangeIdentifier,
SUM("balance_transactions"."fee") AS sum_id
FROM "balance_transactions"
JOIN charges ON balance_transactions.source = charges.balance_id
WHERE "balance_transactions"."account_id" = 6
AND (balance_transactions.type = 'charge'
AND charges.refunded = false
AND charges.invoice IS NOT NULL)
AND ("balance_transactions"."created" BETWEEN '2013-07-01' AND '2013-07-31')
UNION
SELECT
'c' AS DateRangeIdentifier,
SUM("balance_transactions"."fee") AS sum_id
FROM "balance_transactions"
JOIN charges ON balance_transactions.source = charges.balance_id
WHERE "balance_transactions"."account_id" = 6
AND (balance_transactions.type = 'charge'
AND charges.refunded = false
AND charges.invoice IS NOT NULL)
AND ("balance_transactions"."created" BETWEEN '2013-12-20' AND '2014-01-19')
GROUP BY DateRangeIdentifier, sum_id
这种方式至少会产生一个数据库请求,而不是代码中的循环。
请在此处查看更新的小提琴:http://sqlfiddle.com/#!15/9ce0f/5
答案 4 :(得分:0)
如果我理解得很好,你想重新使用日期查询。为此,可以重用的查询部分是每日部分。我的意思是:
SELECT
SUM("balance_transactions"."fee") AS sum_id
FROM "balance_transactions"
JOIN charges ON balance_transactions.source = charges.balance_id
WHERE "balance_transactions"."account_id" = 6
AND (balance_transactions.type = 'charge'
AND charges.refunded = false
AND charges.invoice IS NOT NULL)
AND ("balance_transactions"."created" = 'yyyy-mm-dd');
假设您的“已创建”字段只是日期而非时间戳,并且如果过去几天的数据没有更改,您可以将此查询转储到表中:
insert into sum_table
SELECT
"balance_transactions"."created" balance_created
SUM("balance_transactions"."fee") AS balance_fee
FROM "balance_transactions"
JOIN charges ON balance_transactions.source = charges.balance_id
WHERE "balance_transactions"."account_id" = 6
AND (balance_transactions.type = 'charge'
AND charges.refunded = false
AND charges.invoice IS NOT NULL)
group by "balance_transactions"."created"
;
然后将您的主查询更改为:
SELECT
SUM(balance_fee) AS sum_id
FROM sum_table where balance_created between ('2013-12-20' AND '2014-01-19');
另一个优化是消除介于两者之间,因为通常它不使用索引,如果你有很多不同的日期,它可能会很慢。
更好的方式:
SELECT
SUM(balance_fee) AS sum_id
FROM sum_table where balance_created in ('2013-12-20', '2013-12-21', '2013-12-22' ... '2014-01-19');
但为此,您必须直接在客户端应用程序(ej.DAO)中创建SQL
希望这有帮助。
答案 5 :(得分:0)
这将返回给定日期和今天之间所有月份范围的总和。
元组输出 - SQL Fiddle
select distinct on (s.d)
s.d as "date",
sum(bt.fee) over (
partition by daterange(s.d, (s.d + interval '1 month')::date, '[]')
) as sum_id
from
balance_transactions bt
inner join
charges on bt.source = charges.balance_id
right join
(
select d::date as d
from generate_series (
'2013-12-20'::date,
current_date,
interval '1 day'
) s(d)
) s on s.d = bt.created
where
bt.account_id = 6
and bt.type = 'charge'
and charges.refunded = false
and charges.invoice is not null
order by s.d
数组输出。不适用于SQL Fiddle但适用于我的桌面。
select array_agg(("date", sum_id)) as arr_sum_id
from (
select distinct on (s.d)
s.d as "date",
sum(bt.fee) over (
partition by daterange(s.d, (s.d + interval '1 month')::date, '[]')
) as sum_id
from
balance_transactions bt
inner join
charges on bt.source = charges.balance_id
right join
(
select d::date as d
from generate_series (
'2013-12-20'::date,
current_date,
interval '1 day'
) s(d)
) s on s.d = bt.created
where
bt.account_id = 6
and bt.type = 'charge'
and charges.refunded = false
and charges.invoice is not null
order by s.d
) s
答案 6 :(得分:0)
试试这个:
create table timeframes (
start_dt date,
end_dt date
);
insert into timeframes values ('2013-12-20', '2014-01-19');
insert into timeframes values ('2013-12-21', '2014-01-20');
insert into timeframes values ('2013-12-22', '2014-01-21');
insert into timeframes values ('2013-12-23', '2014-01-22');
insert into timeframes values ('2013-12-24', '2014-01-23');
SELECT
tf.start_date,
tf.end_date,
SUM(CASE
WHEN t.created BETWEEN tf.start_date AND tf.end_date THEN t.fee
ELSE 0.00
END) as transaction_sum
FROM
balance_transactions t
INNER JOIN
charges c
ON
t.source = c.balance_id
INNER JOIN
timeframes tf
ON
t.created BETWEEN tf.start_date AND tf.end_date
WHERE
t.account_id = 6
AND
(
t.type = 'charge'
AND
c.refunded = false
AND
c.invoice IS NOT NULL
)
GROUP BY
tf.start_date,
tf.end_date
答案 7 :(得分:0)
SELECT periods.start_date,
periods.end_date,
SUM(fee) AS Period_Sum
FROM "balance_transactions"
JOIN charges ON balance_transactions.source = charges.balance_id
JOIN
(SELECT CAST('2013-12-20' AS DATE) AS start_date, CAST('2014-01-19' AS DATE) AS end_date UNION ALL
SELECT CAST('2013-12-21' AS DATE),CAST('2014-01-20' AS DATE) UNION ALL
SELECT CAST('2013-12-22' AS DATE), CAST('2014-01-21' AS DATE) UNION ALL
SELECT CAST('2013-12-23' AS DATE), CAST('2014-01-22' AS DATE) UNION ALL
SELECT CAST('2013-12-24' AS DATE), CAST('2014-01-23' AS DATE)) as periods
ON "balance_transactions"."created" BETWEEN periods.start_date AND periods.end_date
WHERE "balance_transactions"."account_id" = 6
AND (balance_transactions.type = 'charge'
AND charges.refunded = false
AND charges.invoice IS NOT NULL)
GROUP BY periods.start_date, periods.end_date
以下链接到SQL Fiddle我测试过的地方: http://sqlfiddle.com/#!10/535ac/11/0
答案 8 :(得分:0)
我参与了以下代码。它使用XML文件。该文件或字符串包含您需要sumarize的日期范围。存储过程将返回一个表格,其中包含每个特定范围的总计。
/*****************CREATES/POPULATES FAKE TABLES WITH A SIMILAR STRUCTURE TO THE ONE YOU ARE USING************/
DECLARE @balance_transactions TABLE(fee FLOAT,
source INT,
account_id INT,
[type] VARCHAR(25),
created DATETIME)
INSERT INTO @balance_transactions
SELECT 12.5, 1, 6, 'charge', '01/15/2012'
UNION
SELECT 70, 2, 6, 'charge', '01/16/2012'
UNION
SELECT 136.89, 3, 6, 'charge', '01/17/2012'
UNION
SELECT 29.16, 4, 6, 'charge', '01/18/2012'
UNION
SELECT 1369.54, 5, 6, 'charge', '02/21/2012'
UNION
SELECT 468.85, 6, 6, 'charge', '02/22/2012'
UNION
SELECT 65.8, 7, 6, 'charge', '02/22/2012'
UNION
SELECT 1236.87, 8, 6, 'charge', '02/22/2012'
DECLARE @charges TABLE(balance_id INT,
refunded BIT,
invoice INT)
INSERT INTO @charges
SELECT 1, 0, 7
UNION
SELECT 2, 0, 8
UNION
SELECT 3, 0, 9
UNION
SELECT 4, 0, 10
UNION
SELECT 5, 0, 11
UNION
SELECT 6, 0, 12
UNION
SELECT 7, 0, null
UNION
SELECT 8, 0, null
/*******************************************************/
/*
You can use the code below for creating an Stored Procedure.
The SP will return a table with the SUM of all those values indicating the Date Range.
spGetTotalsPerDateRange 'your xml goes here'
results:
fromDate |toDate |total
2012-01-15 00:00:00.000 |2012-01-30 00:00:00.000 |248.55
2012-02-15 00:00:00.000 |2012-02-28 00:00:00.000 |3141.06
*/
SET DATEFORMAT MDY
DECLARE @XmlDocumentHandle int
DECLARE @XmlDocument nvarchar(4000)
SET @XmlDocument =
N'<dates>
<range>
<fromDate>01/15/2012</fromDate>
<toDate>01/30/2012</toDate>
</range>
<range>
<fromDate>02/15/2012</fromDate>
<toDate>02/28/2012</toDate>
</range>
</dates>'
EXEC sp_xml_preparedocument @XmlDocumentHandle OUTPUT, @XmlDocument
DECLARE @feeTotal TABLE(fromDate DATETIME,
toDate DATETIME,
total FLOAT)
DECLARE @fromDate DATETIME
DECLARE @toDate DATETIME
DECLARE ranges_cur CURSOR FOR
SELECT fromDate, toDate
FROM OPENXML (@XmlDocumentHandle, '/dates/range',2)
WITH (fromDate DATETIME,
toDate DATETIME);
OPEN ranges_cur;
FETCH NEXT FROM ranges_cur INTO @fromDate, @toDate;
WHILE @@FETCH_STATUS = 0
BEGIN
INSERT INTO @feeTotal
SELECT @fromDate,
@toDate,
SUM(bt.fee)
FROM @balance_transactions bt
INNER JOIN @charges c ON bt.source = c.balance_id
WHERE bt.account_id = 6
AND (bt.type = 'charge'
AND c.refunded = 0
AND c.invoice IS NOT NULL)
AND (bt.created >= @fromDate AND bt.created <= @toDate);
FETCH NEXT FROM ranges_cur INTO @fromDate, @toDate;
END;
CLOSE ranges_cur;
DEALLOCATE ranges_cur;
SELECT fromDate,
toDate,
total
FROM @feeTotal
EXEC sp_xml_removedocument @XmlDocumentHandle
GO
参数化查询将如下所示。
CREATE PROCEDURE spGetTotalsPerDateRange(@XmlDocument NVARCHAR(4000),
@type VARCHAR(50) = 'charge',
@refunded BIT = 0)
AS
BEGIN
SET DATEFORMAT MDY
DECLARE @XmlDocumentHandle INT
EXEC sp_xml_preparedocument @XmlDocumentHandle OUTPUT, @XmlDocument
DECLARE @feeTotal TABLE(fromDate DATETIME,
toDate DATETIME,
total FLOAT)
DECLARE @fromDate DATETIME
DECLARE @toDate DATETIME
DECLARE ranges_cur CURSOR FOR
SELECT fromDate, toDate
FROM OPENXML (@XmlDocumentHandle, '/dates/range',2)
WITH (fromDate DATETIME,
toDate DATETIME);
OPEN ranges_cur;
FETCH NEXT FROM ranges_cur INTO @fromDate, @toDate;
WHILE @@FETCH_STATUS = 0
BEGIN
INSERT INTO @feeTotal
SELECT @fromDate,
@toDate,
SUM(bt.fee)
FROM balance_transactions bt
INNER JOIN charges c ON bt.source = c.balance_id
WHERE bt.account_id = 6
AND (bt.type = 'charge'
AND c.refunded = 0
AND c.invoice IS NOT NULL)
AND (bt.created >= @fromDate AND bt.created <= @toDate);
FETCH NEXT FROM ranges_cur INTO @fromDate, @toDate;
END;
CLOSE ranges_cur;
DEALLOCATE ranges_cur;
SELECT fromDate,
toDate,
total
FROM @feeTotal
EXEC sp_xml_removedocument @XmlDocumentHandle
END
GO
第一个代码是使用虚假数据测试的,它运行正常。您需要根据表定义对SP列和变量类型进行必要的类型和名称调整。
这种方法的想法是能够以您需要的方式报告任何信息。您还可以通过XML属性传递其他参数。在MSDN website
上查看更多OPEN XML信息希望有所帮助
答案 9 :(得分:0)
我来自oracle PL / SQL背景。在我看来,您可以使用以下查询来使用它。我的理解是,如果任何交易发生在19-NOV-2013上午1点,总是会在11月19日到11月20日,所以我们不应该担心我在下面查询创建的范围。我希望它会帮助你。
SELECT DISTINCT account_id,
TRUNC(created)
||' to '
|| (TRUNC(created)+1) period,
SUM(FEE) over (partition BY account_id,TRUNC(created) ) sumid
FROM balance_transactions a,
charges b
WHERE a.source =balance_id
AND b.refunded =0
AND b.invoice IS NOT NULL
AND a.type ='charge'
ORDER BY TRUNC(created)
||' to '
|| (TRUNC(created)+1);