Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2008 Forums
 Transact-SQL (2008)
 How to always select one row (even if NOT EXISTS)

Author  Topic 

Kristen
Test

22859 Posts

Posted - 2010-02-23 : 05:02:09
I want to select a single row from a table (using PK ID), but if does not exist I still want a row

SELECT @Action = CASE WHEN MyPK_ID IS NULL THEN 'Add' ELSE 'Update' END,
@MyPK_ID = CASE WHEN MyPK_ID IS NULL THEN -1 ELSE MyPK_ID END
FROM
(
SELECT 1
) AS X
LEFT OUTER JOIN MyTable
ON MyPK_ID = @MyPK_ID

maybe that is a daft way of doing it? I was trying to keep all the logic in the SELECT in one place. Alternative is to split the logic over two places:

SELECT @Action = 'Update',
@MyPK_ID = MyPK_ID
FROM MyTable
WHERE MyPK_ID = @MyPK_ID
IF @@ROWCOUNT = 0
BEGIN
SELECT @Action = 'Add',
@MyPK_ID = -1
END

but my thinking is the logic in one place is better for maintenance (less chance of missing updating both parts of the SELECT logic).

But my first example looks really terrible!

Any suggestions?

madhivanan
Premature Yak Congratulator

22864 Posts

Posted - 2010-02-23 : 06:24:42
Make use of this hint

select
case
when count(*)>=1 then 'update'
else
'add'
end
from MyTable
where MyPK_ID =@MyPK_ID


Madhivanan

Failing to plan is Planning to fail
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2010-02-23 : 06:35:27
I like the COUNT(*), thanks Madhi.

I also need some columns from MyTable if it exists. Can I do that somehow?

Perhaps using ROW_NUMBER() or OUTPUT (in some crafty way)?
Go to Top of Page

madhivanan
Premature Yak Congratulator

22864 Posts

Posted - 2010-02-23 : 07:43:59
quote:
Originally posted by Kristen

I like the COUNT(*), thanks Madhi.

I also need some columns from MyTable if it exists. Can I do that somehow?

Perhaps using ROW_NUMBER() or OUTPUT (in some crafty way)?


If you need other columns if there exists data for the input, I think it is better you do it in two-steps

In Your example you used variables that get updated. How do you want other columns along with them?

Madhivanan

Failing to plan is Planning to fail
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2010-02-23 : 09:38:21
"How do you want other columns along with them?"

In variables also.

The sort of scenario is:

On a web page allow the user to Vote and enter a comment. They are voting on whether they like a song. If they view that Song again their previous Vote / Comment is shown and can be changed. I need to prevent them voting 2+ times.

VOTE table has PK and a unique alternative reference (User_ID + Song_ID).

If user has not Voted yet then VOTE record will not exist.

User may try to hack the voting system and not submit their Vote_ID from previous vote - so I need to also check User_ID + Song_ID.

If I find a row (by either means) I need to return Vote_ID (NULL = New), User_ID and Song_ID - as variables.

I always have User_ID. I may have Vote_ID or Song_ID, or both (need to check they correspond).

I will use those to further test for vote rigging etc.
Go to Top of Page

madhivanan
Premature Yak Congratulator

22864 Posts

Posted - 2010-02-23 : 10:24:03
Will this help you?

SELECT
@Action = case when counting>=1 then 'Update' else 'Add',
@MyPK_ID = case when counting>=1 then @MyPK_ID else -1,
.
.
.

(
select count(*) as counting
FROM MyTable
WHERE MyPK_ID = @MyPK_ID
) as t


Madhivanan

Failing to plan is Planning to fail
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2010-02-23 : 10:56:41
Not quite, because I may not have been given @MyPK_ID in the Sproc parameters.

I currently have something like this:

SELECT @UpsertFlag = 1, -- Use UpSert to save
@TrackID = NULL, -- Do NOT change existing value (during save)
@VoteID = VoteID,
@SongID = SongID,
@ErrorNo = CASE WHEN ErrorNo = 0 THEN @ErrorNo ELSE ErrorNo END,
@strErrMsg = @strErrMsg
+ CASE WHEN ErrorNo = 0 THEN '' ELSE 'Error in uspMySproc[' + CONVERT(varchar(20), ErrorNo) + '] ' END
FROM
(
SELECT VoteID,
[ErrorNo] = CASE WHEN (@SongID IS NOT NULL AND SongID <> @SongID)
OR (@VoteID IS NOT NULL AND VoteID <> @VoteID)
THEN 1
ELSE 0
END
FROM dbo.VoteLog
WHERE (SongID = @SongID OR VoteID = @VoteID)
AND UserID = @UserID
) AS X
IF @@ROWCOUNT = 0
BEGIN
SELECT @UpsertFlag = 0, -- Insert new record
@VoteID = NULL
IF @SongID IS NULL
BEGIN
SELECT @ErrorNo = -3,
@strErrMsg = @strErrMsg + 'SQL Error(3) @SongID=[' + CONVERT(varchar(20), @SongID) + ']. '
END
END

plus some code to handle @@ROWCOUNT > 1

I was looking if I could get it into one statement so the logic was all in one place ...

Perhaps I could use MAX(@VoteID) and then LEFT JOIN that to the VoteLog table to have the row as well?
Go to Top of Page

madhivanan
Premature Yak Congratulator

22864 Posts

Posted - 2010-02-24 : 02:07:55
[code]

SELECT @UpsertFlag = case when counting>=1 then 1 else 0 end, -- Use UpSert to save
@TrackID = NULL, -- Do NOT change existing value (during save)
@VoteID = case when counting>=1 then VoteId else NULL end,
@SongID = case when counting>=1 then SongID else NULL end,
@ErrorNo =
case when counting>=1 then
CASE WHEN ErrorNo = 0 THEN @ErrorNo ELSE ErrorNo END
else -3 end,
@strErrMsg =
case when counting>=1 then @strErrMsg
+ CASE WHEN ErrorNo = 0 THEN '' ELSE 'Error in uspMySproc[' + CONVERT(varchar(20), ErrorNo) + '] ' END
else 'SQL Error(3) @SongID=[' + CONVERT(varchar(20), @SongID) + ']. ' end
FROM
(
SELECT max(VoteID) as VoteId,
[ErrorNo] = MAX(CASE WHEN (@SongID IS NOT NULL AND SongID <> @SongID)
OR (@VoteID IS NOT NULL AND VoteID <> @VoteID)
THEN 1
ELSE 0
END),
count(*) as counting
FROM dbo.VoteLog
WHERE (SongID = @SongID OR VoteID = @VoteID)
AND UserID = @UserID
) AS X
[/code]

But I dont why do you want to do in a single code. I think it may get complicated when you add some more logic in the future

Madhivanan

Failing to plan is Planning to fail
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2010-02-24 : 02:38:30
I think you are right Madhi. I just think that if code is in two places there is more chance of it becoming "different" during maintenance

What do you think about this approach?

SELECT @UpsertFlag = CASE WHEN VoteID IS NULL Counting=0 THEN 0 ELSE 1 END, -- 0=Insert, 1=UpSert
@TrackID = CASE WHEN VoteID IS NULL Counting=0 THEN @TrackID ELSE NULL END, -- Do NOT change existing value (during save)
@VoteID = CASE WHEN VoteID IS NULL Counting=0 THEN @VoteID ELSE VoteID END,
@SongID = CASE WHEN VoteID IS NULL Counting=0 THEN @SongID ELSE SongID END,
@ErrorNo = @ErrorNo
+ CASE WHEN VoteID IS NULL Counting=0 THEN ''
ELSE

CASE WHEN (@SongID IS NOT NULL AND SongID <> @SongID)
OR (@VoteID IS NOT NULL AND VoteID <> @VoteID)
THEN 1
ELSE 0
END
END,
@strErrMsg = ... CASE WHEN [counting] > 1 THEN 'Multiple records found' ELSE '' END ...
FROM
(
SELECT [MAX_VoteID] = MAX(VoteID),
[counting] = COUNT(*)

FROM dbo.VoteLog
WHERE (SongID = @SongID OR VoteID = @VoteID)
AND UserID = @UserID
) AS X
LEFT OUTER JOIN dbo.VoteLog
ON VoteID = MAX_VoteID

Go to Top of Page

madhivanan
Premature Yak Congratulator

22864 Posts

Posted - 2010-02-24 : 02:44:29
Is VoteID in the outer part actually MAX_VoteID?
Yes. The approach seems correct. But TEST needs to test it

Madhivanan

Failing to plan is Planning to fail
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2010-02-24 : 03:34:59
" Is VoteID in the outer part actually MAX_VoteID?"

Its from the LEFT JOIN, so is NULL if there is no row (i.e. MAX_VoteID IS NULL) - but I do need to test that that works OK!!

In fact I think I will use [counting] for that instead ...

Edit: I made that change above. I think it is easier to read, thanks for that
Go to Top of Page

madhivanan
Premature Yak Congratulator

22864 Posts

Posted - 2010-02-24 : 04:02:57
<<
Its from the LEFT JOIN,
>>

Ok. As you dont specifiy the table alias I thought it is typo

<<
In fact I think I will use [counting] for that instead
>>

Yes that makes more sense and readable

Also note that [counting] > 1 should be [counting] >= 1

Madhivanan

Failing to plan is Planning to fail
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2010-02-24 : 05:17:34
"Also note that [counting] > 1 should be [counting] >= 1"

Thanks. Actually that's just looking for an error. Should be 0 or 1 rows only, if it gets more than 1 then someone has found a way to hack the system!! (i.e. the PK and a separate Unique Index have been dropped )

Right, I'm sorted with this now I think. I like the solution, thanks for your help.
Go to Top of Page

madhivanan
Premature Yak Congratulator

22864 Posts

Posted - 2010-02-24 : 05:26:55
quote:
Originally posted by Kristen

"Also note that [counting] > 1 should be [counting] >= 1"

Thanks. Actually that's just looking for an error. Should be 0 or 1 rows only, if it gets more than 1 then someone has found a way to hack the system!! (i.e. the PK and a separate Unique Index have been dropped )

Right, I'm sorted with this now I think. I like the solution, thanks for your help.


You are welcome

Madhivanan

Failing to plan is Planning to fail
Go to Top of Page
   

- Advertisement -