I'm having trouble with the sql language functions - sql

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

Related

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.

Pass String into #Parameter from a VS report

I did ask a question about this but still cant work out how to get this working, ive looked at some examples where people have used functions, table variables, create types and cant really get any working so wondering if someone could help and maybe explain a little bit of the code they write if its complicated.
Im using Visual studio 2012 to create reports and have on the report a drop down list populated with company names that a user can tick to view info about each of the companies.
What I want to do is get all the company IDs from this drop down and pass it into an In clause in my sql where statement. This is a simplified version of what I have got, im new to this so just trying to see what works for when I have to look at more complex stuff in the future.
Ideally the line ive comented out will be the line that should be used and the values passed into this, but ive added the equivalent line below which I have been using for testing, I get an error on the comma between the 1,2 and
error message syntax error near ','.
If someone can help with how to get this to work using the #companynameParam I have comented out that would be great. CompanyNameParam is the name of the drop down on the VS report.
Declare #ContactID int
--set #ContactID = #CompanyNameParam
set #ContactID = 1,2,3,4,5,6,7,8,9
select CompanyName as 'reportcompanyname'
from company co inner join contacts c on c.CompanyID = co.CompanyID
where ContactID in (#ContactID);
You are getting an error because SET command is used to assign single / scalar value to a single variable at a time and you are trying to assign multiple comma separated string values to a single integer variable #ContactID in one go.
Correct approach is to first declare a local variable of type varchar and not Int as
#CompanyNameParam must be a string.
Declare #ContactID varchar(500);
Then convert comma separated string into a table. [There can be many ways to do this] I'm just giving an example. Create a user defined function as:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[CSVToTable] (#InStr VARCHAR(MAX))
RETURNS #TempTab TABLE
(id int not null)
AS
BEGIN
SET #InStr = REPLACE(#InStr + ',', ',,', ',')
DECLARE #SP INT
DECLARE #VALUE VARCHAR(1000)
WHILE PATINDEX('%,%', #INSTR ) <> 0
BEGIN
SELECT #SP = PATINDEX('%,%',#INSTR)
SELECT #VALUE = LEFT(#INSTR , #SP - 1)
SELECT #INSTR = STUFF(#INSTR, 1, #SP, '')
INSERT INTO #TempTab(id) VALUES (#VALUE)
END
RETURN
END
GO
Once done you can re-write the sproc as:
select CompanyName as 'reportcompanyname'
from company co inner join contacts c on c.CompanyID = co.CompanyID
where ContactID in (SELECT * FROM dbo.CSVToTable(#CompanyNameParam));
Check DEMO here..

Using uniqueidentifier with IN Clause in SQL server

I have a stored procedure that takes as an input a string of GUIDs and selects from table where table GUID IN (#Param).
#Param = 'b2e16cdc-1f1b-40e2-a979-f87a6a2457af,
c275dd13-bb54-4b8c-aa12-220b5980cabd,
af3552ec-37b1-4a76-81ad-1bd6b8c4cd6c,
3a7fda02-558b-49a9-a870-30350254d8c0,'
SELECT * FROM dbo.Table1 WHERE
TableGUID IN (#Param)
However, I noticed that the query return values, only if the first GUID matches, otherwise it will not return anything. which means that it only compares with the first GUID in the string.
anyone knows how solve the problem?
declare #sql varchar(max)
set #sql='SELECT * FROM dbo.Table1 WHERE
TableGUID IN ('+#Param+') '
exec (#sql)
We can't do it, because SQL has no concept of Lists, or array or other useful data structures - it only knows about tables (and table based information) so it converts the string list into a table structure when it compiles the command - and it can't compile a variable string, so it complains and you get annoyed. Or at least, I do.
What we have to do is convert the comma separated values into a table first. My initial version was inline, and rather messy, so I re-worked it to a user function and made it a bit more general purpose.
USE [Testing] GO
CREATE FUNCTION [dbo].[VarcharToTable] (#InStr NVARCHAR(MAX))
RETURNS #TempTab TABLE
(id UNIQUEIDENTIFIER NOT NULL)
AS
BEGIN
;-- Ensure input ends with comma
SET #InStr = REPLACE(#InStr + ',', ',,', ',')
DECLARE #SP INT
DECLARE #VALUE NVARCHAR(MAX)
WHILE PATINDEX('%,%', #INSTR ) <> 0
BEGIN
SELECT #SP = PATINDEX('%,%',#INSTR)
SELECT #VALUE = LEFT(#INSTR , #SP - 1)
SELECT #INSTR = STUFF(#INSTR, 1, #SP, '')
INSERT INTO #TempTab(id) VALUES (#VALUE)
END
RETURN
END
GO
This creates a user function that takes a comma separated value string and converts it into a table that SQL does understand - just pass it the sting, and it works it all out. It's pretty obvious how it works, the only complexity is the REPLACE part which ensures the string is terminated with a single comma by appending one, and removing all double commas from the string. Without this, while loop becomes harder to process, as the final number might or might not have a terminating comma and that would have to be dealt with separately.
DECLARE #LIST NVARCHAR(MAX)
SET #LIST = '973150D4-0D5E-4AD0-87E1-037B9D4FC03B,973150d4-0d5e-4ad0-87e1-037b9d4fc03c'
SELECT Id, Descr FROM TableA WHERE Id IN (SELECT * FROM dbo.VarcharToTable(#LIST))
In addition to MikkaRin's answer: a GUID has to be unclosed in apostrophes, so the value in the parameter should look like
'b2e16cdc-1f1b-40e2-a979-f87a6a2457af',
'c275dd13-bb54-4b8c-aa12-220b5980cabd',
'af3552ec-37b1-4a76-81ad-1bd6b8c4cd6c',
'3a7fda02-558b-49a9-a870-30350254d8c0'
In the end, you have to pass something like:
#Param = '''b2e16cdc-1f1b-40e2-a979-f87a6a2457af'',
''c275dd13-bb54-4b8c-aa12-220b5980cabd'',
''af3552ec-37b1-4a76-81ad-1bd6b8c4cd6c'',
''3a7fda02-558b-49a9-a870-30350254d8c0'''
Pay attention to the last comma of the list. It should be removed.

Variable "IN" expression in SQL [duplicate]

This question already has answers here:
Closed 12 years ago.
Possible Duplicates:
SQL Multiple Parameter Values
SQL Server (2008) Pass ArrayList or String to SP for IN()
I would like to SELECT some rows from a table that have certain values which are not known at the time a stored procedure is written. For example, searching for books of a particular type or types in a library database:
SELECT * FROM Books WHERE Type IN (_expr_);
Where I want _expr_ to be ('Humor', 'Thriller') one run, and maybe ('Education') the next, depending on the user's choices. How can I vary the expression at run-time?
Unfortunately, I still have a lot to learn about SQL in general and am not sure if I'm even asking a question that makes sense. I would appreciate any guidance!
This is trickier than you might think in SQL Server 2005 (2008 has table valued parameters which makes it easier)
See http://www.sommarskog.se/arrays-in-sql-2005.html for a review of the methods.
I feel like I've answered this question before...
anyway, I've long used the following user defined split function:
Usage: dbo.Split("#ParamName", ",") where the 2nd parameter is the separator.
You can then join this onto a table, as it returns a table value function with the elementID and Element.
CREATE FUNCTION [dbo].[Split]
(
#vcDelimitedString varchar(max),
#vcDelimiter varchar(100)
)
RETURNS #tblArray TABLE
(
ElementID smallint IDENTITY(1,1), --Array index
Element varchar(1000) --Array element contents
)
AS
BEGIN
DECLARE #siIndex smallint, #siStart smallint, #siDelSize smallint
SET #siDelSize = LEN(#vcDelimiter)
--loop through source string and add elements to destination table array
WHILE LEN(#vcDelimitedString) > 0
BEGIN
SET #siIndex = CHARINDEX(#vcDelimiter, #vcDelimitedString)
IF #siIndex = 0
BEGIN
INSERT INTO #tblArray VALUES(#vcDelimitedString)
BREAK
END
ELSE
BEGIN
INSERT INTO #tblArray VALUES(SUBSTRING(#vcDelimitedString, 1,#siIndex - 1))
SET #siStart = #siIndex + #siDelSize
SET #vcDelimitedString = SUBSTRING(#vcDelimitedString, #siStart , LEN(#vcDelimitedString) - #siStart + 1)
END
END
RETURN
END
another approach, is to build a sql string and use execute to execute it. The string is of "INSERT...SELECT form" and inserts the results into a temporary table. Then you select from the temp.
declare #sql varchar(1000)
set #sql = 'INSERT INTO sometemptable SELECT * FROM Books WHERE Type IN ('
set #sql = #sql + {code that builds a syntactically correct list}
set #sql = #sql + ')'
execute #s_sql
select * from sometemptable
What you do here for sql server 2005 and prior is put the user parameters in a table, and then select from the table:
select columns
from books
where type in
(
select choices
from userchoices
where sessionkey= #sessionkey and userid= #userid
)

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