Closed meastp closed 6 years ago
I think you misunderstand how bind arrays work in (Oracle) SQL. There is no way "select :whatever from dual" will return more than one row. And a bind array holds one element per row returned. The same goes for "select x from t where y in :whatever". That is a syntax error which is probably why you are trying to go the "select from dual" route. Here is one of the many discussions on that same problem: https://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:210612357425
I'm not sure I understand. I will read the link you provided. The " ... from dual" was just (a misguided attempt) to provide an example that did not depend on a table.
What I have is two uuids: uuid1 and uuid2, and they are both strings.
I have a table, table_a with columns id (integer) and globalid (varchar2).
I want to select all rows from table_a where the globalid is equal to one of my uuid strings.
Are you saying that this is not possible?
EDIT: According to the link, this is possible, but I need to use the create type .. as table of varchar2. Correct?
Hi,
you cannot bind a single host array as a single bind ":1" in statements like 'select ... from ... where ... in :1". This is not an OCILIB limitation but an Oracle client API (OCI) limitation.
There are workarounds available:
Best regards,
Vincent
Oh , sorry. I thought you wanted to try something like "select x from t where y in (select :whatever from dual)" which would bypass the syntax error but still not work as you want it to. Vincent's suggestions are good, but please, please don't generate literal strings of UUIDs ( ... in ('xxx1','xxx2')) if you plan to scale beyond 2 concurrent users and 10 queries per second.
Hi,
Thank you for the quick answers, they are very helpful. What I'm trying to do is to optimize my application from using global temporary tables to using bind arrays.
What I don't understand is why a host array as a single bind ":1" in statements like 'select ... from ... where ... = :1' works for integers, but the same pattern will not work for strings? I have tested both with IN and =...
Can you confirm that this is the case? This issue is just with bind array of strings?
Actually, the Global Temporary Table (GTT) approach is pretty sound. You can populate the GTT with an array insert, which is very efficient and can accommodate lists of huge lengths without blowing up the PGA and SGA on the server. It writes to disk, though, so if you anticipate only shorter lists (below, say, a thousand entries), you'll be better off with the other approaches. Can you give an example for "select x from t where x=:1" returning more than one row (provided there is a unique constraint on t(x))? That would be revolutionary, even if it only worked for integers.
@meastp It should not work for integers. Best case it could take into account only first value but not the whole array
Oh. I was hoping that I could avoid GTT's, primarily to avoid a lot of undo/redo because I need to empty the GTT's I'm using between uses. I'm back to where I started, then (except for UPDATEs and DELETEs, I already use host arrays for INSERTs)
So, is this quote from Pro*C/C++ Precompiler Programmer's Guide Release 9.2 - 8 Host Arrays - Using Arrays in SQL Statements the definitive answer, then?
You can use host arrays as input variables in the INSERT, UPDATE, and DELETE statements and as output variables in the INTO clause of SELECT and FETCH statements.
I admit I did not read the entire chapter too carefully... :/
However, look at this https://docs.oracle.com/cd/E11882_01/appdev.112/e10825/pc_08arr.htm#i1770 from the latest Pro*C/C++ Precompiler Programmer's Guide - it looks like some form of SELECT ... FROM ... WHERE a = :1 is allowed for host arrays after all. Is it only allowed if the select contains INTO?
Or is it only supposed to illustrate a point with the UPDATE statement below?
ProC is a different thing from OCI and may have different features / limitations compared to OCI. Anyway, given examples show that what you're trying to achieve cannot be done in ProC as well (1st example) while the 2nd example is a different use case which is also supported by OCI and thus OCILIB.
@meastp If the undo/redo associated with GTTs is your problem, you definitely need to continue using them. It's because having lots of undo means your GTTs are huge and if you try to put them in the PGA (using any of the PL/SQL approaches) or PGA and SGA (using the in-list of binds approach), you will open a whole new can of worms (unless you have huge SGA and PGA, of course). If using Oracle 12c is an option, you might want to look into Temporary Undo: https://oracle-base.com/articles/12c/temporary-undo-12cr1
Thanks!
I have gone back to using temp-tables for SELECTs. Host arrays are now used for INSERT, UPDATE and DELETE only.
I will look at the link for further optimization.
The above does not close the issue properly, at least for me. What if we rewrite the query as following?
select * from products where name in (:s)
and use the following code
#include "ocilib.h"
#define ARRAY_SIZE 1000
#define STRING_SIZE 20
void err_handler(OCI_Error *err)
{
printf("%s\n", OCI_ErrorGetString(err));
}
int main(void)
{
OCI_Connection *cn;
OCI_Statement *st;
OCI_Error *err;
char tab_str[ARRAY_SIZE][STRING_SIZE+1];
int i;
if (!OCI_Initialize(err_handler, NULL, OCI_ENV_DEFAULT | OCI_ENV_CONTEXT))
{
return EXIT_FAILURE;
}
cn = OCI_ConnectionCreate("DB", "USER", "PASS", OCI_SESSION_DEFAULT);
OCI_SetAutoCommit(cn, TRUE);
st = OCI_StatementCreate(cn);
OCI_Prepare(st, "select * from products where name in (:s)");
OCI_BindArraySetSize(st, ARRAY_SIZE);
OCI_BindArrayOfStrings(st, ":s", (char*)tab_str, STRING_SIZE, 0);
for (i = 0; i < ARRAY_SIZE; i++)
sprintf(tab_str[i], "Name %d", i + 1);
if (!OCI_Execute(st))
{
printf("there was an error: %s\n", OCI_ErrorGetString(OCI_GetLastError()));
}
OCI_Resultset *rs = OCI_GetResultset(st);
while (OCI_FetchNext(rs))
{
printf("code: %i, name %s\n", OCI_GetInt(rs, 1), OCI_GetString(rs, 2));
}
OCI_StatementFree(st);
OCI_ConnectionFree(cn);
OCI_Cleanup();
return EXIT_SUCCESS;
}
output
[amir@jump ~]$ ./test3
code: 1, name Name 1
Still I only get the WHERE clause matched with the first item in array. Something to note, if I use C++ library and Bind(":s", ..., ..., ocilib::BindInfo::In);
it works without any issue.
Any idea what I might be doing wrong?
Does this mean that using bind-arrays in SELECT queries is somehow supported? That would be very convenient for my use case.
no unfortunately, I was wrong with the C++ Statement::Bind working.. it does not.. so yeah.. stuck..
Ok, then the only workaround I've found is using a temp table, unfortunately. (As I understand, this is a limit in Oracle, not ocilib
)
You forgot to add calls to OCI_GetNextResultset, this should get both rows:
OCI_Resultset *rs = OCI_GetResultset(st);
while ( rs != nullptr ) // Each row in it's own resultset when using array bind and returning into
{
while (OCI_FetchNext(rs))
{
printf("code: %i, name %s\n", OCI_GetInt(rs, 1), OCI_GetString(rs, 2));
}
rs = OCI_GetNextResultset(st); // This will get the next row
}
@bj8sk Do you have an official link to where this is documented by Oracle? I'd be interested in the performance implications of this. :)
Although for ocilib lib that's here: http://vrogier.github.io/ocilib/doc/html/group___ocilib_c_api_feature_returning_into.html#ga20d23ea50075c16a1f7c5ae3515bd025
I am struggling with a query that I'm converting to use a bind array. No matter what I try, it looks like the query will only use the first string in the bind array.
The query is really "SELECT x FROM table_a WHERE table_a.x IN :1", but the example below has the same behaviour.
(Note that the queries I already converted that uses integers instead of strings in the bind array works perfectly. I also have INSERT INTO - statements with bind arrays which works as expected.)
ocilib version: latest release v 4.4.0 oracle client: 12.1
This example always returns just a single "Found one uuid".