1

我有以下存储过程:

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

理想情况下,我想更改存储过程,以便能够输入帐号作为参数的一部分。挑战在于帐户数量可能会发生变化。是否可以使用字符串作为参数?我该怎么做呢?

4

2 回答 2

1

Firebird 不支持存储过程的可变数量参数。但是,您可以定义默认参数值。因此,您可以指定第一个不带默认值的参数,然后指定多个带默认值的参数,然后调用带有 1 个或多个参数的存储过程。

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
于 2021-02-18T14:01:56.230 回答
1

您还可以选择使用 reversedLIKE而不是IN. 毕竟,IN随着项目数量的增加,在 Interbase/Firebird 上可能会变慢。LIKE总是会对整个表进行自然扫描。如果项目数量很少,则速度会慢得多,但不会随着项目数量的增加而减慢。

进行自己的分析。您甚至可以根据参数字符串长度切换到一种或另一种策略。您对 32KB 的 FirebirdVarChar长度限制感到困惑,也许这对您的应用程序很重要。

因此,对于一般方向,请参阅我在https://stackoverflow.com/a/43997801/976391的回答

将“Route #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(全局临时表)而不是双重转换,然后join在你的 SP 中自然地做。

insert into SP-helping-GTT 
  SELECT ID FROM source-table WHERE ........;

...然后执行无参数 SP,然后只是COMMIT清理GTT

当然,缺点是隐含的严格耦合和命名空间污染。

但是由于您一次对两个表进行多次过滤 -L并且INV- 并且您可能希望将两个列表都转换为参数,因此joining对于关系数据库引擎来说,两个表 (GTT) 将是自然的,而两个嵌套的自然扫描将变得很差 O(n ^2) 缩放。


此外,您真的会考虑是否需要外来CHAR数据类型而不是VARCHAR. 这确实是一个小麻烦,但人们不知何故一次又一次地被它绊倒。

……这些只是少数。

于 2021-02-19T10:43:48.740 回答