Firebird 存储过程中可变数量的参数
Variable number of parameters in a Firebird stored procedure
我有以下存储过程:
ALTER PROCEDURE SP_STOCK_ANALYSIS
(
MAIN_GROUP CHAR(6)
)
RETURNS
(
STOCK_CODE CHAR(21),
STOCK_GROUP CHAR(6),
DESCRIPTION CHAR(31),
EXPENSE NUMERIC(15, 4)
)
AS
BEGIN
FOR
SELECT
L.STOCK_CODE, INV.DESCRIPTION, INV.STOCK_GROUP, L.BALANCE
FROM
LEDGER L LEFT JOIN INVENTORY INV ON L.STOCK_CODE = INV.STOCK_CODE
WHERE
INV.STOCK_GROUP in (:MAIN_GROUP)
AND L.LEDGER_ACCOUNT in ('71212', '71211' ,'83791')
INTO
STOCK_CODE, STOCK_GROUP, DESCRIPTION, EXPENSE
DO
在select语句中我有以下三个账户:
- 71212
- 71211
- 83791
理想情况下,我想更改存储过程,以便我能够输入帐号作为参数的一部分。挑战在于帐户数量可能会发生变化。是否可以使用字符串作为参数?我该怎么做?
Firebird 不支持存储过程的参数数量可变。但是,您可以定义默认参数值。因此,您可以指定一个没有默认值的第一个参数,然后指定多个具有默认值的参数,然后使用一个或多个参数调用存储过程。
create procedure SP_STOCK_ANALYSIS (
group_1 CHAR(6), group_2 CHAR(6) DEFAULT NULL, group_3 CHAR(6) DEFAULT NULL /* ... etc ...*/)
RETURNS (
STOCK_CODE CHAR(21),
STOCK_GROUP CHAR(6),
DESCRIPTION CHAR(31),
EXPENSE NUMERIC(15, 4))
as
begin
for select L.STOCK_CODE /* ... etc ... */
from LEDGER L LEFT JOIN INVENTORY INV ON L.STOCK_CODE = INV.STOCK_CODE
where INV.STOCK_GROUP in (group_1, group_2, group_3 /* ... etc ... */)
/* ... etc ... */
into STOCK_CODE /* ... etc ... */
do
begin
/* ... etc ... */
end
end
或者,您可以传递逗号分隔的字符串,并使用辅助存储过程将该字符串拆分为多行。
然后你会做类似的事情
create procedure SP_STOCK_ANALYSIS(group_list VARCHAR(8191)
RETURNS (
STOCK_CODE CHAR(21),
STOCK_GROUP CHAR(6),
DESCRIPTION CHAR(31),
EXPENSE NUMERIC(15, 4))
as
begin
for select L.STOCK_CODE /* ... etc ... */
from LEDGER L LEFT JOIN INVENTORY INV ON L.STOCK_CODE = INV.STOCK_CODE
where INV.STOCK_GROUP in (select group_value from split_groups(:group_list))
/* ... etc ... */
into STOCK_CODE /* ... etc ... */
do
begin
/* ... etc ... */
end
end
与split_groups
类似
create procedure split_group(group_list varchar(8191))
returns (group_value varchar(1000))
as
declare previouspos smallint = 1;
declare nextpos smallint;
begin
-- produce no rows for null input
if (group_list is null) then exit;
-- find next , in group_list
nextpos = position(',', group_list);
while (nextpos <> 0) do
begin
-- get item
group_value = substring(group_list from previouspos for nextpos - previouspos);
if (char_length(group_value) > 0) then
-- output item as a row
suspend;
-- first character after the found ,
previouspos = nextpos + 1;
-- find next , in group_list
nextpos = position(',', group_list, previouspos);
end
-- output item after last found ,
group_value = substring(group_list from previouspos);
if (char_length(group_value) > 0) then
suspend;
end
您还可以选择使用反向 LIKE
而不是 IN
。毕竟,随着项目数量的增加,IN
在 Interbase/Firebird 上会变慢。 LIKE
将始终对整个 table 进行自然扫描。如果项目数量少,速度会慢很多,但不会随着项目数量的增加而减慢。
进行您自己的分析。您甚至可以根据参数字符串长度切换到一种或另一种策略。您对 32KB 的 Firebird VarChar
长度限制感到困惑,也许它对您的应用程序很重要。
所以,对于一般方向,请参阅我在
的回答
要将“路线 #2”应用于您的案例,就像这样...
ALTER PROCEDURE SP_STOCK_ANALYSIS
(
MAIN_GROUP varCHAR(32760) character set ascii
)
RETURNS
(
STOCK_CODE varCHAR(21),
STOCK_GROUP varCHAR(6),
DESCRIPTION varCHAR(31),
EXPENSE NUMERIC(15, 4)
)
AS
BEGIN
FOR
SELECT
L.STOCK_CODE, INV.DESCRIPTION, INV.STOCK_GROUP, L.BALANCE
FROM
LEDGER L LEFT JOIN INVENTORY INV ON L.STOCK_CODE = INV.STOCK_CODE
WHERE
(:MAIN_GROUP CONTAINING '~' || INV.STOCK_GROUP || '~')
-- INV.STOCK_GROUP in (:MAIN_GROUP)
AND (L.LEDGER_ACCOUNT in ('71212', '71211' ,'83791'))
INTO
STOCK_CODE, STOCK_GROUP, DESCRIPTION, EXPENSE
DO
.....
然后你这样调用:
SELECT * FROM SP_STOCK_ANALYSIS ('~1~4~8~11~')
您可以使用 LIST
聚合函数将返回 ID 的查询转换为字符串,例如
SELECT '~' || LIST (ID, '~') || '~' FROM source-table WHERE ........
但我认为在工程方面最好使用本地事务 GTT
(全局临时 table)而不是双重转换,然后在 SP 中执行自然 join
。
insert into SP-helping-GTT
SELECT ID FROM source-table WHERE ........;
...然后执行无参数SP,然后COMMIT
清理GTT
当然,缺点是隐含的严格耦合和命名空间污染。
但是由于您同时对两个 table 进行了多次过滤 - L
和 INV
- 而且您 可能 想要转换两者都列为参数,然后 joining
两个 tables (GTT) 对于关系数据库引擎来说是自然的,而两个嵌套的自然扫描将得到较差的 O(n^2) 缩放。
此外,如果您需要异国情调的 CHAR
数据类型而不是 VARCHAR
,您真的会想。这确实是一个小麻烦,但人们不知何故一次又一次地被它绊倒。
- Firebird queries using chars/varchar
- Trim Char fields
- Trim whitespace from right
...这些只是少数。
我有以下存储过程:
ALTER PROCEDURE SP_STOCK_ANALYSIS
(
MAIN_GROUP CHAR(6)
)
RETURNS
(
STOCK_CODE CHAR(21),
STOCK_GROUP CHAR(6),
DESCRIPTION CHAR(31),
EXPENSE NUMERIC(15, 4)
)
AS
BEGIN
FOR
SELECT
L.STOCK_CODE, INV.DESCRIPTION, INV.STOCK_GROUP, L.BALANCE
FROM
LEDGER L LEFT JOIN INVENTORY INV ON L.STOCK_CODE = INV.STOCK_CODE
WHERE
INV.STOCK_GROUP in (:MAIN_GROUP)
AND L.LEDGER_ACCOUNT in ('71212', '71211' ,'83791')
INTO
STOCK_CODE, STOCK_GROUP, DESCRIPTION, EXPENSE
DO
在select语句中我有以下三个账户:
- 71212
- 71211
- 83791
理想情况下,我想更改存储过程,以便我能够输入帐号作为参数的一部分。挑战在于帐户数量可能会发生变化。是否可以使用字符串作为参数?我该怎么做?
Firebird 不支持存储过程的参数数量可变。但是,您可以定义默认参数值。因此,您可以指定一个没有默认值的第一个参数,然后指定多个具有默认值的参数,然后使用一个或多个参数调用存储过程。
create procedure SP_STOCK_ANALYSIS (
group_1 CHAR(6), group_2 CHAR(6) DEFAULT NULL, group_3 CHAR(6) DEFAULT NULL /* ... etc ...*/)
RETURNS (
STOCK_CODE CHAR(21),
STOCK_GROUP CHAR(6),
DESCRIPTION CHAR(31),
EXPENSE NUMERIC(15, 4))
as
begin
for select L.STOCK_CODE /* ... etc ... */
from LEDGER L LEFT JOIN INVENTORY INV ON L.STOCK_CODE = INV.STOCK_CODE
where INV.STOCK_GROUP in (group_1, group_2, group_3 /* ... etc ... */)
/* ... etc ... */
into STOCK_CODE /* ... etc ... */
do
begin
/* ... etc ... */
end
end
或者,您可以传递逗号分隔的字符串,并使用辅助存储过程将该字符串拆分为多行。
然后你会做类似的事情
create procedure SP_STOCK_ANALYSIS(group_list VARCHAR(8191)
RETURNS (
STOCK_CODE CHAR(21),
STOCK_GROUP CHAR(6),
DESCRIPTION CHAR(31),
EXPENSE NUMERIC(15, 4))
as
begin
for select L.STOCK_CODE /* ... etc ... */
from LEDGER L LEFT JOIN INVENTORY INV ON L.STOCK_CODE = INV.STOCK_CODE
where INV.STOCK_GROUP in (select group_value from split_groups(:group_list))
/* ... etc ... */
into STOCK_CODE /* ... etc ... */
do
begin
/* ... etc ... */
end
end
与split_groups
类似
create procedure split_group(group_list varchar(8191))
returns (group_value varchar(1000))
as
declare previouspos smallint = 1;
declare nextpos smallint;
begin
-- produce no rows for null input
if (group_list is null) then exit;
-- find next , in group_list
nextpos = position(',', group_list);
while (nextpos <> 0) do
begin
-- get item
group_value = substring(group_list from previouspos for nextpos - previouspos);
if (char_length(group_value) > 0) then
-- output item as a row
suspend;
-- first character after the found ,
previouspos = nextpos + 1;
-- find next , in group_list
nextpos = position(',', group_list, previouspos);
end
-- output item after last found ,
group_value = substring(group_list from previouspos);
if (char_length(group_value) > 0) then
suspend;
end
您还可以选择使用反向 LIKE
而不是 IN
。毕竟,随着项目数量的增加,IN
在 Interbase/Firebird 上会变慢。 LIKE
将始终对整个 table 进行自然扫描。如果项目数量少,速度会慢很多,但不会随着项目数量的增加而减慢。
进行您自己的分析。您甚至可以根据参数字符串长度切换到一种或另一种策略。您对 32KB 的 Firebird VarChar
长度限制感到困惑,也许它对您的应用程序很重要。
所以,对于一般方向,请参阅我在
要将“路线 #2”应用于您的案例,就像这样...
ALTER PROCEDURE SP_STOCK_ANALYSIS
(
MAIN_GROUP varCHAR(32760) character set ascii
)
RETURNS
(
STOCK_CODE varCHAR(21),
STOCK_GROUP varCHAR(6),
DESCRIPTION varCHAR(31),
EXPENSE NUMERIC(15, 4)
)
AS
BEGIN
FOR
SELECT
L.STOCK_CODE, INV.DESCRIPTION, INV.STOCK_GROUP, L.BALANCE
FROM
LEDGER L LEFT JOIN INVENTORY INV ON L.STOCK_CODE = INV.STOCK_CODE
WHERE
(:MAIN_GROUP CONTAINING '~' || INV.STOCK_GROUP || '~')
-- INV.STOCK_GROUP in (:MAIN_GROUP)
AND (L.LEDGER_ACCOUNT in ('71212', '71211' ,'83791'))
INTO
STOCK_CODE, STOCK_GROUP, DESCRIPTION, EXPENSE
DO
.....
然后你这样调用:
SELECT * FROM SP_STOCK_ANALYSIS ('~1~4~8~11~')
您可以使用 LIST
聚合函数将返回 ID 的查询转换为字符串,例如
SELECT '~' || LIST (ID, '~') || '~' FROM source-table WHERE ........
但我认为在工程方面最好使用本地事务 GTT
(全局临时 table)而不是双重转换,然后在 SP 中执行自然 join
。
insert into SP-helping-GTT
SELECT ID FROM source-table WHERE ........;
...然后执行无参数SP,然后COMMIT
清理GTT
当然,缺点是隐含的严格耦合和命名空间污染。
但是由于您同时对两个 table 进行了多次过滤 - L
和 INV
- 而且您 可能 想要转换两者都列为参数,然后 joining
两个 tables (GTT) 对于关系数据库引擎来说是自然的,而两个嵌套的自然扫描将得到较差的 O(n^2) 缩放。
此外,如果您需要异国情调的 CHAR
数据类型而不是 VARCHAR
,您真的会想。这确实是一个小麻烦,但人们不知何故一次又一次地被它绊倒。
- Firebird queries using chars/varchar
- Trim Char fields
- Trim whitespace from right
...这些只是少数。