【问题标题】:execute SQL query stored in a table执行存储在表中的 SQL 查询
【发布时间】:2020-01-11 01:14:43
【问题描述】:

我有一个表,其中一个列存储 SQL 查询返回的 id 或存储逗号分隔的 id。

创建表来存储查询或ID(以,分隔)

create table test1
(
    name varchar(20) primary key,
    stmt_or_value varchar(500),
    type varchar(50)
);

insert into test1 (name, stmt_or_value, type) 
values ('first', 'select id from data where id = 1;','SQL_QUERY')
insert into test1 (name, stmt_or_value, type) 
values ('second', '1,2,3,4','VALUE')

数据表如下

create table data
(
    id number,
    subject varchar(500)
);

insert into data (id, subject) values (1, 'test subject1');
insert into data (id, subject) values (2, 'test subject2');
insert into data (id, subject) values (3, 'test subject2');

我无法制定在执行存储的 sql 或根据 name 的值解析存储的 id 后返回值的查询。

select id, subject 
  from data 
 where id in( EXECUTE IMMEDIATE stmt_or_value 
              where type='SQL_QUERY'  
                and name = 'first') or 
            ( parse and return ids 
               from stmt_or_value 
              where type='VALUE'  
                and name = 'second')

你能帮我解决这个问题吗? 解析逗号分隔值已完成,我基本上需要以下第一部分查询的帮助: ( 立即执行 stmt_or_value 其中 type='SQL_QUERY'
和 name = 'first')

【问题讨论】:

  • 但是test1 表中没有id(或类似的)列。
  • 动态 SQL 很难。我建议您首先编写要运行的查询的静态版本。一旦你有了这个工作,确定你需要动态的部分并编辑查询以适应。请注意,动态 SQL 不是也不能是纯 SQL 解决方案。您需要 PL/SQL 来组装和执行动态查询。因此,请仔细考虑您要实现的目标,因为我认为您需要一些不同的方法,但我无法建议您需要采用哪种方式,因为我对您的要求没有完全了解。
  • 我们很少希望将逗号分隔的值存储在一列中。我们希望在列中存储列名、表名或查询等元信息也是非常罕见的。所以我在这里和 APC 在一起;您不太可能采用非常复杂的方法来实现更容易实现的目标。可以使用通用数据库和纯 SQL。

标签: oracle plsql


【解决方案1】:

这似乎是一个非常特殊的要求,而且很难以稳健的方式解决。 STMT_OR_VALUE 是一列两用反模式的体现。此外,解析 STMT_OR_VALUE 需要流控制逻辑和动态 SQL 的使用。因此它不可能是一个纯 SQL 解决方案:您需要使用 PL/SQL 来组装和执行动态查询。

这是解决方案的概念证明。我选择了一个可以从 SQL 调用的函数。这取决于一个假设:您插入到 TEST1.STMT_OR_VALUE 的每个查询字符串都有单个数字列的投影,每个值字符串都是仅包含数字数据的 CSV。有了这个附加条件,就可以很容易地构造一个函数,该函数要么执行动态查询,要么将字符串标记为一系列数字;两者都被批量收集到一个嵌套表中:

create or replace function get_ids (p_name in test1.name%type) 
  return sys.odcinumberlist
is
  l_rec test1%rowtype;
  return_value sys.odcinumberlist;
begin

  select * into l_rec
  from test1
  where name = p_name;

  if l_rec.type = 'SQL_QUERY' then 
    -- execute a query
    execute immediate l_rec.stmt_or_value
      bulk collect into return_value;
  else
    -- tokenize a string
    select xmltab.tkn
    bulk collect into return_value
    from ( select l_rec.stmt_or_value from dual) t
        , xmltable(  'for $text in ora:tokenize($in, ",") return $text'
                      passing stmt_or_value as "in"
                      columns tkn number path '.'
                   ) xmltab;
  end if;
  return return_value;
end;
/

请注意,执行动态 SQL 语句的方法不止一种,还有多种方法可以将 CSV 标记为一系列数字。我的决定是任意的:请随意在此处替换您喜欢的方法。

可以通过table() 调用调用此函数:

select * 
from data
where id in ( select * from table(get_ids('first'))) -- execute query
or    id in ( select * from table(get_ids('second'))) -- get string of values
/

这种方法的最大好处是它封装了围绕 STMT_OR_VALUE 评估的逻辑并隐藏了动态 SQL 的使用。因此,很容易在任何 SQL 语句中使用它,同时保持可读性,或者添加更多机制来生成一组 ID。

但是,这种解决方案很脆弱。仅当test1 表中的值遵守规则时,它才会起作用。也就是说,它们不仅必须可转换为单个数字流,而且 SQL 语句必须是有效的并且可以通过 EXECUTE IMMEDIATE 执行。例如,问题示例数据中的尾随分号无效,并会导致 EXECUTE IMMEDIATE 抛出。动态 SQL 很难,尤其是因为它将编译错误转换为运行时错误。

【讨论】:

    【解决方案2】:

    以下是本例使用的设置数据:

    create table test1
    (
        test_id number primary key,
        stmt_or_value varchar(500),
        test_type varchar(50)
    );
    
    insert into test1 (test_id, stmt_or_value, test_type) 
    values (1, 'select id from data where id = 1','SQL_QUERY');
    insert into test1 (test_id, stmt_or_value, test_type) 
    values (2, '1,2,3,4','VALUE');
    insert into test1 (test_id, stmt_or_value, test_type) 
    values (3, 'select id from data where id = 5','SQL_QUERY');
    insert into test1 (test_id, stmt_or_value, test_type) 
    values (4, '3,4,5,6','VALUE');
    
    select * from test1;
    
    TEST_ID STMT_OR_VALUE                    TEST_TYPE
    1       select id from data where id = 1 SQL_QUERY
    2       1,2,3,4                          VALUE
    3       select id from data where id = 5 SQL_QUERY
    4       3,4,5,6                          VALUE
    
    create table data
    (
        id number,
        subject varchar(500)
    );
    
    insert into data (id, subject) values (1, 'test subject1');
    insert into data (id, subject) values (2, 'test subject2');
    insert into data (id, subject) values (3, 'test subject3');
    insert into data (id, subject) values (4, 'test subject4');
    insert into data (id, subject) values (5, 'test subject5');
    
    select * from data;
    
    ID SUBJECT
    1  test subject1
    2  test subject2
    3  test subject3
    4  test subject4
    5  test subject5
    

    解决方法如下:

    declare
      sql_stmt clob; --to store the dynamic sql
      type o_rec_typ is record(id data.id%type, subject data.subject%type);
      type o_tab_typ is table of o_rec_typ;
      o_tab o_tab_typ; --to store the output records
    begin
      --The below SELECT query generates the required dynamic SQL
      with stmts as (
      select (listagg(stmt_or_value, ' union all ') within group(order by stmt_or_value))||' union all ' s
        from test1 t
       where test_type = 'SQL_QUERY')
      select 
      q'{select id, subject
        from data
       where id in (}'||
              nullif(s,' union all ')||q'{
              select distinct to_number(regexp_substr(s, '[^,]+', 1, l)) id 
                from (
                      select level l, 
                             s 
                        from (select listagg(stmt_or_value,',') within group(order by stmt_or_value) s 
                                from test1 
                               where test_type = 'VALUE') inp 
                  connect by level <= length (regexp_replace(s, '[^,]+'))  + 1))}' stmt into sql_stmt
        from stmts; -- Create the dynamic SQL and store it into the clob variable
    
      --execute the statement, fetch and display the output
      execute immediate sql_stmt bulk collect into o_tab; 
      for i in o_tab.first..o_tab.last
      loop
        dbms_output.put_line('id: '||o_tab(i).id||' subject: '||o_tab(i).subject);
      end loop;
    end;
    

    输出:

    id: 1 subject: test subject1
    id: 2 subject: test subject2
    id: 3 subject: test subject3
    id: 4 subject: test subject4
    id: 5 subject: test subject5
    

    学习:

    1. 避免在表名和列名中使用关键字。
    2. 有效地设计应用表以满足当前和未来合理的需求。
    3. 上述 SQL 将起作用。考虑审查表格设计仍然是明智的,因为代码的复杂性将随着未来需求的变化而不断增加。
    4. 了解如何将逗号分隔值转换为记录。 "https://asktom.oracle.com/pls/apex/f?p=100:11:::NO::P11_QUESTION_ID:9538583800346706523"

    【讨论】:

      【解决方案3】:
      declare 
         my_sql varchar2(1000);
         v_num number;
         v_num1 number;
      begin
          select stmt_or_value into my_sql from test1 where ttype='SQL_QUERY';
          execute immediate my_sql into v_num;
          select id into v_num1 from data where id=v_num;
          dbms_output.put_line(v_num1);
      end; 
      

      第 1 部分的答案。请检查。

      【讨论】:

      • 会有多个号码返回,上面的过程是否有效?
      • 我已经提到这是部分答案。请在提问时提及所有必需的信息。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-06-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-23
      • 2017-10-03
      相关资源
      最近更新 更多