SQL Server / SSIS - Dynamic T-SQL, NVARCHAR(MAX) Difference - sql-server

I am writing some dynamic SQL:
DECLARE #sql NVARCHAR(MAX)
SET #sql = '...'
EXEC sp_executesql #sql
If I execute this from SSMS, the query executes successfully because it is less than 8000 (7787 to be precise) characters. If I execute this from SSIS (using an ADO.NET connection) the query fails because NVARCHAR(MAX) is the equivalent of NVARCHAR(4000).
I understand this is something to do with compatibility - the connection from SSIS needs to support more than just SQL Server.
What are my workaround options?

For me, I'd go this way (in order of preference):
Find a way to do it without using dynamic SQL (such a large dynamic script must be a nightmare to debug or change)...
Split some of it out into stored procedures and call those from your dynamic SQL.
Split it into a number of smaller sub queries.

Related

Table-Based Function using variables and OpenQuery

I am attempting to query data from our Oracle server via our SQL server. To perform this in a thin-client manner, I am using OpenQuery. I would like to build a single table-based function that can be used to query various linked tables as needed. I can't quite figure out the syntax to make this work. Here is what I have so far. Any help is greatly appreciated!
CREATE FUNCTION [dbo].[fnTEST](
#Table varchar (100),
#Fields varchar (1000),
#Condition varchar(5000)
)
RETURNS
#TEST TABLE()
AS
BEGIN
DECLARE #OPENQUERY nvarchar(4000);
DECLARE #TSQL nvarchar(4000);
SET #OPENQUERY = 'SELECT * FROM OPENQUERY([TEST-Link],'''
SET #TSQL = 'SELECT ' + #Fields + ' FROM TEST.' + #Table + ' WHERE ' + #Condition + ''')'
EXEC (#OPENQUERY+#TSQL)
END;
The error I am currently getting is:
Msg 102, Level 15, State 1, Procedure fnTEST, Line 12 [Batch Start Line 7]
Incorrect syntax near ')'.
Highlighted at #TEST TABLE()
This is all not recommended for a number of reasons, but here the big one is that, as indicated in the MS doc, you cannot use dynamic SQL from a user-defined function:
Before You Begin
Limitations and restrictions
...
User-defined functions cannot make use of dynamic SQL or temp tables. Table variables are allowed.
...
Here are some of the other problems with this approach:
Your dynamic SQL is injectable. You should never use dynamic SQL unless you understand what SQL Injection is and how to prevent it in your dynamic SQL code.
Using dynamic sql has potential security requirements and restrictions. In this case the dynamic SQL may not have the same rights as your account and may not be able to use an OPENQUERY.
The nature of Database and Server Trustworthy settings may block this anyway.
IMHO, OPENQUERY is not recommended (some disagree), and remote queries are better handled with Linked Servers and the Remote EXEC command.
You are trying to write a "Universal Query" here. Universal Queries are generally not a good idea and have security problems, even after you fix the SQL Inject issues. It's better to define the specific queries needed by your app and code them as stored procedures and/or fixed queries using parameters only for WHERE conditions.
A SQL Function is not the right place for all of this anyway. You should regard a SQL table function as akin to a View, but with parameters for your WHERE clause. You should not treat it as a way to magically do anything.
The way that I would do something link this is as follows:
Define the explicit queries/datasets that your app needs from the Oracle Database.
Write those queries as stored procedures, on the Oracle database.
Setup a Linked Server definition in your SQL Server database to the Oracle database. Configure the security for each side appropriately.
Write specific stored procedures on your SQL Server to call the corresponding procedures in the Oracle database. Use remote EXEC's to do this through the Linked Server definition.
(NOTE: Remote EXEC execution is done with the AT <linkedServer> clause).
Enable the linked server for rpc out and simplify this to
EXEC (#sql) at [TEST-Link]

In SQL Server, is it possible to find all databases that contain some schema, without using dynamic SQL?

I know how to find all databases that contain some schema, using dynamic SQL, but can it be done without dynamic SQL?
There is no server-wide DMV containing such information. You have to either construct one monstrous dynamic query touching all databases, or execute a small query in each database. To use the later, you can use sp_MSforeachdb (or some alternative) to query each of the databases, like this (replace Staging with the name of the schema you are looking for):
DECLARE #command varchar(1000)
SELECT #command = 'USE ? if schema_id(N''Staging'') is not null select db_name() as DatabaseName'
EXEC sp_MSforeachdb #command
When used properly, dynamic SQL is not inefficient. Don't hesitate to use it when appropriate.

SQL Server Dynamic Stored Procedure

I am having the stored procedure. For that i need to pass the Database name as the paramters from another application or another SP. I know the approach of dynamic SQL, something like,
Create procedure mysp(#dbname varchar(20))
as
begin
declare #sql varchar(max)
set #sql='select * from '+#dbname+'.dbo.table'
end
exec mysp 'mydb'
But i dont want the SQL statements as a string. Because in my SP, i have many Sql statements are coming (Not like this only SELECT statement). so can i use,
USE DatabaseName
inside the stored procedure, so that i can use the db name in the sql statements directly without making it as string. Or any other approach is there.
My requirements, only for db name, i dont want the entire the sql statement to be dynamic...
please help me out.
Thanks in advance.
You can add the USE instruction to the dynamic query you are creating. Then you can work with that database's tables and other objects without the qualifier (within the dynamic query):
Create procedure mysp(#dbname varchar(20))
as
begin
declare #sql varchar(max)
set #sql='use '+#dbname;
set #sql=#sql + ';select ... from dbo.table1';
set #sql=#sql + ';update dbo.table2...';
set #sql=#sql + ';insert into dbo.table3...';
...
exec(#sql);
end
exec mysp 'mydb'
However, while you can do that, it's not something that you should do, unless you really have to. You are probably trying to avoid creating the same procedure in different DBs, but you may be getting you other problems with this approach, or robbing yourself of some advantages you might otherwise have without resorting to dynamic queries in SPs.
No, USE isn't allowed in stored procedures, functions and triggers.
A stored procedure is supposed to be local to the database. To access another database, there is one way (as far as I know), and it's the one you used.

Use of '' + in SQL Server 2005 Stored Procedure to build SQL string

I'm building a stored procedure which is rather stretching my experience. With the help of people who responded to this thread [Nested if statements in SQL Server stored procedure SELECT statement I think I'm most of the way there :)
In short, the SP takes a series of paramterised inputs to dynamically build an SQL statement that creates a temporary table of id values ordered in a specific way. The remainder of the SP, which returns the data according to the requested page from the id values in this temporary table is all sorted.
Reconsider the use of dynamic SQL - you should really know what you are doing if you go that route.
What is the problem you are trying to solve? I am sure people here will be able to find a better solution than the dynamic SQL you are proposing to use.
Take a look at CONVERT() and CAST() for the integers.
To concatenate integer values into the dynamic SQL statement you need to convert to a varchar e.g:
....WHERE
OT.site_id = ' + CAST(#siteid AS VARCHAR)
If the SQL statement is always going to be less than 4000 chars, I'd at least consider using sp_executesql to use parameterised SQL.
e.g.
DECLARE #SQL NVARCHAR(4000)
DECLARE #siteid INTEGER
SET #siteid = 1
SET #SQL = 'SELECT * FROM MyTable WHERE site_id = #siteid'
EXECUTE sp_executesql #SQL, N'#siteid INTEGER', #siteid
All in all, what you're doing is not likely to be very performant/scalable/maintainable and you don't really gain much from having it as a sproc. Plus you need to be very very careful to validate the input as you could open up yourself to SQL injection (hence my point about using sp_executesql with parameterised SQL).
You need to cast the int param to be a char/varchar so that you can add it to the existing string. The fact that you aren't surrounding it with quotes in the final sql means it will be interpreted as a number.

Set database name dynamically in SQL Server stored procedure?

How do I set the database name dynamically in a SQL Server stored procedure?
Sometimes, the use of SYNONYMs is a good strategy:
CREATE SYNONYM [schema.]name FOR [[[linkedserver.]database.]schema.]name
Then, refer to the object by its synonym in your stored procedure.
Altering where the synonym points IS a matter of dynamic SQL, but then your main stored procedures can be totally dynamic SQL-free. Create a table to manage all the objects you need to reference, and a stored procedure that switches all the desired synonyms to the right context.
This functionality is only available in SQL Server 2005 and up.
This method will NOT be suitable for frequent switching or for situations where different connections need to use different databases. I use it for a database that occasionally moves around between servers (it can run in the prod database or on the replication database and they have different names). After restoring the database to its new home, I run my switcheroo SP on it and everything is working in about 8 seconds.
Stored Procedures are database specific. If you want to access data from another database dynamically, you are going to have to create dynamic SQL and execute it.
Declare #strSQL VarChar (MAX)
Declare #DatabaseNameParameter VarChar (100) = 'MyOtherDB'
SET #strSQL = 'SELECT * FROM ' + #DatabaseNameParameter + '.Schema.TableName'
You can use if clauses to set the #DatabaseNameParameter to the DB of your liking.
Execute the statement to get your results.
This is not dynamic SQL and works for stored procs
Declare #ThreePartName varchar (1000)
Declare #DatabaseNameParameter varchar (100)
SET #DatabaseNameParameter = 'MyOtherDB'
SET #ThreePartName = #DatabaseNameParameter + '.Schema.MyOtherSP'
EXEC #ThreePartName #p1, #p2... --Look! No brackets

Resources