Return scalar-value after WITH sql - sql

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.

Related

Conditionally modify query based on parameter

I have this query (something like a case statement which I can use and fix it)
select *
from mytable
where 1=1
and (isNull(ID, 0) = 0 OR UtilityID IN (9,40))
I also want to add another statement
select *
from mytable
where 1=1
and UtilityID NOT IN (9,40)
Everything is happening in a procedure, so want to use a variable like declare #something so if that is passed as 1, use the first statement and the if 0 is passed, use the latter one.
While I appreciate the genius in Dale's answer I find this more readable:
IF #something = 0
BEGIN
select *
from mytable
where ID IS NULL OR ID = 0 OR UtilityID IN (9,40);
END
IF #something = 1
BEGIN
select *
from mytable
where UtilityID NOT IN (9,40);
END
It's procedure code, so use IF to direct the control flow. Also expanded and simplified your where clauses
I think I understand your logic, ignoring the 1=1 (which does nothing) you want to only allow id = 0 when #something = 1. This should do it:
declare #something bit = 0;
declare #mytable table (ID int, UtilityID int);
insert into #mytable (ID, UtilityID)
select 0, 1 union all
select 1, 2 union all
select 2, 9 union all
select 3, 40;
select *
from #mytable
where (
(#something = 1 and (isnull(ID, 0) = 0 or UtilityID in (9,40)))
or (#something = 0 and (UtilityID not in (9,40)))
);
A more performant approach for a larger query could be:
select *
from #mytable
where (#something = 1 and (isnull(ID, 0) = 0 or UtilityID in (9,40)))
union all
select *
from #mytable
where (#something = 0 and (UtilityID not in (9,40)));
PS: Hopefully your ID cannot ever by null - it should have a constraint on it.

Select data where condition matches and if none, then select all?

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

find circular transactions in database table

I have a table in sql server database in which records of transactions are stored. Table consists of user id of buyer and user id of seller of product. I have to find the circles in the table for example-
I have to get the records of type- A sells to B, B sells to C, C sells to D AND D sells to A.
Please help.
Use following function:
CREATE FUNCTION dbo.CheckIsCircular(#SellerId INT)
RETURNS BIT
AS BEGIN
DECLARE #IsCircular BIT = 0
DECLARE #Sellers TABLE(Id INT)
DECLARE #TempSellers TABLE(Id INT)
DECLARE #Buyers TABLE(Id INT)
INSERT INTO #TempSellers(Id)VALUES(#SellerId)
WHILE EXISTS(SELECT * FROM #TempSellers)BEGIN
IF EXISTS(SELECT *
FROM #Sellers s
INNER JOIN #TempSellers t ON t.Id = s.Id)BEGIN
SET #IsCircular = 1
BREAK;
END
INSERT INTO #Sellers(Id)
SELECT Id FROM #TempSellers
INSERT INTO #Buyers(Id) SELECT BuyerId FROM YourTable
DELETE #TempSellers
INSERT Into #TempSellers(Id)
SELECT YourTable.SellerId
FROM YourTable
INNER JOIN #Buyers ON [#Buyers].Id = YourTable.SellerId
END
RETURN #IsCircular
END
Your problem is a graph traversal challenge; this is not natively supported in TSQL, but you can simulate it.
This is a skeleton how I do it in Teradata, so syntax must be slightly modified for SQL Server:
WITH RECURSIVE cte (..., Path, isCycle) AS
(
SELECT
...
,',' || CAST(seller AS VARCHAR(1000)) || ',' AS path
,0 AS isCycle
FROM tab
UNION ALL
SELECT
...
,cte.Path || cte.buyer || ',',
,case when cte.Path LIKE '%,' || TRIM(tab.buyer) || ',%' then 1 else 0 end
FROM cte, tab
WHERE cte.buyer = tab.seller
AND cte.isCycle <> 1
)
SELECT ...
,Path || Destination
,isCycle
FROM cte
WHERE isCycle = 1
Build a materialized path of the graph while traversing and check if the next buyer is already in this path.
With a recursive cte
declare #trans table (seller int, buyer int)
insert #trans
values (1,2),(2,3),(3,4),(4,1),(1,5),(2,6),(3,5)
begin try
;with cte as
(
select *, convert(varchar(500),'') as route from #trans
union all
select cte.seller, t1.buyer, convert(varchar(500),route + CONVERT(varchar(5),t1.seller)) from cte
inner join #trans t1 on cte.buyer = t1.seller
)
select * from cte
where seller=buyer
option (maxrecursion 50)
end try
begin catch
print 'loops'
end catch

Modify FindAll function to a DoesExist function in SQL Server

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

How can I use if statement after a CTE (SQL Server 2005)

Last night I was writing a simple T-SQL program something like this
DECLARE #ROLEID AS INT
SELECT #ROLEID = [ROLE ID] FROM TBLROLE
;WITH CTE
AS
(
SELECT * FROM SOMETABLE
)
IF (#ROLEID = 1)
BEGIN
//SOMECODE
END
ELSE IF(#ROLEID = 2)
BEGIN
//SOMECODE
END
ELSE
BEGIN
//SOMECODE
END
I found after compilation that it is throwing error something like "Incorrect statement near if"
What is wrong?
However, I did that by using some other way. But I wanted to know why it did not work!
Common table expressions are defined within the context of a single statement:
WITH cte_name AS (
<cte definition>)
<statement that uses cte>;
So you can do something like:
WITH CTE
AS
(
SELECT * FROM SOMETABLE
)
SELECT * FROM CTE;
or
WITH CTE
AS
(
SELECT * FROM SOMETABLE
)
UPDATE CTE
SET somefield = somevalue
WHERE id = somekey;
A CTE must be followed by a single
SELECT, INSERT, UPDATE, MERGE, or
DELETE statement that references some
or all the CTE columns. A CTE can also
be specified in a CREATE VIEW
statement as part of the defining
SELECT statement of the view
A little late but I can't be the only one bumping into this.
A solution could be to create a temporary table like this:
-- If previous run of this query fails, the temp table will be deleted.
-- Selecting into creates the temp table which fails if it already exists
IF EXISTS(SELECT [name] FROM tempdb.sys.tables WHERE [name] like '#dtBalansOpgesteldGefilterd%') BEGIN
DROP TABLE #temp
END;
;WITH CTE
AS
(
SELECT * FROM SOMETABLE
)
-- Followed by select statement as required
SELECT *
INTO #temp
FROM CTE
IF #awsome = 1
BEGIN
SELECT 'WHATEVERYOUWANT' AS WhateverColumnNameYouWant, *
FROM #temp
END
The closest you'll get is using a UNION ALL to do a crude switched select:
DECLARE #ROLEID AS INT
SELECT #ROLEID = [ROLE ID] FROM TBLROLE
;WITH CTE
AS
(
SELECT * FROM SOMETABLE
)
SELECT
--somecolumns
FROM
CTE
--other stuff too
WHERE
#ROLEID = 1
UNION ALL
SELECT
--somecolumns
FROM
CTE
--other stuff too
WHERE
#ROLEID = 2
UNION ALL
SELECT
--somecolumns
FROM
CTE
--other stuff too
WHERE
#ROLEID = 3
...
UNION ALL
SELECT
--somecolumns
FROM
CTE
--other stuff too
WHERE
#ROLEID = n
Try putting the CTE in the IF. It worked for me.
IF #awsome = 1
BEGIN
;WITH CTE
AS
(
SELECT * FROM SOMETABLE
)
SELECT 'WHATEVERYOUWANT' FROM CTE
END
ELSE IF #awesome = 2
BEGIN
;WITH CTE2
AS
(
SELECT * FROM SOMETABLE
)
SELECT 'WHATEVERYOUWANT' FROM CTE2
END