Why does OLEDB (or Access?) rewrite WHERE clause - sql

I'm facing a strange situation where OledbDataAdapter rearranges the WHERE clause of my query, thus shuffling all parameters. In addition, it doesn't recognize one of the parameters used in the query. Here's how it goes:
A simple table with 4 columns (and infinite rows :)):
Name varchar(50)
Category varchar(20) //One of around 15-20 possible values
YR int //Year
Period int //Month
User will query this table using Year, Month and a comma-separated list of categories that he's interested in. Since .NET doesn't support multi-valued parameters through IN operator, what I do is to accept the list of categories as comma-separated list and then prepend and append a comma to this list within the query and use built-in INSTR() function to filter results for desired categories. User can also supply an empty string for categories filter in which case query will need to return all results (i.e. no filter on categories).
Therefore my query looks like this:
SELECT * FROM [Table1] WHERE
YR = ? AND
Period = ? AND
(LTRIM(RTRIM(?)) = '' OR INSTR(1, ?, ',' + Category + ',') > 0)
This worked with MDBs in the past. But recently I tried it against an ACCDB. The first thing I noted is that when I try to run the query in OledbDataAdapter wizard, Visual Studio rewrites it as:
SELECT * FROM [Table1] WHERE
(YR = ? AND Period = ? AND LTRIM(RTRIM(?)) = '') OR
(YR = ? AND Period = ? AND INSTR(1, ?, ',' + Category + ',') > 0)
In other words, it has rearranged the condition A AND B AND (C OR D) as (A AND B AND C) OR (A AND B AND D).
This would have been fine with me, but the additional problem is that this changes the number of parameters from 4 to 6; the query therefore doesn't return any results even when it should. Moreover, it doesn't recognize that last parameter (the question mark within INSTR function) at all.
So my questions are:
Who is rewriting the query (OledbDataAdapter, Access query parser or something else)?
Why is it doing so? Optimization? Apparently my version of the WHERE clause is more efficient.
Why doesn't it see the last parameter?
How to fix/workaround this issue? (plz do not suggest using 2 separate queries for it).

Related

Dynamic SQL queries for tokio postgres in Rust

I am working with tokio postgres for a Rust project where I have a table Home having two columns 'number' and 'address'.
My queries are being sent from the rust source code using the sql client as shown below,
let rows = client.query(QUERY, &[&"number", "address"]).await?;
where
QUERY: &str =
"
SELECT * FROM Home
WHERE number <= $1
AND address = $2;
";
In the above case, both inputs are valid strings of non-zero length.
Given, this information, I am trying to query the rows of the table following certain rules.
I have provided them below.
If the query input 'address' is null, then the AND part of the string will not be there. In the problem only the 'address' parameter can be null only. The 'number' field is always consistently non-empty.
Some ideas that I came across look like this but the ideas are not that concrete and in the current condition it does not work.
One example,
QUERY: &str =
"
SELECT * FROM Home
WHERE number <= $1
IF $2 IS NOT NULL
THEN address = $2;
";
I will have to modify the rhs side SQL queries. I can still create a dynamic string so that at run time the queries will look different according to the case encountered, but the requirement is to handle it directly with the help of a SQL rather than rust.
Is there a way to achieve this?
You can use coalesce function in your query:
SELECT * FROM Home
WHERE number <= $1
AND address = COALESCE($2, address);
If input parameter $2 will be null, then the field address will will be compared against itself, which will always return true (if there are no nulls in the address field).

How to replace where clause dynamically in query (BIRT)?

In my report query I have a where clause that needs to be replaced dynamically based on the data chosen in the front end.
The query is something like :
where ?=?
I already have a code to replace the value - I created report parameter and linked to the value ? in the query.
Example:
where name=?
Any value of name that comes from front end replaces the ? in the where clause - this works fine.
But now I need to replace the entire clause (where ?=?). Should I create two parameters and link them to both the '?' ?
No, unfortunately most database engines do not allow to use a query parameter for handling a dynamic column name. This is for security considerations.
So you need to keep an arbitrary column name in the query:
where name=?
And then in "beforeOpen" script of the dataset replace 'name' with a report parameter value:
this.queryText=this.queryText.replace("name",params["myparameter"].value);
To prevent SQLIA i recommend to test the value of the parameter in this script. There are many ways to do this but a white list is the strongest test, for example:
var column=params["myparameter"].value;
if (column=="name" || column=="id" || column=="account" || column=="mycolumnname"){
this.queryText=this.queryText.replace("name",column);
}
In addition to Dominique's answer and your comment, then you'll just need a slightly more advanced logic.
For example, you could name your dynamic column-name-value pairs (column1, value1), (column2, value2) and so on. In the static text of the query, make sure to have bind variables for value1, value2 and so on (for example, with Oracle SQL, using the syntax
with params as (
select :value1 as value1,
:value2 as value2 ...
from dual
)
select ...
from params, my_table
where 1=1
and ... static conditions....
Then, in the beforeOpen script, append conditions to the query text in a loop as needed (the loop left as an exercise to the reader, and don't forget checking the column names for security reasons!):
this.queryText += " and " + column_name[i] + "= params.value" + i;
This way you can still use bind variables for the comparison values.

HQL Date throwing unexpected token

Hi I have trouble executing the following query in HQL:
This is a very dangerous approach to running queries. It's the sort of thing that creates SQL injection risks. Never concatenate values into SQL or HQL strings like that. Always use the PreparedStatement approach of using placeholders in the SQL/HQL string and setting the values programmatically. This way the driver (and Hibernate in the case of HQL) can do the correct thing with the SQL that gets generated. Of course here the value is a date and not a user-submitted string, but the principle still holds.
What you need to do is run a query more like
'select stuff from bar b where b.dateCreated = ?'
In HQL you can also use named parameters, and those are usually a lot easier to read and self-documenting, e.g.
'select stuff from bar b where b.dateCreated = :date'
Then set the value as part of the call, not with string concatenation.
The problem here is that the Java/Groovy toString value of a date is nothing at all like what the date format should be in SQL (or HQL). Luckily you don't need to know what that format should be, because the JDBC driver does.
So your query should be
def co = Bid.executeQuery(
'select b.job from Bid b left join b.job j where j.dateCreated = :date',
[date: fromDates[i])
Here I'm using the name date but that's arbitrary, is just has to match the key name in the map with values. You can also use SQL-style ? chars and a list of values:
def co = Bid.executeQuery(
'select b.job from Bid b left join b.job j where j.dateCreated = ?',
[fromDates[i])
Once you get this working you'll find that comparing dates like this rarely works because the dates have to agree to the millisecond, but that's a problem for another question :)

Using the '?' Parameter in SQL LIKE Statement

I'm accessing a Firebird database through Microsoft Query in Excel.
I have a parameter field in Excel that contains a 4 digit number. One of my DB tables has a column (TP.PHASE_CODE) containing a 9 digit phase code, and I need to return any of those 9 digit codes that start with the 4 digit code specified as a parameter.
For example, if my parameter field contains '8000', I need to find and return any phase code in the other table/column that is LIKE '8000%'.
I am wondering how to accomplish this in SQL since it doesn't seem like the '?' representing the parameter can be included in a LIKE statement. (If I write in the 4 digits, the query works fine, but it won't let me use a parameter there.)
The problematic statements is this one: TP.PHASE_CODE like '?%'
Here is my full code:
SELECT C.COSTS_ID, C.AREA_ID, S.SUB_NUMBER, S.SUB_NAME, TP.PHASE_CODE, TP.PHASE_DESC, TI.ITEM_NUMBER, TI.ITEM_DESC,TI.ORDER_UNIT,
C.UNIT_COST, TI.TLPE_ITEMS_ID FROM TLPE_ITEMS TI
INNER JOIN TLPE_PHASES TP ON TI.TLPE_PHASES_ID = TP.TLPE_PHASES_ID
LEFT OUTER JOIN COSTS C ON C.TLPE_ITEMS_ID = TI.TLPE_ITEMS_ID
LEFT OUTER JOIN AREA A ON C.AREA_ID = A.AREA_ID
LEFT OUTER JOIN SUPPLIER S ON C.SUB_NUMBER = S.SUB_NUMBER
WHERE (C.AREA_ID = 1 OR C.AREA_ID = ?) and S.SUB_NUMBER = ? and TI.ITEM_NUMBER = ? and **TP.PHASE_CODE like '?%'**
ORDER BY TP.PHASE_CODE
Any ideas on alternate ways of accomplishing this query?
If you use `LIKE '?%', then the question mark is literal text, not a parameter placeholder.
You can use LIKE ? || '%', or alternatively if your parameter itself never contains a LIKE-pattern: STARTING WITH ? which might be more efficient if the field you're querying is indexed.
You can do
and TP.PHASE_CODE like ?
but when you pass your parameter 8000 to the SQL, you have to add the % behind it, so in this case, you would pass "8000%" to the SQL.
Try String Functions: Left?
WHERE (C.AREA_ID = 1 OR Left(C.AREA_ID,4) = "8000")

Searching a column containing CSV data in a MySQL table for existence of input values

I have a table say, ITEM, in MySQL that stores data as follows:
ID FEATURES
--------------------
1 AB,CD,EF,XY
2 PQ,AC,A3,B3
3 AB,CDE
4 AB1,BC3
--------------------
As an input, I will get a CSV string, something like "AB,PQ". I want to get the records that contain AB or PQ. I realized that we've to write a MySQL function to achieve this. So, if we have this magical function MATCH_ANY defined in MySQL that does this, I would then simply execute an SQL as follows:
select * from ITEM where MATCH_ANY(FEAURES, "AB,PQ") = 0
The above query would return the records 1, 2 and 3.
But I'm running into all sorts of problems while implementing this function as I realized that MySQL doesn't support arrays and there's no simple way to split strings based on a delimiter.
Remodeling the table is the last option for me as it involves lot of issues.
I might also want to execute queries containing multiple MATCH_ANY functions such as:
select * from ITEM where MATCH_ANY(FEATURES, "AB,PQ") = 0 and MATCH_ANY(FEATURES, "CDE")
In the above case, we would get an intersection of records (1, 2, 3) and (3) which would be just 3.
Any help is deeply appreciated.
Thanks
First of all, the database should of course not contain comma separated values, but you are hopefully aware of this already. If the table was normalised, you could easily get the items using a query like:
select distinct i.Itemid
from Item i
inner join ItemFeature f on f.ItemId = i.ItemId
where f.Feature in ('AB', 'PQ')
You can match the strings in the comma separated values, but it's not very efficient:
select Id
from Item
where
instr(concat(',', Features, ','), ',AB,') <> 0 or
instr(concat(',', Features, ','), ',PQ,') <> 0
For all you REGEXP lovers out there, I thought I would add this as a solution:
SELECT * FROM ITEM WHERE FEATURES REGEXP '[[:<:]]AB|PQ[[:>:]]';
and for case sensitivity:
SELECT * FROM ITEM WHERE FEATURES REGEXP BINARY '[[:<:]]AB|PQ[[:>:]]';
For the second query:
SELECT * FROM ITEM WHERE FEATURES REGEXP '[[:<:]]AB|PQ[[:>:]]' AND FEATURES REGEXP '[[:<:]]CDE[[:>:]];
Cheers!
select *
from ITEM where
where CONCAT(',',FEAURES,',') LIKE '%,AB,%'
or CONCAT(',',FEAURES,',') LIKE '%,PQ,%'
or create a custom function to do your MATCH_ANY
Alternatively, consider using RLIKE()
select *
from ITEM
where ','+FEATURES+',' RLIKE ',AB,|,PQ,';
Just a thought:
Does it have to be done in SQL? This is the kind of thing you might normally expect to write in PHP or Python or whatever language you're using to interface with the database.
This approach means you can build your query string using whatever complex logic you need and then just submit a vanilla SQL query, rather than trying to build a procedure in SQL.
Ben