T-SQL error Possibly a scope issue? - tsql

I have this T-SQL code in a procedure, I'm only posting the relevant parts for brevity.
declare #tIDs TABLE (ID int)
While 1=1
begin
set #iCnt = #iCnt + 1
if #iCnt > #iNumberDuplication break
set #iLoop = 0
declare PHcursor cursor for (select REPORT_CUBE_ID from #tPULSE_HYPERCUBE)
open PHcursor
While 1=1
begin
set #iLoop = #iLoop + 1
if #iLoop > #iPHrows break
fetch next from PHcursor into #tIDs
set #iCurrID = (select ID from #tIDs)
set #iIDloop = #iIDloop + 1
set #iREPORT_CUBE_ID = 90000000000000 + #iIDloop
UPDATE #tPULSE_HYPERCUBE SET REPORT_CUBE_ID = #iREPORT_CUBE_ID WHERE REPORT_CUBE_ID = #iCurrID
UPDATE #tPULSE_METRIC_DETAILS SET REPORT_CUBE_ID = #iREPORT_CUBE_ID WHERE REPORT_CUBE_ID = #iCurrID
DELETE FROM #tIDs
end
CLOSE PHcursor
DEALLOCATE PHcursor
insert into X_PULSE_HYPERCUBE (REPORT_CUBE_ID, CM_PHY_OWNER_ID, CM_LOG_OWNER_ID,INTERVAL_C,INTERVAL_START_DATE,PULSE_METRIC_ID,USER_ID) (select * from #tPULSE_HYPERCUBE)
end
I am getting an error that I must declare the scalar variable #tIDs I already have declared it though. Is this a scope issue? It wont go away.

Try this...
declare #loop integer = 30000;
while #loop > 0 begin
insert into #tPULSE_HYPERCUBE (REPORT_CUBE_ID, col2, col3...)
select REPORT_CUBE_ID + 90000000000000 + #loop, col2, col3...
from #tPULSE_HYPERCUBE
where REPORT_CUBE_ID < 90000000000000;
set #loop = #loop - 1;
end

Looking again, #HLGEM is correct; this should be a couple of simple update statements...
update m
set REPORT_CUBE_ID = REPORT_CUBE_ID + 90000000000000
from #tPULSE_HYPERCUBE h
inner join #tPULSE_METRIC_DETAILS m on h.REPORT_CUBE_ID = m.REPORT_CUBE_ID;
update #tPULSE_HYPERCUBE
set REPORT_CUBE_ID = REPORT_CUBE_ID + 90000000000000;

Looks to me as if you are trying to use the in-memory table "#tIDs" as the recipient for the REPORT_CUBE_ID column within the cursor ("fetch next from PHCursor into #tIDs"). The recipient variable for the value of REPORT_CUBE_ID should be defined as the same data type as REPORT_CUBE_ID.
Not too sure what you are doing on the next line either; but it looks like you are trying to get back the REPORT_CUBE_ID value from the same table #tIDs?
Assuming REPORT_CUBE_ID is an integer, define #tIDs as an integer also and then when you "fetch next from PHCursor into #tIDs", #tIDs will contain the value of REPORT_CUBE_ID for the current cursor row and you will not need to then access it with #iCurrID.
Better still, "fetch next from PHCursor into #iCurrID" and get rid of #tIDs altogether.

Related

SQL Server AFTER INSERT trigger not working, how does trigger work?

I wrote a trigger after insert on a table, the number 5 in a specific column on the table, but after I do an insert, nothing happens. I'm wondering what mistakes I have in my code so that after an insert nothing happens
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[trigger_EstadoDoc]
ON [dbo].[EstadoDoc]
AFTER INSERT AS
BEGIN
DECLARE #Puntaje1 INT;
DECLARE #Puntaje2 INT;
SET #Puntaje1 = (SELECT 5
FROM EstadoDoc, inserted
WHERE inserted.IdDoc = EstadoDoc.IdDoc
AND inserted.Estado IN ('A','RyR')
AND EstadoDoc.Estado = 'S')
SET #Puntaje2 = (SELECT 10
FROM EstadoDoc, inserted
WHERE inserted.IdDoc = EstadoDoc.IdDoc
AND inserted.Estado IN ('A')
AND EstadoDoc.Estado = 'RyR')
IF #Puntaje1 != NULL
BEGIN
UPDATE EstadoDoc
SET PuntajePrevio = #puntaje1
FROM EstadoDoc, inserted
WHERE inserted.IdDoc = EstadoDoc.IdDoc
AND inserted.Secuencia = EstadoDoc.Secuencia
END
IF #Puntaje2 != NULL
BEGIN
UPDATE EstadoDoc
SET PuntajePrevio = #puntaje2
FROM EstadoDoc, inserted
WHERE inserted.IdDoc = EstadoDoc.IdDoc
AND inserted.Secuencia = EstadoDoc.Secuencia
END
See all my comments - you basically need to rewrite your trigger to use a proper set-based approach to handle multi-row inserts, and you need to get in the habit of using proper ANSI/ISO JOIN syntax (instead of the deprecated comma-separated table list in the FROM clause).
Try something like this - the trigger also looks a lot simpler that way!
CREATE OR ALTER TRIGGER [dbo].[trigger_EstadoDoc]
ON [dbo].[EstadoDoc]
AFTER INSERT AS
BEGIN
SET NOCOUNT ON;
-- as per charlieface's recommendation - check to see if any rows
-- have actually been inserted at all - if not, bow out
IF (NOT EXISTS (SELECT * FROM Inserted))
RETURN;
UPDATE e
SET PuntajePrevio = 5
FROM dbo.EstadoDoc e
INNER JOIN inserted i ON i.IdDoc = e.IdDoc
AND i.Secuencia = e.Secuencia
WHERE e.Estado = 'S'
AND i.Estado IN ('A', 'RyR');
UPDATE e
SET PuntajePrevio = 10
FROM dbo.EstadoDoc e
INNER JOIN inserted i ON i.IdDoc = e.IdDoc
AND i.Secuencia = e.Secuencia
WHERE i.Estado = 'A'
AND e.Estado = 'RyR';
END

Dynamic SQL with nvarchar(max) variable (again)

I have read about a dozen articles here and I am still stumped with this issue.
I am building a dynamic select statement that will update a view on a monthly schedule.
set ansi_nulls on
go
set quoted_identifier on
go
alter procedure [dbo].[Proc_Name_SP]
as
begin
set nocount on
set quoted_identifier off
declare #dbname varchar(10), #schema_id int, #schema_name varchar(10),
#jacro varchar(10), #rec_cnt int, #tot_rec int
declare #SQL_Main nvarchar(max), #SQL_Final nvarchar(max),
#SQL_schema nvarchar(2000), #SQL_Union nvarchar(max)
declare iteration cursor global static for
-- Begin statement for cursor array
select distinct db, code
from linkedserver.db.schema.Directory
where current_stage = 'live'
order by db
-- End statement for cursor array
-- get total number of cursor iterations to know when to stop
-- "union" statements
select #tot_rec = count(*) from (select distinct db, code
from [linkedserver].db.schema.Directory
where current_stage = 'live') as cur
-- begin loop
open iteration
fetch first from iteration into #dbname, #jacro
while ##fetch_status=0
begin
-- the schema used is not consistent. Because of the linked server it was
-- necessary to get the Schema_ID from the sys.tables and then pull the
-- schema name from sys.schema
set #SQL_schema = 'select #sch_id = schema_id from [linkedserver].'+#dbname+'.sys.tables where name = ''Manuscript'''
execute sp_executesql #SQL_schema, N'#sch_id int OUTPUT', #sch_id = #schema_id output
--print #schema_id
set #SQL_schema ='select #sch_name = name from [linkedserver].'+#dbname+'.sys.schemas where schema_id = '+cast(#schema_id as varchar)+''
execute sp_executesql #SQL_schema, N'#sch_name nvarchar(10) OUTPUT', #sch_name = #schema_name output
--print #schema_name
--building Select statement
set #SQL_Main ='
select jcode.Code as BILLING_ACRO
,s.start_dt as BILLING_DATE
,cmpt_ms_nm as MANUSCRIPT
,isnull(jcode.billing_type, ''reviewed'') as Billing_type
from [linkedserver].'+#dbname+'.'+#schema_name+'.Manuscript as m
join [linkedserver].'+#dbname+'.'+#schema_name+'.Step as s on m.ms_id = s.ms_id and m.ms_rev_no = s.ms_rev_no
join (select j_id, Code, billing_type from [linkedserver].db.schema.Directory where db = '''+#dbname+''') as jcode on jcode.j_id = m.j_id
where jcode.Code = '''+#jacro+'''
and m.ms_rev_no = 0
and s.stage_id = 190
and isnull(cmpt_ms_nm, '''') <> ''''
and s.step_id = (select min(s2.step_id)
from [linkedserver].'+#dbname+'.'+#schema_name+'.Step as s2
where s2.stage_id = 190
and s2.ms_id = m.ms_id
and s2.ms_rev_no = m.ms_rev_no)
'
set #rec_cnt = isnull(#rec_cnt, 0) + 1
if #SQL_Union is null
begin
set #SQL_Union = #SQL_Main
end
else if #tot_rec <> #rec_cnt
begin
set #SQL_Union = #SQL_Union + ' union ' + #SQL_Main
end
else
begin
set #SQL_Union = #SQL_Union + #SQL_Main
end
--print #rec_cnt
fetch next from iteration into #dbname, #jacro --next database
end -- while ##FETCH_STATUS=0
close iteration
deallocate iteration
-- build new view
print len(#SQL_Union)
set #SQL_Final = '
ALTER VIEW [dbo].[View_Name_VW]
AS
'+#SQL_Union+'
'
execute sp_executesql #SQL_Final
--grab string variables to table for troubleshooting
insert into Output_SQL(SQL_Final, SQL_Final_Len, SQL_Union, SQL_Union_Len)
select #SQL_Final, LEN(#SQL_Final), #SQL_Union, LEN(#SQL_Union)
set nocount off
end
go
I have read that others have had problems with this type of truncation and I have tried multiple suggestions but in the end the I am getting capped at 68274 in this code with nvarchar(max). For troubleshooting, I am saving the results of the variables and the len of these variables to a table to eliminate the SSMS cap on the display of strings.
I have tried cast(#varible as nvarchar(max)) on the right side of the = sign. I have changed the data type lengths (as the select that is being built is not that large, it is just large after it has been union for each unique customer)
I am open to any suggestions as I have tried many variations of datatype declarations for these variables.

in if else Loop ,cannot drop the same temp table

i am using if else statement and ,i want to drop the temporary table which is created out side the inner if statement , but when i execute the statement , i get the answer as , There is already an object named '#table' in the database. ---------------------MY CODE IS LIKE enter code here
DECLARE #RowCount INT;
SET #RowCount = 1;
IF #RowCount = 1
BEGIN
IF OBJECT_ID('tempdb..#Guarantor_Details') IS NOT NULL
DROP TABLE #Guarantor_Details
SELECT DISTINCT TOP 1 GUARANTORS.GUARANTOR_CODE
INTO #Guarantor_Details
FROM GUARANTORS
WHERE ISNULL(GUARANTORS.deleted, 0) <> 1;
DECLARE #GuarantorCount INT;
SELECT #GuarantorCount = count(*)
FROM #Guarantor_Details;
IF (#GuarantorCount = 0)
DROP TABLE #Guarantor_Details
BEGIN
IF OBJECT_ID('tempdb..#Guarantor_Details') IS NOT NULL
SELECT TOP 1 CLIENT.Last_Name
INTO #Guarantor_Details
FROM CLIENT
LEFT JOIN GUARANTORS ON GUARANTORS.GUARANTOR_CODE = CLIENT.Financial_Guarantor
END
END
------------------------------------------------------------------------
You don't need to do this type of CREATE / DROP. You can (and probably should) create the table before the loop and simply either TRUNCATE TABLE or DELETE FROM the temp table when necessary to "reset" it. Just pick a datatype that fits both GUARANTOR_CODE and Last_Name.
You also don't need to do a SELECT COUNT(*) on the table when you have the ##ROWCOUNT from the INSERT operation that already has that value in it.
Essentially:
CREATE TABLE #Guarantor_Details (Value NVARCHAR(50));
WHILE (something)
BEGIN
IF (#RowCount = 1)
BEGIN
TRUNCATE TABLE #Guarantor_Details;
INSERT INTO #Guarantor_Details (Value)
SELECT DISTINCT TOP 1 GUARANTORS.GUARANTOR_CODE
FROM GUARANTORS
WHERE ISNULL(GUARANTORS.deleted, 0) <> 1;
IF (##ROWCOUNT = 0)
BEGIN
INSERT INTO #Guarantor_Details (Value)
SELECT TOP 1 CLIENT.Last_Name
FROM CLIENT
LEFT JOIN GUARANTORS
ON GUARANTORS.GUARANTOR_CODE = CLIENT.Financial_Guarantor;
END;
END;
FYI, there appear to be some logic problems in the original code:
There are BEGIN / END tags at the bottom of the IF #RowCount = 1 block that aren't associated to any IF or WHILE. This is technically ok, but usually implies something is missing.
If this code is all of the code, then the IF #RowCount = 1 doesn't work in the loop unless something sets #RowCount past the end of this outer IF block that is not shown here in the question.

TSQL break loop when ##ROWCOUNT = 0

I have insert statements (simplified) in a SPROC like the following
SET ROWCOUNT 100
WHILE(1=1)
BEGIN
INSERT INTO table1
SELECT *
FROM table2
WHERE some_condition
-- EDIT: Realized forgot to include this following vital line that is causing issue
SET #var = #var + ##ROWCOUNT
-- ##ROWCOUNT now takes on a value of 1, which will cause the following IF check to fail even when no lines are inserted
IF(##ROWCOUNT = 0)
BEGIN
BREAK
END
END
But the issue is, after any operation even when no more rows fit my some_condition, ##ROWCOUNT is equal to 1, not 0.
How can I break that loop when there are 0 rows returned matching my some_condition?
The "set" statement creates a row count of 1. What you should do is immediately save ##ROWCOUNT into a #rowCount variable and use that var later on.
declare #rowCount int
WHILE(1=1)
BEGIN
INSERT INTO table1
SELECT *
FROM table2
WHERE some_condition
-- EDIT: Realized forgot to include this following vital line that is causing issue
SET #rowCount = ##ROWCOUNT
SET #var = #var + #rowCount
-- ##ROWCOUNT now takes on a value of 1, which will cause the following IF check to fail even when no lines are inserted
IF(#rowCount = 0)
BEGIN
BREAK
END
END
Also, you can simplify by setting #rowCount to -1 initially and changing the WHILE condition to #rowCount <> 0. The conditional BREAK will no longer be needed.
An alternative solution. This checks each iteration to see if the ID of the last inserted record has changed or not. If it hasn't changed, it indicates that no records were added that iteration.
SET ROWCOUNT 100
declare #id int;
WHILE(1=1)
INSERT INTO table1
SELECT *
FROM table2
WHERE some_condition
IF(#id= ##identity)
BEGIN
BREAK
END
set #id = ##identity;
END
Try this solutions:
1st solution
Using ##ROWCOUNT in loop's condition.
SET ROWCOUNT 100
INSERT INTO table1
SELECT *
FROM table2
WHERE some_condition
WHILE(##ROWCOUNT > 0)
BEGIN
INSERT INTO table1
SELECT *
FROM table2
WHERE some_condition
END
2nd solition
Using goto.
SET ROWCOUNT 100
WHILE(1=1)
BEGIN
INSERT INTO table1
SELECT *
FROM table2
WHERE some_condition
IF(##ROWCOUNT = 0)
BEGIN
goto label
END
END
label1:
print 'After lopp'
I think you should use select to get the ##rowcount into a variable. try this:
declare #number_of_rows int
SET ROWCOUNT 100
WHILE(1=1)
BEGIN
INSERT INTO table1
SELECT *
FROM table2
WHERE some_condition
SELECT #number_of_rows=##ROWCOUNT
IF (#number_of_rows = 0)
BEGIN
BREAK
END
END
Implemented solution similar to Moho, but used SELECT instead of SET to store ##ROWCOUNT.

Reading inserted column names and values in a TSQL trigger

I've been asked to create history tables for every table in a database. Then create a trigger that will write to the history table whenever the primary table is updated.
The history tables have the same structure as the primary table, but with a couple of extra rows ('id' and 'update type')
I've never done anything with triggers before, but I would like to do is dynamically go through the columns in 'Inserted' and construct an insert statement to populate the history table.
However I cannot work out how to read the names of the columns and their individual values.
My half finished trigger currently looks like...
CREATE TRIGGER tr_address_history
ON address
FOR UPDATE
AS
DECLARE #colCount int
DECLARE #maxCols int
SET #colCount = 0
SET #maxCols = (SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'Inserted')
PRINT 'Number of columns = ' + CONVERT(varChar(10),#maxCols)
WHILE (#colCount <= #maxCols)
BEGIN
DECLARE #name varchar(255)
SELECT #name = column_name FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'Inserted'
DECLARE #value varchar(255)
SELECT #value = #name FROM Inserted
PRINT 'name = ' + #name + ' and value = ' + #value
SET #colCount = #colCount + 1
END
PRINT 'Done';
When the trigger runs it just says "Number of columns = 0"
Can anyone tell me what's wrong with :
SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'Inserted'
Thanks...
First solution proposed by Beenay25 is good, but you should use affected table instead of 'inserted' pseudotable.
This is:
SELECT #name = column_name FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'AFFECTED_TABLE'
Instead of 'INSERTED'
Also, you should use dynamic SQL.
This will be a complete working solution:
ALTER TRIGGER [dbo].[tr_address_history]
ON [dbo].[address]
AFTER Insert
AS
DECLARE #ColumnName nvarchar(500)
DECLARE #TableName nvarchar(500)
DECLARE #value nvarchar(500)
DECLARE #Sql nvarchar(500)
Set #TableName='address'
DECLARE ColumnsCursor CURSOR FOR
select column_name FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'address'
OPEN ColumnsCursor
FETCH NEXT FROM ColumnsCursor into #ColumnName
WHILE ##FETCH_STATUS=0
BEGIN
select * into #tmp from inserted
Set #Sql= 'SELECT #value =' + #ColumnName + ' FROM #tmp'
EXEC sp_executesql #Sql, N'#Value nvarchar(500) OUTPUT', #Value OUTPUT
DROP TABLE #TMP
print '[' + #ColumnName +'='+ ltrim(rtrim(#Value))+']'
FETCH NEXT FROM ColumnsCursor into #ColumnName
END
CLOSE ColumnsCursor
DEALLOCATE ColumnsCursor
The 'inserted' table is a pseudo-table; it doesn't appear in INFORMATION_SCHEMA.
There is the UPDATE() operator for use in triggers:
CREATE TRIGGER trigger_name ON tablename
FOR UPDATE
AS
SET NOCOUNT ON
IF (UPDATE(Column1) OR UPDATE(Column2))
BEGIN
your sql here
END
COLUMNS_UPDATED
UPDATE()
There is a way to do what the questioner requires:
I have made something inside a trigger that tests whether all the columns of a particular table actually participated in an insert to that table. If they did, I later copied them to a history table. If they did not, then rollback and print only complete rows may be inserted into the report table. Perhaps they could adapt this to their needs:
here it is:
[
if exists (select 1 from inserted) and not exists (select 1 from deleted) -- if an insert has been performed
begin -- and we want to test whether all the columns in the report table were included in the insert
declare #inserted_columncount int, #actual_num_of_columns int, #loop_columns int, #current_columnname nvarchar(300),
#sql_test nvarchar(max), #params nvarchar(max), #is_there bit
set #actual_num_of_columns = (
select count(*) from (
select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'renameFilesFromTable_report') as z)
set #inserted_columncount = 0
set #loop_columns = 1
declare inserted_columnnames cursor scroll for -- these are not really the inserted ones, but we are going to test them 1 by 1
select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'renameFilesFromTable_report'
set #params = '#is_there_in bit output'
open inserted_columnnames
fetch next from inserted_columnnames into #current_columnname
select * into #temp_for_dynamic_sql from inserted -- this is necessary because the scope of sp_executesql does not include inserted pseudo table
while (#loop_columns <= #actual_num_of_columns) -- looping with independent integer arithmetic
begin
set #sql_test = '
set #is_there_in = 0
if exists (select ['+#current_columnname+'] from #temp_for_dynamic_sql where ['+#current_columnname+'] is not null)
set #is_there_in = 1'
exec sp_executesql #sql_test, #params, #is_there output
if #is_there = 1
begin
fetch next from inserted_columnnames into #current_columnname
set #inserted_columncount = #inserted_columncount + 1
set #loop_columns = #loop_columns + 1
end
else if #is_there <> 1
begin
fetch next from inserted_columnnames into #current_columnname
set #loop_columns = #loop_columns + 1
end
end
close inserted_columnnames
deallocate inserted_columnnames
-- at this point we hold in two int variables the number of columns participating in the insert and the total number of columns
]
Then you can simply do if #inserted_columncount < #actual_num_of_columns ..........
I did this because i have a sp that inserts 1 complete line to the report table every time it runs. That's fine, but i don't want anyone else touching that table by mistake. not even myself. I also want to keep history. So i made this trigger to keep the history but also to check if an insert was attempted without values for all the columns in the report table, and further down the code it checks if an update or delete was attempted and it rollbacks.
i was thinking of expanding this to allow an update but in which all the columns are set.
this could possibly be done as follows:
if update was attempted,
and exists (
select possibly_excluded.COLUMN_NAME from (
select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'renameFilesFromTable_report') as possibly_excluded
group by possibly_excluded.COLUMN_NAME
having COLUMN_NAME not in (
select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'renameFilesFromTable_report' and
sys.fn_IsBitSetInBitmask(#ColumnsUpdated, COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + TABLE_NAME), COLUMN_NAME, 'ColumnID')) <> 0)
)
begin
rollback transaction
print 'Only updates that set the values for a complete row are allowed on the report table..'
end