I have the following recursive function:
ALTER FUNCTION [dbo].[ListAncestors]
(
#Id int
)
RETURNS TABLE
As
RETURN
(
WITH cte As
(
SELECT
UserId,
ManagerId,
Forename,
Surname
FROM
dbo.Users
WHERE
UserId = #Id
UNION ALL
SELECT
T.UserID,
T.ManagerID,
T.Forename,
T.Surname
FROM
cte As C INNER JOIN dbo.Users As T
ON C.UserID = T.ManagerID
)
SELECT
Forename,
Surname
FROM
cte
);
Basically what it does is returns the names of all users below the specified user (based on their ID). What I would like to do is modify this function and create another function which does a check if a specific userID is an ancestor of another.
I imagine the signature would look something like:
CREATE FUNCTION IsAncestor(#Id int, #AncestorId int) RETURNS BIT
How about:
WHILE #Id IS NOT NULL AND #Id <> #AncestorId
BEGIN
SET #Id = (
SELECT ManagerId FROM dbo.Users WHERE UserId = #Id
)
END
RETURN CASE WHEN #Id IS NOT NULL THEN 1 ELSE 0 END
If we accept that the initial CTE takes an ID and lists all the 'ancestors' of that ID, I think that the following query tests for this relation.
WITH cte As
(
SELECT
UserId,
Forename,
Surname
FROM
dbo.Users
WHERE
UserId = #Id
UNION ALL
SELECT
T.UserID,
T.Forename,
T.Surname
FROM
cte As C INNER JOIN dbo.Users As T
ON C.UserID = T.ManagerID and C.UserID <> #ancestorID
)
SELECT CAST (COUNT(*) as BIT) FROM cte WHERE UserID = #ancestorID
It's a bit odd though, since given the initial function a person is in the 'ancestor' relation with themselves.
Incidentally, I removed the ManagerID from the select statements in the CTE since it isn't necessary
Related
select * from (values
('dept1','user1'),
('dept2','user2'),
('dept3','user3'),
('dept4','user4')
)table1([department],[user])
where [user] = #id
scenario1:
#id = 'user1'
dept1
scenario2:
#id = 'user5'
dept1
dept2
dept3
dept4
this is what it looks like from a noobish query
declare #id varchar(12) = 'user1'
declare #var int = (select count(*) from table1 where [user] = #id)
select * from table1 where [user] = #id or #var = 0
DECLARE #id VARCHAR(5) = 'user1';
--DECLARE #id VARCHAR(5) = 'user5';
WITH UsersAndDepartments
AS ( SELECT *
FROM ( VALUES ( 'dept1', 'user1'), ( 'dept2', 'user2'),
( 'dept3', 'user3'), ( 'dept4', 'user4') ) x ( [department], [user] )
)
SELECT *
FROM UsersAndDepartments ud1
WHERE ud1.[user] =
CASE
WHEN EXISTS ( SELECT 1 FROM UsersAndDepartments ud2 WHERE ud2.[user] = #id ) THEN #id
ELSE ud1.[user]
END
The above just checks on user column if any row exists for an id, else matches on all.
declare #tab table (id int , value varchar(10))
declare #id int = 4
insert into #tab
select 1,'Ajay'
union all
select 2,'Ajay1'
union all
select 3,'Ajay2'
union all
select 4,'Ajay3'
union all
select 5,'Ajay4'
select * from #tab
where id = case when exists (select * from #tab where id = #id) then #id else id end
I would do this with a simple OR, not a CASE expression in the WHERE.
In general, you want to avoid CASE expressions in the WHERE clause for several reasons:
The logic can almost be written concisely using basic boolean operations.
Adding additional constructs (in addition to AND, OR, and NOT) just makes the logic harder for people to follow.
It pretty much kills any optimization paths.
I would suggest:
with table1 as
select v.*
from (values ('dept1', 'user1'),
('dept2', 'user2'),
('dept3', 'user3'),
('dept4', 'user4')
) v([department], [user])
)
select t1.*
from table1 t1
where t1.[user] = #id or
not exists (select 1 from table1 t1 where t1.user = #id);
You may have to do a check first something like this
Declare #RowCount int
Select #RowCount = (select count(*) from [Table] Where [Column] = 'xxx')
If #RowCount > 0
begin
Select 1 -- put code here if records
end
else
begin
Select 2 -- put code here if no records
end
you can try this:
DECLARE #id varchar(12) = 'user1'
IF EXISTS(SELECT COUNT(*) FROM table1 WHERE [user] = #id)
BEGIN
SELECT * FROM table1 WHERE [user] = #id
END
ELSE
BEGIN
SELECT * FROM table1
END
you can also read more about "EXISTS" syntax on:
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/exists-transact-sql?view=sql-server-2017
You can have a slightly better execution plan if you separate the "if exist" logic from the actual query:
DECLARE #id varchar(10) = 'user5'
DECLARE #table TABLE ([department] varchar(10), [user] varchar(10))
insert into #table values
('dept1','user1'),
('dept2','user2'),
('dept3','user3'),
('dept4','user4')
DECLARE #exists BIT =
(SELECT 1 FROM #table WHERE [user] = #id)
SELECT department FROM #table
WHERE [user] = CASE #exists WHEN 1 THEN #id ELSE [user] END
I want to write a function that returns 0 or 1. How to do it, if I use WITH-construction in my code
ALTER FUNCTION [dbo].[IsBossFull] (#bossFull int, #user int)
RETURNS bit
AS
BEGIN
;WITH CTE
AS (
SELECT *
FROM [USERS] WHERE [Id] = #user
UNION ALL
SELECT U.*
FROM CTE C
INNER JOIN [USERS] u on U.[Id] = C.[Chief]
)
-- then something like
if exists(
select * from cte where id = #bossFull)
return 1
else return 0
END
You would assign the value to a variable and return that:
DECLARE #retval int;
WITH CTE AS (
SELECT *
FROM USERS
WHERE Id = #user
UNION ALL
SELECT U.*
FROM CTE C INNER JOIN
USERS u
ON U.[Id] = C.[Chief]
)
SELECT #retval = COUNT(*)
FROM cte
WHERE id = #bossfull;
RETURN(#retval);
I haven't actually simplified the code. You can stop the recursion when #bossful is actually found.
If you really want, you can use return(case when retval > 0 then 1 else 0 end). However, #retval cannot be greater than 1, because that would imply cycles in the hierarchy -- and the CTE would not return.
I'm writing a stored procedure to look in two tables PersonTbl, UserTbl. First search the PersonTbl for an userID and if the userID is there get an email address from the UserTbl and return both. However if the ID is not there then search two other tables (PersonsPendingTbl, UsersPendingTbl) for the ID and email. If the ID is not found once again, return null/nulls. So far this is what I've come up with, but not sure if it's the best way of writing it. Let me know if there's any changes you would recommend;
create PROCEDURE [dbo].[MyNewSP]
#ID VARCHAR(MAX)
AS
DECLARE #userID VARCHAR(50)
DECLARE #Email VARCHAR(100)
DECLARE #currentlyActive CHAR
BEGIN
SELECT
#userID = userTbl.ID ,
#Email = personTbl.EMAIL,
#currentlyActive = 'Y'
FROM
personTbl
INNER JOIN userTbl ON personTbl.person_id = userTbl.person_id
WHERE
( userTbl.ID = #ID )
IF ( #userID != #ID ) --Check to see if null
BEGIN
SELECT #currentlyActive = 'N'
SELECT
upt.ID ,
ppt.EMAIL,
#currentlyActive
FROM
PersonsPendingTbl ppt
INNER JOIN dbo.UsersPendingTbl upt ON ppt.person_id = upt.person_id
WHERE
( upt.ID = #ID )
END
ELSE
BEGIN
SELECT
#userID ,
#Email ,
#currentlyActive
END
END
Make a union of both results, but always pick the first row. If the user is registered as Active AND Inactive, it'll return the Active one:
Select *
from (
SELECT userTbl.ID AS UID, personTbl.EMAIL as email, 'Y' as active
FROM personTbl
JOIN userTbl ON personTbl.person_id = userTbl.person_id
WHERE (userTbl.ID = #ID)
union all
SELECT upt.ID AS UID, ppt.EMAIL as email, 'N' as active
FROM PersonsPendingTbl ppt
INNER JOIN dbo.UsersPendingTbl upt ON ppt.person_id = upt.person_id
WHERE (upt.ID = #ID)) user
limit 0,1
I'm not sure about uniqueness of values between your pending and non-pending table, but this should be close enough to get you going.
select
case
when p.PersonId is null and pp.personPendingId is null then null
else userid
end as userid,
case
when p.PersonId is not null then p.email
when p.PersonId is null and pp.PersonPendingID is not null then pp.email
else null
end as email,
case
when p.PersonId is not null then 'Y'
when p.PersonId is null and pp.PersonPendingID is not null then 'N'
else null
end as CurrentlyActive
from userTbl u
left join PersonTbl p on u.Person_id = p.PersonId
left join userPendingTbl up on u.UserId = up.UserPendingId
left join PersonPendingTbl pp on up.personPendingId = pp.PersonPendingID
where u.UserId = #ID
I am writing below SP.But when i try to run this query i am getting this error:
There is already an object named
'#myCourses1' in the database.
So this getting in two else loops. also
create proc [dbo].[GetOrdersByUserIDwithSubscription]
(
#UserID int
)
as
begin
declare #status varchar(500)
declare #substatus char(2)
select #substatus=Subscribe_status from tbl_user where userid=#userid
print #substatus
if #substatus='N'
BEGIN
select a.*, b.CategoryText, Cast('' as Varchar(10)) as SectionsViewed, PurchasedDate as dateadded into #myCourses1 from dbo.Tbl_CourseInformations a JOIN Tbl_Categories b ON a.AssignCategory = b.CategoryID
Join Tbl_Orders c ON c.UserID = #UserID and c.CourseID = a.CourseID and c.courseprice<>'subscriber'
Order By CategoryText, CourseTitle
END
else if #substatus=''
BEGIN
select a.*, b.CategoryText, Cast('' as Varchar(10)) as SectionsViewed, PurchasedDate as dateadded into #myCourses1 from dbo.Tbl_CourseInformations a JOIN Tbl_Categories b ON a.AssignCategory = b.CategoryID
Join Tbl_Orders c ON c.UserID = #UserID and c.CourseID = a.CourseID and c.courseprice<>'subscriber'
Order By CategoryText, CourseTitle
END
else if #substatus='Y'
BEGIN
select a.*, b.CategoryText, Cast('' as Varchar(10)) as SectionsViewed, PurchasedDate as dateadded into #myCourses1 from dbo.Tbl_CourseInformations a JOIN Tbl_Categories b ON a.AssignCategory = b.CategoryID
Join Tbl_Orders c ON c.UserID = #UserID and c.CourseID = a.CourseID
Order By CategoryText, CourseTitle
END
The SQL Parser is choking because you have used the same temp table name in different parts of the IF statement. The IF does not have scope like other programming languages.
If you do not need to reference the temp table outside of each of the IF blocks you can get around the problem by using a different table name in each part.
Have a look at my answer to a similar question.
Also, the monstrocity of a query you have could be reduced to this:
create proc [dbo].[GetOrdersByUserIDwithSubscription](
#UserID int
)
as
begin
declare #substatus char(2)
select #substatus = Subscribe_status
from tbl_user
where userid = #userid
select a.*, b.CategoryText,
Cast("" as Varchar(10)) as SectionsViewed,
PurchasedDate as dateadded
from dbo.Tbl_CourseInformations a
join Tbl_Categories b ON a.AssignCategory = b.CategoryID
join Tbl_Orders c ON c.UserID = #UserID
and c.CourseID = a.CourseID
and (#substatus = 'N' or c.courseprice <> 'subscriber')
order by CategoryText, CourseTitle
END
Explicitly create the temp table at the beginning of the proc.
CREATE TABLE #myCourses1 (
...
)
Then write your SELECT statements as:
INSERT INTO #myCourses1
select a.*, b.CategoryText, Cast('' as Varchar(10)) as SectionsViewed, PurchasedDate as dateadded
from dbo.Tbl_CourseInformations
...
You syntax is
SELECT [Column-List] INTO #TempTable FROM [Rest-of-Query]
When using this syntax, Sql Server attempts to create #TempTable on the fly based on your column list (source).
To get around this, either Drop #TempTable at the beginning of the stored procedure (if you do not need its data beyond the scope of the SP), or make it a permanent table.
I have a Name table with the columns
NameID
Name
TypeID
With the following SQL
SELECT[NameID]
FROM[Name]
WHERE[TypeID] = #TypeID
AND NameID >= (SELECT MIN([NameID])
FROM [Name]
WHERE [Name]='Billy' AND [TypeID]=#TypeID)
Ive been asked to convert this to an Inner Join without using any nested select but not sure how to.
thanks for your help!
Originally I didn't think you needed a join at all,
;WITH n AS
(
SELECT
NameID,
rn = ROW_NUMBER() OVER (ORDER BY NameID)
FROM [Name]
WHERE TypeID = #TypeID
AND [Name] = 'Billy'
)
SELECT NameID
FROM n
WHERE rn > 1;
Then again, maybe I do not have the requirements clear. What is the purpose of this query?
SELECT n1.NameID
FROM [Name] AS n1
INNER JOIN
(
SELECT NameID = MIN(NameID)
FROM [Name]
WHERE TypeID = #TypeID
AND [Name] = 'Billy'
) AS n2
ON n1.NameID >= n2.NameID
WHERE n1.TypeID = #TypeID;
I agree with Lukas, I am not sure why the person who is telling you to change this thinks an inner join will be better than your original.
You could remove the nested part via: -
declare #NameID int
select #NameID = (SELECT MIN([NameID])
FROM [Name] WHERE [Name]='Billy' AND
[TypeID]=#TypeID)
SELECT [NameID] FROM [Name] WHERE [TypeID]
= #TypeID AND NameID >= #NameID
But as stated already, this does not provide any performance benefit as the subquery would only be evaluated once in your version, the same as in this.
Well, it looks like just moving the condition [Name]='Billy' should produce the same result for this specific query. So convert your original:
SELECT[NameID]
FROM[Name]
WHERE[TypeID] = #TypeID
AND NameID >= (SELECT MIN([NameID])
FROM [Name]
WHERE [Name]='Billy' AND [TypeID]=#TypeID)
to:
SELECT[NameID]
FROM[Name]
WHERE[TypeID] = #TypeID
AND[Name]='Billy'