views:

67

answers:

4

I've got code similar to the following in a stored procedure that inserts a row into a table, I'd like to set the last column (FieldD) to @prmSomeValue unless it is null, otherwise just use the default value defined for that column.

IF (@prmSomeValue IS NULL)
   INSERT INTO MyTable (fieldA,FieldB,FieldC)  
      SELECT A,B,C 
      FROM MyOtherTable
ELSE
   INSERT INTO MyTable (fieldA,FieldB,FieldC,FieldD)  
      SELECT A,B,C,@prmSomeValue 
      FROM MyOtherTable

This works, but violates the DRY principle. I'm trying to find some way to do this with a single insert statement. Something along the lines of the following pseudocode.

   INSERT INTO MyTable (fieldA,FieldB,FieldC,FieldD)  
      SELECT A,B,C,ISNULL(@prmSomeValue,DEFAULT)
      FROM MyOtherTable

Anyone have any ideas?

Update - One more twist
The default constraint is not a literal value, but a function as shown below.

...DEFAULT (suser_sname()) FOR [FieldD]

Update
I finally punted and chose the lesser of evils and just copied the default value function into my query instead of falling through to the default configured for the column. I don't love it, but it gets the job done with less repetition in my query.

   INSERT INTO MyTable (fieldA,FieldB,FieldC,FieldD)  
      SELECT A,B,C,ISNULL(@prmSomeValue,**suser_sname()**)
      FROM MyOtherTable
A: 

This might work, depends on if you mean the default value defined in a default constraint, or in code? If "constraint" it fails, if "code" it works. Edit: you mean constraint. doh!

SELECT A,B,C,@prmSomeValue
      FROM MyOtherTable
      WHERE @prmSomeValue IS NOT NULL
UNION ALL
SELECT A,B,C,DEFAULT
      FROM MyOtherTable
      WHERE @prmSomeValue IS NULL

Which ever way you want to do it, specifying a column in the INSERT clause requires a value.

So your first solution is what you have do...

gbn
I meant the default constraint defined for the column. Like the first half of my IF statement would do. So this isn't going to work. Note:It isn't absolutely required to specify the column in the INSERT clause. I can rely on column ordering if it is necessary to make this work, although I prefer not to.
JohnFx
JohnFx, if you leave out the column list, you'll need to have the subsequent select have the same number of columns that match the table, otherwise you'll get: "Msg 213, Level 16, State 1, Line 1Column name or number of supplied values does not match table definition." Even if this did work, I would steal be very cautious about relying on column order (your instinct is right, IMHO).
Aaron Bertrand
+2  A: 

Since essentially this is what SQL Server is doing, you could do something like this to at least avoid two nearly identical statements (pseudo-code):

INSERT (columnA,B,C) ... ;

IF @prmSomeValue IS NOT NULL
    UPDATE ... ;

I don't think there is a way to COALESCE with the default value.

Aaron Bertrand
Interesting idea. Not sure if it is preferable, but definitely worth an up-vote.
JohnFx
Thanks JohnFx, I think it might be preferable once there is more than one optional column that is to be treated this way. Then you can say "IF @foo IS NOT NULL OR @bar IS NOT NULL, UPDATE SET foo = COALESCE(@foo, foo), bar = COALESCE(@bar, bar) WHERE" etc., instead of writing an insert statement for every possible combination.
Aaron Bertrand
Use ISNULL instead of COALESCE, COALESCE is intended for multiple arguments. I wouldn't go for field = parameter / field approaches, it kills SQL performance, making it harder to optimize queries and usage of indexes. A simple if-statement would be far more efficient.
Zyphrax
Agreed. However my situation only demands a single column at this time. Plus it gets a little more complicated because the real query also uses an OUTPUT INTO clause that I left out of my simplified example.
JohnFx
Zyphrax, I disagree about ISNULL(). I use COALESCE() always because I shouldn't have to change my code if I suddenly need an extra argument. Two other things against ISNULL(): it is not ANSI standard, and behaves differently than the same ISNULL() function in other languages (VB, Access) and thus can be confusing to users coming from those places.
Aaron Bertrand
+2  A: 

I would say your method is fine. A simple check followed by one insert. If you are worried about DRY, encapsulate the call so that it be called repeatedly.

I would say that inserts/updates on a db may be costly on some tables (depends on the design goal) so if you have to write extra code to to handle this scenario then I see no problem with the trade off.

Preet Sangha
+1, it's good to try and prevent duplicate blocks of code. However many programmers are trying to take their OO principles and coding practices to T-SQL. Often resulting in beautiful but inefficient queries. Sometimes it's great for performance to write 5 quite similar queries instead of 1 semi-dynamic one - Keep your code as is, it is centralized within one stored procedure, easy to maintain.
Zyphrax
Don't you know that they beat that stuff into our heads to the point where it physically hurts to have practically the same code duplicated on either end of a conditional? =) You could be right though, maybe I am over-thinking it.
JohnFx
When you only have A or B, then I agree that it might be fine as is. But what happens when you have four such columns? The number of possible INSERT statements you need based on which parameters are NULL very quickly explodes. Sometimes you have to balance efficiency and maintenance.
Aaron Bertrand
@aaron. Absolutley maintennance is paramount in most cases
Preet Sangha
A: 

Something like this might work (tho not very pretty):

INSERT INTO MyTable (fieldA,FieldB,FieldC,FieldD)  
SELECT A,B,C,
    case when @prmSomeValue is null 
then
        (SELECT text FROM syscomments WHERE id IN (SELECT cdefault FROM syscolumns
            WHERE id = object_id('MyTable') AND cdefault > 0))
    else @prmSomeValue
    end
FROM MyOtherTable
pjacko
What happens if the default value is actually a formula (such as GETDATE() or NEWID())? it will be interpreted as a string, leading either to an error or incorrect data, depending on the datatype of the column. Also don't you want to make sure you get the right column (there could be more than one column where cdefault > 0, leading to Msg 512 - subquery returned more than one value)? For SQL Server 2008, shouldn't you be using catalog views instead of deprecated system tables?
Aaron Bertrand
I was thinking about a similar approach, but was a little concerned about side-effects or permissions issues associated with using system tables like this, but I suppose it feels solid enough. Any caveats to offer on this technique?
JohnFx
Doh! One major problem with this approach. If the default is a function this just inserts the name of the function, in my case "getdate()". Nice try though.
JohnFx
Yes JohnFx, did you not see the caveats I posted?
Aaron Bertrand
Sorry, I wrote that before seeing your first comment. Sorry.
JohnFx