Trying to select database based on server - sql-server

Can anyone tell me why this doesn't work?
if ((select ##servername) = 'ServerA')
begin
use DatabaseA
select top 5 * from dbo.SignUPRequest
end
else if ((select ##servername) = 'ServerB')
begin
use DatabaseB
select top 5 * from dbo.SignUPRequest
end
When I run this on ServerA, I get a message that DatabaseB doesn't exist on ServerA, which it doesnt, but I don't understand why it's trying to read if the second if evaluates to false.
Msg 911, Level 16, State 1, Line 8
Database 'DatabaseB' does not exist. Make sure that the name is entered correctly.

The code is parsed before it is run. When it is parsed SQL Server checks that it can access everything in the code, it cannot access the database that exists on the other server so the parsing step of running the code fails. As a result, you get the error message you've shown.
If you want to get around that you can put the code in the IF blocks as dynamically executed code (I always feel this is a bit of a hack/workaround).
DECLARE #sql NVARCHAR(4000);
if ((select ##servername) = 'ServerA')
begin
SET #sql = 'use DatabaseA; select top 5 * from dbo.SignUPRequest;'
end
else if ((select ##servername) = 'ServerB')
begin
SET #sql = 'use DatabaseB; select top 5 * from dbo.SignUPRequest'
end
EXEC (#sql)
So, what happens here is that you defer the parsing & running of the code that uses the appropriate database for the server to run time, as that what the EXEC statement at the end does.
UPDATE
Based on the additional comment below you could also rewrite this as:
DECLARE #sql NVARCHAR(4000);
if ((select ##servername) = 'ServerA')
begin
select top 5 * from DatabaseA.dbo.SignUPRequest;
end
else if ((select ##servername) = 'ServerB')
begin
select top 5 * from DatabaseB.dbo.SignUPRequest;
end
So, instead of putting in a USE <database-name> at the start, you can also more fully qualify the name of the table in your select statement. If you only have one line of SQL to deal with this could be more effective.

You get the error when the query is compiled, not on execution. You can execute the statements with exec to get them in a batch that compiles only if the database exists.
if ((select ##servername) = 'ServerA')
begin
exec(N'use DatabaseA
select top 5 * from dbo.SignUPRequest')
end
else if ((select ##servername) = 'ServerB')
begin
exec(N'use DatabaseB
select top 5 * from dbo.SignUPRequest')
end

Related

Formatting XML with `GO` into a set of sql commands to be executed

I have seen this question Replace value in XML using SQL and again I am thankful, however, I have a XML file with all the trigger creation scripts but I have not been able to execute it because of the GO.
Partial view of my XML file:
<Krishna>IF 'MY_SERVER' <> ##ServerName THROW 50001, 'Wrong Server!!!',1 </Krishna>
<Krishna>GO</Krishna>
<Krishna>use [DB_02]</Krishna>
<Krishna>GO</Krishna>
<Krishna>IF EXISTS (SELECT 'Radhe' FROM sys.triggers t wHERE t.[name] = 'tgr_repl_AuPair_Insert' AND CASE WHEN t.parent_id = 0 THEN t.parent_id ELSE object_id('[sub].[repl_Aupair]') END = t.parent_id )
</Krishna>
<Krishna>EXEC('BEGIN DROP TRIGGER [sub].[tgr_repl_AuPair_Insert] END') </Krishna>
<Krishna>GO</Krishna>
<Krishna></Krishna>
<Krishna>CREATE TRIGGER [sub].[tgr_repl_AuPair_Insert]</Krishna>
<Krishna>ON [sub].[repl_Aupair]</Krishna>
<Krishna>FOR INSERT, UPDATE</Krishna>
when I try to get rid of the GO, replacing it like it is suggested here, I get a different error.
DECLARE #XML3 XML
SELECT #XML3 = (SELECT a.trigger_definition AS Krishna FROM TableBackups.dbo._MMiorelli_20220615_triggerdropping_203144_2 a FOR XML PATH(''))
WHILE #xml3.exist(N'//*[text()="GO"]')=1
BEGIN
SET #xml3.modify(N'replace value of (//*[text()="GO"]/text())[1] with ""');
END
exec sp_execXML #dbname=N'APCore'
,#XML=#xml3
,#DEBUG=1
,#PrintOnly=0
this is the way I am executing the commands that are within my XML:
declare #i int = 1
select #sql1 = ''
SELECT #SQL2 = 'Radhe'
WHILE #sql2 is not null
begin
SELECT #sql2 = #XML.value('(/Krishna/text())[sql:variable("#i") cast as xs:int?][1]', 'varchar(max)')
if #DEBUG=1
PRINT COALESCE(#sql2,'#SQL2 WAS NULL' + ' -- #I IS ' + CAST(#I AS VARCHAR(5)))
if #sql2 is not null
begin
SET #sql1 = CAST (#sql1 + #sql2 + #vbCrLf AS NVARCHAR(MAX))
IF #PrintOnly = 1
BEGIN
EXEC sp_DisplayNVarchar #textToDisplay = #SQL2, #debug =0
END
ELSE
BEGIN
EXEC (#SQL2)
END
end
SELECT #i = #i + 1
if #i >= #Limit
SET #sql2 = null
end
BASICALLY:
each line of the XML is a command
SELECT #sql2 = #XML.value('(/Krishna/text())[sql:variable("#i") cast as xs:int?][1]', 'varchar(max)')
My question is:
How could I replace the every GO inside my long script into a new line in my XML?
Every time I meet a GO, that GO is removed but from that place on is a new line in my XML.
this is an example of code and XML that works:
here is the code:
---------------------------------------
----check the data
---------------------------------------
GO
SELECT [##TRANCOUNT]=##TRANCOUNT
TRUNCATE TABLE #the_inserts
TRUNCATE TABLE #VICASA477
INSERT INTO #the_inserts(RADHE1)
SELECT RADHE1='use apcore;' + CHAR(10)+CHAR(13) + 'exec sp_count ' + '''' + E.AP_NAME2 + ''''
FROM #E E
DECLARE #XML3 XML
SELECT #XML3 = (SELECT #the_inserts.radhe1 AS Krishna FROM #the_inserts FOR XML PATH(''))
INSERT INTO #VICASA477
EXEC sp_execXML #dbname=N'APCore'
,#XML=#xml3
,#DEBUG=0
,#PrintOnly=0
select #XML3
SELECT * FROM #vicasa477
GO
Here is the XML: (partial view but you get the idea)
<Krishna>use apcore;
exec sp_count '[sub].[matchAgreementEmailSent]'</Krishna>
<Krishna>use apcore;
exec sp_count '[sub].[receivedLog]'</Krishna>
<Krishna>use apcore;
exec sp_count '[sub].[repl_Airline]'</Krishna>
<Krishna>use apcore;
exec sp_count '[sub].[repl_Airport]'</Krishna>
<Krishna>use apcore;
exec sp_count '[sub].[repl_ArrivalCalendar]'</Krishna>
<Krishna>use apcore;
exec sp_count '[sub].[repl_Aupair]'</Krishna>
<Krishna>
and here the results: (partial view but you get the idea)
EDIT: As mentioned by #DavidBrowne, this answer doesn't work if changing the current database with USE is necessary.
You can run this script using a cursor, which executes each batch separately.
To split the batches we need to use XQuery. This is made significantly more complicated by the fact that the batches are separated by the same Krishna node again, rather than each being contained in a separate child node.
DECLARE #sql nvarchar(max), #crsr CURSOR;
SET #crsr = CURSOR FAST_FORWARD READ_ONLY FOR
SELECT
x.krsh.query('
let $go := .
let $prev := /Krishna[. << $go][text() = "GO"][1]
return /Krishna[text() != "GO"][. << $go and not(. << $prev)]/text()
').value('text()[1]','nvarchar(max)') line
FROM #xml.nodes('/Krishna[text() = "GO"]') x(krsh);
OPEN #crsr;
GOTO Ftch;
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC sp_executesql #sql;
ftch:
FETCH NEXT FROM #crsr
INTO #sql;
END;
db<>fiddle
The logic runs like this:
Use .nodes to grab all Krishna nodes which contain GO.
For each of those, we run the following XQuery expression:
Assign the current node to $go
Find the node previous to this which contains GO.
Find all Krishna nodes which do not contain GO, and...
... are located before $go...
... and after $prev (if any).
Return the inner text()
Concatenate all the text together using .query and .value
Note: This assumes that the final node is always GO.
There's no good way to run that script from TSQL. You need to run each batch separately, which can be done by accumulating the lines for each batch, and executing it when you see GO. Stripping GO is a hack which won't work for DDL scripts as many DDL statements must begin a batch, or be the only statement in the batch.
The thing that won't work is the scripts like
use somedb
go
create table foo(id int)
In TSQL if you parse and exec this as:
exec ('use somedb')
exec ('create table foo(id int)')
The database context will be switched inside the first batch, but revert to the original database context at the end of the first dynamic SQL invocation. You simply can't change the database context permanently for the session in dynamic SQL. So your pre-processor must concatenate the database context switch with the subsequent batches. eg
exec ('use somedb
create table foo(id int)')
Which might work if your script is generated exactly the same and you reliably identify the use database statements. But it's complicated and basically not worth doing. Instead use an external program like sqlcmd or powershell's `invoke-sqlcmd', or even a client program like .NET that can issue top-level batches that permanently change the database context.
And you may find other session-level settings that will have the same problem.

T-SQL Basic custom errors get ignored in stored procedure

EDIT3: SOLUTION
Apparently my code had problems with the BEGIN / END blocks, as the users that replied suggested (thank you!)
Still, I couldn't get it you work, until I found this post and tried this if/else structure: using Switch like logic in T-SQL
EDIT2:
I've written a couple PRINTs in the procedure and the values are passing just right. Now it only says 'Invalid ID', even while sending 200.
220
dsf4
Oct 12 2018 10:10AM
Msg 50000, Level 16, State 1, Procedure p_ValCulture, Line 188
Invalid ID
EDIT:
I've tried setting the error message inside BEGIN / END; it didn't work. This is the code for that part.
IF #operacion LIKE 'I'
BEGIN
IF #id IS NULL OR #id <= 0
BEGIN
SELECT #errorDesc = 'Invalid ID'
RAISERROR(#errorDesc, 16, 1)
END
IF EXISTS(SELECT 1 FROM Production.Culture AS pc WHERE PC.CultureID = #id)
BEGIN
SELECT #errorDesc = 'ID already exists'
RAISERROR(#errorDesc, 16, 1)
END
END
I get this if I execute the stored procedure
Msg 50000, Level 16, State 1, Procedure p_ValCulture4, Line 183
Below that message there is a blank space, the value of an error message that I couldn't set.
These are the parameters I pass:
GO
EXEC p_ValCulture4 200, 'John', '10-12-2018 10:10:10.000','I'
I'm using SQL Server Management 2014, on Windows 64bits.
I'm trying to catch up on some T-SQL I didn't get enough attention.. but I'm totally stuck :/ I've checked Google, Youtube, SO, etc.. and tried many things but nothing works and I'm wasting time totally stuck on this part that I can't understand.
I have a stored procedure that when sent an 'operation' as 'I' (char) will perform an insert into a table. I'm using the AdventureWorks2014 database as practise.
The problem is that I want to send different error messages if the id sent is null, or another one if it already exists in the table, etc. This is the code for the procedure:
CREATE PROCEDURE p_ValCulture4
#id INT,
#name NVARCHAR(50),
#date DATETIME,
#operacion CHAR(1)
AS
BEGIN
print #id
print #name
print #date
SET NOCOUNT ON
DECLARE #errorDesc nvarchar(MAX)
BEGIN TRY
SELECT #operacion = UPPER(#operacion)
IF #operacion = 'I' /* the Insert option */
BEGIN
IF #id IS NULL OR #id <= 0
BEGIN
SELECT #errorDesc = 'Invalid ID'
RAISERROR(#errorDesc, 16, 1)
END
IF EXISTS(SELECT 1 FROM Production.Culture AS pc WHERE PC.CultureID = #id)
BEGIN
SELECT #errorDesc = 'ID already exists'
RAISERROR(#errorDesc, 16, 1)
END
SELECT #errorDesc = 'ERROR: Insert error'
INSERT INTO Production.Culture VALUES
(#id, #name, #date);
SELECT 'Rows: ' + CONVERT(VARCHAR(10),##ROWCOUNT)
END
END TRY
BEGIN CATCH
RAISERROR (#errorDesc,16,1)
END CATCH
END
The first IF, if I send id = null works fine, I get the right error; but if I send an existing id, the IF get completely ignored. The same happens with the insert, it works fine, but only if there are no IFs in the procedure..
I can't get my mind how these IF - BEGIN / END work.. and why it can only read the first IF but ignores subsequent ones..
I've tried putting everything inside an IF - BEGIN / END and then ELSE - BEGIN / END, same results.
I've tried setting the error message inside de IFs, and also outside. Also, inside the IFs, but before BEGIN. I've tried writing the error directly inside RAISERROR, same results.
If anyone can help me understand why the IFs get ignored and the logic behind THESE IFs in T-SQL, it would be PRETTY much appreciated :) Thank you!
Consistent indenting will definitely help you see issues related to your Begin End pairs.
Your first IF test has the BEGIN on the same horizontal position below it, but the next IF test has its BEGIN indented further.
If you make this IF test like the first, in that the BEGIN & END sit on the same horizontal position, it will expose issues like ZLK points out in the above comment, SELECT #errorDesc = 'Invalid ID' is running outside the BEGIN END pair.

T-SQL Update Trigger

I'm trying to create the following trigger in SQL Server, but SSMS throws an error and I have no clue what it is. Any thoughts ?
Msg 156, Level 15, State 1, Line 2
Incorrect syntax near the keyword 'trigger'.
Code:
IF NOT EXISTS(SELECT * FROM sys.triggers
WHERE object_id = OBJECT_ID(N'[dbo].[trAfterUpdateInfoDoc]'))
CREATE TRIGGER [dbo].[trAfterUpdateInfoDoc]
ON [dbo].[InfoDocs]
AFTER UPDATE
AS
BEGIN
DECLARE #infodoctemplateid INT;
DECLARE #infodocid INT;
DECLARE #requireccount FLOAT(2);
DECLARE #filledcount FLOAT(2);
DECLARE #pcnt FLOAT(2);
DECLARE c CURSOR FOR
SELECT id
FROM InfoDocs ifd
WHERE exists (SELECT 1 FROM Inserted AS i WHERE i.id = ifd.id)
OPEN c
FETCH NEXT FROM c INTO #infodocid
WHILE ##Fetch_Status = 0
BEGIN
SELECT #infodoctemplateid = InfoDocTemplateId
FROM InfoDocs
WHERE id = #infodocid;
SELECT #requireccount = COUNT(*)
FROM InfoDocTemplateFields
WHERE InfoDocTemplateId = #infodoctemplateid
AND IsRequired = 1;
IF (#requireccount = 0)
BEGIN
set #pcnt = 100;
END
ELSE
BEGIN
select #filledcount = count(*) from InfoDocFields
where InfoDocId = #infodocid
and InfoDocTemplateFieldId in (select id from InfoDocTemplateFields where InfoDocTemplateId = #infodoctemplateid and IsRequired = 1)
and (BooleanValue is not null or (StringValue is not null and StringValue <> '') or IntValue is not null or DateValue is not null)
set #pcnt = #filledcount / #requireccount * 100.0;
END
update InfoDocs set PercentageCompleted = #pcnt Where id = #infodocid;
Fetch next From c into #infodocid
End
Close c
Deallocate c
END
Create Trigger (Limitations section) must be the first statement in a batch, so you can't use the IF exists check before it.
In SQL Server 2016 SP1 onwards, you can use CREATE OR ALTER TRIGGER... for the same behaviour.
Pre-SQL Server 2016 SP1, there's some suggestions here
I also second Zohar's comment that putting this logic into a trigger could well cause you many performance issues & possibly hard to track down unexpected behaviour/bugs.
Anytime a SQL object like a trigger is created, it needs to be the only object created in the batch. A batch is terminated by the keyword GO.
Try refactoring your code to fit this general structure and see if it works:
IF OBJECT_ID(N'[dbo].[trAfterUpdateInfoDoc]') IS NOT NULL
DROP TRIGGER [dbo].[trAfterUpdateInfoDoc]
GO
CREATE TRIGGER [dbo].[trAfterUpdateInfoDoc]
ON [dbo].[InfoDocs]
AFTER UPDATE
AS
BEGIN
--PLACE CODE HERE
END
GO

Only one expression can be specified in the select list when the subquery is not introduced with EXISTS error

i am trying to create sql server procedure with if statement.
i am new to the ms sql server however i tried with the following statements but it gave me the below error Msg 116, Level 16, State 1, Procedure te, Line 9
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
here is the code i wrote
CREATE PROCEDURE test as
BEGIN
SET NOCOUNT ON;
if (select COUNT(load),load,contractor_id from [test].[dbo].[cont]
group by load,contractor_id
having load = (select MIN(load)from [test].[dbo].[cont])
) > 1
begin
SELECT top 1 COUNT(*),load ,contractor_id,name
FROM [test].[dbo].[cont]
group by load,contractor_id,name
having load = (select MIN(load)from [test].[dbo].[cont])
ORDER BY NEWID()
end
ELSE
BEGIN
SELECT top 1 COUNT(*),load ,contractor_id,name
FROM [test].[dbo].[cont]
group by load,contractor_id,name
having load = (select MIN(load)from [test].[dbo].[cont])
END
END
GO
can anyone help please
As the error says you cannot have multiple column selected when you use IF condition. In your first IF condition you are selecting multiple columns, however if conditions requires to have a query that can lead to one value. Other option is you have to use IF EXISTS, that can check count >1 as below
IF (SELECT COUNT(load),load,contractor_id
FROM [test].[dbo].[cont]
GROUP BY load,contractor_id
HAVING load = (SELECT MIN(load)
FROM [test].[dbo].[cont])
AND COUNT(load) >1)
Another thing what I noticed is you are excecuting the query which calculates the min of load multiple times. You can avoid that by storing it in a variable and use it further.
I have modified the procedure as below. Check if this works or not.
CREATE PROCEDURE test as
BEGIN
SET NOCOUNT ON;
DECLARE #count INT
DECLARE #minload INT
SELECT #minload = MIN(load)from [test].[dbo].[cont]
SELECT #count = COUNT(load) from [test].[dbo].[cont]
GROUP BY load,contractor_id
HAVING load = #minload
IF (#count) > 1
BEGIN
SELECT top 1 COUNT(*),load ,contractor_id,name
FROM [test].[dbo].[cont]
WHERE load = #minload
GROUP BY load,contractor_id,name
ORDER BY NEWID()
END
ELSE
BEGIN
SELECT top 1 COUNT(*),load ,contractor_id,name
FROM [test].[dbo].[cont]
GROUP BY load,contractor_id,name
HAVING load = #minload
END
END
UPDATE
Based on your comments, I suppose you can get the result with one simple query as below.
;WITH minLoad(load)
AS
(
SELECT MIN(load)
FROM [test].[dbo].[cont]
)
SELECT TOP 1 COUNT(*),c.load ,c.contractor_id,c.name
FROM [test].[dbo].[cont] c, minLoad
WHERE c.load = minLoad.load
ORDER BY NEWID();

Setting output parameters in SELECT statement with an IF EXISTS check

I am trying to make an efficient SQL stored procedure for retrieving user data from my database, but I am running into a syntax issue I can't seem to figure out.
Basically, I want to assign my output variable within my SELECT statement. I also want to see if the user actually exists by IF EXISTS. Unfortunately, I can't seem to do both.
Here is my procedure:
CREATE PROCEDURE [dbo].FindUser(#UserID binary(16), #UserExists bit OUTPUT, #Name
nvarchar(MAX) OUTPUT)
AS
SET NOCOUNT ON
IF EXISTS (SELECT #Name = Name FROM Users WHERE UserID = #UserID)
BEGIN
SET #UserExists = 1
END
RETURN
Currently, it gives me an "SQL46010 :: Incorrect syntax near #Name." error.
If I remove IF EXISTS, the statement compiles fine!
Why does the IF EXISTS check cause a syntax error?
set #UserExists = 0;
select #Name = Name,
#UserExists = 1
from Users
where UserID = #UserID;
SET NOCOUNT ON
IF EXISTS (SELECT 1 FROM Users WHERE UserID = #UserID)
BEGIN
SET #UserExists = 1
/* do other stuff here select user name or whatever */
END
If there is a record for #UserID in users table Selecting 1 will return true for exists clause and control will enter the BEGIN..END block.

Resources