views:

480

answers:

8

Hi all,

I want to use parameter for query like this :

SELECT * FROM MATABLE
WHERE MT_ID IN (368134, 181956)

so I think about this

SELECT * FROM MATABLE
WHERE MT_ID IN (:MYPARAM)

but it doesn't work...

Is there a way to do this ?

I actually use IBX and Firebird 2.1

I don't know how many parameters in IN clause.

+2  A: 

Maybe you should wite it like this:

SELECT * FROM MATABLE
WHERE MT_ID IN (:MYPARAM1 , :MYPARAM2)
Yurish
This should work, however if his filter list isn't fixed he'll have to construct each condition individually.
yozey
I allready make this but I have to create SQL by hand which depends on how many parameters I need. I want just by SQL
Hugues Van Landeghem
A: 

If you are using Oracle, then you should definitely check out Tom Kyte's blog post on exactly this subject (link).

Following Mr Kyte's lead, here is an example:

SELECT *
  FROM MATABLE
 WHERE MT_ID IN
       (SELECT TRIM(substr(text, instr(text, sep, 1, LEVEL) + 1,
                           instr(text, sep, 1, LEVEL + 1) -
                            instr(text, sep, 1, LEVEL) - 1)) AS token
          FROM (SELECT sep, sep || :myparam || sep AS text
                  FROM (SELECT ',' AS sep
                          FROM dual))
        CONNECT BY LEVEL <= length(text) - length(REPLACE(text, sep, '')) - 1)

Where you would bind :MYPARAM to '368134,181956' in your case.

Tom
Oracle mode don't work for Firebird but this seems to be what I need
Hugues Van Landeghem
+1  A: 

The answer from Yurish is a solution in two out of three cases:

  • if you have a limited number of items to be added to your in clause
  • or, if you are willing to create parameters on the fly for each needed element (you don't know the number of elements in design time)

But if you want to have arbitrary number of elements, and sometimes no elements at all, then you can generate SLQ statement on the fly. Using format helps.

Mihaela
Yes I want arbitrary number of elements and I don't want to generate SQL statement.
Hugues Van Landeghem
I don't think that's possible, but I wish that I'm wrong. I wanted to do just that and settled for writing the SQL myself. Prepared queries make sense only in the DB engine knows what to expect (how many parameters as well as their respective types). Even If there was a way to parametrize arbitrary number of inputs I think there would be no performance gain compared to just "surprising" the DB engine with the fresh query each time.
Mihaela
Using parameters isn't only for performance gains, it is necessary to protect against SQL injection attacks. Either you spend a lot of effort on sanitizing your input parameter strings, or you handle them as the parameters they are.
mghie
"Necessary" only if the SQL case in question is *susceptible* to an injection attack. If the SQL is formatted by code converting integer values in memory from properties (.ID) of some object(s) for embedding in some literal SQL statement then the potential for an injection attack is negligible. Just because a question involves SQL and parameters does not mean that all concerns involving SQL and parameters necessarily apply. afaic performance is the only significant concern in this case imho and I'd be wary of assuming that formatted SQL would be unacceptable on that score without testing.
Deltics
@Deltics: I think it's better to err on the side of caution. I don't think developers undertake a thorough risk analysis every time they build SQL statements like this. That's why it's IMHO better to completely abandon the practice.
mghie
+1  A: 

I ended up using a global temporary table in Firebird, inserting parameter values first and to retrieve results I use a regular JOIN instead of a WHERE ... IN clause. The temporary table is transaction-specific and cleared on commit (ON COMMIT DELETE ROWS).

TOndrej
A: 

I don't think it's something that can be done. Are there any particular reason why you don't want to build the query yourself?

I've used this method a couple of times, it doesn't use parameters though. It uses a stringlist and it's property DelimitedText. You create a IDList and populate it with your IDs.

Query.SQL.Add(Format('MT_ID IN (%s)', [IDList.DelimitedText]));
johnny
What's wrong with it? See http://stackoverflow.com/questions/332365/xkcd-sql-injection-please-explain
mghie
@mghie, yes it's horribly wrong if we're talking about user input but I made the assumption that since it is a list of IDs it's not something that user would supply. In that case I had expected product codes, invoice numbers etc. It may have been a mistake by me and I thank you for enhancing my answer.
johnny
You may be right that in this particular case SQL injection may not be possible, but it's a real threat and seems to be so difficult for people to understand / remember that I think one should be consequent and not do stuff like this, at all.
mghie
+1  A: 

You might also be interested in reading the following:
http://www.sommarskog.se/dynamic_sql.html
and
http://www.sommarskog.se/arrays-in-sql-2005.html

Covers dynamic sql with 'in' clauses and all sorts. Very interesting.

shunty
Quite true : very interresting.
Hugues Van Landeghem
A: 

Here is a technique I have used in the past to get around that 'IN' statement problem. It builds an 'OR' list based on the amount of values specified with parameters (unique). Then all I had to do was add the parameters in the order they appeared in the supplied value list.

var  
  FilterValues: TStringList;
  i: Integer;
  FilterList: String;
  Values: String;
  FieldName: String;
begin
  Query.SQL.Text := 'SELECT * FROM table WHERE '; // set base sql
  FieldName := 'some_id'; // field to filter on
  Values := '1,4,97'; // list of supplied values in delimited format
  FilterList := '';
  FilterValues := TStringList.Create; // will get the supplied values so we can loop
  try
    FilterValues.CommaText := Values;

    for i := 0 to FilterValues.Count - 1 do
    begin
      if FilterList = '' then
        FilterList := Format('%s=:param%u', [FieldName, i]) // build the filter list
      else
        FilterList := Format('%s OR %s=:param%u', [FilterList, FieldName, i]); // and an OR
    end;
    Query.SQL.Text := Query.SQL.Text + FilterList; // append the OR list to the base sql

    // ShowMessage(FilterList); // see what the list looks like. 
    if Query.ParamCount <> FilterValues.Count then
      raise Exception.Create('Param count and Value count differs.'); // check to make sure the supplied values have parameters built for them

    for i := 0 to FilterValues.Count - 1 do
    begin
      Query.Params[i].Value := FilterValues[i]; // now add the values
    end;

    Query.Open;  
finally
  FilterValues.Free;  
end;

Hope this helps.

yozey
allready made this in one project but my goal was without pascal code
Hugues Van Landeghem
That would be very difficult. I've struggled for years with that. If you have components that support macros you can accomplish this, but using parameters I doubt it and on the server side, I've not been able to accomplish this.
yozey
+1  A: 

Parameters are placeholders for single values, that means that an IN clause, that accepts a comma delimited list of values, cannot be used with parameters.

Think of it this way: wherever I place a value, I can use a parameter.

So, in a clause like: IN (:param)

I can bind the variable to a value, but only 1 value, eg: IN (4)

Now, if you consider an "IN clause value expression", you get a string of values: IN (1, 4, 6) -> that's 3 values with commas between them. That's part of the SQL string, not part of a value, which is why it cannot be bound by a parameter.

Obviously, this is not what you want, but it's the only thing possible with parameters.

Martijn Tonies