SQL with clause dynamic where parameter - sql

I have a tree-style database with the following structure:
Table fields:
NodeID int
ParentID int
Name varchar(40)
TreeLevel int
I would like to use a variable #NodeID in the first part of the with clause to don't get all the table just start from the piece I'm interested in (see where Parent=#ParentID and comment).
with RecursionTest (NodeID,ParentID,ThemeName)
as
(
--if i remove the where from here it spends too much time (the tree is big)--
select Nodeid,ParentID,Name from TreeTable where ParentID=#ParentID
union all
select T0.Nodeid,
T0.ParentID,
T0.Name
from
TreeTable T0
inner join RecursionTest as R on T0.ParentID = R.NodeID
)
select * from RecursionTest
This throws some errors, but my question is:
Is possible to pass a variable to a with clause ?
Thanks a lot in advance.
Best regards.
Jose

Yes.
declare #ParentID int
set #ParentID = 10;
with RecursionTest (NodeID,ParentID,ThemeName) ....
You could wrap the whole thing up in a parameterised inline TVF as well. Example of this last approach.
CREATE FUNCTION dbo.RecursionTest (#ParentId INT)
RETURNS TABLE
AS
RETURN
(
WITH RecursionTest (NodeID,ParentID,ThemeName)
AS
(
/*... CTE definition goes here*/
)
SELECT NodeID,ParentID,ThemeName
FROM RecursionTest
)
GO
SELECT NodeID,ParentID,ThemeName
FROM dbo.RecursionTest(10)
OPTION (MAXRECURSION 0)

Unfortunately <11g this will throw an ORA-32033 - unsupported column aliasing, as this functionality is not supported < that version

Related

Passing a nullable local variable to a where condition

I have the following SQL function:
CREATE or ALTER FUNCTION [dbo].[GET_BOOK_CODE_BY_AUTHOR_ID]
(
#AUTHOR_ID int
)
RETURNS varchar(50)
AS
BEGIN
DECLARE
#MOT_N_ID int,
#SCO_N_ID int,
#BOOK_CODE varchar(50);
select
#MOT_N_ID = AUT.MOT_N_ID,
#SCO_N_ID = AUT.SCO_N_ID
from AUTHOR AUT
where AUT.AUT_ID = #AUTHOR_ID
SELECT
#BOOK_CODE = (
select BOO.BOO_CH_CODE
from BOOK BOO
where
and BOO.MOT_N_ID = #MOT_N_ID
and BOO.SCO_N_ID = #SCO_N_ID
)
RETURN #BOOK_CODE
END;
GO
The variable #SCO_N_ID can be a null value however and when it returns a null value SQL interprets the condition as:
BOO.SCO_N_ID = null
It is not interpreting it as:
BOO.SCO_N_ID is null
Any idea of how to do this?
I would do this check
"the variable is null or the field is equal to the variable"
and (#SCO_N_ID is null or BOO.SCO_N_ID = #SCO_N_ID)
How about something like this:
CREATE or ALTER FUNCTION [dbo].[GET_BOOK_CODE_BY_AUTHOR_ID]
(
#AUTHOR_ID int
)
RETURNS varchar(50)
AS
BEGIN
DECLARE
#BOOK_CODE varchar(50);
SELECT TOP 1 #BOOK_CODE = VEN_CH_CODE
FROM BOOK BOO
INNER JOIN AUTHOR AUT
ON AUT.MOT_N_ID = BOO.MOT_N_ID
WHERE
BOO.SCO_N_ID = AUT.SCO_N_ID OR
(BOO.SCO_N_ID IS NULL AND AUT.SCO_N_ID IS NULL)
ORDER BY BOO.SCO_N_ID, BOO.MOT_N_ID
RETURN #BOOK_CODE
END;
Note I have joined the tables to make one query, also note I have put a TOP 1 because it might be possible for the query to return more than one result set. If you use TOP 1 then you should have an ORDER BY clause to make sure you return the result you want
There are a few important points to note here:
Firstly, to compare while taking nulls into account, instead of a = b you can use this syntax exists (select a intersect select b). This will compile down to an IS comparison, as shown here, and is very efficient
You can dispense with all the variables and just do a simple join
User-defined scalar functions are very slow. You will get much better performance from an inline Table-Valued Function
CREATE or ALTER FUNCTION [dbo].[GET_BOOK_CODE_BY_AUTHOR_ID]
(
#AUTHOR_ID int
)
RETURNS TABLE
AS RETURN (
select VEN.VEN_CH_CODE
from BOOK BOO
join AUTHOR AUT on BOO.MOT_N_ID = AUT.MOT_N_ID
and exists (select BOO.SCO_N_ID
intersect
select AUT.SCO_N_ID)
where AUT.AUT_ID = #AUTHOR_ID
);
GO
You can use it like this
SELECT *
FROM GET_BOOK_CODE_BY_AUTHOR_ID(101) b;
Or like this
SELECT *
FROM OtherTable t
CROSS APPLY GET_BOOK_CODE_BY_AUTHOR_ID(t.authorId) b;
Or this
SELECT *,
(SELECT * FROM GET_BOOK_CODE_BY_AUTHOR_ID(t.authorId))
FROM OtherTable t;
I like to set both sides to an unavailable code, I will use -99 in this case.
ISNULL(BOO.SCO_N_ID,-99) = ISNULL(#SCO_N_ID,-99)

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;

Is there a way to avoid this loop in SQL Server 2008 R2?

I've often seen that expert users advise to try avoid loops at database level (Reference here). I have a short block of code where i can't see other way to achieve the task without the use of a loop. The task is very simple but is there a way to avoid the loop?
DECLARE #id INT = 1
DECLARE #auxId INT
WHILE #id IS NOT NULL
BEGIN
SET #auxId = #id
SELECT #id = id_next_version FROM task WHERE id_task = #id
END
SELECT #aux
Explanation of the code:
I have a table where there are tasks and some rows are updates of other tasks, so I have a column where are the id of the next version. What I want is to find the id of the last version of a task.
EDIT:
Table structure
CREATE TABLE task
(
id_task INT IDENTITY(1,1) NOT NULL,
task NVARCHAR(50) NULL,
id_next_version INT NULL
)
You are traversing a graph -- probably a tree structure actually. You can do this with a recursive CTE:
with cte as (
select id_task, id_next_version, 1 as lev
from task
where id_task = #id
union all
select t.id_task, t.id_next_version, cte.lev + 1
from task t join
cte
on t.id_task = cte.id_next_version
)
select top 1 *
from cte
order by lev desc;
I'm not sure that this is more elegant than your loop. It should be an iota faster because you are only passing in one query.
Here is a SQL Fiddle illustrating the code.

Error using Common Table Expression in SQL User Defined Function

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')

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