HANA - 将字符串变量传递到 SQL 脚本中的 WHERE IN() 子句

Posted

技术标签:

【中文标题】HANA - 将字符串变量传递到 SQL 脚本中的 WHERE IN() 子句【英文标题】:HANA - Passing string variable into WHERE IN() clause in SQL script 【发布时间】:2017-03-05 18:07:42 【问题描述】:

假设我在脚本计算视图中有一些 SQL 脚本,它采用单个值输入参数并为另一个计算视图中的输入参数生成一个包含多个输入的字符串。

BEGIN 
declare paramStr clob;
params = select foo 
         from bar 
         where bar.id = :IP_ID;

select '''' || string_agg(foo, ''', ''') || ''''
into paramStr 
from :params;

var_out = select * 
          from  "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW"(PLACEHOLDER."$$IP_IDS$$" => :paramStr);
END

这按预期工作。但是,如果我更改 var_out 查询并尝试在 where 子句中使用该变量

BEGIN 
...

var_out = select * 
          from  "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW"
          where "IP_IDS" in(:paramStr);
END

视图将激活,但我没有从查询中得到任何结果。没有运行时错误,只是一个空的结果集。当我手动将值传递给WHERE IN() 子句时,一切正常。这似乎是一个基本问题,但我似乎无法让它发挥作用。我什至尝试在连接表达式中使用char(39) 而不是'''',但没有香蕉:(

【问题讨论】:

在这种情况下您会收到什么错误消息?另外:您的视图名称中缺少双引号。你想在这里做什么?要么将值输入到输入参数或变量中 - 这些都是不可交换的。 抱歉,我修正了错字。我没有收到任何激活或运行时错误,当我查询脚本计算视图时,我只是没有得到任何结果,但是当使用 WHERE "SOME_COLUMN" IN('ip1','ip2') 表单手动运行查询时,它工作正常。就像我说的那样,像第一个示例一样使用这种 IP 实现效果很好。 并不是说它与这个问题有关,但我有复杂的服务,HANA 优化器没有正确地向下推过滤器。例如,如果我有一项服务可以计算单个贷款编号的余额,则 HANA 优化器效果很好,但无法转换输入以传递特定帐号的多个贷款编号。如果没有巨大的性能损失,以图形方式加入这种转换是行不通的。相反,我将图形视图包装在脚本计算视图中以转换输入并使用多个参数查询原始视图,而不会牺牲性能。 听起来你做得不对。脚本化的 calcviews 已经被弃用了一段时间,应该使用表函数来代替。如果您必须使用具有多个参数和动态调用的图形计算视图(基本上是您所问的),请参阅我的博客文章。 我一直避免使用动态调用,因为有传言说使用动态 sql 会造成性能损失,但似乎使用动态 IN 语句有类似的损失。谢谢你的建议,我会考虑的。 【参考方案1】:

好的,所以您在这里所做的是试图使语句动态化。 对于 IN 条件,您似乎希望一旦填写了paramStr,它将作为一组参数处理。

根本不是这样。 让我们以评论中的示例为例:paramStr = ' 'ip1','ip2' '

paramStr 被填入你的代码时会发生什么:

var_out = select * 
          from  "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW"
          where "IP_IDS" in(' ''ip1'',''ip2'' ');

因此,您实际上是在寻找与IP_DS = ' 'ip1','ip2' ' 匹配的记录,而不是寻找与IP_DS = 'ip1' or IP_DS = 'ip2' 匹配的记录。

解决此问题的一种方法是使用APPLY_FILTER() 函数。

var_out = select * 
          from  "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW";

filterStr = ' "IP_IDS" in (''ip1'',''ip2'') ';

var_out_filt = APPLY_FILTER(:var_out, :filterStr) ;

我前段时间写过:"On multiple mistakes with IN conditions"。 另外,请查看APPLY_FILTER 的文档。

【讨论】:

这行得通,但现在我在第一次执行时性能很差,当我手动执行相同的查询时,速度快如闪电......【参考方案2】:

SAP 注释“2315085 – Query with Multi-Value Parameter on Scripted Calculation View Fails with Incorrect Syntax Error”实际上展示了我第一次尝试时失败的 APPLY_FILTER() 方法。

它还提供了一个 UDF_IN_LIST 函数,用于将包含项目数组的长 varchar 字符串从输入参数转换为可用的列表内谓词。

不幸的是,尽管某些参数可用(SAP Note 2457876:将错误转换为字符串长度溢出的警告),但仍无法使其在 sps11 rev 111.03 中工作 - (范围 3)字符串太长异常

然后 ALTER SYSTEM ALTER CONFIGURATION ('indexserver.ini', 'System') set ('sqlscript', 'typecheck_procedure_input_param') = 'false' WITH RECONFIGURE;

-重新编译计算视图

然后

select * from :var_tempout where OBJECT_ID in (select I_LIST from "BWOBJDES"."CROSS_AREA::UDF_INLIST_P"(:in_objectids,','));

无效号码异常 - 无效号码

但是将脚本从函数中取出使它可以工作......

对于这个 SPS 11 级别的 APPLY_FILTER 似乎是唯一的解决方法。真的很难说 SPS 12 的版本是多少才能拥有它。

FUNCTION "BWOBJDES"."CROSS_AREA::UDF_INLIST_P"(str_input nvarchar(5000), 
delimiter nvarchar(10)) 
RETURNS table ( I_LIST INTEGER ) LANGUAGE SQLSCRIPT SQL SECURITY INVOKER AS
/********* Begin Function Script ************/
BEGIN
DECLARE cnt int;
DECLARE temp_input nvarchar(128);
DECLARE slice NVARCHAR(10) ARRAY;

temp_input := :str_input;
cnt := 1;
WHILE length(temp_input) > 0 DO
    if instr(temp_input, delimiter) > 0 then
        slice[:cnt] := substr_before(temp_input,delimiter);
        temp_input := substr_after(temp_input,delimiter);
        cnt := :cnt + 1;
    else
        slice[:cnt] := temp_input;
        break;
    end if;
END WHILE;
tab2 = UNNEST(:slice) AS (I_LIST);
return select I_LIST from :tab2;
END;

CREATE PROCEDURE "MY_SCRIPTED_CV/proc"( IN numbers NVARCHAR(5000), OUT 
var_out
MY_TABLE_TYPE ) language sqlscript sql security definer reads sql data with 
result view
"MY_SCRIPTED_CV" as
/********* Begin Procedure Script ************/
 BEGIN
  -- not working
  --var_out = select * from MY_TABLE where NUMBER in (select I_LIST from 
  --UDF_INLIST_P(:numbers,','));

  -- working
  DECLARE cnt int;
  DECLARE temp_input nvarchar(128);
  DECLARE slice NVARCHAR(13) ARRAY;
  DECLARE delimiter VARCHAR := ',';

  temp_input := replace(:numbers, char(39), '');
  cnt := 1;
  WHILE length(temp_input) > 0 DO
    if instr(temp_input, delimiter) > 0 then
        slice[:cnt] := substr_before(temp_input,delimiter);
        temp_input := substr_after(temp_input,delimiter);
        cnt := :cnt + 1;
    else
        slice[:cnt] := temp_input;
        break;
    end if;
  END WHILE;
 l_numbers = UNNEST(:slice) AS (NUMBER);

 var_out=
 SELECT *
 FROM MAIN AS MA
 INNER JOIN l_numbers as LN
 ON MAIN.NUMBER = LN.NUMBER

 END;

【讨论】:

【参考方案3】:

我知道,这是一个相当古老的线程,但我的发现基于我在这里从 Jenova 读到的内容,对其他人来说可能很有趣。所以我把它们写下来。

我也遇到了同样的问题。用户可以在我必须优化的计算视图中的输入参数中发送多个条目。不幸的是,我以前遇到过像 Jenova 和其他人一样的问题。而且,不,恕我直言,这不是关于动态视图执行或动态 sql,而是关于在脚本计算视图或表函数中以干净的方式处理列表(如果它们是在输入参数中发送的)。

Lars 使用 APPLY_FILTER 的建议不适用于我的情况,因为我不能在 APPLY_FILTER 函数本身中使用复杂的语句。我必须在选择中具体化全部数据,我可以将结果分配给 APPLY_FILTER 然后执行过滤。我希望看到在第一步中应用的过滤,而不是在物化未出现在过滤器应用结果中的数据之后。

所以我使用 hdbtablefunction 创建了一个执行参数字符串清理和转换为数组的函数,然后它返回 UNNEST 函数的结果。

由于函数的结果是一个表,它可以用作脚本视图或表函数中的表 - 在连接、子选择等中。

通过这种方式,我能够按预期快速处理用户输入列表,并且消耗更少的资源。

FUNCTION "_SYS_BIC"."package1::transparam" (ip_string NVARCHAR(500) ) 
        RETURNS table ( "PARAMETER" nvarchar(100))
        LANGUAGE SQLSCRIPT
        SQL SECURITY INVOKER AS
        v_test varchar(1000);
        IP_DELIMITER VARCHAR(1) := ',';
        v_out VARCHAR(100):='';
        v_count INTEGER:=1;
        v_substr VARCHAR(1000):='';
        v_substr2 VARCHAR(1000):='';
        id INTEGER array;
        val VARCHAR(100) array;
    BEGIN 
    --
     v_substr:=:ip_string; 
     v_substr := REPLACE(:v_substr, '''', '');
     v_substr := REPLACE(:v_substr, ' ', '');
        while(LOCATE (:v_substr, :ip_delimiter) > 0 ) do

            -- find value
            v_out := SUBSTR(v_substr, 0, LOCATE (:v_substr, :ip_delimiter) - 1 );

            -- out to output
            val[v_count]:=v_out;

            -- increment counter
            v_count:=:v_count+1;

            -- new substring for search
            v_substr2 := SUBSTR(:v_substr, LOCATE (:v_substr, :ip_delimiter) + 1, LENGTH(:v_substr));
            v_substr := v_substr2;

        END while;

        IF(LOCATE (:v_substr, :ip_delimiter) = 0 AND LENGTH(:v_substr) > 0) THEN
            -- no delimiter in string
            val[v_count]:=v_substr;

        END IF;

        -- format output as tables
          rst = unnest(:VAL) AS ("PARAMETER");

       RETURN SELECT * FROM :rst;
    END;

可以像这样调用

select * from "package1.transparam"('''BLU'',''BLA''')

返回两行表格

PARAMETER
---------
BLU
BLA

【讨论】:

【参考方案4】:

最全面的解释在这里: https://blogs.sap.com/2019/01/17/passing-multi-value-input-parameter-from-calculation-view-to-table-function-in-sap-hana-step-by-step-guide/

    创建自定义函数将字符串拆分为多个值

那么就可以用inner/left outer join来过滤了。

【讨论】:

以上是关于HANA - 将字符串变量传递到 SQL 脚本中的 WHERE IN() 子句的主要内容,如果未能解决你的问题,请参考以下文章

将值保存在局部变量 HANA SQL 脚本中

如何将字符串变量传递给动态sql中的where子句

如何将数组传递到 Google 电子表格的 Google App 脚本中的查询字符串

如何将子查询的值存储到 Hana Studio 中的变量中?

如何使用绑定变量执行 SQL 脚本

如何使用for循环将文本文件中的一行字符串作为Bash中另一个脚本的单独变量传递[重复]