PreparedStatement IN clause alternatives?

前端 未结 30 3955
情歌与酒
情歌与酒 2020-11-21 05:19

What are the best workarounds for using a SQL IN clause with instances of java.sql.PreparedStatement, which is not supported for multiple values du

30条回答
  •  [愿得一人]
    2020-11-21 05:27

    Limitations of the in() operator is the root of all evil.

    It works for trivial cases, and you can extend it with "automatic generation of the prepared statement" however it is always having its limits.

    • if you're creating a statement with variable number of parameters, that will make an sql parse overhead at each call
    • on many platforms, the number of parameters of in() operator are limited
    • on all platforms, total SQL text size is limited, making impossible for sending down 2000 placeholders for the in params
    • sending down bind variables of 1000-10k is not possible, as the JDBC driver is having its limitations

    The in() approach can be good enough for some cases, but not rocket proof :)

    The rocket-proof solution is to pass the arbitrary number of parameters in a separate call (by passing a clob of params, for example), and then have a view (or any other way) to represent them in SQL and use in your where criteria.

    A brute-force variant is here http://tkyte.blogspot.hu/2006/06/varying-in-lists.html

    However if you can use PL/SQL, this mess can become pretty neat.

    function getCustomers(in_customerIdList clob) return sys_refcursor is 
    begin
        aux_in_list.parse(in_customerIdList);
        open res for
            select * 
            from   customer c,
                   in_list v
            where  c.customer_id=v.token;
        return res;
    end;
    

    Then you can pass arbitrary number of comma separated customer ids in the parameter, and:

    • will get no parse delay, as the SQL for select is stable
    • no pipelined functions complexity - it is just one query
    • the SQL is using a simple join, instead of an IN operator, which is quite fast
    • after all, it is a good rule of thumb of not hitting the database with any plain select or DML, since it is Oracle, which offers lightyears of more than MySQL or similar simple database engines. PL/SQL allows you to hide the storage model from your application domain model in an effective way.

    The trick here is:

    • we need a call which accepts the long string, and store somewhere where the db session can access to it (e.g. simple package variable, or dbms_session.set_context)
    • then we need a view which can parse this to rows
    • and then you have a view which contains the ids you're querying, so all you need is a simple join to the table queried.

    The view looks like:

    create or replace view in_list
    as
    select
        trim( substr (txt,
              instr (txt, ',', 1, level  ) + 1,
              instr (txt, ',', 1, level+1)
                 - instr (txt, ',', 1, level) -1 ) ) as token
        from (select ','||aux_in_list.getpayload||',' txt from dual)
    connect by level <= length(aux_in_list.getpayload)-length(replace(aux_in_list.getpayload,',',''))+1
    

    where aux_in_list.getpayload refers to the original input string.


    A possible approach would be to pass pl/sql arrays (supported by Oracle only), however you can't use those in pure SQL, therefore a conversion step is always needed. The conversion can not be done in SQL, so after all, passing a clob with all parameters in string and converting it witin a view is the most efficient solution.

提交回复
热议问题