TSQL Collect data from multiple databases - sql-server

Recently started learning SQL and ran into this problem that i'm unable to find solution.
I'm trying to collect data from multiple databases and output it to single table.
Source select outputs data in two columns "acAcct" and "name of source db"
Target table has "acAcct" and column for each db.
Thing that i came up with:
DECLARE #command varchar(1000)
SET #command = 'IF ''[?]'' NOT IN (''[master]'', ''[model]'', ''[msdb]'', ''[tempdb]'')
INSERT INTO _utility.dbo.konta (acAcct, ''?'')
SELECT DISTINCT AC.acAcct
,AD.acName AS ''?''
from tHE_AcctTransItem as AC
LEFT JOIN vDE_SetAccount as AD
ON AC.acAcct = AD.acAcct'
EXEC sp_MSforeachdb #command
I get this error for each DB:
Msg 102, Level 15, State 1, Line 3
Incorrect syntax near 'name of DB'.
I guess i'm missing something with parsing of variable at:
INSERT INTO _utility.dbo.konta (acAcct, ''?'')
Select works fine.
EDIT:
Ended up with this:
DECLARE #command varchar(1000)
SET #command = 'IF ''[?]'' NOT IN (''[master]'', ''[model]'', ''[msdb]'', ''[tempdb]'')
MERGE _utility.dbo.konta AS target
USING (SELECT DISTINCT AC.acAcct
,AD.acName AS [?]
from [?].dbo.tHE_AcctTransItem as AC
LEFT JOIN [?].dbo.vDE_SetAccount as AD
ON AC.acAcct = AD.acAcct) as source
ON (target.acAcct = source.acAcct)
WHEN MATCHED THEN UPDATE SET target.[?] = source.[?]
WHEN NOT MATCHED THEN
INSERT (acAcct, [?])
VALUES (source.acAcct, [?]);
'
EXEC sp_MSforeachdb #command
Original script filled only first column and errored out on others.

If the source database name is a column then it should not be enclosed in single quotes. Square brackets would be safer.
DECLARE #command varchar(1000)
SET #command = 'IF ''[?]'' NOT IN (''[master]'', ''[model]'', ''[msdb]'', ''[tempdb]'')
INSERT INTO _utility.dbo.konta (acAcct, [?])
SELECT DISTINCT AC.acAcct
,AD.acName AS [?]
from tHE_AcctTransItem as AC
LEFT JOIN vDE_SetAccount as AD
ON AC.acAcct = AD.acAcct'
EXEC sp_MSforeachdb #command

Related

Update the same table for multiple databases within the same server in SQL Server 2014

I want to be able to update the column of the same table in different databases at the same time within the same server, but to start before starting to fill it through a temporary table I wanted to test that it came out with a database, it should be clarified that the structure of the table it is the same in all the databases and that the script would be executed going through the temporary table row by row, so I needed to test it with at least one and then replaced the declared variables.
However, I get the following error, I have not found a solution or another way to do it, I do it to avoid doing it manually 1x1 on each database and it is done automatically:
Msg 4104, Level 16, State 1, Line 16
The multi-part identifier "test#gmail.com" could not be bound.
This is the code I tried:
use master
go
DECLARE #SQL NVARCHAR(MAX) = '';
DECLARE #NAMEBD NVARCHAR(MAX) = 'Here_goes_the_name_of_the_DB';
DECLARE #MAIL NVARCHAR(MAX) = 'test#gmail.com';
SELECT #SQL = #SQL +
'USE ' + QUOTENAME(NAME) + ';
UPDATE dbo.companyconfig
SET originMail ='+#MAIL+';'
FROM sys.databases
WHERE name = #NAMEBD;
EXEC sp_executesql #SQL;
I was hoping it would update but I only get the error shown.
I also tried using sp_MSforeachdb and it gives me the same result.
I already managed to solve it, I did it this way:
DECLARE #SQL NVARCHAR(MAX) = '';
DECLARE #NAMEBD NVARCHAR(MAX) = 'Here_goes_the_name_of_the_DB';
DECLARE #MAIL NVARCHAR(MAX) = 'test#gmail.com';
SELECT #SQL = #SQL + '
UPDATE compconfig
SET originMail ='''+#MAIL+'''
FROM '+QUOTENAME(#NAMEBD)+'.dbo.companyconfig As compconfig;'
FROM sys.databases
WHERE name = #NAMEBD;
EXEC sp_executesql #SQL;
It was necessary to set the field having to double the quotes.

Copying multiple stored procedures to other database

In below scenario, I'm trying to import hundreds of procedures into other database.
Solution: SQL Server
Source
SERVER: A
DATABASE: Apple
PROCEDURES: SP1, SP2, SP3 ... SP100
Destination
SERVER: B
DATABASE: Orange
First thing I did was find only non-existing procedures when compared to both databases.
To do so, I used INFORMATION_SCHEMA.ROUTINES from each database and compared in Excel.
After finding a list of procedures to be imported, I wanted to import all procedures at once.
However, if I create procedure in one-lined text, it will be saved one line which has zero visibility.
So, I used sp_helptext to copy by line for each. Then, created little query like below:
create table #proceduretext (runquery varchar(max))
insert into #proceduretext
exec sp_helptext 'SP1'
insert into #proceduretext select 'go'
insert into #proceduretext
exec sp_helptext 'SP2'
insert into #proceduretext select 'go'
insert into #proceduretext
exec sp_helptext 'SP3'
insert into #proceduretext select 'go'
.
.
.
insert into #proceduretext
exec sp_helptext 'SP100'
insert into #proceduretext select 'go'
Then do a select from temp table, paste result, run.
However, above was still insufficient.
Please help with below questions:
Is there a way to use INFORMATION_SCHEMA.ROUTINES for linked servers?
If there is an answer to question 1, how can I loop below query for all missing procedures?
insert into #proceduretext
exec sp_helptext 'SP100'
insert into #proceduretext select 'go'
You can do that with a linked server pretty easily. Here's an example I prepared for you. Run it from within the target database and change the [GREGT580] to whatever your linked server name is.
USE tempdb;
GO
-- Copying from [GREGT580] to local server
SET NOCOUNT ON;
DECLARE #MissingProcedures TABLE
(
MissingProcedureID int IDENTITY(1,1) PRIMARY KEY,
SchemaName sysname,
ProcedureName sysname,
ProcedureDefinition nvarchar(max)
);
INSERT #MissingProcedures
(
SchemaName, ProcedureName, ProcedureDefinition
)
SELECT s.[name], p.[name], sm.definition
FROM [GREGT580].AdventureWorks.sys.procedures AS p
INNER JOIN [GREGT580].AdventureWorks.sys.schemas AS s
ON p.schema_id = s.schema_id
INNER JOIN [GREGT580].AdventureWorks.sys.sql_modules AS sm
ON sm.object_id = p.object_id
WHERE NOT EXISTS (SELECT 1
FROM sys.procedures AS pl
INNER JOIN sys.schemas AS sl
ON sl.schema_id = pl.schema_id
AND sl.[name] = s.[name] COLLATE DATABASE_DEFAULT
AND pl.[name] = p.[name] COLLATE DATABASE_DEFAULT);
DECLARE #SchemaName sysname;
DECLARE #ProcedureName sysname;
DECLARE #ProcedureDefinition nvarchar(max);
DECLARE #Counter int = 1;
WHILE #Counter < (SELECT MAX(MissingProcedureID) FROM #MissingProcedures AS mp)
BEGIN
SELECT #SchemaName = mp.SchemaName,
#ProcedureName = mp.ProcedureName,
#ProcedureDefinition = mp.ProcedureDefinition
FROM #MissingProcedures AS mp
WHERE mp.MissingProcedureID = #Counter;
PRINT #ProcedureDefinition; -- Change to EXEC (#ProcedureDefinition) to create
SET #Counter += 1;
END;
I made the code just print out the procedures, so you could test it. Change the PRINT line to the EXEC option shown when you want to actually created them. (Note that PRINT will truncate what it shows if they are long but EXEC will be fine).
Hope that helps you.
try using https://www.red-gate.com/ compare tool you can download a trail version. Works great

Why we should use QUOTENAME function?

I get acquainted with QUOTENAME function. But I don't understand for what I can use it? Why it is so widely used?
select quotename('[abc]') -- '[[abc]]]'
select quotename('abc') -- '[abc]'
select '[' + 'abc' +']' -- why it is not so good as previous?
Imagine the following script is scheduled to run regularly to clean up tables in schemas other than the dbo schema.
DECLARE #TABLE_SCHEMA SYSNAME,
#TABLE_NAME SYSNAME
DECLARE #C1 AS CURSOR;
SET #C1 = CURSOR FAST_FORWARD
FOR SELECT TABLE_SCHEMA,
TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA <> 'dbo'
OPEN #C1;
FETCH NEXT FROM #C1 INTO #TABLE_SCHEMA, #TABLE_NAME;
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT 'DROP TABLE [' + #TABLE_SCHEMA + '].[' + #TABLE_NAME + ']';
EXEC ('DROP TABLE [' + #TABLE_SCHEMA + '].[' + #TABLE_NAME + ']');
FETCH NEXT FROM #C1 INTO #TABLE_SCHEMA, #TABLE_NAME;
END
If you create the following and run the script then all works as expected despite using the manual string concatenation approach. The table foo.bar is dropped.
CREATE SCHEMA foo
CREATE TABLE foo.bar(x int)
Now create the following and try
CREATE TABLE foo.[[abc]]](x int)
The script fails with an error
DROP TABLE [foo].[[abc]]
Msg 105, Level 15, State 1, Line 6
Unclosed quotation mark after the character string '[abc]'.
Msg 102, Level 15, State 1, Line 6
Incorrect syntax near '[abc]'.
So not using QUOTENAME has caused the script to fail. The closing bracket was not escaped properly by doubling it up. The correct syntax should have been
DROP TABLE [foo].[[abc]]]
Even worse news is that a malicious developer has come to know of the script's existence. They execute the following just before the script is scheduled to run.
CREATE TABLE [User supplied name]];
EXEC sp_addsrvrolemember 'SomeDomain\user2216', 'sysadmin'; --]
(
x int
)
Now the script that ends up being executed is
DROP TABLE [foo].[User supplied name];
EXEC sp_addsrvrolemember 'SomeDomain\user2216', 'sysadmin'; --]
The ] was interpreted as closing off the object name and the remainder as a new statement. The first statement returned an error message but not a scope terminating one and the second one was still executed. By not using QUOTENAME you have opened yourself up to SQL injection and the developer has successfully escalated their privileges
QUOTENAME can be used when generating dynamic SQL statements. It'll indeed place your column name between brackets but will also escape characters which would break your quoted column name and could cause SQL injection.
For example:
SELECT QUOTENAME('abc[]def');
Will return:
[abc[]]def]
For more info: QUOTENAME (MSDN)

SQL Studio Prepared Statement

I am doing work for a company that stores each of their client's info in a different database. When a table needs modification, I have to go to each database and run the ALTER TABLE script. Is there a way I can use a prepared statement to run through all 100+ DBO names?
ALTER TABLE ?.dbo.profileTable
ADD COLUMN profileStatus int
where ? = 'CompanyA, CompanyB, CompanyC' or something similar?
Use Sp_MSforeachdb
EXECUTE master.sys.sp_MSforeachdb 'USE [?]; alter query'
[?] is used as a placeholder for the heretofore unspecified database name
You can modify the query as per your needs ,to exclude system databases use like below..
EXECUTE master.sys.sp_MSforeachdb 'USE [?]; IF DB_ID(''?'') > 4 begin yourquery end'
This will exclude any database that does not have the table you are looking for including system databases.
Declare #TableName Varchar(8000) = 'ProfileTable'
Declare #Sql Varchar(8000)
Select #Sql = Stuff(
(Select ';', 'Alter Table ' + Name + SqlText
From sys.databases
Cross Apply (Select '.dbo.profileTable ADD profileStatus int' SqlText) CA
Where Case When State_Desc = 'ONLINE'
Then Object_Id (QuoteName(Name) + '.[dbo].' + #TableName, 'U')
End Is Not Null
FOR XML PATH('')
),1,1,'')
Exec (#Sql)
This ? before is database ([database].[schema].[table]). Thus you can use sp_MSforeachdb or, as I prefer, use sys.databases view to prepare dynamic queries.
Beware, both methods can interfere with system databases.
Take a look at this solution:
DECLARE #query nvarchar(MAX)='';
SELECT #query = #query + 'USE '+QUOTENAME(name)+';ALTER TABLE dbo.profileTable ADD profileStatus int;'
FROM sys.databases
WHERE OBJECT_ID(QUOTENAME(name)+'.dbo.profileTable', 'U') IS NOT NULL
EXEC(#query)
It adds column col1 int to each dbo.profileTable in every database.

SQL Server Cursor Error

I want find the records which have ~ in the table.
I am using cursor to, but I am getting unexpected error.
Any help would be appreciated.
DECLARE #test VARCHAR(5000)
DECLARE #column_name VARCHAR(2000)
Declare #TABLE_NAME_MAIN VARCHAR(200)
SET #TABLE_NAME_MAIN = 'Ar_Receipt_Item_OHM'
DECLARE cur_name
CURSOR FOR
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #TABLE_NAME_MAIN
OPEN cur_name
FETCH NEXT FROM cur_name INTO #column_name
WHILE ##Fetch_status = 0
BEGIN
SET #test = N'SELECT top 2 * FROM OHMPreStage.dbo.'+#TABLE_NAME_MAIN+' WHERE '+#column_name+' LIKE ''%~%'''
exec #test
FETCH NEXT FROM cur_name INTO #column_name
END
CLOSE cur_name
DEALLOCATE cur_name
SET NOCOUNT OFF
Error:
Msg 911, Level 16, State 4, Line 24
Database 'SELECT top 2 * FROM OHMPreStage' does not exist. Make sure that the name is entered correctly.
You just need to change:
exec #test
To
exec (#test)
EXEC is for executing stored procedures, EXEC() the function puts together a dynamic string and executes it.
You're probably going to have issues searching columns with "LIKE" That aren't string columns.
You should check out the answer to this question here, which gives you a script that scans all tables in a database for specific string content:
Selecting column names that have specified value
Just substitute your search string '%~%', and if you need to, limit the tables searched to just the one (or set of) tables you need.

Resources