Catching multiple errors in loop SQL query - sql

I have the below insert query which selects records from the OriginalData table where everything is of datatype nvarchar(max) and inserts it into the temp table which has specific column definitions i.e MainAccount is of type INT.
I am doing a row by row insert because if there is a record in OriginalData table where the MainAccount value is 'Test' the it will obviously cause a conversion error and the insert will fail. The begin try block is used to update the table with the error.
However if there are multiple errors on the same row I want to be able to capture them both and not just the first one.
TRUNCATE TABLE [Temp]
DECLARE #RowId INT, #MaxRowId INT
SET #RowId = 1
SELECT #MaxRowId = MAX(RowId)
FROM [Staging].[FactFinancialsCoded_Abbas_InitialValidationTest]
WHILE(#RowId <= #MaxRowId)
BEGIN
BEGIN TRY
INSERT INTO [Temp] (ExtractSource, MainAccount,
RecordLevel1Code, RecordLevel2Code, RecordTypeNo,
TransDate, Amount, PeriodCode, CompanyCode)
SELECT
ExtractSource, MainAccount,
RecordLevel1Code, RecordLevel2Code, RecordTypeNo,
TransDate, Amount, PeriodCode, DataAreaId
FROM
[Staging].[FactFinancialsCoded_Abbas_InitialValidationTest]
WHERE
RowId = #RowId;
PRINT #RowId;
END TRY
BEGIN CATCH
Update [Staging].[FactFinancialsCoded_Abbas_InitialValidationTest]
Set ValidationErrors = ERROR_MESSAGE()
where RowId = #RowId
END CATCH
SET #RowId += 1;
END

Instead of doing it this way, I handle this by using TRY_PARSE() or TRY_CONVERT() on each column that I am converting to a non-string column.
If you then need to store the validation failures in another table, you can make a second pass getting all the rows that have a non-null value in the source table and a null value in the destination table, and insert those rows into your "failed validation" table.

Related

SQL Count Unique Deleted Sets

I currently have a simple table in my database that stores sets and values. I want to be able to delete all entries in the database and return the number of distinct sets that were deleted.
create table sets(
SetId varchar(50)
Value int
)
If I have two sets each with two values, then the table will be loaded for four entries.
Set1, 0
Set1, 1
Set2, 0
Set2, 1
If I delete everything I want to be able to count how many unique SetIds were deleted, so in the example above it should return 2.
Right now I can accomplish this by creating a tempTable that contains the deleted SetIds and then I count distinct
CREATE TABLE #temp
(
SetId varchar(50)
);
delete from Sets
OUTPUT DELETED.SetId INTO #temp
select count(distinct SetId) from #temp;
Is there a better way to accomplish this without having to use a temp table?
If you have many rows, and want to avoid temp table (lot of IO) :
declare #cnt int;
set xact_abort on
begin transaction
begin try
select #cnt = count(distinct SetId) from sets;
delete from sets;
commit transaction
end try
begin catch
rollback;
end catch
or :
declare #cnt int;
set xact_abort on
begin transaction
begin try
select #cnt = count(distinct SetId) from sets;
truncate table sets
commit transaction
end try
begin catch
rollback;
end catch

catching error in loop sql query

I have the below insert query which selects records from the OriginalData table where everything is nvarchar(max) and inserts it into the Temp table which has specific field definitions i.e MainAccount is INT.
I am doing a row by row insert because if there is a record in OriginalData table where the MainAccount value is 'Test' the it will obviously cause a conversion error and the insert will fail.
I want to be able to capture this error. There is a field on the originalData table called "error" which I want to populate. However I want this to run thru the entire table as oppose to fail on the first error and stop.
DECLARE #RowId INT
, #MaxRowId INT
Set #RowId = 1
Select #MaxRowId = 60
WHILE(#RowId <= #MaxRowId)
BEGIN
INSERT INTO [Temp] (ExtractSource, MainAccount,RecordLevel1Code, RecordLevel2Code, RecordTypeNo, TransDate, Amount, PeriodCode, CompanyCode)
SELECT ExtractSource, MainAccount,RecordLevel1Code, RecordLevel2Code,RecordTypeNo,TransDate, Amount, PeriodCode, DataAreaId
FROM [OriginalData]
WHERE RowId = #RowId
PRINT #RowId
SET #RowId = #RowId + 1
END
select * from [Temp]
You should use TRY..CATCH block:
WHILE(#RowId <= #MaxRowId)
BEGIN
BEGIN TRY
INSERT INTO [Temp] (ExtractSource, MainAccount,RecordLevel1Code,
RecordLevel2Code, RecordTypeNo, TransDate, Amount, PeriodCode, CompanyCode)
SELECT ExtractSource, MainAccount,RecordLevel1Code, RecordLevel2Code,
RecordTypeNo,TransDate, Amount, PeriodCode, DataAreaId
FROM [OriginalData]
WHERE RowId = #RowId;
PRINT #RowId;
END TRY
BEGIN CATCH
-- error handlingg
END CATCH
SET #RowId += 1;
END

Insert Query to insert multiple rows in a table via select and output clause. SQL Server 2008

I have a created a stored procedure (please ignore syntax errors)
alter proc usp_newServerDetails
(#appid int, #envid int, #serType varchar(20), #servName varchar(20))
as
declare #oTbl_sd table (ID int)
declare #outID1
declare #oTbl_cd table (ID int)
declare #outID2
begin Transaction
insert into server_details(envid, servertype, servername)
output inserted.serverid into #oTbl_sd(ID)
values(#envid, #serType, #servName)
select #outID1 = ID from #oTbl_sd
insert into configdetails(serverid, servertype, configpath, configtype)
output inserted.configid into #oTbl_cd(ID)
(select #outID1, cm.servertype, cm.configpath, cm.configtype
from configpthmaster cm
where cm.appid = #appid )
select #outID2 = ID from #oTbl_cd
insert into configkeydetails(confiid, keyname)
output inserted.Keyid into #oTbl_ckd(ID)
(select #outID2, cm.key
from configpthmaster cm
where cm.appid = #appid)
begin
commit
end
server_details table has an identity column ID with is auto-generated ie. #outID1 and first insert query inserts only 1 row.
configpthmaster table is not related to any other table directly and has 2 unique data rows, which I want to fetch to insert data into other tables, one by one during insertion.
The second insert query fetch data from configpthmaster table
and insert 2 rows in configdetails while generating (auto-generated) ID ie. #outID2.
It also has a FK mapped to server_details.
The problem is "#outID2" giving last inserted ID only (ie. if two id generated 100,101 i am getting 101) which eventually on 3rd insertion, inserting 2 rows with same id 101 only but i want the insertion should be linear. i.e one for 100 and other for 101.
If zero rows affected while insertion how to rollback the transaction?
How can I achieve these requirements? Please help.
Change your procedure like below,and try again.
ALTER PROCEDURE usp_newServerDetails(#appid int, #envid int,#serType varchar(20),#servName varchar(20))
AS
BEGIN
BEGIN TRY
DECLARE #Output TABLE (ID int,TableName VARCHAR(50),cmKey VARCHAR(50)) --table variable for keeping Inserted ID's
BEGIN TRAN
IF EXISTS ( SELECT 1 FROM configpthmaster cm WHERE cm.appid = #appid )
AND ( SELECT 1 FROM configkeydetails ck WHERE ck.appid = #appid ) --add a conditon to satisfy the valid insertions
BEGIN
INSERT INTO server_detials(envid,servertype,servername)
OUTPUT inserted.serverid,'server_detials',NULL INTO #Output(ID,TableName,cmKey )
VALUES(#envid ,#serType ,#servName)
INSERT INTO configdetails(serverid,servertype,configpath,configtype)
OUTPUT inserted.configid,'configdetails',cm.Key INTO #Output(ID,TableName,cmKey )
SELECT t.ID,cm.servertype,cm.configpath,cm.configtype
FROM configpthmaster cm
CROSS APPLY (SELECT ID FROM #Output WHERE TableName='server_detials')t
WHERE cm.appid = #appid
INSERT INTO configkeydetails(configId,keyname)
SELECT ID,cmKey FROM #Output
WHERE TableName='configdetails'
END
COMMIT TRAN
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK
END CATCH
END
Could you try this solution?
alter proc usp_newServerDetails(#appid int, #envid int,#serType varchar(20),#servName varchar(20))
as
declare #oTbl_sd table (ID int)
declare #outID1
declare #oTbl_cd table (ID int)
declare #outID2
begin Transaction
insert into server_detials(envid,servertype,servername)
output inserted.serverid into #oTbl_sd(ID)
values(#envid ,#serType ,#servName)
select #outID1 = ID from #oTbl_sd
insert into configdetails(serverid,servertype,configpath,configtype)
output inserted.configid into #oTbl_cd(ID)
(select #outID1 ,cm.servertype,cm.configpath,cm.configtype from configpthmaster cm where cm.appid = #appid )
select #outID2 = ID from #oTbl_cd
insert into configkeydetails(confiid,keyname)
output inserted.Keyid into #oTbl_ckd(ID)
(select isnull(replace(stuff((SELECT inserted.configid FOR xml path('')), 1, 1, ''), '&', '&'), '') ,cm.key, from configpthmaster cm where cm.appid = #appid )
begin
commit
end
I just added STUFF in your code.
The STUFF function inserts a string into another string.
Do take note that using STUFF drastically slows the processing time of the code.
for more information about STUFF

If else expression compare not working

i want to perform a compare if #accid2 not equal #accid then roll back action, else perform insert.
My result of this trigger is even that is not match but it still insert into my table.
here is my code:
ALTER TRIGGER [dbo].[TG_checkacctypehtl]
ON [dbo].[Accommodation_Hotel] INSTEAD OF INSERT
AS
DECLARE #accid NVARCHAR(50), #accid2 NVARCHAR(50),#hid NVARCHAR(50),#fsp NVARCHAR(50), #fc NVARCHAR(50), #sr NVARCHAR(50);
SELECT #hid = i.hotel_id FROM INSERTED i;
SELECT #fsp = i.facillities_swimming_pool FROM INSERTED i;
SELECT #fc = i.facillities_catering FROM INSERTED i;
SELECT #sr = i.star_rating FROM INSERTED i;
SELECT #accid2 = i.accommodation_id FROM INSERTED i;
SELECT #accid = accommodation_id FROM [dbo].[Accommodation] WHERE accommodation_type= 'hotel' AND accommodation_id=#accid2;
BEGIN
BEGIN TRAN
SET NOCOUNT ON
PRINT #accid2
PRINT #accid
IF(#accid2 != #accid)
BEGIN
RAISERROR('Record Not Inserted, Accommodation ID is not a Hotel Id',16,1); ROLLBACK; END
ElSE BEGIN
INSERT INTO [dbo].[accommodation_hotel] (hotel_id,facillities_swimming_pool,facillities_catering,star_rating,accommodation_id)
VALUES (#hid,#fsp,#fc,#sr,#accid2);COMMIT;
END
END
*print is for check the value i get.
is that my logic error or my syntax error?
I would rewrite the whole trigger something like this...
ALTER TRIGGER [dbo].[TG_checkacctypehtl]
ON [dbo].[Accommodation_Hotel]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [dbo].[accommodation_hotel] (hotel_id,facillities_swimming_pool,facillities_catering,star_rating,accommodation_id)
SELECT i.hotel_id
,i.facillities_swimming_pool
,i.facillities_catering
,i.star_rating
,i.accommodation_id
FROM inserted i
WHERE EXISTS ( SELECT 1
FROM [dbo].[Accommodation] a
WHERE a.accommodation_type= 'hotel'
AND a.accommodation_id = i.accommodation_id)
IF EXISTS (SELECT 1 FROM inserted i
WHERE NOT EXISTS ( SELECT 1
FROM [dbo].[Accommodation] a
WHERE a.accommodation_type= 'hotel'
AND a.accommodation_id = i.accommodation_id)
)
BEGIN
RAISERROR('Records with invalid Accommodation ID is not a Hotel Id not inserted',16,1);
END
END
Insert the rows with valid accommodation ids and raise an error if there are any rows with invalid Hotel Ids, Also no need for all those variables.
Also triggers are fired for each transaction, not for each row. Your code assume there will only be one rows inserted ever in the table at a time.
Should be IF(#accid2 <> #accid)

Update multiple rows in table from table variable

I'm writing a stored procedure to update multiple records based on a table variable parameter.
The existing table is: Tb_Project_Image with relevant columns:
id PK (identity 1,1)
cat_ord decimal(4,2)
The procedure will receive a temporary table variable (shown in the code below) containing the id as PI_ID, and the new value for cat_ord as newCatOrd. idx is a simple identity for each row containing 1...n where n is the rowcount of #tempTable.
For each row in #tempTable, I want to update Tb_Project_Image where id = PI_ID to the corresponding value.
DECLARE #tempTable table (
idx smallint Primary Key IDENTITY(1,1),
PI_ID bigint,
newCatOrd decimal(4, 2) not null )
INSERT INTO #tempTable values (3, 7.01)
INSERT INTO #tempTable values (4, 7.02)
INSERT INTO #tempTable values (5, 7.03)
--etc...
DECLARE #error int
DECLARE #update int
DECLARE #iter int
SET #iter = 1
BEGIN TRAN
WHILE #iter <= (select COUNT(*) from #tempTable)
BEGIN
UPDATE Tb_Project_Image
SET cat_ord = (SELECT newCatOrd FROM #tempTable
WHERE idx = #iter)
WHERE id = (SELECT PI_ID FROM #tempTable
WHERE idx = #iter)
--error checking
set #error = ##ERROR
set #update = ##ROWCOUNT
IF ((#error = 0) AND (#update = 1))
BEGIN
SET #iter = #iter + 1
CONTINUE
END
ELSE
BREAK
END
IF ((#error = 0) AND (#update = 1))
COMMIT TRAN
ELSE
ROLLBACK TRAN
GO
Now, the error checking is because, to ensure integrity, EACH row in the temporary table MUST make 1 update. (explanation omitted to save space) If a single iteration of the while loop threw an error, or didn't effect exactly 1 row, I want to break the loop and rollback the transaction
THE PROBLEM I'm having is that this error checking is not working. I'm currently running it with 14 rows in #tempTable and the 11th uses a PI_ID not found in the Project_Image table. Therefore, #update = 0... but it continues the loop and commits the data.
I'd be doubly glad if someone had a method of doing this that only used a single update statement.
You cannot do it this way, because even SET resets the state of ##ERROR and ##ROWNUMBER variables. In this case ##ROWCOUNT is set to 1 after set #error = ##ERROR. If you do not assign the values to local variables, your code will work:
IF ((##error = 0) AND (##rowcount = 1))
But you might rather try try...catch error handling and test ##rowcount separately after update.
UPDATE: doing it in single update:
UPDATE t
SET cat_ord = tt.newCatOrd
FROM Tb_Project_Image t
INNER JOIN #tempTable tt
ON t.id = tt.PI_ID
-- If there was PI_ID not found in Tb_Project_Image
-- But I think that this should have been dealt with
-- During the initial loading of temporary table
IF ##ROWCOUNT <> (select count (*) from #tempTable)
BEGIN
-- Error reporting here
ROLLBACK TRANSACTION
END
Instead of updating and then rolling back, you could also use a CTE to determine if any records should be updated prior to performing the update. Something like this should work:
WITH NON_SINGLETON AS (
-- Find any records in #tempTable that don't match
-- exactly one record in Tb_Project_Image
SELECT t.PI_ID, COUNT(pi.id) C
FROM #tempTable t
LEFT JOIN Tb_Project_Image pi ON t.PI_ID = pi.id
GROUP BY t.PI_ID
HAVING COUNT(pi.id) != 1
)
UPDATE Tb_Project_Image
SET cat_ord = t.newCatOrd
FROM Tb_Project_Image pi
JOIN #tempTable t ON pi.id = t.PI_ID
-- If any invalid records were found in the CTE,
-- then this condition will fail for all rows
-- and nothing will be updated
WHERE NOT EXISTS(SELECT 1 FROM NON_SINGLETON)
If it's possible for #tempTable to have duplicate entries for the same PI_ID, then this will handle those scenarios as well. And since it's a single statement, you don't have to explicitly managing the transaction in the proc (if it's the only thing that needs to be included in the transaction).