I have written a stored procedure that updates a table and then writes old records to a historical table, using OUTPUT deleted.*
I am now looking to update the procedure and include an #option parameter, which will be either 1 or 2. If the option is 1, the records will be updated in the target table. If it is 2, then they will be deleted from the table.
Is there any way to write in a conditional that will update or insert based on this input parameter? I can write it as
IF #option = 1
BEGIN
...updates...
END
IF #option = 2
BEGIN
...deletes...
END
but there are many fields being updated/deleted and the process repeats three more times, so I am trying to make it more concise.
I have tried doing (CASE #option WHEN 1 THEN UPDATE WHEN 2 THEN DELETE) table1 ... but that is incorrect SQL. I have considered using MERGE and including the conditional, something like WHEN MATCHED AND #option = 1 THEN but am getting syntax errors there. I am also not too familiar with MERGE so if anybody has any insight into this process, it would be greatly appreciated.
To clarify, my question is:
Is there any way to conditionally UPDATE or DELETE (or do other DML operations)?
MERGE works by inserting records that don't exist and updating those that do, which is a common scenario. Even so it's not much more concise than separate INSERT and UPDATE statements, since you still have to supply the statements separately (just wrapped inside a single MERGE statement.
Conditionally updating or deleting is not a common scenario in my experience, so there's not a concise SQL syntax to do so.
I would note that fields aren't specified in a DELETE so there is some difference in the syntax anyways. I think having an if block that separates the UPDATE and DELETE is going to be as concise as you can get.
Related
I have an update trigger in SQL Server and I want to remove this trigger and make update operation with a stored procedure instead of the trigger. But I have UPDATE(end_date) control in update trigger.
What can I use instead of below UPDATE(end_date) control? How can I compare old and new end_dates in stored procedure efficiently?
Update trigger
ALTER TRIGGER [dbo].[trig_tbl_personnel_car_update]
ON [dbo].[tbl_personnel_cars]
FOR UPDATE
AS
IF (UPDATE(end_date))
UPDATE pc
SET pc.owner_changed = 1
FROM tbl_personnel_cars pc, inserted i
WHERE pc.pk_id = i.pk_id
Sample updated script in stored procedure
ALTER PROCEDURE [dbo].[personnel_car_update]
(#PkId INT)
UPDATE tbl_personnel_cars
SET end_date = GETDATE()
WHERE pk_id = #PkId
I update tbl_personnel_cars table inside many stored procedures like this. How can I update this table like trigger does instead of update trigger?
I tried below codes to get old and new end_dates but I can't.
Sample updated script in stored procedure:
ALTER PROCEDURE [dbo].[personnel_car_update]
(#PkId INT)
UPDATE tbl_personnel_cars
SET end_date = GETDATE()
WHERE pk_id = #PkId
EXEC update_operation_sp_instead_trigger #PkId
ALTER PROCEDURE [dbo].[update_operation_sp_instead_trigger]
(#PkId INT)
UPDATE pc
SET pc.owner_changed = 1
FROM tbl_personnel_cars pc
JOIN tbl_personnel_cars pc2 ON pc.pk_id = pc2.pk_id
WHERE pc.end_date <> pc2.end_date
And last question. Is it a correct choice to use stored procedure instead of trigger where the table is updated?
Firstly, I want to clarify a misunderstanding you appear to have about the UPDATE function in Triggers. UPDATE returns a boolean result based on if the column inside the function was assigned a value in the SET clause of the UPDATE statement. It does not check if that value changed. This is both documented feature, and is stated to be "by-design".
This means that if you had a TRIGGER with UPDATE(SomeColumn) the function would return TRUE for both of these statements, even though no data was changed:
UPDATE dbo.SomeTable
SET SomeColumn = SomeColumn;
UPDATE ST
SET SomeColumn = NULL
FROM dbo.SomeTable ST
WHERE SomeColumn IS NULL;
If, within a TRIGGER, you need to check if a value has changed you need to reference the inserted and deleted pseudo-tables. For non-NULLable columns equality (=) can be checked, however, for NULLable columns you'll also need to check if the column changed from/to NULL. In the latest version of the data engine (at time of writing) IS DISTINCT FROM makes this far easier.
Now onto the problem you are actually trying to solve. It looks like you are, in truth, overly complicated this. Firstly, you are setting the value to GETDATE so it is almost certainly impossible that the column will be set to be the same value it already set to; you have a 1/300 second window to do the same UPDATE twice, and if you add IO operations, connection timing, etc, that basically makes hitting that window twice impossible.
For what you want, just UPDATE both columns in your procedure's definition:
ALTER PROCEDURE [dbo].[personnel_car_update] #PkId int AS --This has a trailing comma, which is invalid syntax. The parathesis are also not needed; SP's aren't functions. You were also missing the AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.tbl_personnel_cars --Always schema qualify
SET end_date = GETDATE(),
owner_changed = 1
WHERE pk_id = #PkId;
END;
Larnu gave you a great answer about the stored procedure logic, so I want to answer your question about "Is it a correct choice to use stored procedure instead of trigger where the table is updated?"
The upsides of DML triggers are following in my opinion:
When you have a lot of places that manipulate a table, and there need to be some common logic performed together with this manipulation like audit / logging, trigger can solve it nicely because you don't have to repeat your code in a lot of places
Triggers can prevent "stupid" actions like: DELETEs / UPDATEs without WHERE by performing some specific validation etc. They can also make sure you are setting all mandatory fields (for example change date) when performing adhoc updates
They can simplify quick and dirty patches when you can concentrate your logic to one place instead of X number of procedures / compiled code.
Downside of triggers is performance in some cases, as well as some more specific problems like output clause not working on the triggered tables and more complicated maintenance.
It's seldom you can't solve these issues with other non-trigger solutions, so i'd say if your shop already uses triggers, fine, but if they don't, then there's seldom a really good reason to start either
I have a stored procedure that runs the inputs through a series of validation queries, and kicks out a parameter detailing if record will be saved with "complete" or "incomplete" information. Then, we start a transaction, save the data to a couple of tables, commit. Done.
Essentially this:
EXEC dbo.ValidationProcedure --(sets output params)
BEGIN TRANSACTION
MERGE TABLE A (uses output params)
MERGE TABLE B
COMMIT TRANSACTION
In addition to this, we have a view that has the same validation queries, written such that it returns all occurrences of invalid data within our tables; the queries in the procedure version merely check the inputs provided (both the validation procedure and view look at data for both TABLE A AND B). This view returns the exact error code, message, recordID, etc. and we use it audit queries, to get error messages on displaying the input form, yada yada yada....
My issue is that we have the same logic in multiple places, which I hate. My question, and potential solution to this, would it be bad practice/poor design, to remove the validation procedure all together, and do the following:
BEGIN TRANSACTION
MERGE TABLE A
MERGE TABLE B
IF EXISTS (SELECT * FROM ValidationView T WHERE T.ID = #ID)
BEGIN
UPDATE TABLE A SET Incomplete = 1 WHERE [ID] = #ID;
END
COMMIT TRANSACTION
I thought of doing it this way a while ago, but, I was not fond of affecting the same row twice. It seemed wasteful, unnecessary, and an incorrect way to go about it; I'm hoping I am wrong about this thinking. But now, I'm having second thoughts and would like to know if we can unload some code overhead by going with the second example, or should we keep the course and maintain both the validation procedure and validation view.
I have a huge SQL script with many batches (using GO).
On certain conditions, I want to stop the whole execution of this script.
I tried using NOEXEC, but I keep on getting invalid object errors, since my script includes creating and adding data to new tables and columns.
I do not want to use RAISERROR, is there any other way to do this?
There are no good solutions to this problem. One way you can share data between batches is tables(persistent or temporary). So you can have some logic that depends on some state of that table or value of particular columns in that table like this:
--CREATE TABLE debug(col int)
--INSERT INTO debug VALUES(1)
--DELETE FROM debug
SELECT 1
GO
SELECT 2
SELECT 3
GO
IF EXISTS(SELECT * FROM debug)
SELECT 6
GO
Just add IF EXISTS(SELECT * FROM debug) this line to every place in script that you want to skip and depending on the fact that the table has some rows or not those code blocks will execute or not.
I have a sql statement that first updates, then selects:
UPDATE myTable
SET field1=#someValue
WHERE field2=#someValue2
SELECT 1 returnValue
The process that consumes the reults of this statement is expecting a single result set, simple enough.
The problem arises because an update trigger was added to the table that produces a result set, i.e. it selects like so:
SELECT t_field1, t_field2, t_field3 FROM t_table
The obvious solution is to split up the statments. Unfortunatley, the real world implementation of this is complex and to be avoided if possible. The trigger is also nessecary and cannot be disabled.
Is there a way to supress the results from the update, returning only the value from the select statement?
The ability to return result sets from triggers is deprecated in SQL Server 2012 and will be removed in a future version (maybe even in SQL Server 2016, but probably in the next version). Change your trigger to return the data in some other way. If it is needed just for debugging, use PRINT instead of SELECT. If it is needed for some other reasons, insert the data into a temporary table and perform the SELECT from the calling procedure (only when needed).
I'm using SQL Server 2012, and I'm debugging a store procedure that do some INSERT INTO #temporal table SELECT.
There is any way to view the data selected in the command (the subquery of the insert into?)
There is any way to view the data inserted and/or the temporal table where the insert maked the changes?
It doesn't matter if is the total rows, not one by one
UPDATE:
Requirements from AT Compliance and Company Policy requires that any modification can be done in the process of test and it's probable this will be managed by another team. There is any way to avoid any change on the script?
The main idea is that the AT user check in their workdesktop the outputs, copy and paste them, without make any change on environment or product.
Thanks and kind regards.
If I understand your question correctly, then take a look at the OUTPUT clause:
Returns information from, or expressions based on, each row affected
by an INSERT, UPDATE, DELETE, or MERGE statement. These results can be
returned to the processing application for use in such things as
confirmation messages, archiving, and other such application
requirements.
For instance:
INSERT INTO #temporaltable
OUTPUT inserted.*
SELECT *
FROM ...
Will give you all the rows from the INSERT statement that was inserted into the temporal table, which were selected from the other table.
Is there any reason you can't just do this: SELECT * FROM #temporal? (And debug it in SQL Server Management Studio, passing in the same parameters your application is passing in).
It's a quick and dirty way of doing it, but one reason you might want to do it this way over the other (cleaner/better) answer, is that you get a bit more control here. And, if you're in a situation where you have multiple inserts to your temp table (hopefully you aren't), you can just do a single select to see all of the inserted rows at once.
I would still probably do it the other way though (now I know about it).
I know of no way to do this without changing the script. Howeer, for the future, you should never write a complex strored proc or script without a debug parameter that allows you to put in the data tests you will want. Make it the last parameter with a default value of 0 and you won't even have to change your current code that calls the proc.
Then you can add statements like the below everywhere you will want to check intermediate results. Further in debug mode you might always rollback any transactions so that a bug will not affect the data.
IF #debug = 1
BEGIN
SELECT * FROM #temp
END