Error using Common Table Expression in SQL User Defined Function - sql

CREATE FUNCTION [dbo].[udfGetNextEntityID]
()
RETURNS INT
AS
BEGIN
;WITH allIDs AS
(
SELECT entity_id FROM Entity
UNION SELECT entity_id FROM Reserved_Entity
)
RETURN (SELECT (MAX(entity_id) FROM allIDs )
END
GO
SQL isn't my strong point, but I can't work out what I'm doing wrong here. I want the function to return the largest entity_id from a union of 2 tables. Running the script gives the error:
Incorrect syntax near the keyword 'RETURN'.
I looked to see if there was some restriction on using CTEs in functions but couldn't find anything relevant. How do I correct this?

CREATE FUNCTION [dbo].[udfGetNextEntityID]()
RETURNS INT
AS
BEGIN
DECLARE #result INT;
WITH allIDs AS
(
SELECT entity_id FROM Entity
UNION SELECT entity_id FROM Reserved_Entity
)
SELECT #result = MAX(entity_id) FROM allIDs;
RETURN #result;
END
GO

While you can do it, why do you need a CTE here?
RETURN
(
SELECT MAX(entity_id) FROM
(
SELECT entity_id FROM dbo.Entity
UNION ALL
SELECT entity_id FROM dbo.Reserved_Entity
) AS allIDs
);
Also there is no reason to use UNION instead of UNION ALL since this will almost always introduce an expensive distinct sort operation. And please always use the schema prefix when creating / referencing any object.

You can not return the way your are doing from the function.
Make use of a local variable and return the same.
CREATE FUNCTION [dbo].[udfGetNextEntityID]()
RETURNS INT
AS
BEGIN
DECLARE #MaxEntityId INT;
WITH allIDs AS
(
SELECT entity_id FROM Entity
UNION SELECT entity_id FROM Reserved_Entity
)
SELECT #MaxEntityId = MAX(entity_id) FROM allIDs;
RETURN #MaxEntityId ;
END
GO

create function tvfFormatstring (#string varchar(100))
returns #fn_table table
(id int identity(1,1),
item int)
as
begin
insert into #fn_table(item)
declare #result int
set #string = #string+'-'
;with cte (start,number)
as
(
select 1 as start , CHARINDEX('-',#string,1) as number
union all
select number+1 as start , CHARINDEX('-',#string,number+1) as number from cte
where number <= LEN(#string)
)
select #result = SUBSTRING(#string,start,number-start) from cte ;
return #result;
end
select * from tvfFormatstring ('12321-13542-15634')

Related

How do I create a function to accept [[customerID]] and return CustName Please look at details

I have been asked to created a Function to accept CustomerID and return CustomerName for the CustomerID, I m a new Student/Developer Please if the question is not clear let me know so i can add more details about it, but that is what I was exactly asked.
functions in SQL are of three types.ignoring rest CLR functions ...
create table test
(
id int,
name varchar(4)
)
insert into test
select 1,'abc'
union all
select 2,'cde'
1.Scalar function takes one value and return one value
now for the above table ,you can create scalar function like below
create function dbo.test
(
#id int
)
returns varchar(4)
as
begin
declare #name varchar(4)
select #name=name from test where id =#id
return #name
End
You invoke it like:
select dbo.test(1)
2.Inline table valued functions:takes a single input same like scalar functions and returns table
create function dbo.test
(
#id int
)
as
returns TABLE
(
select * from test where id=#id)
You invoke it like:
select * from dbo.test(1)
3.Multi table valued function:
create function dbo.test
(
#id int
)
returns
#test table
(
id int,
name varchar(4)
)
as
begin
insert into #test
select * from test where id =#id
return
end
You invoke it like:
select * from dbo.test(1)
Take any one of Itzik Ben Gan books and start learning SQL the way it should be learned

Transact SQL Subquery calling a function incorrect syntax

I get an incorrect syntax near '.' and can't seem to identify why in the following code:
select
o.object_id,
(select top 1 Zone from dbo.getzone(o.object_id)) as Zone from object as o
getzone is a table-valued Function that works perfectly when I reference it directly, or if I put a specific object_id in, but everytime I try to make it dynamic, I get the syntax error.
What am I missing?
You can't do that. You need to have a scalar version that returns only one result. It can be just a wrapper script if you want. Something like this:
CREATE FUNCTION [dbo].[getSingleZone](#object_id varchar(20))
RETURNS varchar(20)
AS
BEGIN
DECLARE #Zone varchar(20)
select #Zone = max(Zone) from dbo.getzone(#object_id)
return #Zone
END
select
o.object_id,
dbo.getSingleZone(o.object_id) as Zone from object o
I don't know your data types, so I guessed.
Fix your alias
select o.object_id,
(select top 1 Zone from dbo.getzone(o.object_id)) as Zone
from object AS o
Perhaps I'm missing the problem, but this seems to work. Using the name of a built-in function (OBJECT_ID) as a column name might not be helping.
SQL fiddle example or code below.
-- TVF without parameter.
create function dbo.GetZone()
returns table as
return
select Id, Letter
from
( values ( 1, 'Aleph' ), ( 2, 'Beth' ), ( 3, 'Gimmel' ) ) as Letters( Id, Letter );
go
-- TVF with parameter;
create function dbo.GetZone2( #Id as Int )
returns table as
return
select Id, Letter
from dbo.GetZone() where Id = #Id;
go
select * from dbo.GetZone();
select * from dbo.GetZone2( 2 );
-- Sample table and data.
declare #Objects as table ( Id Int Identity, Letter VarChar(16) );
insert into #Objects values ( 'Alpha' ), ( 'Beta' ), ( 'Gamma' );
select * from #Objects;
-- Correlated subquery.
select O.Id, O.Letter as [Greek],
( select top 1 Letter from dbo.GetZone( ) where Id = O.Id ) as [Hebrew]
from #Objects as O;
select O.Id, O.Letter as [Greek],
( select top 1 Letter from dbo.GetZone2( O.Id ) ) as [Hebrew]
from #Objects as O;
-- Houseclean.
drop function dbo.GetZone;
drop function dbo.GetZone2;

How to execute the GO statement with dynamic count?

How to set dynamic count for GO statement?
I am getting the following error:
A fatal scripting error occurred.Incorrect syntax was encountered
while parsing Go.
when I tried to run the below query:
Declare #count int
Select #count=COUNT(*) From Users
Insert Into #DummyUsers
Select * from Users where UserName = 'Sachin'
GO #Count
But the same is working fine when I use the below query with hard coded count.
Declare #count int
Select #count=COUNT(*) From Users
Insert Into #DummyUsers
Select * from Users where UserName = 'Sachin'
GO 5
Appreciate your suggestions if you have any idea on this.
You can't. As soon as SSMS encounters GO the batch is terminated and your variable no longer exists.
You can't use a variable for the count parameter to GO, but in your example (which may be contrived) you could just join back to Users :
Insert Into #DummyUsers
Select U.* from Users U
INNER JOIN Users U2
ON U.UserName = 'Sachin'
Other options:
Dynaimc SQL (building up SQL by concatenating strings) and executing via SQLCMD.EXE or OSQL.EXE
Using a WHILE loop with a counter
If you simply want to insert a repeated row you could use a CTE or numbers table.
-- Sample data.
declare #Users as Table ( UserId Int Identity, Name VarChar(16) );
insert into #Users ( Name ) values
( 'Bob' ), ( 'Carol' ), ( 'Ted' ), ( 'Alice' );
select * from #Users;
-- Load another table with repetitions of a single user.
declare #TempUsers as Table ( UserId Int, Name VarChar(16) );
declare #Repetitions as Int = ( select Count(*) from #Users );
with TempUsers as (
select UserId, Name, 1 as Repetitions
from #Users
where Name = 'Ted'
union all
select UserId, Name, Repetitions + 1
from TempUsers
where Repetitions < #Repetitions
)
insert into #TempUsers ( UserId, Name )
select UserId, Name
from TempUsers;
select * from #TempUsers;
Instead try this.
DECLARE #cntr INT=1
WHILE #cntr <= #count
BEGIN
INSERT INTO #DummyUsers
SELECT *
FROM Users
WHERE UserName = 'Sachin'
SET #cntr+=1
END
I would just loop it
Declare #count int
Select #count=COUNT(*) From Users
WHILE(#count > 0)
BEGIN
Insert Into #DummyUsers
Select *
FROM Users
WHERE UserName = 'Sachin'
SET #count = #count - 1;
END
While I agree with the others that there is likely a better way to achieve what you are trying to do, if there is some limitation that we are not seeing, you could look into using a sequence
The sequence you create persists and can be reset as needed and you can "increment" it by calling the NEXT VALUE FOR function

Can the Table Valued function that I wrote be turned into a View in Sql Server 2005

I am still so new to all this and I think I may have not done this the best way. I have a Table Valued function that I wrote, but I think that it could be written as a view.
The big catch as to why I used a table val function is that if the select query returns no results then I wanted to return a "default" row that showed empty values along with the timestamp and I didn't know how to do that in a view.
I betting the experts here know how to. Here's the function:
alter FUNCTION [dbo].[GetCurrentRTBindingConstraints]()
RETURNS
#CurrentBindingConstraints table (
CONSTRAINTNAME [nvarchar] (120),
MKTHOUR_EST [dateTime],
MARGINALVALUE [nvarchar] (20)
)
AS
BEGIN
INSERT INTO #CurrentBindingConstraints
select * from
OPENQUERY(UDS9, 'select
CONSTRAINTNAME, MKTHOUR -(5/24) as MKTHOUR_EST,MARGINALVALUE
from UDS9.MKTPLANCONSTRAINT mpc
where MARGINALVALUE != 0.00 and mpc.caseid=(SELECT caseid FROM uds9.MktCase
WHERE casestartinterval=(SELECT MAX(casestartinterval) FROM uds9.MktCase WHERE casestate=5 AND studymodeid=5)
AND casestate=5 AND studymodeid=5)')
DECLARE #cnt INT
SELECT #cnt = COUNT(*) FROM #CurrentBindingConstraints
IF #cnt = 0
INSERT INTO #CurrentBindingConstraints (
[CONSTRAINTNAME],
[MKTHOUR_EST],
[MARGINALVALUE])
VALUES ('None',dbo.RoundTime(dbo.GetGMTtoEST(getutcdate())),'None')
RETURN
END
You can use a common table expression (CTE) and a ranking function as follows:
;with Defaulted as (
select 'none' as Col1,CURRENT_TIMESTAMP as Col2,'none' as Col3,1 as init -- This is your default row
union all
select name,DATEADD(day,-1,CURRENT_TIMESTAMP),name,0 from sys.objects -- This is where you query for real rows
), Ranked as (
select Col1,Col2,Col3,RANK() OVER (ORDER BY init) as rnk from Defaulted
)
select * from Ranked where rnk = 1
The above is just an example - you'd need to replace the two selects inside the first CTE with your real queries, and should use column names rather than select *. It works because the ranking function (RANK()) is able to assess the result set as a whole.
Edit - trying with your actual queries:
create view CurrentRTBindingConstraints
as
;with Defaulted as (
select CONSTRAINTNAME,MKTHOUR_EST,MARGINALVALUE,0 as init from
OPENQUERY(UDS9, 'select
CONSTRAINTNAME, MKTHOUR -(5/24) as MKTHOUR_EST,MARGINALVALUE
from UDS9.MKTPLANCONSTRAINT mpc
where MARGINALVALUE != 0.00 and mpc.caseid=(SELECT caseid FROM uds9.MktCase
WHERE casestartinterval=(SELECT MAX(casestartinterval) FROM uds9.MktCase WHERE casestate=5 AND studymodeid=5)
AND casestate=5 AND studymodeid=5)')
union all
select 'None',dbo.RoundTime(dbo.GetGMTtoEST(getutcdate())),'None',1
), Ranked as (
select CONSTRAINTNAME,MKTHOUR_EST,MARGINALVALUE,RANK() OVER (ORDER BY init) as rnk from Defaulted
)
select CONSTRAINTNAME,MKTHOUR_EST,MARGINALVALUE from Ranked where rnk = 1

how to return a cell into a variable in sql functions

I want to define a scaler function which in that I'm going to return the result into a variable but I do not know how to do this.
CREATE FUNCTION dbo.Funname ( #param int )
RETURNS INT
AS
declare #returnvar int
select #returnvar = select colname from tablename where someconditions = something
return(#returnvar)
I want to make a function something like the top. I mean the result of the select statement which is:
select colname from tablename where someconditions = something
Is only a single cell and we are sure about it. I want to store it into a variable and return it from the function. How can I implement this thing?
I should probably mention that scalar UDFs do come with a considerable health warning and can cause performance issues depending upon how you use them.
Here's an example though.
CREATE FUNCTION dbo.Funname ( #param INT )
RETURNS INT
WITH RETURNS NULL ON NULL INPUT
AS
BEGIN
RETURN (SELECT number FROM master.dbo.spt_values WHERE number < #param)
END
In the above example I didn't use a variable as it is redundant. The version with variable is
BEGIN
DECLARE #Result int
SET #Result = (SELECT number FROM master.dbo.spt_values WHERE number < #param)
RETURN #Result
END
For both of the above you would need to be sure the Query returned at most one row to avoid an error at runtime. For example
select dbo.Funname(-1) Returns -32768
select dbo.Funname(0) Returns error "Subquery returned more than 1 value."
An alternative syntax would be
BEGIN
DECLARE #Result int
SELECT #Result = number FROM master.dbo.spt_values WHERE number < #param
RETURN #Result
END
This would no longer raise the error if the subquery returned more than one value but you would just end up with an arbitrary result with no warning - which is worse.
Following Comments I think this is what you need
CREATE FUNCTION dbo.getcustgrade(#custid CHAR(200))
RETURNS INT
WITH RETURNS NULL ON NULL INPUT
AS
BEGIN
RETURN
( SELECT [cust grade]
FROM ( SELECT customerid,
DENSE_RANK() OVER (ORDER BY COUNT(*) DESC) AS [cust grade]
FROM Orders
GROUP BY CustomerID
)
d
WHERE customerid = #custid
)
END