How to EXEC a query generated in a CTE - sql

I have a CTE in which a SELECT statement is generated, but SQL Server (2012) does not allow calling EXEC on it. Here's the query:
DECLARE #guidToFind uniqueidentifier = 'E4069560-091A-4026-B519-104F1C7693B3';
WITH GuidCols (TableName, ColName, Query) As
(
SELECT
C.TABLE_NAME,
C.COLUMN_NAME,
'SELECT ' +
QUOTENAME(C.TABLE_NAME) + '.' +
QUOTENAME(C.COLUMN_NAME) + '
FROM ' +
QUOTENAME(C.TABLE_NAME) + '
WHERE ' +
QUOTENAME(C.COLUMN_NAME) + ' = ''' + cast(#guidToFind AS VARCHAR(50))+
''''
FROM
INFORMATION_SCHEMA.COLUMNS C
INNER JOIN INFORMATION_SCHEMA.TABLES T
ON C.TABLE_NAME = T.TABLE_NAME AND
T.TABLE_TYPE = 'BASE TABLE'
WHERE
C.DATA_TYPE = 'uniqueidentifier'
)
-- SELECT * FROM
EXEC( GuidCols.Query )
The problem is not solve if I uncomment the SELECT statement after CTE.
The purpose of this query is to find all instances of a GUID in a database. Currently I'm solving this problem using a script like this. However I would be happier to solve this problem without iterating through rows, using set operations and other techniques.

You can not use exec as a part of a CTE.
From WITH common_table_expression (Transact-SQL)
A CTE must be followed by a single SELECT, INSERT, UPDATE, or DELETE
statement that references some or all the CTE columns.
What you can do instead is to store the result from your query in a temp table, loop over the rows and execute one statement at a time.

The EXEC() function is available from T-SQL, but it is not accessible from within a SQL statement. I would assume this is the case because SQL is executed in several steps... something like:
Your statement is parsed.
An execution plan is generated.
The execution plan is run against the database and results are returned.
The EXEC() function is designed to dynamically execute a statement, so in order to do what you're asking, SQL Server would have to perform the first two steps, then while executing the statement, it would have to return back to the first step to parse/execute the dynamic statement. This would have to be repeated for each row in your results.
I assume this type of 'feature' is not available because it could adversely affect the performance and complexity of the db engine; so they leave it to us to execute the first statement, then loop through each record to execute the dynamic statements individually.

Exec executes a T-SQL command that you embed in its parentheses. The difference is that the EXEC() function can execute a string or string variable, as you saw in the preceiding code. The argument for EXEC() can be a literal string, but the real power of EXEC() comes to light when you use a variable.
It executes the command contained in the variable, but in a different context from the session calling the function. That's important, because it introduces some severe constraints. Any tables you create are visible to the EXEC() context, but variables declared by the calling session aren't visible to the EXEC() context, and vice versa.
I found an interesting article related to this at : http://msdn.microsoft.com/en-us/library/aa175921(v=sql.80).aspx
Although above is self sufficient, you can also go through : http://blog.sqlauthority.com/2007/09/13/sql-server-difference-between-exec-and-execute-vs-exec-use-execexecute-for-sp-always/

You may use OPENQUERY instead of EXEC:
SELECT * FROM OPENQUERY (OracleSvr, 'SELECT name FROM joe.titles WHERE name = ''NewTitle''');
Reference: https://learn.microsoft.com/en-us/sql/t-sql/functions/openquery-transact-sql?view=sql-server-2017

Related

SQL Query for paramaterized dynamic table

One of the product teams I manage has an abundance of queries that use dynamic sql queries injecting the table in using concatenation. While it has sanitisation, I am trying to completely remove dynamic sql.
Is there a way to parameterise the table name?
I am trying to think of how I can query a table something like:
SELECT * FROM (SELECT DISTINCT Table_Name FROM INFORMATION_SCHEMA.Tables WHERE Table_Name = :queryParam)
Is this possible?
There is no way to "properly" prevent SQL injection completely from within SQL, the calling application layer should do this prior to executing any SQL statement.
Solve the problem by using an ORM or building the code to protect yourself from SQL injection when you generate the SQL in the application code.
This feels like a classic XY problem, try to take a step back and consider that you need to protect the access to the SQL server itself rather than sanitise everything from within "after" your SQL server has been accessed.
I am trying to completely remove dynamic sql.
You can't do it without Dynamic SQL.
Is this possible?
No, it's not possible, you cannot parameterize identifiers in SQL queries.
Why?
From the Books online page for Variables, Variables can be used only in expressions, not in place of object names or keywords. To construct dynamic SQL statements, use EXECUTE.
This is the only way to do it:
DECLARE #Column SysName = N'Table_Name',
#Param NVARCHAR(128) = N'ParamValue';
DECLARE #SQL NVARCHAR(MAX)=N'SELECT *
FROM(
SELECT '+ QUOTENAME(#Column) +
'FROM INFORMATION_SCHEMA.Tables
WHERE ' + QUOTENAME(#Column) + ' = #Param
) T';
EXECUTE sp_executesql #SQl,
N'#Param NVARCHAR(128)',
#Param;

Optimization of query in Oracle

Update table from statement. Using subquery.
I need write an update statement that used same table.
I used subquery for update multiple columns.
My example.
UPDATE USER_BANCU.REGISTRU_21052016_AE NEW
SET (
NEW.LIST_COND,
NEW.LISTA_FOND,
NEW.GEN_ACT_NE_LIC,
NEW.GEN_ACT_LIC
)
=
(
SELECT
OLD.LIST_COND,
OLD.LISTA_FOND,
OLD.GEN_ACT_NE_LIC,
OLD.GEN_ACT_LIC
FROM (
SELECT
VB.IDNO IDNO ,
trim_vb(VB.LIST_COND) LIST_COND,
trim_vb(VB.LISTA_FOND) LISTA_FOND,
REPLACE(VB.GEN_ACT_NE_LIC, ' ','' ) GEN_ACT_NE_LIC,
REPLACE(VB.GEN_ACT_LIC, ' ','' ) GEN_ACT_LIC
FROM USER_BANCU.REGISTRU_21052016_AE VB
) OLD
WHERE
OLD.IDNO=NEW.IDNO
)
WHERE EXISTS (
SELECT *
FROM (
SELECT
VB.IDNO IDNO ,
trim_vb(VB.LIST_COND) LIST_COND,
trim_vb(VB.LISTA_FOND) LISTA_FOND,
REPLACE(VB.GEN_ACT_NE_LIC, ' ','' ) GEN_ACT_NE_LIC,
REPLACE(VB.GEN_ACT_LIC, ' ','' ) GEN_ACT_LIC
FROM USER_BANCU.REGISTRU_21052016_AE VB
) OLD
WHERE
OLD.IDNO=NEW.IDNO
)
Update table from statement.
Using subbquery.
Ability to optimize? Is it possible to create a procedure or a cursor in this case?
I have an error when you run a query --ORA-01427: single-row subquery returns more than one row.
It looks like you've massively overcomplicated the update. Since you're updating every single row in the table with values from the same table, I think you're just trying to do:
update user_bancu.registru_21052016_ae
set list_cond = trim_vb(vb.list_cond),
lista_fond = trim_vb(vb.lista_fond),
gen_act_ne_lic = replace(vb.gen_act_ne_lic, ' '),
gen_act_lic = replace(vb.gen_act_lic, ' ');
N.B. I removed the '' from the replace parameters because in Oracle, there isn't such a thing as an empty string - it's treated the same as null. And as the default value of the string-to-replace-with parameter is null, you can just remove the parameter altogether.
Also, replacing the above statement with a procedure involving looping round a cursor is likely to be slower. If you have to have a procedure, just use the update statement directly in the procedure.
If you need to speed things up even further, than the above update statement, I suggest you take a look at the trim_vb function calls - if you can move the logic directly into the update statement, then that should speed things up even more (certainly in pre-12c, user defined function calls in DML statements involve context switching between the SQL and PL/SQL engines, which slows things down.).

MySQL - Using stored procedure results to define an IN statement

I'd like to use a stored procedure to define the IN clause of a select statement.
This is (a simplified version of) what I'm trying to do:
SELECT *
FROM myTable
WHERE columnName IN (CALL myStoredProc)
myStoredProc performs some complicated logic in the database and returns a list of possible matching values for columnName. The statement above does not work obviously. The select statement may be performed in another stored procedure if that makes a difference.
Is this at all possible in mySQL?
What return type does your current stored procedure have? You are speaking of "a list", so TEXT?
Maybe there's an easier way, but one thing you can do (inside another stored procedure) is to build another query.
To do that, we need to work around two limitations of MySQL: a) To execute dynamic SQL inside a stored procedure, it needs to be a prepared statement. b) Prepared statements can only be created out of user variables. So the complete SQL is:
SET #the_list = myStoredProc();
SET #the_query = CONCAT('SELECT * FROM myTable WHERE columnName IN (' , #the_list , ')');
PREPARE the_statement FROM #the_query;
EXECUTE the_statement;
If you're talking about returning a result set from a stored routine and then using it as table, that is not possible. You need to make a temporary table to work around this limitation.

Why can't use INSERT EXEC statement within a stored procedure called by another stored procedure?

First I try to explain the circumstances.
I store the the filter expression in one column separated by line breaks. The base idea was this:
SELECT
'SELECT ''' + REPLACE(topic_filter,CHAR(10),''' UNION ALL SELECT ''') + ''''
FROM dbo.topic_filter T
WHERE
T.id = #id
FOR XML PATH('')
After this I simply execute this string to put the datas into a temp table.
My problem starts here.
The snippet is in a stored procedure and used by multiple stored procedures to generate the base source to fill.
Approach 1:
Call this sp from another SP to fill a temp table.
Result 1:
An INSERT EXEC statement cannot be nested.
(If I call simply with exec dbo... style the code is working. I only get the error if I try to call within a stored procedure)
Approach 2:
I put the code above into a table values function.
Result 2:
Invalid use of a side-effecting operator 'INSERT EXEC' within a function.
(The function itself don't compiled)
Thanks,
Péter
In the meantime I managed to solve the problem (with help :) ). The solution is simple:
exec('insert into t2 ' + #str)
Where #str contains a select statement.
I don't know why but this way there is no error. The method I call the stored procedure:
SET #exec = 'exec dbo.trFilterTopic ''' + #id+ ''',null,null,1'
INSERT INTO #filtered
exec (#exec)
I hope I spare some time to other folks with this solution.
Bye,
Péter
It is an SQL Server restriction. You cannot have a nested insert exec (I'm not sure why).
If you go:
insert into t(value)
exec dbo.proc
, and inside dbo.proc you have
insert into t2(value2)
exec(#str)
, then it will not run.
Consider different ways of passing tables around, such as temporary tables or table-valued parameters.
Functions on SQL Server have limitations,
they arenot procedures, you can't use dynamic SQL like 'EXECUTE STRING', 'INSERT EXEC'...

EXEC(query) AT linkedServer With Oracle DB

I am using Microsoft SQL server 2005. I need to sync data between SQL server and an Oracle db. First thing I need is to find out if the count of data on Oracle side with certain filters(here I use ID as a simple example).
SELECT COUNT(*) FROM oracleServer..owner.table1 WHERE id = #id;
The problem I have is that the table on the lined server or Oracle is very big with 4M rows of data. The above query took about 2minutes to get data back. This code is just a simplied piece. Actually my SP has some other queries to update, insert data from the lined server to my SQL server. The SP took hours or 10+ hours to run with large Oracle db. Therefore T-SQL with lined server is not good for me.
Recently I found OPENQUERY and EXEC (...) AT linedServer. OPENQUERY() is very fast. It took about 0 time to get the same result. However, it does not support variable query or expressions. The query has to be a literal constant string.
EXEC() is in the same way to pass-through query to Oracle. It is fast as well. For example:
EXEC ('SELECT COUNT(*) FROM owner.table1 WHERE id = ' + CAST(#id AS VARCHAR))
AT oracleServer
The problem I have is how to pass the result COUNT(*) back. I tried to google examples in web and msdn. All I can find are SQL or ExpressSQL linedServer examples like:
EXEC ('SELECT ? = COUNT(*) FROM ...', #myCount OUTPUT) AT expressSQL
This query does not work for Oracle. It seems in Oracle, you can set value as output in this way:
SELECT COUNT(*) INTO myCount ...
I tried this:
EXEC ('SELECT COUNT(*) INTO ? FROM ...', #myCount OUTPUT) AT oracleServer
EXEC ('SELECT COUNT(*) INTO : FROM ...', #myCount OUTPUT) AT oracleServer
EXEC ('SELECT : = COUNT(*) FROM ...', #myCount OUTPUT) AT oracleServer
None of those working. I got error message saying query not executable on Oracle server.
I could write a .Net SQL Server project to do the job. Before that, I just wonder if there is anyway way to pass value out as oupput parameter so that I put the better performance T-SQL codes in my SP?
Just a quick update on this. I think I got the solution. I found it in a discussion on a similar issue at Dev NewsGroup. Based on the information, I tried this:
DECLARE #myCount int;
DECLARE #sql nvarchar(max);
set #sql =
N'BEGIN
select count(*) into :myCount from DATAPARC.CTC_MANUAL_DATA;
END;'
EXEC (#sql, #myCount OUTPUT) AT oracleServer;
PRINT #myCount; -- 3393065
Wa! I got the result back in 3 seconds comparing T-SQL query directly on Orable DB (+2minutes). The important thing is to use "BEGIN" and "END;" to wrap the query as anonymous block and don't miss ";" after END
You need anonymous block for output parameters. If you only have input or no parameters, you don't need the block and the query works fine.
Enjoy it! By the way, this is a quick update. If you don't see me again, I would not have any trouble on this issue.
With Linked Services the biggest issue is performance (IMHO)
[linkedserver]...[dbo.RemoteTable] vs OPENQUERY(linkedserver, 'Select * from dbo.RemoteTable') always use the second one
Now to answer the question. OPENQUERY and EXEC() AT is much quicker.
EXEC(Select * from dbo.RemoteTable) AT linkedserver will show the results, but there is no way to re-use.
My simple solution:
SELECT * INTO LocalTable FROM OPENQUERY(linkedserver, 'Select * from dbo.RemoteTable')
OR
INSERT INTO LocalTable SELECT * FROM OPENQUERY(linkedserver, 'Select * from dbo.RemoteTable')
much^10 faster than
SELECT * INTO LocalTable FROM [linkedserver]...[dbo.RemoteTable]