Turn off implicit transactions - sql-server

I would like to do a insert into using a select, but I know that some rows might fail (that is expected). Is there a way to change the implicit transactions of SQL Server 2008 off so that the ones that have not failed are not rolled back?
-- get the count of the customers to send the sms to
SELECT #count = COUNT(*)
FROM PreCampaignCustomer
WHERE Processed = 0 AND CampaignID = #campaignid
AND ErrorCount < 5
WHILE (#count > 0)
BEGIN
DECLARE #customerid INT,
#carrierid INT,
#merchantcustomerid INT,
#smsnumber NVARCHAR(50),
#couponcode NVARCHAR(20)
SELECT TOP 1 #customerid = pcc.CustomerID, #merchantcustomerid = pcc.MerchantCustomerID,
#carrierid = c.CarrierID, #smsnumber = c.SMSNumber
FROM PreCampaignCustomer pcc
INNER JOIN Customer c ON c.ID = pcc.CustomerID
WHERE pcc.Processed = 0 AND pcc.CampaignID = #campaignid
AND pcc.ErrorCount < 5
ORDER BY pcc.ErrorCount
--set the couponcode
IF #couponlength = -1
BEGIN
SET #couponcode = 'NOCOUPON'
END
ELSE
BEGIN
EXEC [GenerateCouponCode]
#length = 9,
#couponcode = #couponcode OUTPUT
END
BEGIN TRY
--use try/catch just in case the coupon code is repeated or any other error
--Set the coupon text
DECLARE #coupontext NVARCHAR(200),
#smsrequestxml XML
IF #coupontypecode = 1 --NONE
BEGIN
SET #coupontext = #merchantname + ': ' + #smsmessage + ', Use Code: ' + dbo.FormatCouponCode(#couponcode, #couponcodegrouping) + '. Reply STOP to quit'
END
ELSE
BEGIN
SET #coupontext = #merchantname + ': ' + #smsmessage + '. Reply STOP to quit'
END
EXEC GetSMSRequest #config = #smsconfig,
#smsType = 1, --Submit
#address = #smsnumber,
#carrierID = #carrierid,
#message = #coupontext,
#xml = #smsrequestxml OUTPUT
BEGIN TRAN
--create the CampaignCustomer record
INSERT INTO CampaignCustomer
(CampaignID, CustomerID, CouponCode, Active)
VALUES
(#campaignid, #customerid, #couponcode, 1)
--Add the record to the queue
INSERT INTO SMSQueue
(CarrierID, [Address], [Message], TimeToSend, RequestXML, QueueID, HTTPStatusCode, Retries)
VALUES
(#carrierid, #smsnumber, #coupontext, #timetosend, #smsrequestxml, #queueid, 0, 0)
--Create Outgoing SMS Log
INSERT INTO SMSOutgoingLog
(MerchantID, MerchantGroupID, MessageTypeCode, CreatedDate, Active)
VALUES
(#merchantid, #merchantgroupid, #messagetypecode, GETDATE(), 1)
--Update the LastSentSMSTime of the MerchantCustomer
UPDATE MerchantCustomer
SET LastSentSMS = GETDATE(),
ModifiedDate = GETDATE()
WHERE ID = #merchantcustomerid
UPDATE PreCampaignCustomer
SET Processed = 1,
ModifiedDate = GETDATE()
WHERE CustomerID = #customerid
AND CampaignID = #campaignid
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
-- Set the error
UPDATE PreCampaignCustomer
SET ErrorCount = ErrorCount + 1,
ModifiedDate = GETDATE(),
ErrorMessage = ERROR_MESSAGE(),
ErrorNumber = ERROR_NUMBER()
WHERE CustomerID = #customerid
AND CampaignID = #campaignid
END CATCH
SELECT #count = COUNT(*)
FROM PreCampaignCustomer
WHERE Processed = 0 AND CampaignID = #campaignid
AND ErrorCount < 5
END

no, the INSERT is a single command. Transactions control how multiple commands are combined together into single units of work, and not how rows are combined within a single command. You can't have some rows INSERT and the ones that fail (some constraint issue) and just be ignored. if any rows fail, then the entire INSERT fails.
why not try modifying the SELECT to exclude rows that will fail?
something like:
INSERT INTO YourTable
(col1, col2, col3)
SELECT
colA, colB, ColC
FROM YourOtherTable
WHERE ColA NOT IN (SELECT col1 FROM YourTable)

Thinking out of the box, if you use SSIS to do this, you can send your failed rows down a differnt path or just throw them out.

You're probably thinking about the IGNORE_DUP_KEY property of a unique index.
See this related SO question and the official MSDN article on IGNORE_DUP_KEY.
You'll have to either use ALTER INDEX to add this property, or (if the unique constraint is on the primary key) drop and re-create it.
Once this is in place, any inserts should only reject invalid rows instead of the entire batch.

It is possible to control behaviour of transansactions using SET XACT_ABORT OFF (ON) -- more here.

Related

MSSQL2016 UPDATE trigger to INSERT record in other table based on SUM of bit column

I'm attempting to automate our order status update system. Here is the flow we have in place:
orders come in to our system as one order (via FTP file), and get split into 2 or more orders (inserted into SplitOrdersHeader_tb)
resulting multiple orders are sent from the SplitOrders tables (header and detail) to ERP system
ERP system produces order confirmations for each order (inserted into OrderConfirmationHeader_tb, and detail table)
need to update original single order status after all split order confirmations have been received
Here are the tables involved:
CREATE TABLE SplitOrdersHeader_tb(
OriginalCustomerPONumber varchar(20),
NewCustomerPONumber varchar(20),
Company varchar(2),
CustomerNumber varchar(10),
OrderProcessed bit DEFAULT 0,
OrderMoved bit DEFAULT 0,
OrderConfirmationReceived bit DEFAULT 0
)
CREATE TABLE OrderConfirmationHeader_tb(
MasterOrderNumber varchar(20),
CustomerPONumber varchar(20),
Company varchar(2),
CustomerNumber varchar(10)
)
CREATE TABLE UpdateOtherSystem_tb(
OriginalCustomerPONumber varchar(20)
)
I have a trigger on the OrderConfirmationHeader_tb that updates the status of each split order, once the order confirmations have been loaded:
CREATE TRIGGER dbo.INSERT_Update_SplitOrderConfirmations_tg
ON dbo.OrderConfirmationHeader_tb
AFTER INSERT
AS
SET NOCOUNT ON
BEGIN
UPDATE SOH
SET SOH.OrderConfirmationReceived = 1,
SOH.MasterOrderNumber = LTRIM(RTRIM(I.MasterOrderNumber))
FROM OrderSplitting.SplitOrdersHeader_tb SOH
INNER JOIN inserted I ON SOH.CustomerNumber = I.Customer
AND SOH.NewCustomerPONumber = I.Reference
AND SOH.Company = I.Company
AND SOH.OrderProcessed = 1
AND SOH.OrderMoved = 1
END
What I'm wanting to do is create an UPDATE trigger on the SplitOrdersHeader_tb that will:
- count the number of split orders from the original CustomerPONumber
- sum the number of OrderConfirmationReceived values
- if COUNT = SUM then insert a new record into UpdateOtherSystem_tb, provided the MasterOrderNumber does not already exist in the UpdateOtherSystem_tb
I have this, but it feels way too clunky:
CREATE TRIGGER OrderSplitting.UPDATE_Update_WCO_Status_tg
ON OrderSplitting.SplitOrdersHeader_tb
AFTER UPDATE AS
SET NOCOUNT ON
BEGIN
DECLARE #NEW_CUSTOMER_PO_NUMBER varchar(255),
#ORIGINAL_CUSTOMER_PO_NUMBER varchar(255),
#COUNT_OF_ORDER_HEADERS int,
#TOTAL_CONFIRMED_ORDERS int
SELECT #NEW_CUSTOMER_PO_NUMBER = NewCustomerPONumber
FROM inserted
SELECT #ORIGINAL_CUSTOMER_PO_NUMBER = OriginalCustomerPONumber,
#COUNT_OF_ORDER_HEADERS = COUNT(*),
#TOTAL_CONFIRMED_ORDERS = SUM(CAST(OrderConfirmationReceived as int))
FROM OrderSplitting.SplitOrdersHeader_tb
WHERE OriginalCustomerPONumber IN (SELECT OriginalCustomerPONumber
FROM OrderSplitting.SplitOrdersHeader_tb
WHERE NewCustomerPONumber = #NEW_CUSTOMER_PO_NUMBER)
GROUP BY OriginalCustomerPONumber
IF #COUNT_OF_ORDER_HEADERS = #TOTAL_CONFIRMED_ORDERS
BEGIN
BEGIN TRY
INSERT INTO OrderSplitting.UpdateOtherSystem_tb(OriginalCustomerPONumber)
VALUES(#ORIGINAL_CUSTOMER_PO_NUMBER)
END TRY
BEGIN CATCH
DECLARE #ERROR_MESSAGE varchar(MAX)
SET #ERROR_MESSAGE = ERROR_MESSAGE()
EXEC msdb..sp_send_dbmail
#recipients = <app_support>,
#subject = 'Update Trigger Error',
#body = #ERROR_MESSAGE
END CATCH
END
END
I think I have something, but would like some additional feedback, please:
CREATE TRIGGER OrderSplitting.UPDATE_Update_WCO_Status_tg
ON OrderSplitting.SplitOrdersHeader_tb
AFTER UPDATE AS
SET NOCOUNT ON
BEGIN TRY
INSERT INTO OrderSplitting.UpdateOtherSystem_tb(OriginalCustomerPONumber)
SELECT SOH.OriginalCustomerPONumber
FROM OrderSplitting.SplitOrdersHeader_tb SOH
INNER JOIN inserted I ON SOH.OriginalCustomerPONumber = I.OriginalCustomerPONumber
LEFT OUTER JOIN OrderSplitting.UpdateOtherSystem_tb UOS ON SOH.OriginalCustomerPONumber = UOS.OriginalCustomerPONumber
WHERE UOS.OriginalCustomerPONumber IS NULL
GROUP BY SOH.OriginalCustomerPONumber
HAVING COUNT(SOH.OriginalCustomerPONumber) = SUM(CAST(SOH.OrderConfirmationReceived as int))
END TRY
BEGIN CATCH
DECLARE #ERROR_MESSAGE varchar(MAX)
SET #ERROR_MESSAGE = ERROR_MESSAGE()
EXEC msdb..sp_send_dbmail
#recipients = <app_support>,
#subject = 'Update Trigger Error',
#body = #ERROR_MESSAGE
END CATCH

Using a SP to retrieve information or generate it if data exists in table

I have a requirement in which I need to run a select statement for data in a table if that data exists I need to return the values in a few of the columns. If the data doesn't exist, I need to insert a new row and return the inserted data.
I'll be using an API to execute the stored procedure and return the data, and then write that to a machine via OPC.
What I'm struggling with currently, is that a new entry is created, but does not increment the two columns I need to increment by a digit. In the code below, a new entry will create, but still returns and inputs the same value as the previous lot number. Is there a better way to achieve what I'm wanting to do?
CREATE PROCEDURE [dbo].[Lotconfirmation]
#plcid nvarchar(6) OUTPUT,
#supplierlot nvarchar(25),
#internallotnum nvarchar(25)OUTPUT,
#plclotnum nvarchar(25) OUTPUT,
#suppliercode nvarchar(1),
#supplierpartnum nvarchar(25),
#suppliermodel nvarchar(25),
#qtyconsumed int,
#id int OUTPUT,
#errormsg nvarchar(max) OUTPUT,
#errornum int OUTPUT,
#errorproc nvarchar(max) OUTPUT,
#errorstate int OUTPUT,
#errorline int OUTPUT
AS
BEGIN
BEGIN TRY
/* Check if lot already exists */
SELECT
#internallotnum = InternalLotNum, #plcid = PLCID,
#plclotnum = plclotnum, #id = id
FROM
dbo.ShrinkLotData
WHERE
SupplierMfgLot = #supplierlot
IF #internallotnum IS NULL
BEGIN
DECLARE #table TABLE
(
plcid nvarchar(6),
internallotnum nvarchar(25),
plclotnum nvarchar(25),
id int
)
INSERT INTO dbo.ShrinkLotData(PLCID, SupplierMfgLot, InternalLotNum, PLCLotNum, SupplierCode, SupplierPartNum, SupplierModel, QtyConsumed, Month, Day, Year, TimeStamp)
OUTPUT inserted.plcid, inserted.InternalLotNum, inserted.PLCLotNum, inserted.ID
VALUES (#plcid, #supplierlot,
(SELECT (MAX(InternalLotNum) + 1) FROM dbo.ShrinkLotData),
(SELECT MAX(RIGHT(InternalLotNum, 2) + 1) FROM dbo.ShrinkLotData),
#suppliercode, #supplierpartnum, #suppliermodel,
#qtyconsumed,
MONTH(GETDATE()), DAY(GETDATE()), YEAR(GETDATE()),
CURRENT_TIMESTAMP)
SELECT #plcid = plcid, #internallotnum = internallotnum, #plclotnum = plclotnum, #id = id
FROM #table
END
END TRY
/* E-mail if errors occurs */
BEGIN CATCH
SET #errormsg = 'SP Failed with msg:' + ERROR_MESSAGE()
SET #errornum = ERROR_NUMBER()
SET #errorproc = ERROR_PROCEDURE()
SET #errorstate = ERROR_STATE()
SET #errorline = ERROR_LINE()
/* Place holder to insert fail data into a table
INSERT INTO KSError (datestamp, errormsg, errorproc, errorstate, errorline)
VALUES (#datestamp, #errormsg, #errornum, #errorproc, #errorstate, #errorline)
*/
EXECUTE msdb.dbo.sp_send_dbmail
#recipients = 'email#domain.com',
#profile_name = 'Profile Alert',
#subject = 'KepServer Stored Procedure:',
#body = #errormsg
END CATCH
END
GO
EDIT:
It seems to be working when I cast values as an integer, so I'll need to review those data types and probably just set them up as integers.
(SELECT MAX(CAST(InternalLotNum AS INT)) + 1 FROM dbo.ShrinkLotData),
(SELECT MAX(RIGHT(CAST(InternalLotNum AS Int), 2) + 1) FROM dbo.ShrinkLotData),
While the question is still up, do you guys see a better / more efficient way to do what I'm hoping?
Thanks!
Your OUTPUT clause is returning the values directly to the client, instead of inserting them into your table variable. Should be something like:
INSERT INTO dbo.ShrinkLotData(PLCID, SupplierMfgLot, InternalLotNum, PLCLotNum, SupplierCode, SupplierPartNum, SupplierModel, QtyConsumed, Month, Day, Year, TimeStamp)
OUTPUT inserted.plcid, inserted.InternalLotNum, inserted.PLCLotNum, inserted.ID
INTO #table(plcid,InternalLotNum,PLCLotNum,ID)
VALUES (#plcid, #supplierlot,
(SELECT (MAX(InternalLotNum) + 1) FROM dbo.ShrinkLotData),
(SELECT MAX(RIGHT(InternalLotNum, 2) + 1) FROM dbo.ShrinkLotData),
#suppliercode, #supplierpartnum, #suppliermodel,
#qtyconsumed,
MONTH(GETDATE()), DAY(GETDATE()), YEAR(GETDATE()),
CURRENT_TIMESTAMP)
SELECT #plcid = plcid, #internallotnum = internallotnum, #plclotnum = plclotnum, #id = id
FROM #table

SQL Server if statement does not execute as expected

I am trying to use the following stored procedure but there are some instances WHERE only the incremental happens AND the code does not run. What I need is that, when the program enters the IF statement, either it should run both the statements or None.
Stored procedure goes like this:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spflpunch]
AS
BEGIN
DECLARE #id NUMERIC(18,0)
DECLARE #studname NVARCHAR(50)
DECLARE #punchtime DATETIME
DECLARE #samedaycount NUMERIC(2)
SELECT #id = (MAX(lastid)) FROM [smartswype].[dbo].[read]
PRINT #id
SELECT #studname = studname
FROM [SSWYPE_WEBDB].[dbo].[attdview]
WHERE id =#id
PRINT #studname
SELECT #punchtime = punchtime
FROM [SSWYPE_WEBDB].[dbo].[attdview]
WHERE id = #id
PRINT #punchtime
--SELECT #punchvarchar = CONVERT(VARCHAR(10),#punchtime, 103) + ' ' + CONVERT(VARCHAR(5), #punchtime, 14)
IF #id = (SELECT MAX(id) FROM [SSWYPE_WEBDB].[dbo].[attdview])
BEGIN
SELECT #samedaycount = COUNT(*)
FROM [SSWYPE_WEBDB].[dbo].[attdview]
WHERE (studname = #studname
AND CONVERT(DATE, punchtime) = CONVERT(DATE, #punchtime)) -- If firstpunch = 1 then it is the first punch
PRINT #samedaycount
IF #samedaycount =1
BEGIN
INSERT INTO [smartswype].[dbo].[firstlastpunch] ([studname], [DATE], [punch1], [punch2])
VALUES(#studname, CONVERT(DATE, #punchtime), #punchtime, NULL);
UPDATE [smartswype].[dbo].[read]
SET lastid = #id + 1;
END
ELSE IF (#samedaycount > 1)
BEGIN
UPDATE [smartswype].[dbo].[firstlastpunch]
SET punch2 = #punchtime
WHERE (studname = #studname AND DATE = CONVERT(DATE, #punchtime));
UPDATE [smartswype].[dbo].[read]
SET lastid = #id + 1;
END
END
END
If you want to ensure that both or none of the statements run, you should wrap the contents of the if statement in a transaction.
By wrapping it in a transaction, you can ensure that if one statement fails, that the other statement will not run.
Here is a link to the docs on transactions in SQL Server
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/commit-transaction-transact-sql

Insert trigger for duplicate records not working sql server

ALTER TRIGGER [dbo].[STOK_HARKETLERI_Insert]
ON [dbo].[STOK_HAREKETLERI]
FOR INSERT
AS BEGIN
declare #tip int
declare #miktar float
declare #stokkod nvarchar
declare #tarih datetime
declare #counter int
Select
#tip = sth_tip, #miktar = sth_miktar,
#stokkod = sth_stok_kod, #tarih = sth_tarih
from inserted
select #Counter = COUNT(sth_tip)
from STOK_HAREKETLERI
where sth_evraktip = 6
and sth_tip = #tip
and sth_miktar = #miktar
and #stokkod = sth_stok_kod
and #tarih = sth_tarih
if (#counter>=1)
begin
rollback
RAISERROR ('Record already exists', 17, -1) with log
end
END
GO
The trigger is not being triggered on insert statements, however if I remove the variables and fill the data and run it on SQL Server it is running fine.
Any suggestions?
One more thing if I change the line (#counter >= 1) to (#counter >= 0) it starts working again.
If you insert more than one row, there will be more than one row in "inserted", but you're only checking the last of them. It might be easier to make a check constraint, depending on what the rules regarding "sth_evraktip = 6 " actually are (can there be more rows done with update later etc).
With insert trigger something like this might work:
if exists (select 1 from inserted i
where exists(select 1 from STOK_HAREKETLERI S
where S.sth_evraktip = 6
and S.sth_tip = i.sth_tip
and S.sth_miktar = i.sth_miktar
and S.sth_stok_kod = i.sth_stok_kod
and S.sth_tarih = i.sth_tarih
and S.sth_RECno < i.sth_RECno)) begin
rollback
RAISERROR ('Record already exists', 17, -1) with log
end
If any of the columns can contain NULL, then you'll have to add more logic to handle that.
If i skip the variable declaration and value passing to variable trigger works flawlessly.FOR EACH LINE in my case
Edited code is posted below.
Create TRIGGER [dbo].[STOK_HARKETLERI_Insert]
ON [dbo].[STOK_HAREKETLERI]
FOR INSERT
AS
BEGIN
Declare #counter int
select #counter = COUNT(sth_tip) from STOK_HAREKETLERI
where sth_evraktip = 6
and sth_tip = (select sth_tip from inserted i)
and sth_miktar =(select sth_miktar from inserted)
and sth_stok_kod =(select sth_stok_kod from inserted)
and sth_tarih = (select sth_tarih from inserted)
and sth_RECno < (select sth_RECno from inserted)
if (#counter>=1)
begin
rollback
RAISERROR ('Record already exists', 17, -1) with log
end
END

T - SQL statement IF EXIST SELECT and INSERT

How can I make this possible..really need advice? I want to get the id where my condition is met, then used it in my queries.
IF EXISTS (Select sn_id as snid FROM device.sn WHERE dname_id = 62 and sn_value = '123415')
BEGIN
SELECT MAX(id) AS maxid FROM device.list
INSERT INTO parts (sn_id,device_id) VALUES (snid, maxid)
END
ELSE
BEGIN
PRINT 'id does not exist'
return
END
You can use variables to store the results from the two queries and then use those values in your INSERT statement.
If you're using Microsoft SQL Server then the following may work (but there may be superficial syntax errors as it hasn't been tested). Note that I've assumed the type of your columns is int.
DECLARE #snid int
SET #snid = NULL
Select #snid = sn_id FROM device.sn WHERE dname_id = 62 and sn_value = '123415'
IF #snid IS NULL
BEGIN
PRINT 'id does not exist'
END
ELSE
BEGIN
DECLARE #maxid int
SELECT #maxid = MAX(id) AS maxid FROM device.list
INSERT INTO parts (sn_id,device_id) VALUES (#snid, #maxid)
END
In SQLServer. This script at first insert records and after checks count of the inserted rows
INSERT INTO parts (sn_id, device_id)
SELECT sn_id, (SELECT MAX(id) FROM device.list)
FROM device.sn
WHERE dname_id = 62 and sn_value = '123415'
IF ##ROWCOUNT = 0 PRINT 'id does not exist'
Declare #snid int=null
Declare #maxid int=1 -- if no value exists in device.list table
set #snid = (select sn_id from device.sn WHERE dname_id = 62 and sn_value = '123415')
set #maxid = (select MAX(id) AS maxid FROM device.list)
if #snid is not null
Begin
insert into parts(sn_id,device_id)
values(#snid,#maxid)
End
else
Begin
print 'ID does not exist'
End

Resources