如何提高计算 运行 总数的 select 语句的性能
How to improve performance of a select statement, which calculates a running total
我正在使用 Firebird 2.5.2 并试图找出如何提高 select 语句的性能,该语句计算 运行 总数。情况是这样的:
A table“PAYMENTS”(为了更好的概览而进行了简化)
CREATE TABLE PAYMENTS
(
ID INTEGER NOT NULL,
PAYMENT_TYPE SMALLINT DEFAULT 0,
BANKING_ACCOUNTID INTEGER DEFAULT -100,
AMOUNT DOUBLE PRECISION,
CALCAMOUNT DOUBLE PRECISION,
PAYMENT_DATE TIMESTAMP
);
ALTER TABLE PAYMENTS ADD CONSTRAINT PK_PAYMENTS PRIMARY KEY (ID);
CREATE GENERATOR PAYMENTS_PRIMARYKEY;
CREATE ASC INDEX PAYMENTS_AMOUNT ON PAYMENTS (AMOUNT);
CREATE ASC INDEX PAYMENTS_BANKACCOUNT ON PAYMENTS (BANKING_ACCOUNTID);
CREATE ASC INDEX PAYMENTS_BDATE ON PAYMENTS (PAYMENT_DATE);
ALTER TABLE PAYMENTS ADD CONSTRAINT FK_PAYM_BANKING_ACCOUNTID FOREIGN KEY (BANKING_ACCOUNTID) REFERENCES BANKINGACCOUNTS (ID) ON UPDATE CASCADE ON DELETE SET DEFAULT;
“金额”中的值始终为正数。无论是收款还是付款,都是通过“PAYMENT_TYPE”定义的。两者都确定“CALCAMOUNT”中的值,即出于计算原因,此处的值是“-100”或“100”。
计算总和的存储过程:
SET TERM ^^ ;
create procedure SP_BALANCE_FOR_DATE_AND_BANKID (PAYMENT_DATE timestamp, BANKING_ACCOUNTID integer) returns (BALANCE double precision)
as
declare variable TempAmount double precision;
begin
TempAmount = 0;
select sum(CALCAMOUNT) as SUMME from PAYMENTS where (PAYMENT_DATE < :PAYMENT_DATE) and (BANKING_ACCOUNTID = :BANKING_ACCOUNTID) into TempAmount;
if (TempAmount is null) then TempAmount = 0;
BALANCE = TempAmount;
suspend;
end
^^
SET TERM ; ^^
实际select语句:
select PAYMENTS.*,
case when PAYMENT_TYPE = 1 then ((select BALANCE from SP_BALANCE_FOR_DATE_AND_BANKID(PAYMENT_DATE, BANKING_ACCOUNTID)) + AMOUNT)
else ((select BALANCE from SP_BALANCE_FOR_DATE_AND_BANKID(PAYMENT_DATE, BANKING_ACCOUNTID)) - AMOUNT) end
as CURRENTBALANCE
from PAYMENTS
where (ID > 0) and (BANKING_ACCOUNTID = :BANKING_ACCOUNTID) and ((PAYMENT_TYPE = 1) or (PAYMENT_TYPE = 2)) and (PAYMENT_DATE >= '01.11.2021') and (PAYMENT_DATE <= '30.11.2021 23:59:59')
order by PAYMENT_DATE
用户可以定义银行账户和日期范围,结果显示在网格中,除了“PAYMENTS”的所有字段外,还显示“CURRENTBALANCE”字段,显示余额/总计在 之后适当付款。无论要显示的记录的定义日期范围如何,都必须为该记录计算一条记录之前的 所有 条记录(基于 PAYMENT_DATE)。
问题:很慢,记录越多
我在发帖之前进行了搜索,发现了以下解决方案:
- 和我的一样,但是没有存储过程,即在 select 语句
中有一个子查询
- 带有自连接 PAYMENTS 的查询 table
- 使用解析函数的查询
前两个的性能差不多,就是很慢。在测试之前,我也尝试过添加更多的索引。
显然,第三个只有 Firebird 3.0 才有可能。由于我仍在使用 Firebird 2.5,我想问一下,如何提高 select 语句的性能?
当然可以选择在存储每条记录后直接存储适当的总数,但这里的问题是,一旦付款被删除或修改,删除/修改后所有记录的总数一个变得不正确。
您应该将整个查询放入存储过程中,在一个简单的循环中计算并返回总数 运行。这将是最快的方法。
像这样:
CREATE PROCEDURE RUNNING_BALANCE(BIGINT ACCOUNTID, DATE_BEGIN DATE, DATE_END DATE)
RETURNS (BALANCE DOUBLE PRECISION)
AS
DECLARE t SMALLINT;
DECLARE a DOUBLE PRECISION;
BEGIN
BALANCE = 0;
select sum(CALCAMOUNT) from PAYMENTS
where (PAYMENT_DATE < :DATE_BEGIN)
and (BANKING_ACCOUNTID = :ACCOUNTID)
into :BALANCE;
FOR select PAYMENT_TYPE, AMOUNT from PAYMENTS
where (ID > 0)
and (BANKING_ACCOUNTID = :ACCOUNTID)
and ((PAYMENT_TYPE = 1) or (PAYMENT_TYPE = 2))
and (PAYMENT_DATE >= :DATE_BEGIN)
and (PAYMENT_DATE < :DATE_END + 1)
order by PAYMENT_DATE
INTO :t, :a DO
BEGIN
IF (t = 1) THEN
BALANCE = BALANCE + a;
ELSE
BALANCE = BALANCE - a;
SUSPEND;
END
END
我正在使用 Firebird 2.5.2 并试图找出如何提高 select 语句的性能,该语句计算 运行 总数。情况是这样的:
A table“PAYMENTS”(为了更好的概览而进行了简化)
CREATE TABLE PAYMENTS
(
ID INTEGER NOT NULL,
PAYMENT_TYPE SMALLINT DEFAULT 0,
BANKING_ACCOUNTID INTEGER DEFAULT -100,
AMOUNT DOUBLE PRECISION,
CALCAMOUNT DOUBLE PRECISION,
PAYMENT_DATE TIMESTAMP
);
ALTER TABLE PAYMENTS ADD CONSTRAINT PK_PAYMENTS PRIMARY KEY (ID);
CREATE GENERATOR PAYMENTS_PRIMARYKEY;
CREATE ASC INDEX PAYMENTS_AMOUNT ON PAYMENTS (AMOUNT);
CREATE ASC INDEX PAYMENTS_BANKACCOUNT ON PAYMENTS (BANKING_ACCOUNTID);
CREATE ASC INDEX PAYMENTS_BDATE ON PAYMENTS (PAYMENT_DATE);
ALTER TABLE PAYMENTS ADD CONSTRAINT FK_PAYM_BANKING_ACCOUNTID FOREIGN KEY (BANKING_ACCOUNTID) REFERENCES BANKINGACCOUNTS (ID) ON UPDATE CASCADE ON DELETE SET DEFAULT;
“金额”中的值始终为正数。无论是收款还是付款,都是通过“PAYMENT_TYPE”定义的。两者都确定“CALCAMOUNT”中的值,即出于计算原因,此处的值是“-100”或“100”。
计算总和的存储过程:
SET TERM ^^ ;
create procedure SP_BALANCE_FOR_DATE_AND_BANKID (PAYMENT_DATE timestamp, BANKING_ACCOUNTID integer) returns (BALANCE double precision)
as
declare variable TempAmount double precision;
begin
TempAmount = 0;
select sum(CALCAMOUNT) as SUMME from PAYMENTS where (PAYMENT_DATE < :PAYMENT_DATE) and (BANKING_ACCOUNTID = :BANKING_ACCOUNTID) into TempAmount;
if (TempAmount is null) then TempAmount = 0;
BALANCE = TempAmount;
suspend;
end
^^
SET TERM ; ^^
实际select语句:
select PAYMENTS.*,
case when PAYMENT_TYPE = 1 then ((select BALANCE from SP_BALANCE_FOR_DATE_AND_BANKID(PAYMENT_DATE, BANKING_ACCOUNTID)) + AMOUNT)
else ((select BALANCE from SP_BALANCE_FOR_DATE_AND_BANKID(PAYMENT_DATE, BANKING_ACCOUNTID)) - AMOUNT) end
as CURRENTBALANCE
from PAYMENTS
where (ID > 0) and (BANKING_ACCOUNTID = :BANKING_ACCOUNTID) and ((PAYMENT_TYPE = 1) or (PAYMENT_TYPE = 2)) and (PAYMENT_DATE >= '01.11.2021') and (PAYMENT_DATE <= '30.11.2021 23:59:59')
order by PAYMENT_DATE
用户可以定义银行账户和日期范围,结果显示在网格中,除了“PAYMENTS”的所有字段外,还显示“CURRENTBALANCE”字段,显示余额/总计在 之后适当付款。无论要显示的记录的定义日期范围如何,都必须为该记录计算一条记录之前的 所有 条记录(基于 PAYMENT_DATE)。
问题:很慢,记录越多
我在发帖之前进行了搜索,发现了以下解决方案:
- 和我的一样,但是没有存储过程,即在 select 语句 中有一个子查询
- 带有自连接 PAYMENTS 的查询 table
- 使用解析函数的查询
前两个的性能差不多,就是很慢。在测试之前,我也尝试过添加更多的索引。
显然,第三个只有 Firebird 3.0 才有可能。由于我仍在使用 Firebird 2.5,我想问一下,如何提高 select 语句的性能?
当然可以选择在存储每条记录后直接存储适当的总数,但这里的问题是,一旦付款被删除或修改,删除/修改后所有记录的总数一个变得不正确。
您应该将整个查询放入存储过程中,在一个简单的循环中计算并返回总数 运行。这将是最快的方法。
像这样:
CREATE PROCEDURE RUNNING_BALANCE(BIGINT ACCOUNTID, DATE_BEGIN DATE, DATE_END DATE)
RETURNS (BALANCE DOUBLE PRECISION)
AS
DECLARE t SMALLINT;
DECLARE a DOUBLE PRECISION;
BEGIN
BALANCE = 0;
select sum(CALCAMOUNT) from PAYMENTS
where (PAYMENT_DATE < :DATE_BEGIN)
and (BANKING_ACCOUNTID = :ACCOUNTID)
into :BALANCE;
FOR select PAYMENT_TYPE, AMOUNT from PAYMENTS
where (ID > 0)
and (BANKING_ACCOUNTID = :ACCOUNTID)
and ((PAYMENT_TYPE = 1) or (PAYMENT_TYPE = 2))
and (PAYMENT_DATE >= :DATE_BEGIN)
and (PAYMENT_DATE < :DATE_END + 1)
order by PAYMENT_DATE
INTO :t, :a DO
BEGIN
IF (t = 1) THEN
BALANCE = BALANCE + a;
ELSE
BALANCE = BALANCE - a;
SUSPEND;
END
END