SQL 2005 - Linked Server to Oracle Queries Extremely Slow - sql-server-2005

On my SQL 2005 server, I have a linked server connecting to Oracle via the OraOLEDB.Oracle provider.
If I run a query through the 4 part identifier like so:
SELECT * FROM [SERVER]...[TABLE] WHERE COLUMN = 12345
It takes over a minute to complete. If I run the same query like so:
SELECT * FROM OPENQUERY(SERVER, 'SELECT * FROM TABLE WHERE COLUMN = 12345')
It completes instantly. Is there a setting I'm missing somewhere to get the first query to run in a decent period of time? Or am I stuck using openquery?

In your first example using "dot" notation, client cursor engine is used and most things are evaluated locally. If you're selecting from a large table and using a WHERE clause, the records will be pulled down locally from the remote db. Once the data has been pulled across the linked server, only then is the WHERE clause is applied locally. Often this sequence is a performance hit. Indexes on the remote db are basically rendered useless.
Alternately when you use OPENQUERY, SQL Server sends the sql statement to the target database for processing. During processing any indexes on the tables are leveraged. Also the where clause is applied on the Oracle side before sending the resultset back to SQL Server.
In my experience, except for the simplest of queries, OPENQUERY is going to give you better performance.
I would recommend using OpenQuery for everything for the above reasons.
One of the pain points when using OpenQuery that you may have already encountered is single quotes. If the sql string being sent to the remote db requires single quotes around a string or date date they need to be escaped. Otherwise they inadvertantly terminate the sql string.
Here is a template that I use whenever I'm dealing with variables in an openquery statement to a linked server to take care of the single quote problem:
DECLARE #UniqueId int
, #sql varchar(500)
, #linkedserver varchar(30)
, #statement varchar(600)
SET #UniqueId = 2
SET #linkedserver = 'LINKSERV'
SET #sql = 'SELECT DummyFunction(''''' + CAST(#UniqueId AS VARCHAR(10))+ ''''') FROM DUAL'
SET #statement = 'SELECT * FROM OPENQUERY(' + #linkedserver + ', '
SET #Statement = #Statement + '''' + #SQL + ''')'
EXEC(#Statement)

Related

Why is the openquery returning 0 rows on SQL server but has 900k rows in the target Server?

Need help to figure ut why the remote query I execute on the server return 0 rows , but the same query returns over 900k rows in the target DB.
the string is less 8000 characters long so I won't post it here. but this is the sctructure basically:
declare #SQL varchar(MAX);
declare #D varchar(15);
declare #Per varchar(15);
declare #NextPer varchar(15);
declare #NextYPer varchar(15);
set #D = N'01-JUN-2019'
set #Per = N'2020004';
set #NextYPer = N'2021004'
set #NextPer = N'2020005'
set #SQL = N' SELECT ...... '
set #SQL = N'select * from openquery ([LK1], "'+#SQL+'")';
execute( #SQL);
print #SQL;
Note: the linked server works and is used on other openqueries with shorter strings successfully. I tried using EXECUTE (#SQL) AT and I still get 0 rows. When i exexute the print output directly on the Oracle DB , the query runs for about 15 min and gives results.
First off, OPENQUERY requires the second parameter to be a query string. A string in SQL Server is written between single quotes. From OPENQUERY documentation:
OPENQUERY ( linked_server ,'query' )
Not only that, the SQL that appears in that string, needs to have any single-quotes that appear in the query to be doubled. Suppose you had SQL you wanted to execute the following query:
SELECT * FROM some_table WHERE name='TT.';
You would write this as:
OPENQUERY(lks,'SELECT * FROM some_table WHERE name=''TT.''')
But if you have this in a dynamic SQL statement, this would become
DECLARE #s VARCHAR(MAX);
SET #s='SELECT * FROM OPENQUERY(lks,''SELECT * FROM some_table WHERE name=''''TT.'''''')';
So that's some explosion of single quotes for even the more trivial SQL queries. Count ye quotes, make sure the query itself is up to snuff (i.e. quotes have been properly doubled).
Thanks all for the input.
The root cause is simply the format of the Date parameter, which didn't run correctly on the linked server.
All I had to do is change my query to use this:
SO_BOOK_DATE < to_date(''#D'' , ''DD-MON-YYYY'')
instead of
SO_BOOK_DATE < ''#D'' .

SQL Server Linked Server Join

I have added a linked server in my sql server 2008. I want to fetch data from a table and a table valued function residing on my linked server and join this data on a local table by following below given naming convention.
<server>.<database>.<schema>.<table>
However my first problem is I need to fetch <server> part from a table. So when I try to do something like following it fails
Select * FROM #ServerNameVariable.database.dbo.myTable
Any idea how can I form fully qualified linked server table name with a user defined variable ?
My SP is as follows
CREATE PROCEDURE TEST_SP
AS
BEGIN
DECLARE #NetworkDBName VARCHAR(255)
SET #NetworkDBName = '[MyLinkedServerName]'
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
select * from #NetworkDBName + '.' + testDatabase.dbo.Invoice_tb
END
GO
You cannot use variables in place of database, schema or table names.
Instead you can build and execute dynamic SQL statements, using sp_ExecuteSQL.
This example won't work, as the server name is seen as a string and not a server object.
Failed Example
/* Anti-pattern.
* Does not work.
*/
DECLARE #Server SYSNAME = 'Server001';
SELECT
*
FROM
#Server.Database1.dbo.Table1
;
This example shows a method that does work. Here the SQL statement is built as a string, which is then executed.
/* Dynamic SQL statement.
* Will work.
*/
DECLARE #Server SYSNAME = 'Server001';
DECLARE #Statement NVARCHAR(255);
SET #Statement = 'SELECT * FROM ' + QUOTENAME(#Server) + '.Database1.dbo.Table1;';
EXECUTE sp_ExecuteSQL #Statement;
As ever; please be careful when generating and executing dynamic SQL statements. You do not want to open yourself up to SQL injection attacks. Look into OPENROWSET or check the passed server name against the code kindly supplied by #Devart above (SELECT name FROM sys.servers WHERE server_id > 0) before executing.
EDIT 1: Added more detail to the paragraph on SQL injection.
EDIT 2: Removed square brackets from 2nd example query, replaced with QUOTENAME, as per #TTs comment.
Use the name of link server it will a 4 part qualifier e.g
Select * From [Link Server Name ].[Database].[Schema].[Table]

SQL Server: How to get and use name of the database in SP

I'm working on two different servers with the same structure, one of them is where we develope, and the other one is the live server. My main database (let's call it MainDB) has the same name in both databases, but the others (call those DBi in live server and DB_i in developement server) do not. I have some syncronization stored procedures which transfer data from the databases DBi to MainDB. In the developement server, I use different names then the live server, then after every change in the sync procedures of developement server, I have to change the names of the databases before transfering them to live server.
Now, my aim is to write functions which would return the names of the databases. However, I have no idea what type of data to return in order to use the return value as the name of the database.
For instance, assume I have a query like:
SELECT * FROM DB1.dbo.Table1
in live server. In the developement server, it's:
SELECT * FROM DB_1.dbo.Table1
And my aim is to be able to define something like:
DECLARE #DBName1 [SOME_TYPE]= GetDBName(#DBID_1)
and then use it as:
SELECT * FROM #DBName1.dbo.Table1
It doesn't have to be exactly like this of course, but the functionality must be the same. You may ask why would I need something like that. I don't wanna change the code before all the transfers. I wanna use the same sp in both servers, but get different names for databases from the functions, as I would only change the return value of the functions in servers for only one time.
CREATE PROCEDURE usp_SomeProc
#DB_Name NVARCHAR(128)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = N'SELECT * FROM ' + QUOTENAME(#DBName1) + N'.[dbo].[Table1] '
EXECUTE sp_executesql #Sql
END
Edit
DECLARE #Query NVARCHAR(MAX);
DECLARE #DBName nvarchar(50) = 'DBName'
SET #Query = N'SELECT * FROM ' + QUOTENAME(#DBName) + N'.dbo.Table'
EXECUTE sp_executesql #Query

SQL: Executing a query to dynamic remote server

I want to be able to execute remote queries based on the results of a local query.
For instance:
DECLARE #REMOTESERVER VARCHAR(10)
Select TOP 1 #REMOTESERVER = RemoteServer from TABLE
--Execute the next query on a remote server from the value I retrieved above
Select * from tblCustomers
What RDBMS are you using? Some will not support a pure sql way of doing this. Others, like SQL Server, might support this scenario. Is the remote server accessible via a linked server that you can access. You could then use dynamic sql to create your sql string. Something like this should work in SQL Server:
SET #Sql = 'SELECT * FROM [' + #RemoteServer + '].dbname.schema.tblCustomers'
EXEC #Sql
Here is a post about linked servers: https://stackoverflow.com/a/4091984/1073631

Using Parameter Values In SQL Statement

I am trying to write a database script (SQL Server 2008) which will copy information from database tables on one server to corresponding tables in another database on a different server.
I have read that the correct way to do this is to use a sql statement in a format similar to the following:
INSERT INTO <linked_server>.<database>.<owner>.<table_name> SELECT * FROM <linked_server>.<database>.<owner>.<table_name>
As there will be several tables being copied, I would like to declare variables at the top of the script to allow the user to specify the names of each server and database that are to be used. These could then be used throughout the script. However, I am not sure how to use the variable values in the actual SQL statements. What I want to achieve is something like the following:
DECLARE #SERVER_FROM AS NVARCHAR(50) = 'ServerFrom'
DECLARE #DATABASE_FROM AS NVARCHAR(50) = 'DatabaseTo'
DECLARE #SERVER_TO AS NVARCHAR(50) = 'ServerTo'
DECLARE #DATABASE_TO AS NVARCHAR(50) = 'DatabaseTo'
INSERT INTO #SERVER_TO.#DATABASE_TO.dbo.TableName SELECT * FROM #SERVER_FROM.#DATABASE_FROM.dbo.TableName
...
How should I use the # variables in this code in order for it to work correctly?
Additionally, do you think my method above is correct for what I am trying to achieve and should I be using NVARCHAR(50) as my variable type or something else?
Thanks
There is probably a better way to do this, but what you are probably trying to do in your example is what's called dynamic SQL where you treat the statement as a string and the execute it. This would be section #2 here:
http://www.mssqltips.com/tip.asp?tip=1160
There are some major downsides to dynamic SQL. You see a couple other approaches that might be better in that article.
If you want to execute a dynamically generated query then you have to use sp_ExecuteSQL
HTH
For the nvarchar(50) - you'd be better using sysname. This is a synonym in SQL Server (for nvarchar(128)) and represents the maximum length of an object identifier.
have a look at http://msdn.microsoft.com/en-us/library/ms188001.aspx - sp_executesql takes a parameter that is a string and executes the sql in that string. so you'd need to concatenate #SERVER_FROM and other params with the INSERT INTO part to make the entire sql statement, and then pass to sp_executesql.
nvarchar(50) is fine, unless your server/database names are longer than that :)
You can create the select statement by concatenating all the information together and then use sp_executesql
so:
sp_executesql 'INSERT INTO ' + #SERVER_TO + '.' + #DATABASE_TO +
'.dbo.TableName SELECT * FROM ' + #SERVER_FROM + '.' +
#DATABASE_FROM+'.dbo.TableName'
I like to make templates for dynamic SQL things like this - it's a lot easier to maintain complex statements and also sometimes easier to handle nested quotes - and definitely easier when terms need to be repeated in multiple places (like column lists):
DECLARE #sql AS nvarchar(max);
SET #sql = 'INSERT INTO {#SERVER_TO}.{#DATABASE_TO}.dbo.TableName
SELECT *
FROM {#SERVER_FROM}.{#DATABASE_FROM}.dbo.TableName'
SET #sql = REPLACE(#sql, '{#SERVER_TO}', QUOTENAME(#SERVER_TO))
SET #sql = REPLACE(#sql, '{#DATABASE_TO}', QUOTENAME(#DATABASE_TO))
SET #sql = REPLACE(#sql, '{#SERVER_FROM}', QUOTENAME(#SERVER_FROM))
SET #sql = REPLACE(#sql, '{#DATABASE_FROM}', QUOTENAME(#DATABASE_FROM))
EXEC(#sql)