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.
Related
I want to create a variable that holds a list of UPCs that we need to search over. Instead of using:
select * from foo
where upc in (3410015215, 3410015217, 3410015243)
I want to say something like:
Declare #UPCList Varchar(255) = (3410015215, 3410015217, 3410015243)
--If you want to check all stores, swap the commentation status of the next two lines
--Declare #Store int = NULL
Declare #Store int = 203
My second issue is, I want to search fleem for a specific store, but if someone wants to search all stores, then they should be able to make #Store equal to NULL .
select * from foo p
inner join fleem f on p.upc = f.upc_id
if (#Store is NULL)
begin
where f.BOH = 1
end
else (#Store is not NULL)
begin
where f.store = #Store AND f.BOH = 1
end
Are these things possible?
If so, what is the best way to go about doing them?
There is no 'list' type in SQL. In SQL a list would just be a table or a temporary table with a single column and however many rows.
To solve your first issue you could store the values in the temporary table and then join against that to create the filter you are looking for.
CREATE TABLE #MyList (
upcs VARCHAR(MAX)
)
INSERT INTO #MyList (upcs)
VALUES
(123),
(456),
(789)
And then use #MyList in an inner join with foo,
SELECT * FROM foo AS f
INNER JOIN #MyList AS ml
ON f.upcs = ml.upcs
Then for your second issue all you need to do is include a WHERE clause with a CASE statement after your join,
select * from foo p
inner join fleem f on p.upc = f.upc_id
WHERE f.BOH = CASE
WHEN #Store IS NULL THEN f.BOH
ELSE #Store
END
Pretty sure this should really be two separate questions, but here are my answers. I'm assuming you're using SQL Server, but I could be wrong as you haven't tagged a DBMS. The code below may work on other systems, but I don't make any promises:
Q1: Table Variables
You can use table variables to do the sort of thing you've asked in your first question. See the code below.
DECLARE #UPCList as TABLE (upc int)
INSERT INTO #UPCList
VALUES
(3410015215),
(3410015217),
(3410015243)
After this you can do with #UPCList all the things you could do with an ordinary table.
Q2: Set Logic
You're not allowed to use IF/THEN inside a query. However, the logic you've described is easy to turn into a standard WHERE clause.
SELECT
*
FROM
foo p
JOIN fleem f on p.upc = f.upc_id
WHERE
f.boh = 1
AND (f.store = #Store
OR #Store is NULL)
I have the following stored procedure, which is intended to iterate through a list of strings, which contains several substrings of the form prefix.bucketName. I want to iterate through each string and each bucket name, and replace the old prefix with a new prefix, but keep the same bucket name.
To give an example, consider this original string:
"(OldPrefix.BucketA)(OldPrefix.BucketB)"
So for example I would like to get:
"(NewPrefix.BucketA)(NewPrefix.BucketB)"
What I actually get is this:
"(OldPrefix.BucketA)(NewPrefix.BucketB)"
So, in general, only one of the prefixes get updated, and it is not predictable which one. Based on some investigation I have done, it appears that both replacements actually work, but only the last one is actually saved. It seems like SQL should be locking this column but instead, both are read at the same time, the replace is applied, and then both are written, leaving the last write as what shows in the column.
Here is the query - All variable names have been changed for privacy - Some error handling and data validation code was left out for brevity:
DECLARE #PrefixID INT = 1478,
DECLARE #PrefixName_OLD NVARCHAR(50) = 'OldPrefix',
DECLARE #PrefixName_NEW NVARCHAR(50) = 'NewPrefix'
BEGIN TRAN
-- Code to rename the section itself here not shown for brevity
UPDATE
dbo.Component
SET
AString= REPLACE(AString,'('+#Prefix_OLD+'.'+b.BucketName+')', '('+#PrefixName_NEW+'.'+b.BucketName+')'),
FROM
dbo.Component sc
JOIN
dbo.ComponentBucketFilterInString fis
ON
sc.ComponentID = fis.ComponentID
JOIN
dbo.Buckets b
ON
fis.BucketID = b.BucketID
WHERE
b.PrefixID = #PrefixID
COMMIT
RETURN 1
When I write the same query using a while loop, it performs as expected:
DECLARE #BucketsToUpdate TABLE
(
BucketID INT,
BucketName VARCHAR(256)
)
INSERT INTO #BucketsToUpdate
SELECT BucketID, BucketName
FROM Buckets WHERE PrefixID = #PrefixID
WHILE EXISTS(SELECT 1 FROM #BucketsToUpdate)
BEGIN
DECLARE #currentBucketID INT,
#currentBucketName VARCHAR(256)
SELECT TOP 1 #currentBucketID = bucketID, #currentBucketName = bucketName FROM #BucketsToUpdate
UPDATE
dbo.Component
SET
AString = REPLACE(AString,'('+#PrefixName_OLD+'.'+#currentBucketName+')', '('+#PrefixName_NEW+'.'+#currentBucketName+')')
FROM
dbo.Component sc
JOIN
dbo.ComponentBucketFilterInString fis
ON
sc.ComponentID = fis.ComponentID
WHERE fis.BucketID = #currentBucketID
DELETE FROM #BucketsToUpdate WHERE BucketID = #currentBucketID
END
Why does the first version fail? How can I fix it?
The problem you are experiencing is "undefined" behavior when there is more than single match possible for UPDATE FROM JOIN.
In order to make your update possible you should run it multiple times updating one pair of values at a time as you proposed in your second code demo.
Related: How is this script updating table when using LEFT JOINs? and Let’s deprecate UPDATE FROM!:
SQL Server will happily update the same row over and over again if it matches more than one row in the joined table, >>with only the result of the last of those updates sticking<<.
Not sure why you are making the whole process so complex. May be I am not clearly understanding the requirement. As per my understanding, you are looking to update only Prefix part for column 'AString' in the table dbo.Component. Current value for example is-
(OldPrefix.BucketA)(OldPrefix.BucketB)
You wants to update the value as-
(NewPrefix.BucketA)(NewPrefix.BucketB)
Am I right? If yes, you can update all records with a simple Update script as below-
DECLARE #PrefixID INT = 1478
DECLARE #PrefixName_OLD NVARCHAR(50) = 'OldPrefix'
DECLARE #PrefixName_NEW NVARCHAR(50) = 'NewPrefix'
UPDATE Component
SET AString= REPLACE(AString,#PrefixName_OLD,#PrefixName_NEW)
I want to be able to run a snippet of my SQL query by selecting it and pressing F5. Issue is, if that selection contains a variable name I get an error: Must declare the scalar variable "#variableName".. Is there anyway to resolve this? I want variableName to be the value it would otherwise be had I run the whole statement at that moment in time that I've selected...
Sample of full code:
DECLARE #cat INT;
SET #cat = 2;
SELECT * FROM TableName
WHERE ColumnName = #cat;
Sample of my selection that I want to run without including declaration/set lines:
SELECT * FROM TableName
WHERE ColumnName = #cat;
Probably not possible but I figured it'd be worth a shot.
P.S. I'm a SQL noobie so if I'm missing something obvious let me know!
I understand where you are coming from. This is a snippet in a long procedure or something and naturally you want to keep the declarations at the top, which I agree with. In this case, when you are testing, the only real way to circumvent this is to re-declare it and set it at the top of your snippet. Then, when you are running the entire batch of code just comment out this line. Otherwise you'd have to wrap the snippet in a try / catch block to try and catch compile errors which is tricky.
Also, this is usually how I've seen people put a select * from someWorkTable to test results along the way. Then it's commented out when the batch is ran.
Left click on table name, right click design and you are able to view the datatype of the column name.
if it is nvarchar..
DECLARE #cat INT;
SET #cat = 2;
Convert(varchar, #cat)
SELECT * FROM TableName
WHERE ColumnName = #cat;
OR
directly declare
DECLARE #Cat VARCHAR(3)
SET #cat = '2'
SELECT * FROM TableName
WHERE ColumnName = #cat;
Hope this helps
You can simulate your variable as declared in a select query with an alias result. Then you can just use alias.column as part of your join...
SELECT
TN.*
FROM
( select '2' as TmpColumn ) tmpAlias
JOIN TableName TN
on tmpAlias.TmpColumn = TN.ColumnName
this way, no "scalar" variable is required, but not as practical as a simple parameterized query using a direct WHERE clause. Additionally, you could use the alias.column throughout in case you had other tables relations, etc or even additional "variables" you wanted to apply in your query.
I created the following script below. I am pretty much looking to update a colomn called "C1Int" with the number 1234567 randomly based on the pkey of the row.
I created a random generator for the pkey that uses 1 as the min and the total rows as the max.
Then there is a loop that should update the rows over and over based on the number in the WHILE statement. When I run it, it just updates one random row with the 1234567 number and even though its still running the loop, it never updates anything else. Am I missing something? Is there a better way to do this?
DECLARE #a INT
DECLARE #maxpkey INT
DECLARE #minpkey INT
DECLARE #randompkey INT
SET #a = 1
SET #maxpkey = (select count(*) from [LoadTestTwo].[dbo].[actbenchdb.Table1])
SET #minpkey = 1
SET #randompkey = ROUND(((#maxpkey - #minpkey -1) * RAND() + #minpkey),0)
WHILE #a < 500000000000000000000
BEGIN
UPDATE [LoadTestTwo].[dbo].[actbenchdb.Table1]
SET C1Int = (1234567)
WHERE Pkey = #randompkey
SET #a = #a + 1
END
Your current loop only updates a single row because you set the random key outside the loop and then just run the same update statement 500,000,000,000,000,000,000 times (Which assuming 1 million executions per second would still take 15 million years to complete, and would likely hit all your records anyway).
It is clear what you are trying to do, I just don't know why you would want to randomly change data in your database. Anyway, I may not understand why, but I can at least say how, if you want to update n random rows, then rather than running a loop n times, it would be better to perform a single update:
DECLARE #n INT = 100; -- NUMBER OF RANDOM ROWS TO UPDATE
UPDATE t
SET CInt = 12345467
FROM ( SELECT TOP (#n) *
FROM [LoadTestTwo].[dbo].[actbenchdb.Table1]
ORDER BY NEWID()
) AS t;
There are simpler and better ways to create a DB Load Generator!
Let me Google that for you
Working with Sql Server. Writing a stored procedure. Here is the pseudocode for what I want to achieve:
IF EXISTS ( SELECT field1
FROM t1
WHERE field1 = ... AND field2 = ...)
BEGIN
SELECT field1
FROM t1
WHERE field1 = ... AND field2 = ...
END
any better way of doing this? Any help appreciated.
Chirayu
Update: The problem is that the same query is executed twice. I cannot also just the run query once and return null (if the result is null i would like to return an alternative result).
I have done this before using a CTE and table variable, it requires more lines of code but the query is only written once, therefore your logic exists in a single place.
DECLARE #Results TABLE (Result INT);
WITH ResultsCTE AS
(
--Your query goes here
SELECT 1 as Result
WHERE 1 = 1
)
INSERT INTO #Results
SELECT Result
FROM ResultsCTE
IF (SELECT COUNT(*) FROM #Results) > 0
BEGIN
SELECT * FROM #Results
END
ELSE BEGIN
SELECT 'Do Something Else or Do Nothing!'
END
You could check ##ROWCOUNT after running the query once to determine whether or not to return the value:
http://msdn.microsoft.com/en-us/library/ms187316.aspx
If the select doesn't yield any results, no results will be returned. I don't see any reason to use a condition here, unless I'm missing something...
A stored procedure that sometimes returns a result while sometimes it doesn't would be a nightmare to use from any API. The client side API has different entry points depending on whether you return a result set (SqlCommand.ExecuteReader) or it does not return a result set (SqlCommand.ExecuteNonQuery). It would be impossible for the application to know ahead of time which API to use! Modeling tools use the SET FMTONLY option to analyze the metadata of returned result sets and the modeling tools are very confused when your returned result set start changing shape at random. In other words, you are down the wrong path, stop and turn around.
Just run the query, it no rows match your criteria it will simply return an empty result set. Which is exactly what every client API and modeling tool expects from your procedure.