Executing select statement as variable from TVF - sql

I have to get a list of results from a Table value function from a variable. I have done something like this:
DECLARE #Date char(8) = '20200508'
DECLARE #Type varchar(100) = 'Inbound'
DECLARE #Offset INT = 3600
DECLARE #EmployeeID INT = null
DECLARE #TypeFunc as varchar(max)
SET #TypeFunc= N'select EmpID, Callcount from dbo.fn_' + #Type + '('''+ #Date +''','+ CAST(#Offset as Varchar(100))+','+ CAST(#EmployeeID as varchar(100))+')';
EXEC (#TypeFunc)
I expect to see a list of results as if I'm doing a normal select query, however, it is just coming back with 'Commands completed successfully.' in the results grid, which doesn't seem like its doing it correctly.
The query it should run should look like
Select EmpID, Callcount From dbo.fn_Inbound('20200508', 3600, null)
Anything I'm missing here?

I found 2 mistakes in your Query:
1.) Use CONCAT instead of + because if any of your concatenating string is null it makes the whole Concatenation as NULL (For your case EmpID is null it will makes the Whole Query as null by using +)
2.)ISNULL(CAST(#EmployeeID as varchar(100)),'NULL') Use ISNULL fn to pass as null for that Parameter in your function
SET #TypeFunc= CONCAT(N'select EmpID, Callcount from dbo.fn_' , #Type , '(''', #Date
,''',', CAST(#Offset as Varchar(100)),',',ISNULL(CAST(#EmployeeID as
varchar(100)),'NULL'),')');

Related

Subquery as parameter into SQL Server UDF

My question is similar to this.
I made a scalar function like follows:
CREATE FUNCTION [dbo].[MyFunction](#table [TableModel] READONLY)
RETURNS DECIMAL(18, 6) AS
BEGIN
DECLARE #sql NVARCHAR(MAX), #params NVARHCAR(MAX), #value DECIMAL(16, 8);
SELECT #sql = formula, #params = params FROM formulas WHERE id = (SELECT TOP 1 id_formula FROM #table)
EXEC sp_executesql #sql, #params, #table=#table, #value=#value OUTPUT;
RETURN #value;
END
Where a formula SQL could be something like:
SELECT #value = SUM(value) / AVG(value) FROM #table
And it could have more columns if needed.
The model table looks like so:
CREATE TYPE [dbo].[TableModel] AS TABLE(
[formula] INT NOT NULL,
[value] DECIMAL(16, 8) NOT NULL
)
And I want to use it like so:
SELECT od.id, od.col1, od.col2,
dbo.MyFunction((SELECT id.formula, id.value FROM #raw_data id WHERE id.id = od.id)) as result
FROM #data od
GROUP BY od.id, od.col1, od.col2
Where the ID unique and multiple rows will have the same id.
Basically, what I'm trying to do in a single query I want to call a function that has a table parameter. But I want this table to be a subquery.
I'm aware that you can call the function with a table variable as mentioned in this answer.
Is this possible in any way? I'm having this error at executing:
Msg 116, Level 16, State 1, Line 30
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.

How to validate string in SQL with characters

Check if the string is following the correct format or not. The correct format is as follows:
0000/00000. So far this is what i have got:
declare #ID nvarchar = '0000/00000'
SELECT (case when len(#id) not between 1 and 12 OR #id not like( '[0-9][0-9][0-9][0-9]' + '/' + '[0-9][0-9][0-9][0-9][0-9]')
OR LEFT(#id,13) LIKE '%[0-9]&' then 'OK' else 'ERROR' end
Why not just use not like?
where #id not like '[0-9][0-9][0-9][0-9]/[0-9][0-9][0-9][0-9][0-9]'
I assume that "0" means that any digit is allowed.
First, this will be a problem:
declare #ID nvarchar = '0000/00000'
This:
declare #ID nvarchar = '0000/00000';
SELECT #ID LEN(#ID), DATALENGTH(#ID);
Returns:
ID LEN
---- ----
0 1
With that in mind, note that this: PATINDEX({your pattern},#Id) will return a natural Boolean result.
DECLARE #ID1 NVARCHAR(1000) = '5555/12312',
#ID2 NVARCHAR(1000) = '1234/12345678',
#pattern VARCHAR(100) = '[0-9][0-9][0-9][0-9]/[0-9][0-9][0-9][0-9][0-9]';
SELECT IsCool = PATINDEX(#pattern,f.Id)
FROM (VALUES(#ID1),(#ID2)) AS f(Id);
Returns:
IsCool
-----------
1
0
If, by 0000/00000 you mean: Four digits + "/" + five digits then these expressions all work (note I made the pattern a variable for cleaner, easier to read code, it's not required):
DECLARE #ID NVARCHAR(1000) = '5555/12312',
#pattern VARCHAR(100) = '[0-9][0-9][0-9][0-9]/[0-9][0-9][0-9][0-9][0-9]';
SELECT CASE PATINDEX(#Pattern,#ID) WHEN 1 THEN 'Ok' ELSE 'Error' END;
SELECT IIF(PATINDEX(#Pattern,#ID)>0,'Ok','Error');
SELECT CHOOSE(PATINDEX(#Pattern,#ID)+1,'Error','Ok');
Your code suggests that #ID can be up to 12 characters long. Let's say, for example, the acceptable formats were:
0000/00000
0000/000000
0000/0000000
Then you could do this:
DECLARE #ID NVARCHAR(1000) = '5555/12312';
SELECT CASE WHEN LEN(#ID)<13 AND SIGN(PATINDEX('%[0-9][0-9]',#ID)) *
PATINDEX('[0-9][0-9][0-9][0-9]/[0-9][0-9][0-9][0-9][0-9]%',#ID) = 1
THEN 'Ok' ELSE 'Error' END;
Two final thoughts:
LEN({string}) can't be negative; LEN(#ID) < 13 will do the trick
If the max length is really 12 then make the parameter or variable NVARCHAR(13). This way, for example, someone passes in an 8000 character string, SQL doesn't have to scan the whole thing to determine that it's not valid.

How to filter data based on different values of a column in sql server

I am stuck at a point.
I want to select based on the column entitytype if entitytype value is Booking or JOb then it will filter on its basis but if it is null or empty string('') then i want it to return all the rows containing jobs and bookings
create proc spproc
#entityType varchar(50)
as
begin
SELECT TOP 1000 [Id]
,[EntityId]
,[EntityType]
,[TenantId]
FROM [FutureTrakProd].[dbo].[Activities]
where TenantId=1 and EntityType= case #EntityType when 'BOOKING' then 'BOOKING'
when 'JOB' then 'JOB'
END
end
Any help would be appreciable
Thankyou
create proc spproc
#entityType varchar(50)
as
begin
SELECT TOP 1000 [Id]
,[EntityId]
,[EntityType]
,[TenantId]
FROM [FutureTrakProd].[dbo].[Activities]
where TenantId=1 and (#EntityType is null OR EntityType= #EntityType)
end
You don't need to use case expression you can do :
SELECT TOP 1000 [Id], [EntityId], [EntityType], [TenantId]
from [FutureTrakProd].[dbo].[Activities]
WHERE TenantId = 1 AND
(#EntityType IS NULL OR EntityType = #EntityType)
ORDER BY id; -- whatever order you want (asc/desc)
For your query procedure you need to state explicit ORDER BY clause otherwise TOP 1000 will give random Ids.
You don't need a CASE expression for this, you just need an OR. The following should put you on the right path:
WHERE TenantId=1
AND (EntityType = #EntityType OR #EntityType IS NULL)
Also, note it would also be wise to declare your parameter as NULLable:
CREATE PROC spproc #entityType varchar(50) = NULL
This means that someone can simply exclude the paramter, value than having to pass NULL (thus EXEc spproc; would work).
Finally, if you're going to have lots of NULLable parameters, then you're looking at a "catch-all" query; the solution would be different if that is the case. "Catch-all" queries can be notoriously slow.
You can execute a dynamic sql query.
Query
create proc spproc
#entityType varchar(50)
as
begin
declare #sql as nvarchar(max);
declare #condition as nvarchar(2000);
select = case when #entityType is not null then ' and [EntityType] = #entityType;' else ';' end;
select #sql = 'SELECT TOP 1000 [Id], [EntityId], [EntityType], [TenantId] FROM [FutureTrakProd].[dbo].[Activities] where TenantId = 1 ';
exec sp_executesql #sql,
N'#entityType nvarchar(1000)',
#entityType = #entityType
end

Using a CASE statement in a WHERE clause in SQL Server

I am trying to create query form on a website. First object is a dropdown list with operators. Default first value in the dropdown list is NULL(1), second value is LIKE(2), etc... Second object is a textbox where the user can enter a string like "A".
Therefore, I am trying to build the below SQL query to simulate the variables coming from the website. It runs and returns all values when the #op = 1. But I keep getting the following error when I change #op = 2:
Conversion failed when converting the nvarchar value 'Tom LIKE A%' to data type int."
DECLARE #StartDate DATETIME2(7) = '2017-11-08 00:00:00.0000000 +00:00'
DECLARE #EndDate DATETIME2(7) = '2017-11-08 00:00:00.0000000 +00:00'
DECLARE #Op INT = 2
DECLARE #name NVARCHAR(25) = 'A'
SELECT
name,
dttm
FROM
tableName
WHERE
dttm BETWEEN #StartDate AND #EndDate
AND CASE #Op
WHEN 1 THEN 1
WHEN 2 THEN name + ' LIKE ' + #name +'%'
END <> 0
Don't use case. Just use regular boolean logic:
WHERE dttm BETWEEN #StartDate AND #EndDate AND
( (#op = 1) OR
(#op = 2 AND name LIKE #name + '%')
)
Your specific issue involves constructing a LIKE comparison in a string. However, the above appears to be what you want to do.

Charindex in SQL doesn't give the desired result

I have a string which is an output from a function, for example: "1,3,16,..,..".
I used the following SQL query and ran it in the query builder in Visual Studio, and it didn't give me any syntax errors.
SELECT ItemID, Name, RelDate, Price, Status FROM item_k WHERE (ItemID = cast(charindex(',', #itemIDs) as int))
I gave 3,16 as the #itemID parameter values, but it didn't give the desired results.
Then I used the following SQL query (without charindex):
SELECT ItemID, Name, RelDate, Price, Status FROM item_k WHERE (ItemID = #itemIDs)
I gave 3 as the #itemID parameter value, and I got a result for it.
I also gave 16 (on a separate occasion) as the #itemID parameter value, and I got a result for it. I conclude that there are values for ItemID 3 & 16.
Why doesn't an SQL query with charindex give me any result?
I can't seem to figure out the issue here, please help.
Here's yet another solution. In my experience, when you have a list of ItemIds as a string of comma separated values, you need a split function. This is very useful to have.
With a split function, you can simply do an INNER JOIN with the results of calling the split function and passing the list of ItemIds and associated delimeter as follows:
DECLARE #ItemIDs varchar(100)
SET #ItemIDs = '1,3,16,22,34,35'
SELECT
ItemID, Name, RelDate, Price, Status
FROM item_k
INNER JOIN dbo.UTILfn_Split(#ItemIDs,',') itemIds
ON itemIds.Value = item_k.ItemID
While this may look complicated at first, it is the more elegant and maintainable solution. Here's the code for creating the dbo.UTILfn_Split function. You need to run this first:
IF EXISTS (SELECT * FROM sysobjects WHERE id =
object_id(N'[dbo].[UTILfn_Split]') AND xtype IN (N'FN', N'IF', N'TF'))
DROP FUNCTION [dbo].[UTILfn_Split]
GO
CREATE FUNCTION dbo.UTILfn_Split
(
#String nvarchar (4000),
#Delimiter nvarchar (10)
)
RETURNS #ValueTable TABLE ([Value] nvarchar(4000))
BEGIN
DECLARE #NextString nvarchar(4000)
DECLARE #Pos int
DECLARE #NextPos int
DECLARE #CommaCheck nvarchar(1)
--Initialize
SET #NextString = ''
SET #CommaCheck = RIGHT(#String,1)
--Check for trailing Comma, if not exists, INSERT
--if (#CommaCheck <> #Delimiter )
SET #String = #String + #Delimiter
--Get position of first Comma
SET #Pos = CHARINDEX(#Delimiter,#String)
SET #NextPos = 1
--Loop while there is still a comma in the String of levels
WHILE (#pos <> 0)
BEGIN
SET #NextString = SUBSTRING(#String,1,#Pos - 1)
INSERT INTO #ValueTable ( [Value]) Values (#NextString)
SET #String = SUBSTRING(#String,#pos +1,LEN(#String))
SET #NextPos = #Pos
SET #pos = CHARINDEX(#Delimiter,#String)
END
RETURN
END
CHARINDEX just returns the postition where the character is found within the string.
So when #ItemIDs is set to '3,16' then your WHERE clause...
WHERE (ItemID = CAST(CHARINDEX(',', #ItemIDs) AS INT))
...is equivalent to...
WHERE ItemID = 2
...because CHARINDEX returns 2 since the comma character is found at position 2 of the string '3,16'.
I'm guessing that (a) you don't have a row in your table where ItemID is 2, and (b) you don't really want the position of the comma to dictate which rows are returned.
You can create a query dynamically that uses the in operator:
declare #Sql varchar(1000)
set #Sql = 'select ItemID, Name, RelDate, Price, Status from item_k where ItemID in (' + #itemIDs + ')'
exec(#Sql)
Be careful with what you send into the procedure, though. As with any dynamic SQL, if the data comes from user input without validation, the procedure is wide open for SQL injection.
Edit:
This is what happens in the query:
First we declare a variable to hold the dynamic query. This is just a varchar variable that is large enough.
In the variable we put the #itemIDs variable between two strings to form the query. The comma separated values is put between the parentheses of the in operator to form an expression similar to: where ItemID in (1,3,16)
Finally the exec command executes the query in the variable.
Try
SELECT ItemID, Name, RelDate, Price, Status FROM item_k WHERE ItemID in (#itemIDs)