{"id":17,"date":"2012-08-23T21:24:31","date_gmt":"2012-08-23T21:24:31","guid":{"rendered":"http:\/\/blog.daskalov.org\/?p=17"},"modified":"2012-08-23T22:06:06","modified_gmt":"2012-08-23T22:06:06","slug":"how-to-pass-a-list-of-items-as-parameter-to-a-query-in-oracle-database","status":"publish","type":"post","link":"https:\/\/blog.daskalov.org\/?p=17","title":{"rendered":"How to pass a list of items as parameter to a query in Oracle Database"},"content":{"rendered":"<p>Ofter application developers are puzzled with task to allow the user to pick multiple items from some list, and then perform something for all selected items. For example the task could be to display more details, or to increase salary of selected employees.<\/p>\n<p>One of the choices could be to pass the list as a comma separated list in a string parameter. For example:<\/p>\n<pre>declare\r\n\r\n  procedure print_collection(p_emp_ids_collection in string) is\r\n    c sys_refcursor;\r\n    v_employee_id employees.employee_id%type;\r\n    v_first_name employees.first_name%type;\r\n    v_last_name employees.last_name%type;\r\n    v_email employees.email%type;\r\n    cnt number := 0;\r\n  begin\r\n    dbms_output.put_line('Starting to print');\r\n    open c for\r\n      'select employee_id, first_name, last_name, email\r\n       from employees\r\n       where employee_id in ('||p_emp_ids_collection||')';\r\n\r\n    loop\r\n    fetch c into v_employee_id , v_first_name , v_last_name, v_email ;\r\n    exit when c%notfound;\r\n    dbms_output.put_line('Emp ID='||v_employee_id||' , name='||v_first_name||' '||v_last_name||', email='||v_email );\r\n    cnt := cnt + 1;\r\n  end loop;\r\n  dbms_output.put_line('End of printing '||cnt||' records.');\r\nend;\r\n\r\nbegin\r\n  print_collection('100,102,108');\r\nend;\r\n\/<\/pre>\n<p>However this is not good in most occasions. It has a number of problems:<\/p>\n<ul>\n<li>It is prone to sql injection<\/li>\n<li>It requires dynamic sql, which will be syntactically checked only in run time, not at compile time of the program.<\/li>\n<li>You cannot use this approach in a declarative <em>cursor for loop<\/em>, which has fetch arraysize of 100. So you will either fetch records with arraysize 1 (using plsql fetch), or you will have to write with DBMS_SQL and use DEFINE_ARRAY and FETCH_ROWS to achieve fetching in a portion of more than 1 row.<\/li>\n<\/ul>\n<pre>declare\r\n  the_emp_ids_collection t_employee_ids_collection;\r\n\r\n  procedure print_collection(p_emp_ids_collection t_employee_ids_collection) is\r\n  begin\r\n    dbms_output.put_line('Starting to print');\r\n    for r in (\r\n      select *\r\n      from employees\r\n      where employee_id in (select * from table(p_emp_ids_collection)))\r\n    loop\r\n      dbms_output.put_line('Emp ID='||r.employee_id||' , name='||r.first_name||' '||r.last_name||', email='||r.email );\r\n    end loop;\r\n    dbms_output.put_line('End of printing '||p_emp_ids_collection.count||' records.');\r\n  end;\r\n\r\nbegin\r\n  select cast (collect (cast (employee_id as number)) as t_employee_ids_collection)\r\n  into the_emp_ids_collection from employees;\r\n  print_collection(the_emp_ids_collection);\r\n\r\n  the_emp_ids_collection := t_employee_ids_collection (100,102,108);\r\n  print_collection(the_emp_ids_collection);\r\n\r\nend;\r\n\/<\/pre>\n<p>The last variant is a very good one, and can be used in many cases. It can be used with bind variables from Java or ODP.NET (and others probably). However it could have one problem &#8211; the optimizer doesn&#8217;t currently care about how many entries the collection contains. It assumes a default cardinality, and uses always the same value.<\/p>\n<p>I thought that it could do dynamic sampling, but even if it does &#8211; it will do it only on the first attemp to choose an execution plan. In Oracle 11g the cursor can be bind aware (you can even force this with BIND_AWARE hint &#8211; <a title=\"see the aritcle at the Optimizer team blog\" href=\"https:\/\/blogs.oracle.com\/optimizer\/entry\/how_do_i_force_a\" target=\"_blank\"><\/a> ).<\/p>\n<p>some alternative to this could be the variant where MEMBER OF is used like in this example.<\/p>\n<pre>declare\r\n  the_emp_ids_collection t_employee_ids_collection;\r\n\r\n  procedure print_collection(p_emp_ids_collection t_employee_ids_collection) is\r\n  begin\r\n    dbms_output.put_line('Starting to print');\r\n    for r in (\r\n      select *\r\n      from employees\r\n      where employee_id member of p_emp_ids_collection )\r\n    loop\r\n      dbms_output.put_line('Emp ID='||r.employee_id||' , name='||r.first_name||' '||r.last_name||', email='||r.email );\r\n    end loop;\r\n    dbms_output.put_line('End of printing '||p_emp_ids_collection.count||' records.');\r\n  end;\r\n\r\nbegin\r\n  select cast (collect (cast (employee_id as number)) as t_employee_ids_collection)\r\n  into the_emp_ids_collection from employees;\r\n  print_collection(the_emp_ids_collection);\r\n\r\n  the_emp_ids_collection := t_employee_ids_collection (100,102,108);\r\n  print_collection(the_emp_ids_collection);\r\n\r\nend;\r\n\/<\/pre>\n<p>I expect the second variant to have worse performance in most cases, since the member of operation cannot be transformed to a sqlset rowset, and it will process every row of the main table. Even for small tables i think that the performance of hash join is better than <code>memeber of<\/code> implementation for a collection, because AFAIK &#8216;normal table of&#8217; collections (as opposed to collection which are table of XXX indexed by varchar2) is not implemented as a hash map, but rather as a list (so it will be like nested loop with full scan of table and for every row full scan of collection). Yet, I still have to test this (there might be difference between a <code>MULTISET<\/code> or <code>SET<\/code> ).<\/p>\n<p>However there could be implementations that will use a different sql statement based on the number of collection elements. For example if less that 10 records are used &#8211; a hint cardinality(5) could be added to the query, if between 10 and 100 &#8211; then a hint cardinality 50 could be used, for between 100 and 1000 &#8211; cardinality 500, and cardinality 5000 in other cases. With this approach the optionmizer can generate different execution plans depending on the size of collection &#8211; so sometimes it will prefer a nested loops plan for small (&lt;10) collections, or a hash join and full table scans for larger collections.<\/p>\n<pre>declare\r\n  the_emp_ids_collection t_employee_ids_collection;\r\n\r\n  procedure print_collection(p_emp_ids_collection t_employee_ids_collection) is\r\n  begin\r\n    dbms_output.put_line('Starting to print');\r\n    if p_emp_ids_collection.length = 0 then\r\n      null; -- no records, so no need to do the select at all\r\n    elsif p_emp_ids_collection.length &lt; 10 then\r\n      for r in (\r\n        select *\r\n        from employees\r\nwhere employee_id in (select \/*+ cardinality(z,5) *\/ * from table(p_emp_ids_collection) z )\r\n      loop\r\n        dbms_output.put_line('Emp ID='||r.employee_id||' , name='||r.first_name||' '||r.last_name||', email='||r.email );\r\n      end loop;\r\n    elsif p_emp_ids_collection.length &lt; 100\r\n      for r in (\r\n        select *\r\n        from employees\r\nwhere employee_id in (select \/*+ cardinality(z,50) *\/ * from table(p_emp_ids_collection) z )\r\n      loop\r\n        dbms_output.put_line('Emp ID='||r.employee_id||' , name='||r.first_name||' '||r.last_name||', email='||r.email );\r\n      end loop;\r\n    elsif p_emp_ids_collection.length &lt; 1000\r\n      for r in (\r\n        select *\r\n        from employees\r\nwhere employee_id in (select \/*+ cardinality(z,500) *\/ * from table(p_emp_ids_collection) z )\r\n      loop\r\n        dbms_output.put_line('Emp ID='||r.employee_id||' , name='||r.first_name||' '||r.last_name||', email='||r.email );\r\n      end loop;\r\n    else\r\n      for r in (\r\n        select *\r\n        from employees\r\nwhere employee_id in (select \/*+ cardinality(z,5000) *\/ * from table(p_emp_ids_collection) z )\r\n      loop\r\n        dbms_output.put_line('Emp ID='||r.employee_id||' , name='||r.first_name||' '||r.last_name||', email='||r.email );\r\n      end loop;\r\n    end if;\r\n    dbms_output.put_line('End of printing '||p_emp_ids_collection.count||' records.');\r\n  end;\r\n\r\nbegin\r\n  select cast (collect (cast (employee_id as number)) as t_employee_ids_collection)\r\n  into the_emp_ids_collection from employees;\r\n  print_collection(the_emp_ids_collection);\r\n\r\n  the_emp_ids_collection := t_employee_ids_collection (100,102,108);\r\n  print_collection(the_emp_ids_collection);\r\n\r\nend;\r\n\/<\/pre>\n<p>Please note that <code>cardinality<\/code> hint is not documented, as is BIND_AWARE, and may not be supported on all versions. It works on Oracle 10.2, and I think it no longer works in 11gR2. However you could replace it with some that causes similar effects. You could use hints to driver specific join order, method for selecting the original table <code>\/*+ FULL(empooyees) *\/<\/code> ), decision to use an index, or the join method &#8211; e.g. USE_NL for nested loops or HASH_SJ HASH SEMI join.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Ofter application developers are puzzled with task to allow the user to pick multiple items from some list, and then perform something for all selected items. For example the task could be to display more details, or to increase salary &hellip; <a href=\"https:\/\/blog.daskalov.org\/?p=17\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,3],"tags":[],"class_list":["post-17","post","type-post","status-publish","format-standard","hentry","category-dev","category-oracle"],"_links":{"self":[{"href":"https:\/\/blog.daskalov.org\/index.php?rest_route=\/wp\/v2\/posts\/17","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.daskalov.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.daskalov.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.daskalov.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.daskalov.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=17"}],"version-history":[{"count":11,"href":"https:\/\/blog.daskalov.org\/index.php?rest_route=\/wp\/v2\/posts\/17\/revisions"}],"predecessor-version":[{"id":29,"href":"https:\/\/blog.daskalov.org\/index.php?rest_route=\/wp\/v2\/posts\/17\/revisions\/29"}],"wp:attachment":[{"href":"https:\/\/blog.daskalov.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=17"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.daskalov.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=17"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.daskalov.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=17"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}