Frequently SQL statements in code use an "in-list". That is, SQL that resembles the following:
select employee_id,
first_name,
last_name
from employees
where employee_id in (102,127,206)
Here I am providing an in-list for the employee_id column. Using the HR sample that ships with Oracle Database, the above query returns the following results:
EMPLOYEE_ID FIRST_NAME LAST_NAME
----------- -------------------- -------------------------
102 Lex De Haan
206 William Gietz
127 James Landry
Of course you may wish to vary the number of items in the in-list as well as the actual values. When the value of an item in the list changes, you may wish to use a bind variable for that item. See my Oracle Magazine column here for more details on bind variables and why you might want to use them.
The above SQL statement re-factored to use bind variables would look like:
select employee_id,
first_name,
last_name
from employees
where employee_id in (:0,:1,:2)
In this re-factored statement I am still using 3 values in the in-list. This is definitely not a requirement: the in-list can contain anywhere from 1 to 1000 items. See the official Oracle documentation here for more details.
When attempting to use an in-list with bind variables you may be tempted to supply the comma-delimited values as a single string argument to the in-list. This may resemble:
select employee_id,first_name,
last_name
from employees
where employee_id in (:0)
In this case you may try to pass the string "102,127,206" as the value for the bind variable. However, this will not work as you might expect. You will receive "ORA-1722: invalid number" if you try this because the string "102,127,206" can not be converted to a numeric value. Instead you must create the statement with a separate bind variable for each item you wish to pass in the in-list.
One technique to do this uses a StringBuilder object to create the SQL statement and dynamically build the in-list items. Here is some sample code that illustrates this technique:
using System;
using System.Data;
using System.Text;
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;
namespace VariableInListDemo
{
class Program
{
static void Main(string[] args)
{
// change connection string for your environment
string constr = "User Id=hr;" +
"Password=hr;" +
"Data Source=orademo;" +
"Pooling=false;" +
"Enlist=false";
// the "base" sql statement to which the in-list will be appended
StringBuilder sbSQL = new StringBuilder("select employee_id, " +
"first_name, " +
"last_name " +
"from employees " +
"where employee_id in (");
// if no command-line arguments specified, simply return
if (args.Length == 0)
{
Console.WriteLine("No employee_id values specified...");
Console.WriteLine("Please specify values such as: 102,127,206");
return;
}
// create and open connection
OracleConnection con = new OracleConnection(constr);
con.Open();
// split the command-line argument into separate tokens
// the command-line argument should be similar to:
// 102,127,206
string[] EmpNos = args[0].Split(',');
// build the in-list part of the sql
for (int i = 0; i < EmpNos.Length; i++)
{
// append the bind variables into the sql
sbSQL.Append(":" + i.ToString());
// add a "," after all but the last bind variable
if (i < EmpNos.Length - 1)
{
sbSQL.Append(",");
}
}
// close the in-list with the trailing ")"
sbSQL.Append(")");
// create the command object
OracleCommand cmd = con.CreateCommand();
cmd.CommandText = sbSQL.ToString();
// create an array of OracleParameter objects for each
// empno specified on the command-line
OracleParameter[] EmpNoParams = new OracleParameter[EmpNos.Length];
// set the properties for each parameter
// and add to parameters collection
for (int i = 0; i < EmpNos.Length; i++)
{
EmpNoParams[i] = new OracleParameter();
EmpNoParams[i].OracleDbType = OracleDbType.Decimal;
EmpNoParams[i].Value = Convert.ToDecimal(EmpNos[i]);
cmd.Parameters.Add(EmpNoParams[i]);
}
// create a data reader
OracleDataReader dr = cmd.ExecuteReader();
// iterate over the reader and display to console
while (dr.Read())
{
Console.WriteLine(dr[0].ToString() + ", " +
dr[1].ToString() + ", " +
dr[2].ToString());
}
// add clean up code as necessary for your environment
// prevent console from closing when run from VS environment
Console.WriteLine();
Console.WriteLine("ENTER to continue...");
Console.ReadLine();
}
}
}
Running the above sample from a console window using the values "102,127,206" produces the following results:
C:\>VariableInListDemo.exe 102,127,206
102, Lex, De Haan
206, William, Gietz
127, James, Landry
ENTER to continue...
If you need to use a variable in-list in your application, I encourage you to experiment with this technique using different values and numbers of items as a possible solution.
9 comments:
Mark,
I was looking a solution for this problem; this is te easer solution for developer but when I look from the DBA point of view Im creating several SQL to be hard parsed for the database, is there other option???,.
Thkns
Juan Jose Lopez.
Hi, the use of bind variables in the code is there to help with parsing issues.
- Mark
Yes, but as many statement versions in library cache as calls with different list item counts.
Hi Moha,
Yes, you are absolutely correct. That is why I said "is there to help..." above - it will not eliminate the situation (as there must be at least 1 parse), but this approach at least helps minimize the number of distinct statements.
Thanks,
Mark
It is much better to bind an Oracle collection (a nested table) and to join employees with table(:parm).
Possibly; however, in December 2007 (when the post was written) that was not possible as UDT support was not available via ODP.NET at that time. Unless I've mixed up dates in my head that is!
That is to say, 12 December 2007... ODAC 11.1.0.6.20 was released a few weeks later...
It is also possible to bind an associate array and that was possible before 11.1.0.6.20 .
Using Associative Arrays will introduce PL/SQL into the mix.
Post a Comment