SSRS Multivalue Parameter in Dataset Query issue - sql

I Have an embedded dataset in my report which I pass parameters into.
This works fine for a single select using the = Sign in my And line
I would of thought and google results seem to be saying the same that i can just change the = sign to 'IN'
FROM [database].[dbo].[itemTable]
right Outer Join [database].[dbo].[CategoryTable]
on [database].[dbo].[itemTable].Category= [database].[dbo].[CategoryTable].Category And ([database].[dbo].[itemTable].Region = #pRegion) And ([database].[dbo].[itemTable].CategoryLN = #pCategoryLN )
where [database].[dbo].[CategoryTable].Category != 'RETIRED'
Above works fine but if I change to
[database].[dbo].[itemTable].Region IN #pRegion'
The query window says Incorrect syntax near '#pRegion'.

Looks like all you are missing is brackets around the parameter.
[database].[dbo].[itemTable].Region IN (#pRegion)
Also make sure you don't edit/parse the parameter values.

We've resolved this issue by using a database table-valued function (probably found somewhere on the internet, but I can't remember where)
CREATE FUNCTION [database].[dbo].[ParamSplit]
(
#List nvarchar(max), -- string returned from multivalue report parameter
#SplitOn nvarchar(5) -- separator character
)
RETURNS #RtnValue table
(
Id int identity(1,1),
Value nvarchar(100)
)
AS
BEGIN
While (Charindex(#SplitOn,#List)>0)
Begin
Insert Into #RtnValue (value)
Select Value = ltrim(rtrim(Substring(#List,1,Charindex(#SplitOn,#List)-1)))
Set #List = Substring(#List,Charindex(#SplitOn,#List)+len(#SplitOn),len(#List))
End
Insert Into #RtnValue (Value)
Select Value = ltrim(rtrim(#List))
Return
END
Then you can use it in your dataset query.
where [database].[dbo].[itemTable].Region IN (Select [dbo].[ParamSplit].[Value] from [database].[dbo].[ParamSplit](#pRegion,','))

Related

How to set a value to variable from table

I'm trying to create this function, this return the final score of a student in a subject, this student can take the test multiple times
create function finalResult(#stC varchar(10), #sj varchar(10))
returns float
as begin
declare #result float
set #result = (select sum(score)/count(studentCode)
from Results
where #stC = studentCode and #sj = subject)
return #result
end
The problem here is it says "Select statements included within a function cannot return data to a client" when I remove the () wrapping the select. Idk how this thing works can anyone explain
you can thin it down a bit. I am also assuming SCORE is NOT an int
Example
create function finalResult(#stC varchar(10), #sj varchar(10))
returns float --<< consider using a decimal()
as
begin
return (
select sum(score)/nullif(count(*) ,0) -- added a divide by zero error trap
from Results
where #stC = studentCode
and #sj = subject
)
end

How to select from text list randomly?

I'm trying to build SQL function that I can use as a default value for a column. The function is about selecting an avatar image path randomly if the user didn't assign an image.
I have tried to but a completely wrong example to just approach the image not the solution
what I need to do is something like this
select top 1 from "avatar1,png, avatar2,png, avatar3.png, avatar4.png, avatar5.png" order by rand();
and I will convert it to a function like this
CREATE FUNCTION dbo.ReturnAvatar()
RETURNS nvarchar(100)
AS
BEGIN
DECLARE #ret nvarchar(100);
SET #ret = (select top 1 from "avatar1,png, avatar2,png, avatar3.png, avatar4.png, avatar5.png" as tbl order by rand());
RETURN #ret;
END;
this is just to explain the idea that I'm not able to apply. I don't know if SQL server has something like this or not.
Here is one way:
CREATE VIEW getNewID AS SELECT newid() as new_id
CREATE FUNCTION dbo.ReturnAvatar()
RETURNS nvarchar(100)
AS
BEGIN
DECLARE #ret nvarchar(100);
SET #ret = (SELECT TOP 1 value
FROM
STRING_SPLIT('avatar1.png,avatar2.png,avatar3.png,avatar4.png,avatar5.png', ',')
ORDER BY (SELECT new_id FROM getNewID));
RETURN #ret;
END;
Note that your current CSV string of filenames does not seem proper, because comma does not indicate the start of the extension in either Windows or Linux. So, I have assumed dot everywhere. In addition, if you want to use STRING_SPLIT, you may only split on a single character. Therefore, I assume that comma will be the delimiter here.
You do not need to create a table at all. Simply put the number inside your string and choose the number randomly:
select 'avatar'+str(round(rand()*5+1,0))+'.png'
would be fine.
Put that into your function and you are all set.
rand() produces 0..1(excl.) so you can simply multiply it by 5 and add 1 to get your range of 1...5
Demo: http://sqlfiddle.com/#!18/9eecb/82866
Documentation:
ROUND ( numeric_expression , length [ ,function ] )
STR ( float_expression [, length [, decimal]])
rand(seed)
So essentially you could boil it down to:
select 'avatar'+ltrim(str(rand()*5+1,20,0))+'.png'
with
ltrim(string) taking care of the space
create function dbo.ReturnAvatar(#uid uniqueidentifier, #avatars int = 10)
returns varchar(100)
as
begin
return ('avatar' + cast(abs(checksum(#uid)) % isnull(abs(#avatars), 10)+1 as varchar(100)) + '.png')
end
go
create table myusers
(
username varchar(50),
theavatar varchar(100) default( dbo.ReturnAvatar(newid(), default))
);
insert into myusers(username)
select top (10000) 'user' + cast(row_number() over(order by(select null)) as varchar(50))
from master.dbo.spt_values as a
cross join master.dbo.spt_values as b;
go
select theavatar, count(*)
from myusers
group by theavatar;
go
drop table myusers;

Cannot call methods on table ? Table Variable

I am trying to put outer apply on the table varible but I am getting error like below
Cannot call methods on table.
I have Split function which split the string to certain length
CREATE FUNCTION Split(#String varchar(MAX), #SplitLength int)
RETURNS #Result TABLE (Splited varchar(MAX))
AS
BEGIN
Declare #Cnt int
Set #Cnt = FLOOR((len(#String)/#SplitLength));
While #Cnt!=0
Begin
SET #Cnt=#Cnt-1;
While len(#String)>#SplitLength
Begin
INSERT INTO #Result VALUES (SUBSTRING(#String,1,#SplitLength))
SET #String=SUBSTRING(#String,#SplitLength+1,len(#String)-#SplitLength)
End
End
RETURN
END
which I join with the table variable which contain column which have string to be splited
DECLARE #LeftSuper TABLE
(
KeyTerm VARCHAR(MAX),
Data VARCHAR(MAX) ,
)
Query is as following Which generates error (Cannot call methods on table )
select KeyTerm ,D.Splited from #LeftSuper
outer apply [Split](#LeftSuper.Data,300) as D
Note: code works fine with Real Table in db.
Introduce an alias for the table variable and use that in the expression:
select KeyTerm ,D.Splited from #LeftSuper ls
outer apply [Split](ls.Data,300) as D
This is actually fairly common - because tables may appear more than once in a query, each time that #LeftSuper is encountered in the query, it's treated as a new reference to the table - not as the reference that has already been added - which the alias allows you to reference.

SQL Server 2008 inconsistent results

I just released some code into production that is randomly causing errors. I already fixed the problem by totally changing the way I was doing the query. However, it still bothers me that I don't know what was causing the problem in the first place so was wondering if someone might know the answer. I have the following query inside of a stored procedure. I'm not looking for comments about that's not a good practice to make queries with nested function calls and things like that :-). Just really want to find out why it doesn't work consistently. Randomly the function in the query will return a non-numeric value and cause an error on the join. However, if I immediately rerun the query it works fine.
SELECT cscsf.cloud_server_current_software_firewall_id,
dbo.fn_GetCustomerFriendlyFromRuleName(cscsf.rule_name, np.policy_name) as rule_name,
cscsf.rule_action,
cscsf.rule_direction,
cscsf.source_address,
cscsf.source_mask,
cscsf.destination_address,
cscsf.destination_mask,
cscsf.protocol,
cscsf.port_or_port_range,
cscsf.created_date_utc,
cscsf.created_by
FROM CLOUD_SERVER_CURRENT_SOFTWARE_FIREWALL cscsf
LEFT JOIN CLOUD_SERVER cs
ON cscsf.cloud_server_id = cs.cloud_server_id
LEFT JOIN CLOUD_ACCOUNT cla
ON cs.cloud_account_id = cla.cloud_account_id
LEFT JOIN CONFIGURATION co
ON cla.configuration_id = co.configuration_id
LEFT JOIN DEDICATED_ACCOUNT da
ON co.dedicated_account_id = da.dedicated_account_id
LEFT JOIN CORE_ACCOUNT ca
ON da.core_account_number = ca.core_account_id
LEFT JOIN NETWORK_POLICY np
ON np.network_policy_id = (select dbo.fn_GetIDFromRuleName(cscsf.rule_name))
WHERE cs.cloud_server_id = #cloud_server_id
AND cs.current_software_firewall_confg_guid = cscsf.config_guid
AND ca.core_account_id IS NOT NULL
ORDER BY cscsf.rule_direction, cscsf.cloud_server_current_software_firewall_id
if you notice the join
ON np.network_policy_id = (select dbo.fn_GetIDFromRuleName(cscsf.rule_name))
calls a function.
Here is that function:
ALTER FUNCTION [dbo].[fn_GetIDFromRuleName]
(
#rule_name varchar(100)
)
RETURNS varchar(12)
AS
BEGIN
DECLARE #value varchar(12)
SET #value = dbo.fn_SplitGetNthRow(#rule_name, '-', 2)
SET #value = dbo.fn_SplitGetNthRow(#value, '_', 2)
SET #value = dbo.fn_SplitGetNthRow(#value, '-', 1)
RETURN #value
END
Which then calls this function:
ALTER FUNCTION [dbo].[fn_SplitGetNthRow]
(
#sInputList varchar(MAX),
#sDelimiter varchar(10) = ',',
#sRowNumber int = 1
)
RETURNS varchar(MAX)
AS
BEGIN
DECLARE #value varchar(MAX)
SELECT #value = data_split.item
FROM
(
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as row_num FROM dbo.fn_Split(#sInputList, #sDelimiter)
) AS data_split
WHERE
data_split.row_num = #sRowNumber
IF #value IS NULL
SET #value = ''
RETURN #value
END
which finally calls this function:
ALTER FUNCTION [dbo].[fn_Split] (
#sInputList VARCHAR(MAX),
#sDelimiter VARCHAR(10) = ','
) RETURNS #List TABLE (item VARCHAR(MAX))
BEGIN
DECLARE #sItem VARCHAR(MAX)
WHILE CHARINDEX(#sDelimiter,#sInputList,0) <> 0
BEGIN
SELECT #sItem=RTRIM(LTRIM(SUBSTRING(#sInputList,1,CHARINDEX(#sDelimiter,#sInputList,0)-1))), #sInputList=RTRIM(LTRIM(SUBSTRING(#sInputList,CHARINDEX(#sDelimiter,#sInputList,0)+LEN(#sDelimiter),LEN(#sInputList))))
IF LEN(#sItem) > 0
INSERT INTO #List SELECT #sItem
END
IF LEN(#sInputList) > 0
INSERT INTO #List SELECT #sInputList -- Put the last item in
RETURN
END
The reason it is "randomly" returning different things has to do with how SQL Server optimizes queries, and where they get short-circuited.
One way to fix the problem is the change the return value of fn_GetIDFromRuleName:
return (case when isnumeric(#value) then #value end)
Or, change the join condition:
on np.network_policy_id = (select case when isnumeric(dbo.fn_GetIDFromRuleName(cscsf.rule_name)) = 1)
then dbo.fn_GetIDFromRuleName(cscsf.rule_name) end)
The underlying problem is order of evaluation. The reason the "case" statement fixes the problem is because it checks for a numeric value before it converts and SQL Server guarantees the order of evaluation in a case statement. As a note, you could still have problems with converting numbers like "6e07" or "1.23" which are numeric, but not integers.
Why does it work sometimes? Well, clearly the query execution plan is changing, either statically or dynamically. The failing case is probably on a row that is excluded by the WHERE condition. Why does it try to do the conversion? The question is where the conversion happens.
WHere the conversion happens depends on the query plan. This may, in turn, depend on when the table cscf in question is read. If it is already in member, then it might be read and attempted to be converted as a first step in the query. Then you would get the error. In another scenario, the another table might be filtererd, and the rows removed before they are converted.
In any case, my advice is:
NEVER have implicit conversion in queries.
Use the case statement for explicit conversions.
Do not rely on WHERE clauses to filter data to make conversions work. Use the case statement.

How do I make a function in SQL Server that accepts a column of data?

I made the following function in SQL Server 2008 earlier this week that takes two parameters and uses them to select a column of "detail" records and returns them as a single varchar list of comma separated values. Now that I get to thinking about it, I would like to take this table and application-specific function and make it more generic.
I am not well-versed in defining SQL functions, as this is my first. How can I change this function to accept a single "column" worth of data, so that I can use it in a more generic way?
Instead of calling:
SELECT ejc_concatFormDetails(formuid, categoryName)
I would like to make it work like:
SELECT concatColumnValues(SELECT someColumn FROM SomeTable)
Here is my function definition:
FUNCTION [DNet].[ejc_concatFormDetails](#formuid AS int, #category as VARCHAR(75))
RETURNS VARCHAR(1000) AS
BEGIN
DECLARE #returnData VARCHAR(1000)
DECLARE #currentData VARCHAR(75)
DECLARE dataCursor CURSOR FAST_FORWARD FOR
SELECT data FROM DNet.ejc_FormDetails WHERE formuid = #formuid AND category = #category
SET #returnData = ''
OPEN dataCursor
FETCH NEXT FROM dataCursor INTO #currentData
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #returnData = #returnData + ', ' + #currentData
FETCH NEXT FROM dataCursor INTO #currentData
END
CLOSE dataCursor
DEALLOCATE dataCursor
RETURN SUBSTRING(#returnData,3,1000)
END
As you can see, I am selecting the column data within my function and then looping over the results with a cursor to build my comma separated varchar.
How can I alter this to accept a single parameter that is a result set and then access that result set with a cursor?
Others have answered your main question - but let me point out another problem with your function - the terrible use of a CURSOR!
You can easily rewrite this function to use no cursor, no WHILE loop - nothing like that. It'll be tons faster, and a lot easier, too - much less code:
FUNCTION DNet.ejc_concatFormDetails
(#formuid AS int, #category as VARCHAR(75))
RETURNS VARCHAR(1000)
AS
RETURN
SUBSTRING(
(SELECT ', ' + data
FROM DNet.ejc_FormDetails
WHERE formuid = #formuid AND category = #category
FOR XML PATH('')
), 3, 1000)
The trick is to use the FOR XML PATH('') - this returns a concatenated list of your data columns and your fixed ', ' delimiters. Add a SUBSTRING() on that and you're done! As easy as that..... no dogged-slow CURSOR, no messie concatenation and all that gooey code - just one statement and that's all there is.
You can use table-valued parameters:
CREATE FUNCTION MyFunction(
#Data AS TABLE (
Column1 int,
Column2 nvarchar(50),
Column3 datetime
)
)
RETURNS NVARCHAR(MAX)
AS BEGIN
/* here you can do what you want */
END
You can use Table Valued Parameters as of SQL Server 2008, which would allow you to pass a TABLE variable in as a parameter. The limitations and examples for this are all in that linked article.
However, I'd also point out that using a cursor could well be painful for performance.
You don't need to use a cursor, as you can do it all in 1 SELECT statement:
SELECT #MyCSVString = COALESCE(#MyCSVString + ', ', '') + data
FROM DNet.ejc_FormDetails
WHERE formuid = #formuid AND category = #category
No need for a cursor
Your question is a bit unclear. In your first SQL statement it looks like you're trying to pass columns to the function, but there is no WHERE clause. In the second SQL statement you're passing a collection of rows (results from a SELECT). Can you supply some sample data and expected outcome?
Without fully understanding your goal, you could look into changing the parameter to be a table variable. Fill a table variable local to the calling code and pass that into the function. You could do that as a stored procedure though and wouldn't need a function.