Cross-database query with database name in variable and cursor - sql-server

I have a problem with name of database in a cursor.
Here the current code
DECLARE #IDES INT
DECLARE #IDPROD INT
DECLARE #count INT
SET #count = 0
DECLARE CUR_CONSO CURSOR LOCAL fast_forward FOR
SELECT E2.id_es ,P3.id_prod FROM
[gpto_v004p001].[dbo].[GPTO_PRODETAB] P1
INNER JOIN
[fer_v008].[dbo].[T_PRODUIT] P3
ON P3.GPTO_PRODUIT_ID = P1.GPTO_PRODUIT_ID
INNER JOIN [gpto_v004p001].[dbo].[GPTO_ETAB] E1
ON E1.ETABID = P1.ETABID
INNER JOIN
[fer_v008].[dbo].[t_etablissement] E2
ON E1.ETABUC = LEFT(E2.code_es,5)
LEFT JOIN
[fer_v008].[dbo].[t_produit_etablissement] PE1
ON PE1.id_prod = P3.id_prod AND PE1.id_es = E2.id_es
WHERE PE1.id_es IS NULL AND GPTO_PRODUIT_ETAPE = 4
OPEN CUR_CONSO
FETCH CUR_CONSO INTO #IDES , #IDPROD
WHILE ##FETCH_STATUS = 0
BEGIN
IF NOT EXISTS (Select * from [fer_v008].[dbo].[t_produit_etablissement] where id_es=#IDES and id_prod=#IDPROD) -- Pas d'enregistrements
BEGIN
INSERT INTO [fer_v008].[dbo].[t_produit_etablissement]
([id_es],[id_prod],[gest_prod])
VALUES
(#IDES,#IDPROD,0)
SET #count = #count + 1
END
FETCH CUR_CONSO INTO #IDES , #IDPROD
END
CLOSE CUR_CONSO
DEALLOCATE CUR_CONSO
As the database is versionned, I need to use database name as variable saved in parameter table.
For easy script, I use Execute command as this
DECLARE #base_travail varchar(128)
SELECT #base_travail = val_str_par FROM t_parametre WHERE nom_par = 'base_travail'
DECLARE #execcmd varchar(max)
SET #execcmd = 'insert into #tmpfiltres SELECT TOP 1 filtre_exu FROM '
+ #base_travail + '.dbo.t_export_util WHERE id_exu =' + convert(varchar,#id_exu)
Execute (#execcmd)
But how do this when I have a cursor ? The 1st sample code is just a sample, whole script go over 400 lines, so I can't switch all the script in string mode.
Thanks for your help.

I had the same issue. Had to do with Database Compatibility Level.
This Code:
DECLARE #VARSql varchar(2000), #ID int;
SET #VARSql = 'USE [SomeOtherDatabase]; DECLARE cur CURSOR GLOBAL for
SELECT Max(SomeTableID) FROM [dbo].[SomeTable];';
Exec(#VARSql); open cur; fetch next from cur into #ID; close cur; deallocate cur;
PRINT #ID
Generated this error:
Msg 16958, Level 16, State 3, Line 3
Could not complete cursor operation because the set options have changed since the cursor was declared.
Running on SQL 2008 server.
Calling database has Compatibility Level 90 (Sql Svr 2005).
SomeOtherDatabase in code above has Compatibility Level 100 (Sql Svr 2008).
If you alter compatibility level so calling and called databases are the same, problem resolved.
select * from sys.databases
ALTER DATABASE [CallingDatabase] SET COMPATIBILITY_LEVEL = 100;

I had a same problem. Here is my solution;
DECLARE CUR_CORSO CURSOR **FAST_FORWARD FORWARD_ONLY** FOR
...

Related

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

Alternative to for loop in SQL Server

I'm trying to migrate stored procedure in Oracle to Sql server 2014. It has two "for loops" written in it. I thought of using cursors but I would appreciate if someone has better alternative to cursors or it is appropriate to use it ?
The procedure takes each deleted programs from a table present in db_1 schema and deletes the associated values from the table present in second DB_2
CREATE OR REPLACE PROCEDURE PD_DELETE
AS
v_PID VARCHAR2(40 BYTE);
BEGIN
-- Finding all the deleted subparts in DB_1
FOR i IN
(SELECT ad.ID AS ID
, ad.D_FAM AS D_FAM
, SUBSTR( ad.ID, 0, 18 ) AS A_fac_id
, SUBSTR (ad.ID, 20, 7) AS A_CODE
, SUBSTR( ad.ID, 20 ) AS A_SubCode
FROM DB_1.IC_DELETE ad
WHERE ad.IC_Status_ID <> 'P'
AND ad.D_FAM = 'A_Program_Subpart'
)
LOOP
Database DB_2
SELECT A_ID INTO v_PID FROM DB_2.IC_Program
WHERE A_ID = i.a_fac_id
and A_CODE = i.A_CODE;
DELETE
FROM DB_2.AP_SUBPART t
WHERE t.A_ID = v_PID
AND t.SUBPARTCODE = i.A_SubCode;
--- Reset the status in DB_1
UPDATE DB_1.IC_DELETE ad
SET ad.IC_Status_ID = 'P',
ad.IC_DATE = SYSDATE
WHERE ad.ID = i.ID
AND ad.D_FAM = i.D_FAM;
-- Commit data for a single record
COMMIT;
END LOOP;
========================================
SQL SERVER using cursor
AS
BEGIN
Declare #V_ID varchar(40),
#id varchar(10), #fam varchar(max), #code varchar(max)
Declare DB_cursor CURSOR LOCAL FORWARD_ONLY FOR
Select id, fam, code from IC
where ind <> 'P'
OPEN DB_Cursor
BEGIN
FETCH DB_Cursor into #id, #fam, #code
select #V_id = id from AP
...............
DELETE
FROM
..............
UPDATE
.............
END
Close DB_Cursor
Deallocate DB_Cursor
Good grief your column and table names are so cryptic!!!
It is a shot in the dark here are it is not 100% clear what you are trying to do but I think this should be pretty much it.
This would replace all the looping here.
update ad
SET ind = 'P'
, date = GETDATE()
from ad
join IC on IC.id = a.ID
where IC.ind <> 'P'
delete IC
from IC
join AP on AP.afid = IC.afacid
AND AP.acode = IC.code

Procedure takes long time to execute query

I have the following SP for SQL Server. Strangely the SP has weired behaviour when executing the query
Select #max_backup_session_time = Max(MachineStat.BackupSessionTime) from MachineStat where MachineStat.MachineID = #machine_id;
It takes 1 second if the MachineStat table has rows pertaining to #machine_id but if there are no rows for a #machine_id then it takes more than half a minute to execute. Can someone please help me understand this.
SET NOCOUNT ON;
DECLARE #MachineStatsMId TABLE (
MachineId INT NULL,
BackupSessiontime BIGINT NULL,
MachineGroupName NVARCHAR(128) NULL )
DECLARE #machine_id AS INT;
DECLARE #Machine_group_id AS INT;
DECLARE #machine_group_name AS NVARCHAR(128);
DECLARE #max_backup_session_time AS BIGINT;
SET #machine_id = 0;
SET #Machine_group_id = 0;
SET #machine_group_name = '';
DECLARE MachinesCursor CURSOR FOR
SELECT m.MachineId,
m.MachineGroupId,
mg.MachineGroupName
FROM Machines m,
MachineGroups mg
WHERE m.MachineGroupId = mg.MachineGroupId;
OPEN MachinesCursor;
FETCH NEXT FROM MachinesCursor INTO #machine_id, #machine_group_id, #machine_group_name;
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #max_backup_session_time = Max(MachineStat.BackupSessionTime)
FROM MachineStat
WHERE MachineStat.MachineID = #machine_id;
INSERT INTO #MachineStatsMId
VALUES (#machine_id,
#max_backup_session_time,
#machine_group_name);
FETCH NEXT FROM MachinesCursor INTO #machine_id, #machine_group_id, #machine_group_name;
END;
SELECT *
FROM #MachineStatsMId;
CLOSE MachinesCursor;
DEALLOCATE MachinesCursor;
GO
Here is an alternate version that avoids a cursor and table variable entirely, uses proper (modern) joins and schema prefixes, and should run a lot quicker than what you have. If it still runs slow in certain scenarios, please post the actual execution plan for that scenario as well as an actual execution plan for the fast scenario.
ALTER PROCEDURE dbo.procname
AS
BEGIN
SET NOCOUNT ON;
SELECT
m.MachineId,
BackupSessionTime = MAX(ms.BackupSessionTime),
mg.MachineGroupName
FROM dbo.Machines AS m
INNER JOIN dbo.MachineGroups AS mg
ON m.MachineGroupId = mg.MachineGroupId
INNER JOIN dbo.MachineStat AS ms -- you may want LEFT OUTER JOIN here, not sure
ON m.MachineId = ms.MachineID
GROUP BY m.MachineID, mg.MachineGroupName;
END
GO

SQL Server - Insert or Update Trigger not always fired when replication is involved

We have a notification system in our Microsoft SQL Server database. What happens is a file is created by a SQL Trigger called NotifyOnPropertyChangedTrigger. Our client software uses the .NET FileSystemWatcher class to wait for a new file to appear, which it reads and picks up the new property setting. This works great, unless SQL replication is involved.
The problem goes like this:
So we insert a change into our main database (which replicates to 3 other SQL Server DBs) The change is detected by the SQL Trigger and the file is correctly created on the hard disk. Our DB replication correctly updates the 3 other DQL DBs to reflect the latest changes. However the Trigger only works occasionally, which means the file is not always created on each of the 3 other hard disks. (Which it should be. 4 total files: 1 created on the master DB, 3 created on the replication DBs)
We are using Merge Replication.
Any ideas why this would be occurring?
Thanks in advance.
Here is the trigger:
ALTER TRIGGER [dbo].[NotifyOnPropertyChangeTrigger]
ON [dbo].[PropertyValues]
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON
DECLARE #propertyValueID int
DECLARE PropertyValueCursor CURSOR FOR
SELECT DISTINCT [PropertyValueID]
FROM (SELECT [PropertyValueID] FROM inserted
UNION ALL
SELECT [PropertyValueID] FROM deleted) AS Combined
OPEN PropertyValueCursor
FETCH NEXT FROM PropertyValueCursor INTO #propertyValueID
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #propertyID int
DECLARE #profileID int
DECLARE #contractName nvarchar(255)
IF ((SELECT COUNT(*) FROM inserted WHERE [PropertyValueID] = #propertyValueID) > 0)
BEGIN
SELECT #propertyID = [PropertyID], #profileID = [ProfileID], #contractName = [ContractName]
FROM inserted
END
ELSE
BEGIN
SELECT #propertyID = [PropertyID], #profileID = [ProfileID], #contractName = [ContractName]
FROM deleted
END
DECLARE #propertyName nvarchar(255)
DECLARE #needToSendNotification bit
SELECT #propertyName = [PropertyName], #needToSendNotification = [NotifyOnUpdate]
FROM [Properties]
WHERE [PropertyID] = #propertyID
IF (#needToSendNotification = 1)
BEGIN
DECLARE #profileName nvarchar(50)
SELECT #profileName = [ProfileName]
FROM [Profiles]
WHERE [ProfileID] = #profileID
DECLARE #fileName nvarchar(600) -- Must be at least 50 + 1 + 255 + 1 + 255 + 19 + 4
SET #fileName = dbo.CleanStringForFileName(#profileName)
+ '-'
+ dbo.CleanStringForFileName(#contractName)
+ '-'
+ dbo.CleanStringForFileName(#propertyName)
+ '-'
+ dbo.GetUTCDateTimeStampWithSeparator('-')
+ '.txt'
DECLARE #commandString nvarchar(650) -- Must be at least 5 + 10 + 27 + 585
SET #commandString = 'echo ' + CAST(#propertyValueID AS nvarchar(10)) + ' >> C:\ClientNotifications\' + #fileName
EXEC master.dbo.xp_cmdshell #commandString, NO_OUTPUT
END
FETCH NEXT FROM PropertyValueCursor INTO #propertyValueID
END
CLOSE PropertyValueCursor
DEALLOCATE PropertyValueCursor
END

Is a recursively called stored procedure possible in SQL Server?

Here is what I have as VBScript Subroutine:
sub buildChildAdminStringHierarchical(byval pAdminID, byref adminString)
set rsx = conn.execute ("select admin_id from administrator_owners where admin_id not in (" & adminString & ") and owner_id = " & pAdminID)
do while not rsx.eof
adminString = adminString & "," & rsx(0)
call buildChildAdminStringHierarchical(rsx(0),adminString)
rsx.movenext
loop
end sub
Is there anyway to turn this into a stored procedure since it's got the recursive call in the subroutine?
Here is what I've tried...
CREATE PROCEDURE usp_build_child_admin_string_hierarchically
#ID AS INT,
#ADMIN_STRING AS VARCHAR(8000),
#ID_STRING AS VARCHAR(8000) OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #index int;
DECLARE #length int;
DECLARE #admin_id int;
DECLARE #new_string varchar(8000);
SET #index = 1;
SET #length = 0;
SET #new_string = #ADMIN_STRING;
CREATE TABLE #Temp (ID int)
WHILE #index <= LEN(#new_string)
BEGIN
IF CHARINDEX(',', #new_string, #index) = 0
SELECT #length = (LEN(#new_string) + 1) - #index;
ELSE
SELECT #length = (CHARINDEX(',', #new_string, #index) - #index);
SELECT #admin_id = CONVERT(INT,SUBSTRING(#new_string, #index, #length));
SET #index = #index + #length + 1;
INSERT INTO #temp VALUES(#admin_id);
END
DECLARE TableCursor CURSOR FOR
SELECT Admin_ID FROM Administrator_Owners WHERE Admin_ID NOT IN (SELECT ID FROM #temp) AND Owner_ID = #ID;
OPEN TableCursor;
FETCH NEXT FROM TableCursor INTO #admin_id;
WHILE ##FETCH_STATUS = 0
BEGIN
IF LEN(#ID_STRING) > 0
SET #ID_STRING = #ID_STRING + ',' + CONVERT(VARCHAR, #admin_id);
ELSE
SET #ID_STRING = CONVERT(VARCHAR, #admin_id);
EXEC usp_build_child_admin_string_hierarchically #admin_id, #ID_STRING, #ID_STRING;
FETCH NEXT FROM TableCursor INTO #admin_id;
END
CLOSE TableCursor;
DEALLOCATE TableCursor;
DROP TABLE #temp;
END
GO
But I get the following error when that stored procedure is called...
A cursor with the same name 'TableCursor' already exists.
You can specify a LOCAL cursor, like this:
DECLARE TableCursor CURSOR LOCAL FOR
SELECT ...
At least in SQL Server 2008 R2 (my machine), this allows you to recursively call the sproc without running into "Cursor already exists" errors.
The problem is that while your cursor isn't global, it is a session cursor. Since you're doing recursion, even though each iteration is creating a cursor in a new proc scope, they're all being created in the same PID (connection) at the same time, thus the collision.
You'll need to generate unique cursor names in each iteration of the procedure based on some criteria that won't be reproduced during the recursion.
Or, preferably, find a way to do what you need using set logic, and handle any necessary recursion using a recursive CTE.
You can, but it's usually not a good idea. SQL is made for set-based operations. Also, in MS SQL Server at least, the recursion is limited to the number of recursive calls that it can make. You can only nest up to 32 levels deep.
The problem in your case is that the CURSOR lasts through each call, so you end up creating it more than once.

Resources