SQL partial update of columns based on case statement? - sql

Recently I have had to do a few variable updates to a table, and although I am aware of the MERGE statement (although need to catch up on all of that!), I also performed the following statement to optional update a table and wish to check if this is "a good idea" or has some hidden consequences that I not aware of.
So in my case, I pass a primary key to a table, however depending on if parameters passed are null or not, I update the column.. obviously if you had to ensure a forceful update (of a status etc.) then you would just update the column.. this is to save having multiple "IF / THEN" type structures..
create procedure sp_myprocedure
as
#id bigint,
#field1 int = null,
#field2 varchar(255) = null,
#field3 char(1) = null
begin
update my_table
set
field1 = case when #field1 is not null then #field1 else field1 end,
field2 = case when #field2 is not null then #field2 else field2 end,
field3 = case when #field3 is not null then #field3 else field3 end,
where
id = #id
end
Just after some thoughts of the above or is it best to pursue the MERGE statement for scenarios like the above?
Many thanks in advance,

This is fine although it can be written in a cleaner way.
update my_table
set
field1 = coalesce (#field1,field1)
,field2 = coalesce (#field2,field2)
,field3 = coalesce (#field3,field3)
where
id = #id and coalesce(#field1,#field2,#field3) is not null
You can also move the coalesce(#field1,#field2,#field3) is not null to a wrapper block
if coalesce(#field1,#field2,#field3) is not null
begin
update my_table
set
field1 = coalesce (#field1,field1)
,field2 = coalesce (#field2,field2)
,field3 = coalesce (#field3,field3)
where
id = #id
end
MERGE statement is not relevant here.
With MERGE the decision is if to INSERT, UPDATE or DELETE a record base on the non-existent/existent of a record with the same merge keys in the source/target table.
In your case it is always UPDATE.

create procedure sp_myprocedure
#id bigint,
#field1 int = null,
#field2 varchar(255) = null,
#field3 char(1) = null
as
begin
IF coalesce(#field1,#field2,#field3) is not null
update dbo.my_table
set
field1 = coalesce (#field1,field1),
field2 = coalesce (#field2,field2),
field3 = coalesce (#field3,field3)
where id = #id
END

Responding to the answer by #Dudu Markovitz:
MERGE statement is not relevant here.
I disagree , I think MERGE is entirely relevant here.
The idea is to create a source table expression using the parameter values with which to update the target table:
MERGE my_table T
USING ( VALUES ( #id, #field1, #field2, #field3 ) )
AS S ( id, field1, field2, field3 )
ON T.id = S.id
WHEN MATCHED THEN
UPDATE
SET field1 = COALESCE( S.field1, T.field1 ),
field2 = COALESCE( S.field2, T.field2 ),
field3 = COALESCE( S.field3, T.field3 );
Of course, if there was a single table-valued parameter (as arguable there should be) then the relevance of MERGE is even more obvious.

Related

sql atomic exists/insert - multiple unique fields

Ok, so I've learned from other posts/sites, that the following style of query will execute atomically:
INSERT INTO Foo(field1, field2)
SELECT #field1, #field2
WHERE NOT EXISTS (SELECT * FROM Foo
WHERE Field1 = #field1
OR Field2 = #field2)
This would be used to satisfy a design requirement that both field1 and field2 be unique. So far, all is fine and dandy.
BUT, in the case of such a record already existing (where the query inserts 0 rows) what if I want to know WHICH field already had a record with the same value. (i.e., which of the following statements are true?
there was already an existing record where Field1 = #field1
there was already an existing record where Field2 = #field2
both of the above
You would need to know which field is at fault in order to show an error message that tells the user which field they need to change to a unique value. I could always check with a second query, but that would not be atomic. The data may have changed between the attempted insert and the 2nd query to determine which field was at fault, and the error message may not reflect the actual state of the DB.
Any ideas on how to handle this case?
Assumptions:
You expect most INSERTs to succeed.
You have a unique contraint such that a bad INSERT will fail.
You aren't concerned about matching NULLs.
set transaction isolation level serializable;
begin transaction;
begin try
insert into Foo ( Field1, Field2 ) values ( #Field1, #Field2 );
end try
begin catch
-- Really ought to confirm that the error was the expected one here.
select
#Field1Match = case when Field1 = #Field1 then Cast( 1 as Bit ) else Cast( 0 as Bit ) end,
#Field2Match = case when Field2 = #Field2 then Cast( 1 as Bit ) else Cast( 0 as Bit ) end
from Foo
where Field1 = #Field1 or Field2 = #Field2;
end catch;
commit transaction;
Based on my research (here & here), unless you have a very high confidence that over 95% of the inserts will succeed, I would use the opposite approach as HABO. Borrowing from the same assumption that you aren't concerned about matching NULLs:
DECLARE #Col1Match VARCHAR(20), #Col2Match VARCHAR(20), #msg NVARCHAR(4000);
SET ANSI_WARNINGS OFF;
BEGIN TRANSACTION;
SELECT #Col1Match = MAX(CASE Field1 WHEN #Field1 THEN 'Field1 exists:' END),
#Col2Match = MAX(CASE Field2 WHEN #Field2 THEN 'Field2 exists:' END)
FROM dbo.Foo WITH (HOLDLOCK)
WHERE Field1 = #Field1 OR Field2 = #Field2;
IF #Col1Match IS NULL AND #Col2Match IS NULL
BEGIN
INSERT dbo.Foo(Field1, Field2) SELECT #Field1, #Field2;
END
ELSE
BEGIN
SET #msg = LTRIM(COALESCE(' ' + #Col1Match + ' (' + #Field1 + ')', '')
+ COALESCE(' ' + #Col2Match + ' (' + #Field2 + ')', ''));
END
COMMIT TRANSACTION;
IF #msg IS NOT NULL
BEGIN
RAISERROR(#msg, 11, 1);
END

Sql If statement based on COUNT return

Basically, I am looking to modify a stored procedure that I have in my database already that updates a table's records (table x) with data coming from a web application. After updating table x, I want to develop that stored procedure to check table x to see if there exist anymore records for that instance of the primary key where column_z is null. Basically, something like
ALTER PROCEDURE [cred].[UpdateTablex]
(#field0 int,
#field1 int,
#field2 int,
#field3, int,
#field4 VARCHAR(100),
#field5 datetime,
#field6 datetime)
AS
UPDATE tablex
SET field4 = #field4,
field6 = #field6
WHERE field1 = #field1 AND
field2 = #field2 AND
field3 = #field3 AND
field0 = #field0 AND
field5 = #field5;
The rest of this will be psuedocode for my idea and the way I thought it might be developed
IF ((SELECT COUNT(field0) FROM tablex WHERE field6 is null AND field2 = #field2
AND field3 = #field3 AND field5 = #field5) equals 0)
exec cred.Demobilize(#field0, #field1);
Or simply, if that Select statement returns any results indicating that field6 is null anywhere then we do nothing. Obviously that would mean then that if the Select statement returns nothing, then I want to execute another stored procedure.
This is probably something simple so please forgive me as I'm kinda new to certain types of SQL syntax and usage, this being one of them.
Also, could anybody point me in a proper direction to further educate myself on topics like this?
Thank you.
You can do the if without using a variable:
IF (0 = (SELECT COUNT(field0)
FROM tablex
WHERE field6 is null AND field2 = #field2 AND field3 = #field3 AND field5 = #field5
)
)
begin
exec cred.Demobilize(#field0, #field1);
end;
In practice, I think this would more commonly be written using not exists:
IF (not exists (SELECT 1
FROM tablex
WHERE field6 is null AND field2 = #field2 AND field3 = #field3 AND field5 = #field5
)
)
begin
exec cred.Demobilize(#field0, #field1);
end;
The not exists version can be faster, because the first row encountered will satisfy the condition clause. The count(*) version has to find all matching rows and count them.

SQL Server - check input parameter for null or zero

I have a stored procedure for sql server 2008 like this:
create procedure test_proc
#someval int,
#id int
as
update some_table
set some_column = ISNULL(#someval, some_column)
where id = #id
go
If the parameter #someval is NULL, this SP will just use the existing value in some_column.
Now I want to change this behaviour such that if value for #someval is 0, a NULL is stored in some_column otherwise it behave just the way it is doing now.
So I am looking for something like:
if #someval == 0
set some_column = NULL
else
set some_column = ISNULL(#someval, some_column)
I don't have the option to create a varchar #sql variable and call sq_executesql on it (at least that is the last thing I want to do). Any suggestions on how to go about doing this?
You can do this using the CASE expression. Something like this:
update some_table
set some_column = CASE WHEN #someval = 0 THEN NULL
WHEN #someval IS NULL THEN somcolumn
ELSE #someval -- the default is null if you didn't
-- specified one
END
where id = #id
something like this?
create procedure test_proc
#someval int,
#id int
as
update some_table
set some_column = CASE
WHEN #someval = 0 THEN NULL
ELSE ISNULL(#someval, some_column) END
where id = #id
go
I think it's a really bad idea - I'd suggest that if someone wants to store a NULL, they really shouldn't have to pass some other magical value to cause it to happen. However, let's show how it can be done:
update some_table
set some_column = CASE WHEN #someVal = 0 THEN NULL ELSE ISNULL(#someval, some_column) END
where id = #id
Given the simplicity of the stored procedure in your question, of course, the whole matter can be cleared up by not calling the stored procedure if you don't want to alter some_column. I'd imagine that your real procedure is more complex. Instead, what I'd do is have:
create procedure test_proc
#someval int,
#someval_specified bit,
#id int
as
update some_table
set some_column = CASE WHEN #someval_specified = 1 THEN #someval ELSE some_column END
where id = #id
And now NULL means NULL, 0 means 0, etc.

tsql: can "returning select" come before "update"?

I want to write a t-sql stored procedure (aka sproc) which selects 3 columns from 'MyTable'. In addition, I want to update the table in the same sproc:
I select the third value from the table.
If it equals 'true', I want to update the relevant record in the table to 'false'
I wasn't sure what syntax should I use. Could you help me out?
ALTER procedure [dbo].[My_PROC]
#ID varchar(10)
AS
BEGIN
declare #Col3 bit;
set #Col3 = select Col3
from dbo.MyTable with (nolock)
where #ID = ID
if #Col3 = 'true'
update dbo.dbo.MyTable set col3 = 'false'
where #ID = ID
select Col1,
Col2,
Col3
from dbo.MyTable table with (nolock) where #ID = ID,
table.Col1,
table.Col2,
#Col3
END
edit: I want to return the original Col3 (not the updated value).
Use:
ALTER procedure [dbo].[My_PROC]
#ID varchar(10)
AS
BEGIN
SELECT t.col1,
t.col2,
t.col3
FROM dbo.MyTable AS t WITH (NOLOCK)
WHERE t.id = #ID
-- No need for an IF statement to determine updating...
UPDATE dbo.dbo.MyTable
SET col3 = 'false'
WHERE id = #ID
AND t.col3 = 'true'
END
I don't know what you're intending for the final SELECT, but I can update it once I understand what you intended.
If you are using SQL Server 2005 or later, you can use an OUTPUT clause to output the original value if it was actually updated by the query. OUTPUT won’t give you the original value if the row is not updated by the query, however.
declare #t table (
ID int,
tf bit
);
insert into #t values
(1,0),
(2,1),
(3,0);
declare #ID int = 2;
select * from #t
update #t set
tf = 0
output deleted.ID, deleted.tf
where ID = #ID;
select * from #t;

Insert default value when parameter is null

I have a table that has a column with a default value:
create table t (
value varchar(50) default ('something')
)
I'm using a stored procedure to insert values into this table:
create procedure t_insert (
#value varchar(50) = null
)
as
insert into t (value) values (#value)
The question is, how do I get it to use the default when #value is null? I tried:
insert into t (value) values ( isnull(#value, default) )
That obviously didn't work. Also tried a case statement, but that didn't fair well either. Any other suggestions? Am I going about this the wrong way?
Update: I'm trying to accomplish this without having to:
maintain the default value in multiple places, and
use multiple insert statements.
If this isn't possible, well I guess I'll just have to live with it. It just seems that something this should be attainable.
Note: my actual table has more than one column. I was just quickly writing an example.
Christophe,
The default value on a column is only applied if you don't specify the column in the INSERT statement.
Since you're explicitiy listing the column in your insert statement, and explicity setting it to NULL, that's overriding the default value for that column
What you need to do is "if a null is passed into your sproc then don't attempt to insert for that column".
This is a quick and nasty example of how to do that with some dynamic sql.
Create a table with some columns with default values...
CREATE TABLE myTable (
always VARCHAR(50),
value1 VARCHAR(50) DEFAULT ('defaultcol1'),
value2 VARCHAR(50) DEFAULT ('defaultcol2'),
value3 VARCHAR(50) DEFAULT ('defaultcol3')
)
Create a SPROC that dynamically builds and executes your insert statement based on input params
ALTER PROCEDURE t_insert (
#always VARCHAR(50),
#value1 VARCHAR(50) = NULL,
#value2 VARCHAR(50) = NULL,
#value3 VARCAHR(50) = NULL
)
AS
BEGIN
DECLARE #insertpart VARCHAR(500)
DECLARE #valuepart VARCHAR(500)
SET #insertpart = 'INSERT INTO myTable ('
SET #valuepart = 'VALUES ('
IF #value1 IS NOT NULL
BEGIN
SET #insertpart = #insertpart + 'value1,'
SET #valuepart = #valuepart + '''' + #value1 + ''', '
END
IF #value2 IS NOT NULL
BEGIN
SET #insertpart = #insertpart + 'value2,'
SET #valuepart = #valuepart + '''' + #value2 + ''', '
END
IF #value3 IS NOT NULL
BEGIN
SET #insertpart = #insertpart + 'value3,'
SET #valuepart = #valuepart + '''' + #value3 + ''', '
END
SET #insertpart = #insertpart + 'always) '
SET #valuepart = #valuepart + + '''' + #always + ''')'
--print #insertpart + #valuepart
EXEC (#insertpart + #valuepart)
END
The following 2 commands should give you an example of what you want as your outputs...
EXEC t_insert 'alwaysvalue'
SELECT * FROM myTable
EXEC t_insert 'alwaysvalue', 'val1'
SELECT * FROM myTable
EXEC t_insert 'alwaysvalue', 'val1', 'val2', 'val3'
SELECT * FROM myTable
I know this is a very convoluted way of doing what you need to do.
You could probably equally select the default value from the InformationSchema for the relevant columns but to be honest, I might consider just adding the default value to param at the top of the procedure
Try an if statement ...
if #value is null
insert into t (value) values (default)
else
insert into t (value) values (#value)
As far as I know, the default value is only inserted when you don't specify a value in the insert statement. So, for example, you'd need to do something like the following in a table with three fields (value2 being defaulted)
INSERT INTO t (value1, value3) VALUES ('value1', 'value3')
And then value2 would be defaulted. Maybe someone will chime in on how to accomplish this for a table with a single field.
Probably not the most performance friendly way, but you could create a scalar function that pulls from the information schema with the table and column name, and then call that using the isnull logic you tried earlier:
CREATE FUNCTION GetDefaultValue
(
#TableName VARCHAR(200),
#ColumnName VARCHAR(200)
)
RETURNS VARCHAR(200)
AS
BEGIN
-- you'd probably want to have different functions for different data types if
-- you go this route
RETURN (SELECT TOP 1 REPLACE(REPLACE(REPLACE(COLUMN_DEFAULT, '(', ''), ')', ''), '''', '')
FROM information_schema.columns
WHERE table_name = #TableName AND column_name = #ColumnName)
END
GO
And then call it like this:
INSERT INTO t (value) VALUES ( ISNULL(#value, SELECT dbo.GetDefaultValue('t', 'value') )
This is the best I can come up with. It prevents sql injection uses only one insert statement and can ge extended with more case statements.
CREATE PROCEDURE t_insert ( #value varchar(50) = null )
as
DECLARE #sQuery NVARCHAR (MAX);
SET #sQuery = N'
insert into __t (value) values ( '+
CASE WHEN #value IS NULL THEN ' default ' ELSE ' #value ' END +' );';
EXEC sp_executesql
#stmt = #sQuery,
#params = N'#value varchar(50)',
#value = #value;
GO
chrisofspades,
As far as I know that behavior is not compatible with the way the db engine works,
but there is a simple (i don't know if elegant, but performant) solution to achive your two objectives of DO NOT
maintain the default value in multiple places, and
use multiple insert statements.
The solution is to use two fields, one nullable for insert, and other one calculated to selections:
CREATE TABLE t (
insValue VARCHAR(50) NULL
, selValue AS ISNULL(insValue, 'something')
)
DECLARE #d VARCHAR(10)
INSERT INTO t (insValue) VALUES (#d) -- null
SELECT selValue FROM t
This method even let You centralize the management of business defaults in a parameter table, placing an ad hoc function to do this, vg changing:
selValue AS ISNULL(insValue, 'something')
for
selValue AS ISNULL(insValue, **getDef(t,1)**)
I hope this helps.
The best option by far is to create an INSTEAD OF INSERT trigger for your table, removing the default values from your table, and moving them into the trigger.
This will look like the following:
create trigger dbo.OnInsertIntoT
ON TablenameT
INSTEAD OF INSERT
AS
insert into TablenameT
select
IsNull(column1 ,<default_value>)
,IsNull(column2 ,<default_value>)
...
from inserted
This makes it work NO MATTER what code tries to insert NULLs into your table, avoids stored procedures, is completely transparent, and you only need to maintain your default values in one place, namely this trigger.
You can use default values for the parameters of stored procedures:
CREATE PROCEDURE MyTestProcedure ( #MyParam1 INT,
#MyParam2 VARCHAR(20) = ‘ABC’,
#MyParam3 INT = NULL)
AS
BEGIN
-- Procedure body here
END
If #MyParam2 is not supplied, it will have the 'ABC' value...
You can use the COALESCE function in MS SQL.
INSERT INTO t ( value ) VALUES( COALESCE(#value, 'something') )
Personally, I'm not crazy about this solution as it is a maintenance nightmare if you want to change the default value.
My preference would be Mitchel Sellers proposal, but that doesn't work in MS SQL. Can't speak to other SQL dbms.
Don't specify the column or value when inserting and the DEFAULT constaint's value will be substituted for the missing value.
I don't know how this would work in a single column table. I mean: it would, but it wouldn't be very useful.
Hope To help to -newbie as i am- Ones who uses Upsert statements in MSSQL.. (This code i used in my project on MSSQL 2008 R2 and works simply perfect..May be It's not Best Practise.. Execution time statistics shows execution time as 15 milliSeconds with insert statement)
Just set your column's "Default value or binding" field as what you decide to use as default value for your column and Also set the column as Not accept null values from design menu and create this stored Proc..
`USE [YourTable]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROC [dbo].[YourTableName]
#Value smallint,
#Value1 bigint,
#Value2 varchar(50),
#Value3 varchar(20),
#Value4 varchar(20),
#Value5 date,
#Value6 varchar(50),
#Value7 tinyint,
#Value8 tinyint,
#Value9 varchar(20),
#Value10 varchar(20),
#Value11 varchar(250),
#Value12 tinyint,
#Value13 varbinary(max)
-- in my project #Value13 is a photo column which storing as byte array..
--And i planned to use a default photo when there is no photo passed
--to sp to store in db..
AS
--SET NOCOUNT ON
IF #Value = 0 BEGIN
INSERT INTO YourTableName (
[TableColumn1],
[TableColumn2],
[TableColumn3],
[TableColumn4],
[TableColumn5],
[TableColumn6],
[TableColumn7],
[TableColumn8],
[TableColumn9],
[TableColumn10],
[TableColumn11],
[TableColumn12],
[TableColumn13]
)
VALUES (
#Value1,
#Value2,
#Value3,
#Value4,
#Value5,
#Value6,
#Value7,
#Value8,
#Value9,
#Value10,
#Value11,
#Value12,
default
)
SELECT SCOPE_IDENTITY() As InsertedID
END
ELSE BEGIN
UPDATE YourTableName SET
[TableColumn1] = #Value1,
[TableColumn2] = #Value2,
[TableColumn3] = #Value3,
[TableColumn4] = #Value4,
[TableColumn5] = #Value5,
[TableColumn6] = #Value6,
[TableColumn7] = #Value7,
[TableColumn8] = #Value8,
[TableColumn9] = #Value9,
[TableColumn10] = #Value10,
[TableColumn11] = #Value11,
[TableColumn12] = #Value12,
[TableColumn13] = #Value13
WHERE [TableColumn] = #Value
END
GO`
With enough defaults on a table, you can simply say:
INSERT t DEFAULT VALUES
Note that this is quite an unlikely case, however.
I've only had to use it once in a production environment. We had two closely related tables, and needed to guarantee that neither table had the same UniqueID, so we had a separate table which just had an identity column, and the best way to insert into it was with the syntax above.
The most succinct solution I could come up with is to follow the insert with an update for the column with the default:
IF OBJECT_ID('tempdb..#mytest') IS NOT NULL DROP TABLE #mytest
CREATE TABLE #mytest(f1 INT DEFAULT(1), f2 INT)
INSERT INTO #mytest(f1,f2) VALUES (NULL,2)
INSERT INTO #mytest(f1,f2) VALUES (3,3)
UPDATE #mytest SET f1 = DEFAULT WHERE f1 IS NULL
SELECT * FROM #mytest
The pattern I generally use is to create the row without the columns that have default constraints, then update the columns to replace the default values with supplied values (if not null).
Assuming col1 is the primary key and col4 and col5 have a default contraint
-- create initial row with default values
insert table1 (col1, col2, col3)
values (#col1, #col2, #col3)
-- update default values, if supplied
update table1
set col4 = isnull(#col4, col4),
col5 = isnull(#col5, col5)
where col1 = #col1
If you want the actual values defaulted into the table ...
-- create initial row with default values
insert table1 (col1, col2, col3)
values (#col1, #col2, #col3)
-- create a container to hold the values actually inserted into the table
declare #inserted table (col4 datetime, col5 varchar(50))
-- update default values, if supplied
update table1
set col4 = isnull(#col4, col4),
col5 = isnull(#col5, col5)
output inserted.col4, inserted.col5 into #inserted (col4, col5)
where col1 = #col1
-- get the values defaulted into the table (optional)
select #col4 = col4, #col5 = col5 from #inserted
Cheers...
The easiest way to do this is to modify the table declaration to be
CREATE TABLE Demo
(
MyColumn VARCHAR(10) NOT NULL DEFAULT 'Me'
)
Now, in your stored procedure you can do something like.
CREATE PROCEDURE InsertDemo
#MyColumn VARCHAR(10) = null
AS
INSERT INTO Demo (MyColumn) VALUES(#MyColumn)
However, this method ONLY works if you can't have a null, otherwise, your stored procedure would have to use a different form of insert to trigger a default.
The questioner needs to learn the difference between an empty value provided and null.
Others have posted the right basic answer: A provided value, including a null, is something and therefore it's used. Default ONLY provides a value when none is provided. But the real problem here is lack of understanding of the value of null.
.