Conditional dynamic SQL with cursor - tsql

I have a query which uses a cursor to cycle through the results of a select statement.
The select statement in short selects all of the records from a mapping table I have. One of the columns is 'SourceTableName'.
I use this field to generate some dynamic SQL.
I am looking to add a parameter to my stored procedure wrapped around this, which will allow me to only create dynamic SQL for the 'SourceTableName' that I want - IF I pass in a 'SourceTableNameFilter'.
I am stuck with some logic which wraps my dynamic SQL.
IF #SourceTableNameFilter(SP parameter) = #SourceTableName(from mapping table)
BEGIN
Generate and execute some dynamic SQL based on the SourceTableName.
The problem is, I want this to either work on all tables that come back from a select against 'SourceTableName' BUT if a #SourceTableNameFilter parameter is present and not null - then only generate dynamic SQL for any rows in the cursor which match my filter parameter.
Is there a way for me to accomplish this with an IF statement without copying the logic inside the IF/ELSE twice?
FETCH NEXT FROM TABLECUR INTO #SourceTableName
,#SourceInColumn
,#SourceOutColumn
,#TargetTableName
,#TargetLookupColumn
,#TargetLookupResultColumn
,#MappingTableID
WHILE (##fetch_status <> -1)
BEGIN
IF (##fetch_status <> -2)
BEGIN
IF (#SourceTableName = #SourceTableNameFilter)
--GENERATE DYNAMIC SQL
ELSE
--GENERATE DYNAMIC SQL FOR ALL RECORDS
The generate dynamic SQL string is the same in both the if and the else, any way to change the conditions so that I'm not duplicating the dynamic SQL generation and to not generate dynamic SQL when the #SourceTableName != #SourceTableNameFilter?
Thank you

Consider adding this logic to the cursor definition, rather than having that logic within the processing of each cursor record.
So if the cursor is normally:
DECLARE MY_CURSOR Cursor FOR
SELECT SourceTableName, SourceInColumn, SourceOutColumn
,TargetTableName, TargetLookupColumn
,TargetLookupResultColumn, MappingTableID
FROM MappingTable
--get source tables when filter is specified; otherwise get all
WHERE (SourceTableName = #SourceTableNameFilter) OR (LEN(ISNULL(SourceTableNameFilter,'')=0)
Now you can execute your business logic within the cursor without having to detect the filtered table or not. The cursor is loaded with the records you need to care about. It sounds, from the question, that the business logic is the same, no matter if the filter was passed in or not. If this is incorrect, or if it doesn't satisfy your requirement, please comment.

Knowing nothing about the dynamic sql you're building, I'd recommend doing something along the lines of:
SET #DynamicCommand = '<whatever, first part>'
+ isnull(#SourceTableNameFilter
,'<no special action, perhaps just empty string>'
,'<add conditional text dependent upon contents of #SourceTableNameFilter>')
+ '<whatever, second part>'

Related

PostgreSQL, allow to filter by not existing fields

I'm using a PostgreSQL with a Go driver. Sometimes I need to query not existing fields, just to check - maybe something exists in a DB. Before querying I can't tell whether that field exists. Example:
where size=10 or length=10
By default I get an error column "length" does not exist, however, the size column could exist and I could get some results.
Is it possible to handle such cases to return what is possible?
EDIT:
Yes, I could get all the existing columns first. But the initial queries can be rather complex and not created by me directly, I can only modify them.
That means the query can be simple like the previous example and can be much more complex like this:
WHERE size=10 OR (length=10 AND n='example') OR (c BETWEEN 1 and 5 AND p='Mars')
If missing columns are length and c - does that mean I have to parse the SQL, split it by OR (or other operators), check every part of the query, then remove any part with missing columns - and in the end to generate a new SQL query?
Any easier way?
I would try to check within information schema first
"select column_name from INFORMATION_SCHEMA.COLUMNS where table_name ='table_name';"
And then based on result do query
Why don't you get a list of columns that are in the table first? Like this
select column_name
from information_schema.columns
where table_name = 'table_name' and (column_name = 'size' or column_name = 'length');
The result will be the columns that exist.
There is no way to do what you want, except for constructing an SQL string from the list of available columns, which can be got by querying information_schema.columns.
SQL statements are parsed before they are executed, and there is no conditional compilation or no short-circuiting, so you get an error if a non-existing column is referenced.

Get the ResultSet of an SQL injection

Suppose the server side code is something like that:
String id = getIdFromHttpRequest();
String value = getValueFromHttpRequest();
ResultSet rs = new ResultSet();
String query = "INSERT INTO users VALUES ('" + id + "', '" + value + "');"
rs = SQL.doQuery(query); // i know it's not the syntax, but the point is clear
Well, the injection is easy, I can make it execute an SQL command, but the problem is I want to see the result set (I inject SELECT command).
Is there a way of doing so?
You probably cannot achieve this.
As you know, an INSERT statement has no result set, even if you use SQL injection. At best, you could make it execute a SELECT as a scalar subquery. It's not hard to spoof your example to execute the following:
INSERT INTO users VALUES ('8675309', '' || (SELECT ...blah blah...) || '');
But that still would not return a result set, because INSERT never has a result set.
You would need to execute a second query to do that. Some query interfaces do support multi-query in a single call to doQuery(), but this is not always true (depends on the brand of database you use, and possibly some configuration options).
INSERT INTO users VALUES (...whatever...);
SELECT * FROM secure_table WHERE (id = '8675309');
With SQL injection, you can manipulate the SQL, but you can't manipulate the rest of the code in the application that runs the SQL. In the example you show, the app is designed to run an INSERT query, not an INSERT followed by a SELECT. The app would have no reason to fetch a result set after executing an INSERT.
It's hard to imagine how you could use SQL injection alone to trick the code you show into fetching and displaying a result set.
I don't think it is possible to use SQL injection do read data by exploiting a non-reading query.

What command should I use to make my void function display a paticular table using a variable from the function in the where clause?

I basically have a void function that creates a tuple on a existing table. Now at the end of the function I want display the table with the updated tuple. I am running it problems when trying to do this.
This is the statement I am using:
EXECUTE 'SELECT * FROM table WHERE IDNo = idnumber';
-- (idnumber is a variable that is assigned a value in the function)
I get the following error:
ERROR: column "idnumber" does not exist.
Can someone please help me find a solution.
For the actual query, you would want to do something like this:
execute 'select * from table where IDNo = $1' using idnumber;
With the key being the $1 and the USING clause to interpolate the variable.
That should resolve the column error regarding idnumber.
However, I'm not quite sure what you mean by:
display the table with the updated tuple
Do you mean you want to return all the rows in the table including the newly added row? Or just the newly added row? or something else?
Edit in response to comment from OP:
The substitution variables, e.g. $1, $2, $3... are scoped (i.e. unique) to each separate execute statement. So if you had two statements, the first with 3 variables, the second with three, you could use $1, $2, $3 in each and they would refer to the variables mentioned in the USING clause for that individual statement.
See the Postgres Basic Statements doc, specifically the section entitled 40.5.4. Executing Dynamic Commands, for more detail.
Second edit in response to display comments from OP:
When executeing statements, they won't output the way, say, a select statement would if you were doing it within psql or pgadmin. Rather, you have a couple different options, depending on what you ultimately want to do.
First, you could use an INTO clause to put the result into a record (although how you do this depends on whether it's just one row or many rows).
You would need to declare it in that case in the declaration section something like this: foo RECORD;
And then add INTO foo before the USING clause. If it complains about more than one record, you could add LIMIT 1 clause at the end of the query.
You could then do whatever else you wanted to with that record, including RAISE NOTICE with interpolating the record's columns, which would print it to the console.
If you want the entire table, and you want it to "display" more like psql or similar would (that is, return the rows obtained), you would want to have the function return a setof a specific type.
So it may then look something like this:
create function get_table() returns setof table as $$
execute 'select * from table where IDNo = $1' using idnumber;
$$ language 'plpgsql';
Where table is the name of the table you want. If you just want to return the existing rows of the table, this sort of query should work. It would then "display" in a client (e.g. psql, etc.) as the result set.
If you want to modify that (say, by dynamically adding some columns), then you would need to define that new type specifically, and then use that as the type being returned.
See the Postgres Wiki for more details. The wiki content is pretty old (Postgres 7.x vintage), but it generally still applies.

Re-write this Query to make it more scalable

I have a page on my site which has multiple drop down boxes as filters.
So the SQL procedure for that page would be something like this
IF #Filter1 = 0, #Filter2 = 0, #Filter3 = 0
BEGIN
SELECT * FROM Table1
END
ELSE IF #Filter1 = 1, #Filter2 = 0, #Filter3 = 0
BEGIN
SELECT * FROM Table2
END
At the beginning, there were only a few results per filter so there weren't that many permutations. However, more filters have been added such that there are over 20 IF ELSE checks now.
So if each filter has 5 options, I will need to do 5*5*5 = 125 IF ELSE checks to return data dependent on the the filters.
Update
The first filter alters the WHERE condition, the second filter adds more tables to the result set, the third filter alters the ORDER BY condition
How can I make this query more scalable such that I don't have to write a new bunch of IF ELSE statements to check for every condition everytime a new filter is added to the list besides using dynamic SQL...
You must have to have a rule table with formulaes maybe bitwise and construct a query that might plug variable data from the table and appends to a string to form the sql and the use dynamic sql to run them.
As much as I dislike dynamic SQL, this may be the time for it. You can build the query a little at a time, then execute it at the end.
If you're unfamiliar, the syntax is something like:
DECLARE #SQL VARCHAR(1000)
SELECT #SQL = 'SELECT * FROM ' + 'SOME_TABLE'
EXEC(#SQL)
Make sure you deal with SQL injection attacks, proper spacing, etc.
In this case, I'd do my best to put this logic in application code, but that's not always possible. If you're using LINQ-to-SQL or another LINQ framework, you should be able to do this safely, but it may take some creativity to get the LINQ query built properly.
You can set up a bunch of views, one for each "filter" and then select from the appropriate view based on which "filter" was selected.

Is it possible to use CASE with IN?

I'm trying to construct a T-SQL statement with a WHERE clause determined by an input parameter. Something like:
SELECT * FROM table
WHERE id IN
CASE WHEN #param THEN
(1,2,4,5,8)
ELSE
(9,7,3)
END
I've tried all combination of moving the IN, CASE etc around that I can think of. Is this (or something like it) possible?
try this:
SELECT * FROM table
WHERE (#param='??' AND id IN (1,2,4,5,8))
OR (#param!='??' AND id in (9,7,3))
this will have a problem using an index.
The key with a dynamic search conditions is to make sure an index is used, instead of how can I easily reuse code, eliminate duplications in a query, or try to do everything with the same query. Here is a very comprehensive article on how to handle this topic:
Dynamic Search Conditions in T-SQL by Erland Sommarskog
It covers all the issues and methods of trying to write queries with multiple optional search conditions. This main thing you need to be concerned with is not the duplication of code, but the use of an index. If your query fails to use an index, it will preform poorly. There are several techniques that can be used, which may or may not allow an index to be used.
here is the table of contents:
Introduction
The Case Study: Searching Orders
The Northgale Database
Dynamic SQL
Introduction
Using sp_executesql
Using the CLR
Using EXEC()
When Caching Is Not Really What You Want
Static SQL
Introduction
x = #x OR #x IS NULL
Using IF statements
Umachandar's Bag of Tricks
Using Temp Tables
x = #x AND #x IS NOT NULL
Handling Complex Conditions
Hybrid Solutions – Using both Static and Dynamic SQL
Using Views
Using Inline Table Functions
Conclusion
Feedback and Acknowledgements
Revision History
if you are on the proper version of SQL Server 2008, there is an additional technique that can be used, see: Dynamic Search Conditions in T-SQL Version for SQL 2008 (SP1 CU5 and later)
If you are on that proper release of SQL Server 2008, you can just add OPTION (RECOMPILE) to the query and the local variable's value at run time is used for the optimizations.
Consider this, OPTION (RECOMPILE) will take this code (where no index can be used with this mess of ORs):
WHERE
(#search1 IS NULL or Column1=#Search1)
AND (#search2 IS NULL or Column2=#Search2)
AND (#search3 IS NULL or Column3=#Search3)
and optimize it at run time to be (provided that only #Search2 was passed in with a value):
WHERE
Column2=#Search2
and an index can be used (if you have one defined on Column2)
if #param = 'whatever'
select * from tbl where id in (1,2,4,5,8)
else
select * from tbl where id in (9,7,3)