I'm using a linked server and finding it very painful to write queries like this:
select * from [10.150.10.109].lhf.[dbo].[TABLE_NAME]
Is it possible to use a synonym for something like this:
CREATE SYNONYM [DataRelay] FOR [10.150.10.109].[lhf].[dbo]
in order to be able to query like this:
select * from DataRelay.TABLE_NAME
Without the capabilities of Intellisense, this is just painful...
No, there is no short hand for linked servers, however, you can alias tables in your queries to make it a bit easier.
select * from [10.150.10.109].lhf.[dbo].[TABLE_NAME] T
WHERE
T.FieldName=1
OR
T.FieldName=2
Now that I had a minute what I was saying in my comment is that you cannot create a synonym for just part of an object path as you desire. But you can dynamically script the drop and creation of synonyms for any object in your remote database pretty easily. here is an example of how to do if for user tables. For other objects you can use the sys.objects instead of sys.table system view.
Technique key words for more learning. Dynamic SQL, cursor, schema views.
DECLARE #ServerAndDB SYSNAME = '[10.150.10.109].[lhf]'
DECLARE #SynonymSchema SYSNAME = '[syn]'
DECLARE #ObjectPath NVARCHAR(1000)
DECLARE #SynonymName NVARCHAR(1000)
DECLARE CursorName CURSOR FOR
SELECT
#ServerAndDB + QUOTENAME(SCHEMA_NAME(t.schema_id)) + '.' + QUOTENAME(t.name) as ObjectPath
,#SynonymSchema + '.' + QUOTENAME(t.name) as SynonymName
FROM
[10.150.10.109].[lhf].sys.tables t
WHERE
t.type = 'U'
OPEN CursorName
FETCH NEXT FROM CursorName
INTO #ObjectPath, #SynonymName
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
DECLARE #SQL NVARCHAR(MAX)
IF EXISTS (SELECT * FROM sys.synonyms WHERE object_id = OBJECT_ID(#SynonymName))
BEGIN
SET #SQL = 'DROP SYNONYM ' + #SynonymName
EXECUTE sp_executesql #SQLString
SET #SQL = ''
END
SET #SQL = 'CREATE SYNONYM ' + #SynonymName + ' FOR ' + #ObjectPath
EXECUTE sp_executesql #SQLString
SET #SQL = ''
END TRY
BEGIN CATCH
--Can do error handling here
END CATCH
FETCH NEXT FROM CursorName
INTO #ObjectPath, #SynonymName
END
CLOSE CursorName
DEALLOCATE CursorName
Related
Somebody create database with special characters name like 'PRİNCE' long time ago.So we can't change database name anymore.
Because of ' İ ' we can't call database from sys.databases.
select name
from dbo.sysdatabases
where name=cast('PRİNCE' as varchar(100)) COLLATE SQL_Latin1_General_CP1253_CI_AI
it works well
But there is a procedure which we using gives error
'Database 'PRİNCE' does not exist. Make sure that the name is entered correctly.'
DECLARE #dbname VARCHAR(50) -- database name
Declare #strSQL nvarchar(max)
DECLARE c CURSOR FOR
select name from MASTER.dbo.sysdatabases where (name not in('master','model','msdb','tempdb'))
OPEN c;
FETCH NEXT FROM c INTO #dbname;
WHILE ##FETCH_STATUS = 0
begin
select #strSQL='use '+cast(#dbname as varchar(100))COLLATE SQL_Latin1_General_CP1253_CI_AI+' Select Db_name(DB_ID()) as Dbname from sys.database_files ;'
exec sp_executesql #strSQL
fetch next from c into #dbname
end
close c
deallocate c
The problem is your use of varchar throughout and not nvarchar. The specific problem statement is this one:
select #strSQL='use '+cast(#dbname as varchar(100))COLLATE SQL_Latin1_General_CP1253_CI_AI+' Select Db_name(DB_ID()) as Dbname from sys.database_files ;'
I am guessing your database (master I assume) is not in the collation SQL_Latin1_General_CP1253_CI_AI so as soon as the assignment happens, the value is lost.
See, for example, the below:
DECLARE #Database sysname = N'USE ' + 'İ' COLLATE SQL_Latin1_General_CP1253_CI_AI + ';';
SELECT #Database;
This returns USE I; not USE İ;. As object names are a sysname (a synonym of nvarchar(128) NOT NULL) then make sure you use that data type through your code:
USE master;
GO
DECLARE #dbname sysname; -- database name
DECLARE #strSQL nvarchar(max);
DECLARE c CURSOR FOR
SELECT [name]
FROM sys.databases --Don't use the old objects!
WHERE ([name] NOT IN(N'master',N'model',N'msdb',N'tempdb'));
OPEN c;
FETCH NEXT FROM c INTO #dbname;
WHILE ##FETCH_STATUS = 0 BEGIN
SET #strSQL = N'USE ' + QUOTENAME(#dbname) + N'; SELECT DB_NAME(DB_ID()) AS DBName from sys.database_files;'; --Note QUOTENAME as well for proper quoting.
EXEC sys.sp_executesql #strSQL;
FETCH NEXT FROM c INTO #dbname;
END;
CLOSE c;
DEALLOCATE c;
Ideally, I'd get rid of the CURSOR as well, but that's a completely different question.
My users are trying to find records in my SQL db by providing simple text strings like this:
SCRAP 000000152 TMB-0000000025
These values can be in any order and any may be excluded. For example, they may enter:
SCRAP
TMB-0000000025 SCRAP
000000152 SCRAP
SCRAP 000000152
TMB-0000000025 000000152
All should work and include the same record as the original search, but they may also contain additional records because fewer columns are used in the match.
Here is a sample table to use for the results:
DECLARE #search1 varchar(50) = 'SCRAP 000000152 TMB-0000000025'
DECLARE #search2 varchar(50) = 'SCRAP'
DECLARE #search3 varchar(50) = 'TMB-0000000025 SCRAP'
DECLARE #search4 varchar(50) = '000000152 SCRAP'
DECLARE #search5 varchar(50) = 'SCRAP 000000152'
DECLARE #search6 varchar(50) = 'TMB-0000000025 000000152'
DECLARE #table TABLE (WC varchar(20),WO varchar(20),PN varchar(20))
INSERT INTO #table
SELECT 'SCRAP','000000152','TMB-0000000025' UNION
SELECT 'SCRAP','000012312','121-0000121515' UNION
SELECT 'SM01','000000152','121-0000155' UNION
SELECT 'TH01','000123151','TMB-0000000025'
SELECT * FROM #table
One additional wrinkle, the user does not have to enter 000000152, they can enter 152 and it should find the same results.
I can use patindex, but it requires the users to enter the search terms in a specific order, or for me to have an exponentially larger string to compare as I try to put them in all possible arrangements.
What is the best way to do this in SQL? Or, is this outside the capabilities of SQL? It is quite possible that the table will have well over 10,000 records (for some instances even over 100,000), so the query has to be efficient.
Agree with #MitchWheat (as usual). This database is not designed for queries like that, nor would any kind of "basic query" help. Best way would be to build a list of strings appearing in any column of the database, mapped back to the source column and row, and search that lookup table for your strings. This is pretty much what Lucene and any other full-text search library will do for you. SQL has a native implementation, but if the pros say go with a third party implementation, I'd say it's worth a look-see.
You can try this SP:
USE master
GO
CREATE PROCEDURE sp_FindStringInTable #stringToFind VARCHAR(100), #schema sysname, #table sysname
AS
DECLARE #sqlCommand VARCHAR(8000)
DECLARE #where VARCHAR(8000)
DECLARE #columnName sysname
DECLARE #cursor VARCHAR(8000)
BEGIN TRY
SET #sqlCommand = 'SELECT * FROM [' + #schema + '].[' + #table + '] WHERE'
SET #where = ''
SET #cursor = 'DECLARE col_cursor CURSOR FOR SELECT COLUMN_NAME
FROM ' + DB_NAME() + '.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = ''' + #schema + '''
AND TABLE_NAME = ''' + #table + '''
AND DATA_TYPE IN (''char'',''nchar'',''ntext'',''nvarchar'',''text'',''varchar'')'
EXEC (#cursor)
OPEN col_cursor
FETCH NEXT FROM col_cursor INTO #columnName
WHILE ##FETCH_STATUS = 0
BEGIN
IF #where <> ''
SET #where = #where + ' OR'
SET #where = #where + ' [' + #columnName + '] LIKE ''' + #stringToFind + ''''
FETCH NEXT FROM col_cursor INTO #columnName
END
CLOSE col_cursor
DEALLOCATE col_cursor
SET #sqlCommand = #sqlCommand + #where
--PRINT #sqlCommand
EXEC (#sqlCommand)
END TRY
BEGIN CATCH
PRINT 'There was an error. Check to make sure object exists.'
IF CURSOR_STATUS('variable', 'col_cursor') <> -3
BEGIN
CLOSE col_cursor
DEALLOCATE col_cursor
END
END CATCH
This will have results as follow:
USE AdventureWorks
GO
EXEC sp_FindStringInTable 'Irv%', 'Person', 'Address'
USE AdventureWorks
GO
EXEC sp_FindStringInTable '%land%', 'Person', 'Address'
That's all there is to it. Once this has been created you can use this against any table and any database on your server.(Read More)
I am using SQL Server 2008 R2 on dev, and SQL Azure for test and live.
I wish to write a little procedure to reset the identity seeds since SQL Azure does not support DBCC.
I have some workaround code which works, but I do not want to write it out for each table, so was trying to write a routine that iterates through the DB tables.
Tables:
SELECT * FROM information_schema.tables
Code:
delete from TABLE_NAME where Id>150000
GO
SET IDENTITY_INSERT [TABLE_NAME] ON
GO
INSERT INTO [TABLE_NAME](Id) VALUES(150000)
GO
delete from TABLE_NAME where Id=150000
GO
SET IDENTITY_INSERT [TABLE_NAME] OFF
GO
I guess I need to wrap this in a loop. Sorry my T-SQL is not that strong, hence the request for help.
Also it would be helpful to omit all tables with TABLE_NAME starting with aspnet_ and use only TABLE_TYPE = "BASE TABLE"
Any help hugely appreciated.
Unless somebody else knows a trick that I don't, you're probably stuck using dynamic SQL and iterating through a list of table names using either a cursor or a temporary table. The cursor approach would look something like this:
declare #TableName nvarchar(257);
declare #sql nvarchar(max);
declare TableCursor cursor read_only for
select
TABLE_SCHEMA + '.' + TABLE_NAME
from
INFORMATION_SCHEMA.TABLES
where
TABLE_NAME not like 'aspnet\_%' escape '\' and
TABLE_TYPE = 'BASE TABLE';
open TableCursor;
fetch next from TableCursor into #TableName;
while ##fetch_status = 0
begin
set #sql = 'select top 1 * from ' + #TableName;
exec sp_executesql #sql;
fetch next from TableCursor into #TableName;
end
close TableCursor;
deallocate TableCursor;
You can read more about cursors here. Alternatively, you could do it with an in-memory table like this:
declare #Tables table (RowId int identity(1, 1), TableName nvarchar(257));
declare #TableName nvarchar(257);
declare #Index int;
declare #TableCount int;
declare #sql nvarchar(max);
insert into #Tables (TableName)
select
TABLE_SCHEMA + '.' + TABLE_NAME
from
INFORMATION_SCHEMA.TABLES
where
TABLE_NAME not like 'aspnet\_%' escape '\' and
TABLE_TYPE = 'BASE TABLE';
set #TableCount = ##rowcount;
set #Index = 1
while #Index <= #TableCount
begin
select #TableName = TableName from #Tables where RowId = #Index;
set #sql = 'select top 1 * from ' + #TableName;
exec sp_executesql #sql;
set #Index = #Index + 1;
end
In the interest of brevity, my examples use a much simpler SQL statement than yours—I'm just selecting one record from each table—but this ought to be enough to illustrate how you can get this done.
I have a query that I want to be able to use across database schemas. Right now it is written so that I need to replace the schema in several places of the query. How do I set that up as a variable so that all I need to do is change it in one place?
You can do this with Dynamic SQL:
DECLARE #sql VARCHAR(MAX)
,#schema VARCHAR(255) = 'dbo'
SET #sql = 'SELECT *
FROM '+#schema+'.yourTable
'
EXEC (#sql)
You could use this in a cursor to loop through schema's:
DECLARE #Iterator varchar(255)
,#strSQL varchar(MAX)
DECLARE xyz CURSOR
FOR
--Select stuff to iterate over
SELECT name
FROM sys.schemas
OPEN xyz
FETCH NEXT FROM xyz
INTO #Iterator
WHILE ##FETCH_STATUS = 0
BEGIN
--Do stuff
SET #strSQL = 'SELECT *
FROM '+#Iterator+'.yourTable
'
Exec (#strSQL)
FETCH NEXT FROM xyz
INTO #Iterator
END
CLOSE xyz
DEALLOCATE xyz
GO
To test your dynamic SQL statements, you can change EXEC to PRINT and ensure that the resulting query is as you intended.
Synonyms might be the answer to your question.
You can use dynamic SQL. Here is an example -
DECLARE #Column varchar(25)
DECLARE #sqlStmt varchar(max)
SET #Column = 'MyColumn'
SET #sqlStmt = N'SELECT ' + #Column + ' FROM MyTable'
EXEC (#sqlStmt)
I have the following code (cursor):
DECLARE #SN VARCHAR(20);
DECLARE #sql NVARCHAR(MAX);
DECLARE C CURSOR LOCAL FAST_FORWARD
FOR SELECT DISTINCT(SERVERNAME) FROM INSTALLATION
where DATABASETYPE = 'MsSql' AND SERVERNAME IN ('x');
OPEN C;
FETCH NEXT FROM C INTO #SN;
WHILE (##FETCH_STATUS = 0)
BEGIN
PRINT #SN;
-- you could loop here for each database, if you'd define what that is
SELECT name
FROM master.dbo.sysdatabases
WHERE name not in ('master','model','msdb','tempdb');
SET #sql = N'SELECT TOP 1 NAME FROM TABLE ';
EXEC sp_executesql #sql;
FETCH NEXT FROM C INTO #SN;
END
CLOSE C;
DEALLOCATE C;
I would like to be able to loop through every server and execute a select statement on some (not all) of their databases.
The query is something like:
SELECT TOP 1 NAME FROM TABLE
The server from where I am running the cursor has all the others as linked servers.
DECLARE #SN VARCHAR(20);
DECLARE C CURSOR LOCAL FAST_FORWARD
FOR SELECT DISTINCT(SERVERNAME) FROM TABLE
where SERVERNAME NOT IN ('SRV1','SRV2','SRV3');
OPEN C;
FETCH NEXT FROM C INTO #SN;
WHILE (##FETCH_STATUS = 0)
BEGIN
PRINT #SN;
-- you could loop here for each database, if you'd define what that is
SET #sql = N'SELECT * FROM ' + #SN + '.master.dbo.TABLE;';
EXEC sys.sp_executesql #sql;
FETCH NEXT FROM C INTO #SN;
END
CLOSE C;
DEALLOCATE C;
Changes:
There is no reason to use the default cursor options here - global, updatable, dynamic, scrollable, etc. Background.
As a habit / best practice, use sp_executesql and not EXEC(). While it doesn't really matter in this case, it can matter in others, so I'd prefer to always code the same way. Background.
Also, please get in the habit of terminating your statements with semi-colons. You'll have to, eventually. Background.
EDIT
Now that we have a little more information about your actual requirements, I suggest this bit of code. Oh, and look, no cursors (well, no explicit cursor declarations and all the scaffolding that comes with them)!
SET NOCOUNT ON;
DECLARE #dbs TABLE(SERVERNAME SYSNAME, DBNAME SYSNAME);
DECLARE #sql NVARCHAR(MAX) = N'';
-- first, let's get the databases on each server:
SELECT #sql += N'SELECT ''' + SERVERNAME + ''', name FROM '
+ QUOTENAME(SERVERNAME) + '.master.sys.databases
WHERE database_id > 4
AND name NOT IN (N''somedb'',N''someotherdb'');'
FROM dbo.INSTALLATION
WHERE DATABASETYPE = 'MsSql'
AND SERVERNAME IN ('x');
INSERT #dbs EXEC sys.sp_executesql #sql;
SELECT #sql = N'';
-- now, build a command to run in each database context:
SELECT #sql += N'
EXEC ' + QUOTENAME(SERVERNAME) + '.'
+ QUOTENAME(DBNAME) + '.sys.sp_executesql #sql;'
FROM #dbs;
-- feel free to change the 3rd parameter here:
EXEC sys.sp_executesql #sql, N'#sql NVARCHAR(MAX)',
N'SELECT ##SERVERNAME, DB_NAME(), actual_columns FROM dbo.table_name;';
This will fail if table_name doesn't exist, so you may still have some work to do if you want to facilitate error handling. But this should get you started.
Also, please be conscious of, and consistently use, the schema prefix. Background.