Assigning variable from select statement - sql

Which is the correct syntax?
DECLARE #TotalRows INT
SET #TotalRows = (SELECT COUNT(*) FROM MyTable WHERE MyID = Value)
OR
DECLARE #TotalRows INT
SELECT #TotalRows = COUNT(*) FROM MyTable WHERE MyID = Value
Does it even matter?

Difference between set vs select
SET is the ANSI standard for variable assignment, SELECT is not.
SET can only assign one variable at a time, SELECT can make multiple assignments at once.
If assigning from a query, SET can only assign a scalar value. If the query returns multiple values/rows then SET will raise an error. SELECT will assign one of the values to the variable and hide the fact that multiple values were returned (so you'd likely never know why something was going wrong elsewhere - have fun troubleshooting that one)
When assigning from a query if there is no value returned then SET will assign NULL, where SELECT will not make the assignment at all (so the variable will not be changed from its previous value)
As far as speed differences - there are no direct differences between SET and SELECT. However SELECT's ability to make multiple assignments in one shot does give it a slight speed advantage over SET.

Related

Explain syntax: select #au_id = au_id from #mytemp (Equals sign in SELECT clause) [duplicate]

What are the differences between the SET and SELECT statements when assigning variables in T-SQL?
Quote, which summarizes from this article:
SET is the ANSI standard for variable assignment, SELECT is not.
SET can only assign one variable at a time, SELECT can make multiple assignments at once.
If assigning from a query, SET can only assign a scalar value. If the query returns multiple values/rows then SET will raise an error. SELECT will assign one of the values to the variable and hide the fact that multiple values were returned (so you'd likely never know why something was going wrong elsewhere - have fun troubleshooting that one)
When assigning from a query if there is no value returned then SET will assign NULL, where SELECT will not make the assignment at all (so the variable will not be changed from its previous value)
As far as speed differences - there are no direct differences between SET and SELECT. However SELECT's ability to make multiple assignments in one shot does give it a slight speed advantage over SET.
I believe SET is ANSI standard whereas the SELECT is not. Also note the different behavior of SET vs. SELECT in the example below when a value is not found.
declare #var varchar(20)
set #var = 'Joe'
set #var = (select name from master.sys.tables where name = 'qwerty')
select #var /* #var is now NULL */
set #var = 'Joe'
select #var = name from master.sys.tables where name = 'qwerty'
select #var /* #var is still equal to 'Joe' */
When writing queries, this difference should be kept in mind :
DECLARE #A INT = 2
SELECT #A = TBL.A
FROM ( SELECT 1 A ) TBL
WHERE 1 = 2
SELECT #A
/* #A is 2*/
---------------------------------------------------------------
DECLARE #A INT = 2
SET #A = (
SELECT TBL.A
FROM ( SELECT 1 A) TBL
WHERE 1 = 2
)
SELECT #A
/* #A is null*/
Aside from the one being ANSI and speed etc., there is a very important difference that always matters to me; more than ANSI and speed. The number of bugs I have fixed due to this important overlook is large. I look for this during code reviews all the time.
-- Arrange
create table Employee (EmployeeId int);
insert into dbo.Employee values (1);
insert into dbo.Employee values (2);
insert into dbo.Employee values (3);
-- Act
declare #employeeId int;
select #employeeId = e.EmployeeId from dbo.Employee e;
-- Assert
-- This will print 3, the last EmployeeId from the query (an arbitrary value)
-- Almost always, this is not what the developer was intending.
print #employeeId;
Almost always, that is not what the developer is intending. In the above, the query is straight forward but I have seen queries that are quite complex and figuring out whether it will return a single value or not, is not trivial. The query is often more complex than this and by chance it has been returning single value. During developer testing all is fine. But this is like a ticking bomb and will cause issues when the query returns multiple results. Why? Because it will simply assign the last value to the variable.
Now let's try the same thing with SET:
-- Act
set #employeeId = (select e.EmployeeId from dbo.Employee e);
You will receive an error:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
That is amazing and very important because why would you want to assign some trivial "last item in result" to the #employeeId. With select you will never get any error and you will spend minutes, hours debugging.
Perhaps, you are looking for a single Id and SET will force you to fix your query. Thus you may do something like:
-- Act
-- Notice the where clause
set #employeeId = (select e.EmployeeId from dbo.Employee e where e.EmployeeId = 1);
print #employeeId;
Cleanup
drop table Employee;
In conclusion, use:
SET: When you want to assign a single value to a variable and your variable is for a single value.
SELECT: When you want to assign multiple values to a variable. The variable may be a table, temp table or table variable etc.
Surround everything in select with ().
Make sure you are only returning 1 item
eg
ET #sql_update = (select left(#sql_update, len(#sql_update)-1))
SET #Telephone2 = (SELECT REPLACE(LTRIM(REPLACE(#Telephone2, '0', ' ')), ' ', '0'))

SQL Set variable to select result

I was wondering if it is possible to set a declared variable to a return value from a select result? Something like:
#WatchedSeconds
SET #WatchedSeconds = 200
DECLARE #SelectedVideo int
SET #SelectedVideo = (SELECT TOP 1 * FROM Video v WHERE v.VideoID = 12)
IF #SelectedVideo IS NOT NULL
BEGIN
IF #SelectedVideo.VideoLength = #WatchedSeconds
BEGIN
--DO SOMETHING
END
IF #SelectedVideo.SomeOtherColumn = #SomethingElse
BEGIN
END
END
It's for using some information from the SELECT result multiple places in a Stored Procedure.
I know that I can set a variable to e.g, a integer, and set it to the selected result, if it returns a integer, e.g:
DECLARE #VideoSeconds int
SET #VideoSeconds = (SELECT v.Length FROM Video v WHERE v.VideoID = #VideoID)
This way I have to make multiple variables, and multiple SELECT calls if I need to use more values from the Video result. And that's what I want to avoid.
You can do this simply by running:
SELECT #videoSeconds = v.Length FROM Video v WHERE v.VideoID = #VideoID
so as to not add the SET part.
Also, you must make sure that only 1 row is being returned by the query, otherwise it will generate an error.
You can try something like
(declare variables first...)
SELECT TOP 1 #var1=col1, #var2=col2, #var3=col3, [...] FROM YourTable WHERE YourFilter
EDIT: All together this seems not to be the best approach... With SQL you should not think in values and single rows but rather in result sets (set based programming). Your thinking leads to many tiny selects, while loops, cursors and all this stuff one should avoid.
You can store the results in a temporary table or table variable:
SELECT TOP 1 *
INTO #SelectedVideo
FROM Video v
WHERE v.VideoID = 12;
Then you can assign values from the table later in your code. Something like:
IF ( (SELECT VideoLength FROM #SelectedVideo) = #WatchedSeconds)
However, for your particular example, if you have an index on video(VideoId), then there is little to be gained performance-wise from using a temporary table.
If what you're trying to get is similar to returning a dataset in a procedural language (so you can type something like Result.Field1 = 'Test') then I don't think this is possible. You'll just need to declare multiple variables and make the SELECT call as
SELECT TOP 1 #var1=col1, #var2=col2, #var3=col3, [...] FROM YourTable WHERE YourFilter
as #Shnugo suggests
The 'dataset' equivalent structure in SQL is cursors, but they require variables to be set up as well, so there's no benefit there.

DB2 Stored Procedure. How can I dynamically create a variable name inside a loop using the iteration variable?

I'm trying to create a list of variables dependent on the result of a query. I could be creating any number of variables so I want it to be dynamic. However, I can't work out how to incorporate the loop iteration number into the variable names.
I'm thinking about something like this:
declare rc int default 0;
declare n_rows int;
--The number of variables to declare below is unknown
select count(1) into n_row from MyTable;
declare varname1 to varname#n_rows varchar(100);
while rc < n_rows do
select varname into var#rc from (select varname, rownumber() over() as rn from MyTable) where rn = rc;
-- I want this to resolve to something like:
select varname into var1...
select varname into var2...
select varname into var3..
end while;
Generally speaking, if you find yourself in the situation where you have to store an unknown number of variables, it is an indication of an incorrect program design. Why do you think you need to store every row in memory? Databases are optimized for set processing.
However, if you insist on doing things your way, consider using arrays; an array can grow as you add elements to it (until it consumes all application memory and crashes the database, but that's none of your concern, right?)

How can I have a temporary variable declared in a MERGE statement in SQL Server 2008?

I need to use a temporary variable declared in stored procedure. I need to use this variable to assign a value and do some function in a Matched statement. How can I use? is there any other way to have value??
Thanks in advance
This is how you can define a local variable in SQL Server:
DECLARE #MyVariable INT
SET #MyVariable = 12
SELECT HouseNumber + #MyVariable as NewHouseNumber FROM MyTable WHERE Id = 1
If you declare the variable preceding the MERGE statement then you can indeed use that variable within the MERGE statement. This apples to table variables as well as scalar variables.
I think he ment something like this
MERGE TargetTable as tar
USING SourceTable as src
ON tar.SomeID = src.OtherID
DECLARE #BossId INT
SET #BossId = (SELECT ID FROM EmployeeTable WHERE [BossID] = src.BossID)
--Here we take dynamicly an ID from another table
WHEN NOT MATCHED THEN
INSERT (list OF fields, [BossID])
VALUES (list OF values, #BossId)
WHEN MATCHED THEN
UPDATE
SET (list OF SET statements);
This way in every INSERT statement will have different BossID. Is this even possible? If not - How to insert records this way? Imagine that the SourceTable (which in my case is an input parameter in a SP) came with ID which needs to be mapped with another talbe. Any suggestions?
My post is more like an addition to the original question.

Assigning a variable inside an IF EXISTS clause

Trying to assign a variable inside an if exists clause for TSQL
DECLARE #myvar int
IF EXISTS (SELECT #myvar = theTable.varIWant..... )
I thought this would work, but apparently not? Or perhaps (more likely) I'm doing it wrong.
In my installation of SQL Server 2008 R2, it simply doesn't compile. The parser complains about there being incorrect syntax near =.
I believe it must have something to do with mixing value assignment and data retrieval in a single SELECT statement, which is not allowed in SQL Server: you can have either one or the other. Since, when you assign values, the row set is not returned but the EXISTS predicate expects it to be, the assignment cannot be allowed in that context, so, to avoid confusion, perhaps, the limitation must have been imposed explicitly.
Your workaround, which you are talking about in a comment, is a decent one, but might not work well somewhere in the middle of a batch when the variable has already got a value before the assignment. So I would probably use this workaround instead:
SELECT #myvar = ...
IF ##ROWCOUNT > 0 ...
As per MSDN, the ##ROWCOUNT system function returns the number of rows read by the query.
Rather than doing IF EXISTS, you could just do
DECLARE #myvar int
SELECT #myvar = theTable.varIWant.....;
IF #myvar IS NULL
BEGIN...
It will not work just because in EXISTS construction sql server just validates if any row exists and it does not matter the select-columns or assignment section.
This is done for optimizing the performance.
Have you tried count?
SELECT #Exists = CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END
FROM [dbname].[dbo].[tableorviewname];