Understanding SQL syntax - sql

I'm a C# developer trying to become more familiar with SQL Server stored procedures.
I'm a little confused as to why the syntax in "A" works and "B" does not work with Set #id. What is happening here that makes "B" require Select instead of Set?
Example A (works)
DECLARE #currDateTime DateTime
SET #currDateTime = GetDate()
SELECT #currDateTime
Example B (does not work)
DECLARE #id int
SET #id = ID FROM [MyTable] WHERE [Field1] = 'Test'
Example C (works)
DECLARE #id int
SELECT #id = ID
FROM [MyTable]
WHERE [Field1] = 'Test'

SELECT is a built-in type of SQL clause that runs a query and returns a result-set in the format of a table or it assigns variables to the results from a query.
SET is a clause that sets a variable.
The two are very different. SELECT has various other associated clauses, such as FROM, WHERE and so on; SET does not. SELECT returns values as a result table in its normal usage; SET does not.
Admittedly, both look the same in an expression such as:
set #currDateTime = GetDate();
select #currDateTime = GetDate();
However, it is really a coincidence that the syntax for setting a single value happens to look the same.

It doesn't work because it's incorrect SQL syntax, You need SELECT when fetching data from table/view/table function.
You could use SET when using an expression though i.e:
DECLARE #Id bigint
SET #Id = (SELECT TOP 1 Id
FROM MyTable
WHERE Field1 = 'Test')

Related

How to set -- Comments inside the variable declare?

SELECT * FROM CUSTOMERS
--WHERE ID='1'
I want the result like above statement, anyone can advice this can be declare comments or any other way?
DECLARE #TEST as VARCHAR(2) ='--'
SELECT * FROM CUSTOMERS
#TEST WHERE ID='1'
There is no preprocessor that can do what you want, and if there would be a preprocessor, it would probably only work in specific client software (like SQL Server Management Studio).
The best I can come up with is declaring a BIT variable and include it in your WHERE statement's logic.
DECLARE #TEST BIT = 0; -- Change to 1 for testing (which will show all records instead of only the record with ID = '1')
SELECT * FROM CUSTOMERS
WHERE ID = '1' OR #TEST = 1;
But using ORs in a WHERE-clause can become a performance issue (due to not using indexes), so using a UNION might be a solution in that case:
DECLARE #TEST BIT = 0; -- Change to 1 for testing (which will show all records instead of only the record with ID = '1')
SELECT * FROM CUSTOMERS
WHERE ID = '1'
UNION
SELECT * FROM CUSTOMERS
WHERE #TEST = 1;
I would personally go for the first option and fall back to the second option if that would produce a better query execution plan.
The latter solution does have the drawback that you have to duplicate your query logic, so changes have to be made in two places instead of one.
It's also the question if you would like to include such a testing variable at all. You can include a complete test query in comments:
/*
-- For testing purposes:
SELECT * FROM CUSTOMERS
*/
SELECT * FROM CUSTOMERS
WHERE ID = '1';
In SSMS, you can simply select the text inside the comment block and hit F5. Only the selected statement(s) will be parsed and executed.
Finally, if you have a large script with many testing cases, you could consider using a separate test script for that. In case of changes, you would have to maintain two scripts, of course, but everything related to testing is cleanly separated that way.
Try following statement:
DECLARE #TEST BIT = 1
DECLARE #ID NVARCHAR(50) = N'1'
SELECT *
CUSTOMERS as c
WHERE #TEST = 1 OR c.ID = #ID
So you can test without commenting out...
Another option is dynamic statements:
DECLARE #Statement NVARCHAR(4000) = NULL
DECLARE #TEST BIT = 1
IF #TEST = 1
BEGIN
SET #Statement = N'SELECT * FROM CUSTOMERS AS c'
END
ELSE
BEGIN
SET #Statement = N'SELECT * FROM CUSTOMERS AS c WHERE c.ID = #ID'
END
EXEC sp_executesql #Statement,
N'#ID NVARCHAR(50)',
#ID = N'1'
By Using Dynamic SQL Query.You can Set your Common SQL Command in One Variable and the based on your need/Requirement you can CONCAT the String and this can be achieved from by using Dynamic SQL
DECLARE #TEST VARCHAR(MAX),#Where VARCHAR(MAX)=' WHERE ID=''1'''
IF(Condition Check)
SET #TEST='SELECT * FROM CUSTOMERS' +#Where
ELSE
SET #TEST='SELECT * FROM CUSTOMERS'
SELECT #TEST
EXEC (#TEST)
Another Option:
DECLARE #TEST VARCHAR(2) =NULL
SET #TEST='1'
SELECT * FROM CUSTOMERS
WHERE (ID=#TEST OR #TEST IS NULL)
For quick debugging I'm using pattern like next:
declare #var int -- can be some function or procedure param
select f1, f2, ... -- can be update or delete too
--declare #var int = 7; select *
from mytable
where id = #var

I'm having trouble with the sql language functions

I have been working in the SQL language for 1 month. That's why I may not understand things. But I always get an error when creating these functions. I wrote the codes down there. The error message is as follows:
Only one expression can be specified in the select list when
the subquery is not introduced with EXISTS.
CREATE FUNCTION deneme(
#ID int
)
RETURNS nvarchar(max)
AS
BEGIN
DECLARE #value nvarchar(max)
SET #value = (
SELECT * FROM information
WHERE #ID = Person_id
)
RETURN #value
END
You cannot assign all columns values into one variable like that, and since you're passing an ID of the person and you want your function to returns the info of that person
CREATE FUNCTION dbo.deneme
(
#ID int
)
RETURNS NVARCHAR(300)
AS
BEGIN
DECLARE #Value NVARCHAR(300) = N'';
SELECT #Value = CONCAT(I.FirstName, N' ', I.LastName)
FROM Information I
WHERE I.PersonId = #ID;
RETURN #Value;
END
As others have pointed out you are trying to place multiple columns/fields in a single column/field.
#ID is a single column. "Select *" is presumably returning more than a single column or else it wouldn't be much help!
In order to change this and make it work as you are trying here, you would need to concat the columns you are trying to return. This is almost surely not the best way to accomplish this but sometimes concating names (for example) is fine.
The other issue you may be running into is even if you changed this to "Select ID" but still have errors it may be because the query returns more than one row matching that criteria. You can work around this (it is a work around most of the time) by limiting the number of rows returned with "TOP 1". But be careful as this may not return the information you want. You can use an order by statement to help ensure it is the correct information (such as order by Time_entered).
The code below with "TOP 1" and concatenating multiple columns (and casting as the same type) will always work.
Again, these are not Best Practices and shouldn't be used to sanitize data in production... but it does show you why you are getting these errors and how to prevent them.
CREATE FUNCTION deneme(
#ID int
)
RETURNS nvarchar(max)
AS
BEGIN
DECLARE #value nvarchar(max)
SET #value = (
SELECT TOP 1 cast(First_name as nvarchar) + N' ' + cast(Last_name as nvarchar) FROM information
WHERE #ID = Person_id
Order by Time_entered desc
)
RETURN #value
END

Conditional WHERE Clauses In A Stored Procedure

This question may boil down to something simpler, but I am still curious as to how close SQL Server / TSQL can get to conditional WHERE clauses (and reasoning behind why they don't exist would also be interesting).
I have a stored procedure that, for a few parameters, takes in an enumeration array (which has been accordingly translated to a user-defined table type which essentially mocks an int array). For reference the data type is as follows:
CREATE TYPE myIntArray AS TABLE (
val INT
);
My stored procedure is along the following lines (altered to be more simplistic):
CREATE PROCEDURE myProc
#homeID INT,
#name VARCHAR(500),
#hometype_enum myIntArray READONLY,
#country_enum myIntArray READONLY
AS
BEGIN
SELECT * FROM my_table
WHERE name=#name
END
GO
What I am wanting to do is additionally filter the results of my query based upon the values of the enum arrays that were passed in as INT tables, IFF they even have values passed in (it is possible the tables might be empty). The pseudo code would look something like this:
SELECT *
FROM my_table
WHERE name = #name
IF((SELECT COUNT(val) FROM #hometype_enum) > 0)
BEGIN
AND hometype IN (SELECT val FROM hometype_enum)
END
IF((SELECT COUNT(val) FROM #country_enum ) > 0)
BEGIN
AND country IN (SELECT val FROM country_enum )
END
The two enums are independent of each other, so it's possible a search could be made and be filtered on no enum (both tables empty), either-or, or both enums.
My actual query involves multiple columns, tables, and unions (ugly, I know), so it's not as nice as just being able to copy/paste a 3-line SELECT for each scenario. I am currently using some pretty ugly temp table logic that I'll spare the reader's eyes from at the moment.
Aside from figuring out my particular problem, my main question is: does SQL Server support conditional WHERE clause statements (I am convinced it does not from my research)? Why is this (architectural, time complexity, space complexity issues)? Are there any more-or-less terse ways to go about emulating a conditional clause, such as taking advantage of conditional short-circuiting?
Thank you all for your insights. Another day of learning!
As suggested in the comments, the best way to handle this kind of conditional where clause would be to use dynamic-sql ..... Something like....
CREATE PROCEDURE myProc
#homeID INT,
#name VARCHAR(500),
#hometype_enum myIntArray READONLY,
#country_enum myIntArray READONLY
AS
BEGIN
SET NOCOUNT ON
Declare #Sql NVarchar(MAX);
SET #Sql = N' SELECT * FROM my_table '
+ N' WHERE name = #name '
+ CASE WHEN EXISTS (Select * FROM #hometype_enum)
THEN N' AND hometype IN (SELECT val FROM hometype_enum) ' ELSE N' ' END
+ CASE WHEN EXISTS (Select * FROM #country_enum)
THEN N' AND country IN (SELECT val FROM country_enum ) ' ELSE N' ' END
Exec sp_executesql #Sql
,N'#homeID INT , #name VARCHAR(500),
#hometype_enum myIntArray, #country_enum myIntArray'
,#homeID
,#name
,#hometype_enum
,#country_enum
END
GO
Using sp_executesql will allow sql server to store parameterised execution plans for the same stored procedure. It is different execution plans for different sets/combinations of a parameters for the same stored procedure for optimal performance.
The below one works fine too. There is no need for dynamic-sql. The MS SQL will handle it without issues (with good performance).
CREATE PROCEDURE myProc
#homeID INT,
#name VARCHAR(500),
#hometype_enum myIntArray READONLY,
#country_enum myIntArray READONLY
AS
BEGIN
SELECT * FROM my_table
WHERE name=#name
AND ( ( SELECT count(val) FROM #hometype_enum ) = 0
OR hometype IN (SELECT val FROM #hometype_enum) )
AND ( ( SELECT count(val) FROM #country_enum ) = 0
OR country IN (SELECT val FROM #country_enum) )
END
GO
The OR will make the job for you. When first part : SELECT count(val) FROM #hometype_enum ) = 0 will return true then the second one will not be executed at all - no error from empty IN clause. When the first part will return some value grater than 0 then the second one will be evaluated correctly.

declare variables in sql 2005

In my script i have few select statements and update statements, as an example
SELECT * from TABLE1
WHERE userID= 'US001'
UPDATE TABLE2
SET value= 'months'
WHERE userID='US001'
statements going so on, so in this i have to copy and paste userID to every statement.
i want to declare a variable and assign to userID to refer it, so i don't need to add userID number to every query and i need to execute
i have tried this
Delcare #theID userID
SET userID ='us001'
but didn't work it out
please let me know..
thanks
You'll need to declare the type, and assign it. In Sql Server, variables are prefixed with #, like so:
DECLARE #theID NVARCHAR(20);
SET #theID ='us001';
UPDATE TABLE2 SET value= 'months' WHERE userID=#theID;
DECLARE #theID varchar(10);
SET #theID = 'us001';
In your statement you are declaring your variable as userID, which is not a valid data type.
In addition to the previous answers, in SQL Server 2008 and higher you can also declare and set the variable in a single line.
DECLARE #UserID NVARCHAR(20) = 'us001';
This is what works for me under SQL2005 in a stored procedure:
DECLARE #name varchar(100)
SELECT #name = 'Robin'
// and this should be do the update
SET userID = #name
// or in you query it should be
WHERE userID = #name

TSQL Statement IN

I am having a small problem with the IN SQL statement. I was just wondering if anyone could help me?
#Ids = "1,2,3,4,5"
SELECT * FROM Nav WHERE CONVERT(VARCHAR,NavigationID) IN (CONVERT(VARCHAR,#Ids))
This is coming back with the error below, I am sure this is pretty simple!
Conversion failed when converting the varchar value '1,' to data type int.
The SQL IN clause does not accept a single variable to represent a list of values -- no database does, without using dynamic SQL. Otherwise, you could use a Table Valued Function (SQL Server 2000+) to pull the values out of the list & return them as a table that you can join against.
Dynamic SQL example:
EXEC('SELECT *
FROM Nav
WHERE NavigationID IN ('+ #Ids +')')
I recommend reading The curse and blessings of dynamic SQL before using dynamic SQL on SQL Server.
Jason:
First create a function like this
Create FUNCTION [dbo].[ftDelimitedAsTable](#dlm char, #string varchar(8000))
RETURNS
--------------------------------------------------------------------------*/
/*------------------------------------------------------------------------
declare #dlm char, #string varchar(1000)
set #dlm=','; set #string='t1,t2,t3';
-- tHIS FUNCION RETUNRS IN THE ASCENDING ORDER
-- 19TH Apr 06
------------------------------------------------------------------------*/
--declare
#table_var TABLE
(id int identity(1,1),
r varchar(1000)
)
AS
BEGIN
declare #n int,#i int
set #n=dbo.fnCountChars(#dlm,#string)+1
SET #I =1
while #I <= #N
begin
insert #table_var
select dbo.fsDelimitedString(#dlm,#string,#i)
set #I= #I+1
end
if #n =1 insert #TABLE_VAR VALUES(#STRING)
delete from #table_var where r=''
return
END
And then
set quoted_identifier off
declare #ids varchar(max)
select #Ids = "1,2,3,4,5"
declare #nav table ( navigationid int identity(1,1),theother bigint)
insert #nav(theother) select 10 union select 11 union select 15
SELECT * FROM #Nav WHERE CONVERT(VARCHAR,NavigationID) IN (select id from dbo.ftDelimitedAsTable(',',#Ids))
select * from dbo.ftDelimitedAsTable(',',#Ids)
What you're doing is not possible with the SQL IN statement. You cannot pass a string to it and expect that string to be parsed. IN is for specific, hard-coded values.
There are two ways to do what you want to do here.
One is to create a 'dynamic sql' query and execute it, after substituting in your IN list.
DECLARE #query varchar(max);
SET #query = 'SELECT * FROM Nav WHERE CONVERT(VARCHAR,NavigationID) IN (' + #Ids + ')'
exec (#query)
This can have performance impacts and other complications. Generally I'd try to avoid it.
The other method is to use a User Defined Function (UDF) to split the string into its component parts and then query against that.
There's a post detailing how to create that function here
Once the function exists, it's trivial to join onto it
SELECT * FROM Nav
CROSS APPLY dbo.StringSplit(#Ids) a
WHERE a.s = CONVERT(varchar, Nav.NavigationId)
NB- the 'a.s' field reference is based on the linked function, which stores the split value in a column named 's'. This may differ based on the implementation of your string split function
This is nice because it uses a set based approach to the query rather than an IN subquery, but a CROSS JOIN may be a little complex for the moment, so if you want to maintain the IN syntax then the following should work:
SELECT * FROM Nav
WHERE Nav.NavigationId IN
(SELECT CONVERT(int, a.s) AS Value
FROM dbo.StringSplit(#Ids) a