Using while exists in triggers sends the query in infinite loop - sql

I created a trigger for insert and it works fine. This creates the trigger just fine:
While (exists(Select Id from #temp))
But the insert query is going into an infinite loop. I am using while exists to accommodate multiple insertions at a time. Can anyone tell me what is causing the infinite loop?
Create Table sqltutorial.Employee
(
Id int,
Name nvarchar(50),
Salary int,
Gender nvarchar(50),
DepartmentId int
)
Alter Trigger sqltutorial.trg_forinsert_Employee
on sqltutorial.Employee
For Insert
As
Begin
print 'Audit Begins'
Declare #Id int, #Name nvarchar(50), #Salary int,
#Gender nvarchar(50), #DepartmentId nvarchar(50)
Declare #AuditText nvarchar(500)
Select *
into #temp
from inserted
While (exists(Select Id from #temp))
Select #Id = Id from #temp
Select
#Id = Id, #Name = Name, #Salary = Salary,
#Gender = Gender, #DepartmentId = DepartmentID
from
#temp
Set #AuditText = 'New Record Inserted With Id='+Cast(#Id As nvarchar(50))+',Name='+#Name+' ,Salary='+CAST(#Salary as nvarchar(50))+' Gender'+#Gender
+' ,Department Id='+#DepartmentId+' on '+CAST((Select GETDATE()) AS nvarchar(50))+' by '+(Select system_user)
Insert into sqltutorial.AuditTrial
values (#AuditText)
Delete from #temp
where Id = #Id
print 'Audit Ends'
End

The reason for the infinite loop is that you did not specify a BEGIN and END to your WHILE loop code block like this:
WHILE SomeCondition = true
BEGIN
Do stuff
END
When you use WHILE and don't specify BEGIN..END, the WHILE loop repeats the next statement only, over and over until the WHILE condition is no longer met. And in your code, that would never happen, since the next statement doesn't delete anything from #temp.
In other words, in your code, this is what you are looping:
While (exists(Select Id from #temp))
Select #Id = Id from #temp
The rest of the code after this never even executes because the WHILE loop never exits.

You don't need to use all the variables and temp tables in your trigger simply do the following:
ALTER TRIGGER sqltutorial.trg_forinsert_Employee
ON sqltutorial.Employee
FOR INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO sqltutorial.AuditTrial
SELECT
' New Record Inserted With Id=' + CAST([Id] AS NVARCHAR(50)) +
',Name=' + [Name] +
',Salary=' + CAST(Salary AS NVARCHAR(50)) +
',Gender' + Gender +
',Department Id=' + DepartmentId +
' on ' + CAST((SELECT GETDATE()) AS NVARCHAR(50)) +
' by ' + CAST(system_user AS NVARCHAR(256))
FROM
inserted
END

Related

SQL Server loop to table

so I have received the following piece of code and am asked to rewrite it as a table:
USE [Database_name]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [dbo].[DumLoop]
AS
SET nocount ON
IF OBJECT_ID('dbo.DummyLoopy','U') IS NOT NULL DROP TABLE dbo.DummyLoopy
create table dbo.DummyLoopy
(value VARCHAR(1000))
DECLARE
#Counter INT = 1
,#MaxInteger INT = ((select count(*) from dbo.OUTPUT_DEF))
,#MaxInteger_2 INT = ((select count(*) from dbo.OUTPUT_DEF))
,#MaxInteger_3 INT = ((select count(*) from dbo.OUTPUT_DEF))
,#MaxInteger_4 INT = (select count(*) from dbo.OUTPUT_TABLE)
,#MyNumber NVARCHAR(100)
,#JustAChar NVARCHAR (100)
,#SecondRow NVARCHAR(500)
,#1 NVARCHAR(100)
,#2 NVARCHAR(100)
,#3 NVARCHAR(100)
WHILE(#Counter <= #MaxInteger)
BEGIN
SELECT
#MyNumber = convert(varchar(100))
,#JustAChar = '&'
,#SecondRow = '{8181:ABC12345' + convert(varchar(100), [ID]) + '}{123:45678}{LALA:'
,#1 = ':2020:' + '123456789' + convert(varchar(100), [ID])
,#2 = ':2323:ZUP'
,#3 = ':3333:1111' + convert(varchar(100), [ID]) + ',123'
FROM dbo.OUTPUT_TABLE
WHERE main.[ID] = #Counter
insert into dbo.DummyLoopy Values('')
insert into dbo.DummyLoopy Values(#JustAChar)
insert into dbo.DummyLoopy Values(#SecondRow)
insert into dbo.DummyLoopy Values(#1)
insert into dbo.DummyLoopy Values(#2)
insert into dbo.DummyLoopy Values(#3)
SET #Counter = #Counter
END
So this just writes every value on a newline and creates a table with only one column - the column value. I would like it also to write away every value as a new column. I don't even know how to go about it.
Output would then look something like:
&
:2020:1234567891
:2323:ZUP
:3333:11111,123
&
:2020:1234567892
:2323:ZUP
:3333:11112,123
&
:2020:1234567893
:2323:ZUP
:3333:11113,123
etc.
You can use pivot to convert rows to columns in your final output instead of physically creating new columns for every value. Following link can help you out.
Efficiently convert rows to columns in sql server
If there is a must requirement to physically create new columns for every new value in while loop, then one way to achieve that is to use dynamic SQL commands.
Execute Dynamic SQL commands in SQL Server
you could do this :
DECLARE
#Counter INT = 1
, #ID INT
, #MaxInteger INT
IF OBJECT_ID('TempDB..#ID') IS NOT NULL
DROP TABLE #ID
SELECT
ROW_NUMBER() OVER(ORDER BY ID) AS RN
, ID
INTO #ID
FROM
dbo.OUTPUT_TABLE
IF OBJECT_ID('TempDB..#DummyLoopy') IS NOT NULL
DROP TABLE #DummyLoopy
CREATE TABLE #DummyLoopy([value] VARCHAR(1000))
SET #MaxInteger = (SELECT MAX(RN) FROM #ID)
WHILE #Counter <= #MaxInteger
BEGIN
SET #ID = (SELECT ID FROM #ID WHERE RN = #Counter)
INSERT INTO #DummyLoopy
VALUES
(' '),
('&'),
('{8181:ABC12345' + CONVERT(VARCHAR(100), #ID) + '}{123:45678}{LALA:'),
(':2020:' + '123456789' + CONVERT(VARCHAR(100), #ID)),
(':2323:ZUP'),
(':3333:1111' + CONVERT(VARCHAR(100), #ID) + ',123')
SET #Counter = #Counter + 1
END
SELECT * FROM #DummyLoopy

Writing a Stored procedure to search for matching values in columns with optional Input parameters

Requirement: To write stored Procedure(s) such that the values passed in stored procedures are matched against the values in columns in the table and then arranged with highest to lowest matching number of attributes.Then are inserted in a dynamically created temporary table inside the stored procedure.
Problem:
I have say 15-20 attributes that are matched against to confirm the suggestions made in response to record search. Basically, There is a table that stores Patients information and multiple parameters may be passed into the stored procedure to search through so that a Temporary table is created that suggests records in decreasing order of matching attributes.
To frame the basic structure, I tried with 3 attributes and respective stored procedures to match them which in turn are collectively called from a calling procedure based on the input parameter list which in turn creates the required temporary table.
Here is the SQL code(Just to give the gist of what I have tried so far):
But as a matter of fact it is, I realize this is way too naive to be used in a real time application which may require 80-90% of accuracy.So, what exactly can replace this technique for better efficiency?
Create Procedure NameMatch
(
#Name nvarchar(20),
#PercentContribution nvarchar(4) OUT, #PatientName nvarchar(20) out
)
As
declare #temp int
DECLARE #Query nvarchar(500)
if Exists(select Name from dbo.PatientDetails where Name = #Name)
Begin
set #PatientName = #Name
set #query = 'select * from dbo.PatientDetails where Name =' + #Name
set #temp = 0.1*100
set #PercentContribution = #temp + '%'
Execute(#query)
Return
End
Create Procedure AgeMatch
(
#Name nvarchar(20),
#Age int,
#PercentContribution nvarchar(4) OUT, #PatientName nvarchar(20) out
)
As
declare #temp int
DECLARE #Query nvarchar(500)
if Exists(select Name from dbo.PatientDetails where Name =#Name and Age = + #Age)
Begin
set #PatientName = #Name
set #query = 'select * from dbo.PatientDetails where Name = ' + #Name + ' and Age = '+ #Age
set #temp = 0.2*100
set #PercentContribution = #temp + '%'
Execute(#query)
Return
End
Create Procedure Nationality
(
#Name nvarchar(20),
#Age int,
#Nation nvarchar(10),
#PercentContribution nvarchar(4) OUT, #PatientName nvarchar(20) out
)
As
declare #temp int
DECLARE #Query nvarchar(500)
if Exists(select Name from dbo.PatientDetails where Name = #Name and Age = #Age and Nationality = #Nation )
Begin
set #PatientName = #Name
set #query = 'select * from dbo.PatientDetails where Name = ' + #Name + ' and Age = '+ #Age + ' and Nationality = ' + #Nation
set #temp = 0.3*100
set #PercentContribution = #temp + '%'
Execute(#query)
Return
End
create procedure CallingProcedure
(
#Name nvarchar(20),
#Age int = null,
#Nation nvarchar(10)= null
)
As
declare #PercentMatch nvarchar(4)
Begin
create table #results(PatientName nvarchar(30), PercentMatch nvarchar(4))
if(#Nation IS NOT NULL)
Insert into #results exec Nationality #Nation, #Name output, #PercentMatch output
else if(#Age is not Null)
Insert into #results exec AgeMatch #Age, #Name output, #PercentMatch output
else
Insert into #results exec NameMatch #Name, #Name output, #PercentMatch output
End
Setting aside nuances of stored procedure syntax, given parameters 1-n that if not null should match columns 1-n and the results sorted by highest number of matches first, a dynamic query is not needed - plain SQL can do it.
select *
from patient
where #param1 is null or column1 = #param1
or #param2 is null or column2 = #param2
...
or #paramN is null or columnN = #paramN
order by if(column1 = #param1, 1, 0)
+ if(column2 = #param2, 1, 0)
...
+ if(columnN = #paramN, 1, 0) desc

SQL Looping temp table and reading data

I have following script to create temp data
DECLARE #Name NVARCHAR(100), #Marks INT
DECLARE #MYTABLE TABLE
(
[Name][nvarchar](100) NULL,
[Marks][INT] NULL
)
INSERT INTO #MYTABLE ([Name],[Marks]) VALUES ('Mark',50);
INSERT INTO #MYTABLE ([Name],[Marks]) VALUES ('Steve',50);
INSERT INTO #MYTABLE ([Name],[Marks]) VALUES ('Don',50);
Now I want loop it, as shown in below script
SELECT #MaxPK = MAX(PK) from #MYTABLE
WHILE #PK <= #MaxPK
BEGIN
SET #Name = SELECT Name from #MYTABLE
SET #Marks = SELECT Marks from #MYTABLE
print #Name
print #Marks
SET #PK = #PK + 1
END
But I get error near SELECT statement.
"Incorrect syntax near the keyword SELECT"!
The two rows where you set the variables can be put together like this. That way you will only scan the table once.
SELECT #Name = Name, #Marks = Marks FROM #MYTABLE
Just know that the row chosen to be put in your variables is completely arbitary (and will probably be the same row every time) unless you add a whereclause.
Please try below while loop:
WHILE #PK <= #MaxPK
BEGIN
SELECT #Name = Name, #Marks = Marks from #MYTABLE
print #Name
print #Marks
END
Note: I guess, a where condition is required in select statement in order to print all data.

retrieving all Foreign Keys and their records

What I'm trying to achieve is the following.
When I delete a record I want to check if there are any FK relationships and it needs to be recursive. That way I can display a list of all records that are related to the one you want to delete.
So a small example of nested links
project 1 -> phase 1 -> block 1 -> ..
So when I try to delete project 1 I need to get a list of the items you need to delete first:
phase 1
block 1
....
I wanted to do this with a stored procedure that takes an ID and a tablename (format [chema].[tablename]) and finds all these linked records.
The problem I'm having is with the recursive part.
Here's my code so far:
ALTER PROCEDURE core.usp_CanBeDeleted
#entityId int,
#entityName nvarchar(250)
AS
BEGIN
DECLARE #NumberRecords int, #RowCount int
DECLARE #childId int
DECLARE #query nvarchar(max)
DECLARE #eName nvarchar(250) , #keyName nvarchar(250)
DECLARE #columnName nvarchar(250)
DECLARE #keys TABLE(
RowID int IDENTITY(1, 1),
name nvarchar(250),
entityName nvarchar(250),
columnName nvarchar(250)
)
if not exists (select * from sysobjects where name='partialResults' and xtype='U')
BEGIN
CREATE TABLE partialResults(
RowID int IDENTITY(1, 1),
id int,
parentId int,
name nvarchar(250),
FK_name nvarchar(250)
)
END
DECLARE #recusiveResults TABLE(
RowID int,
id int,
parentId int,
name nvarchar(250),
FK_name nvarchar(250)
)
DECLARE #results TABLE(
RowID int,
id int,
parentId int,
name nvarchar(250),
FK_name nvarchar(250)
)
SET #RowCount = 1
-- get all FK's of the entity
INSERT INTO #keys
SELECT name, '[' + OBJECT_SCHEMA_NAME(parent_object_id) + '].[' + OBJECT_NAME(parent_object_id)+ ']',cu.column_name
from sys.foreign_keys k
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU
ON k.name = CU.CONSTRAINT_NAME
where k.referenced_object_id = OBJECT_ID(#entityName)
-- set variable to number of records in temp table
SET #NumberRecords = ##ROWCOUNT
-- loop through the FK's an get all linked entities
WHILE(#RowCount <= #NumberRecords)
BEGIN
SELECT #keyName = name, #eName = entityName, #columnName = columnName
FROM #keys
WHERE RowId = #RowCount
-- get all FK information
SET #query = 'INSERT INTO partialResults(FK_name, name, id, parentId)'
+ ' SELECT ''' + #keyName + ''','''+ #eName + ''',' + 'id,' + cast(#entityId as varchar(25)) + ' as parentid'
+ ' FROM ' +#eName
+ ' WHERE id in '
+ ' (SELECT ' + #columnName
+ ' FROM ' + #entityName
+ ' WHERE id = ' + cast(#entityId as varchar(25))
+ ' )'
--print #query
EXEC (#query)
SET #RowCount = #RowCount + 1
END
-- rest number of records
SET #RowCount = 1
SELECT #NumberRecords = count(id)
FROM partialResults
-- save partialResults
INSERT INTO #results--(FK_name, name, id, parentId)
SELECT *--FK_name, name, id, parentId
FROM partialResults
DELETE FROM partialResults
WHILE(#RowCount <= #NumberRecords)
BEGIN
-- select next row
SELECT #childId = id, #eName = name
FROM #results
WHERE RowId = #RowCount
INSERT INTO #recusiveResults
EXEC core.usp_CanBeDeleted #childId, #eName
SET #RowCount = #RowCount + 1
END
INSERT INTO #results
SELECT *
FROM #recusiveResults
if exists (select * from sysobjects where name='partialResults' and xtype='U')
BEGIN
-- remove temp tables
DROP TABLE partialResults
END
-- return results
SELECT *
FROM #results
END
GO
the problem lies here:
INSERT INTO #recusiveResults
EXEC core.usp_CanBeDeleted #childId, #eName
Apparantly you can't nest an insert exec.
however I don't really see any other way to do it.
I've tried converting it into a function but then there are other problems like the dynamic query.
Any help would be greatly apreciated.
Split the procedure into an outer and an inner procedure.
In the outer procedure create a #results temp-table and then call the inner procedure.
In the inner procedure put all the logic including the recursion, but instead of selecting out the result at the end insert the result into the already existing #results table.
That way you safe a lot of time because you dont have to move data around as much. You also don't have to nest INSERT...EXEC anymore.
You also don't need the dbo.PartialResults table anymore as you can write directly into the #results table within the dynamic statement. If you still need it, to make the recursion work replace it with a #partialResults temp table that you create in the inner procedure (DON'T check for existence, just create the new one. See http://sqlity.net/en/1109/temp-tables-scoping-eclipsing/ for an explanation of temp table scoping). That way each execution is creating its own temp table and you don't have to deal with the clean-up. This is also a little less heavy compared to using a real table.
Finally, all the table variables can go too.
At the end of the inner procedure you can then do a simple SELECT * FROM #results; to output all the collected results.

Using variable name to run query on multiple tables

what I am trying to do is run a query multiple times over multiple tables, so what I have here is a table of the table names that cycles through setting #tablename to the name of the table on each iteration that I want to run the query on.
As you can see below #tablename is the name of the table I want to run the queries on but how do i run these queries using #tablename as the table name?
CREATE TABLE [BusinessListings].[dbo].[temptablenames]
(id int,
name nvarchar(50),
)
INSERT INTO [BusinessListings].[dbo].[temptablenames] (id, name)
VALUES
(1,'MongoOrganisationsACT1'),
(2,'MongoOrganisationsNSW1'),
(3,'MongoOrganisationsNT1'),
(4,'MongoOrganisationsQLD1'),
(5,'MongoOrganisationsSA1'),
(6,'MongoOrganisationsTAS1'),
(7,'MongoOrganisationsVIC1'),
(8,'MongoOrganisationsWA1');
DECLARE #tablename sysname,
#id int
SET #id = 1
WHILE (#id < 9)
BEGIN
select #tablename = name from temptablenames where id = #id
select #tablename
select _key_out, sum(quality_score) as sumscore, count(*) as reccount, (sum(quality_score) / count(*)) as ave
into tempga0
from #tablename
group by _key_out
select _key_out, count(*) as reccount
into tempga3
from #tablename
where dedupe_result is null
group by _key_out
having count(*)>1
select a._key_out, max(quality_score) as maxdedupetotalscore
into tempga4
from
#tablename a
join
tempga3 b
on a._key_out = B._key_out
--where isdeleted is null
group by a._key_out
--- keep records
update #tablename
set dedupe_result = 'Keep'
from
#tablename a
join
tempga4 b
on a._key_out = B._key_out
where a.quality_score = b.maxdedupetotalscore
--and isdeleted is null
and dedupe_result is null
SET #id = #id + 1
END
GO
DROP TABLE [BusinessListings].[dbo].[temptablenames]
note: this is only part of the queries that I want run, I just want to figure out how to subsitute the variable in the query as the table name. Also I know this isnt good form but there is a reason I need to do it this way.
updated working code here:
DECLARE #tablename nvarchar(30),
#id int,
#SQLStr nvarchar(1000)
SET #id = 1
WHILE (#id < 9)
BEGIN
select #tablename = name from temptablenames where id = #id
IF OBJECT_ID('tempga0') IS NOT NULL
DROP TABLE tempga0
set #SQLStr = 'select _key_out, sum(quality_score) as sumscore, count(*) as reccount, (sum(quality_score) / count(*)) as ave
into tempga0
from ' + #tablename + ' group by _key_out'
exec(#SQLStr)
SET #id = #id + 1
END
GO
Use the Exec command. Write your query in a variable like and execute it
Declare #SQLStr = 'Select * into X from ' + #tablename
exec(#SQLStr)
You just have to be carefull. I see that you are using into statements. You will have to check that the table does not already exist because you will get an exception. You will need to drop the tables, or a better way would be to do this before you start your loop:
CREATE TABLE tempga0 (
_key_out int,
sumscore numeric(18,9),
reccount int,
ave numeric(18,9))
--rest of the tables to be created here...
Create all the tables, and when you start your While loop add a
WHILE (#id < 9)
BEGIN
TRUNCATE TABLE tempga0
--truncate the rest of the tables
--Do the rest of your stuff here
END
Hope it helps