While loop taking so much time in SQL Server - sql

In my stored procedure, I have 2 while loops and I need to avoid those loops and use cursor or recursive cte
create procedure Parent_Child_UserDetails_Schedule
as
begin
Set nocount on
create table #temptab(id int, userid int, parentid int)
select userid, 1 as valid
into #users
from userdetails
where isactive = 1
truncate table Parent_Child_UserDetails
while(select count(*) from #users where valid = 1) > 0
begin
declare #userid int
select top 1 #userid = userid
from #users
where valid = 1
truncate table #temptab
insert into #temptab(id, userid, parentid)
values(1, #userid, #userid)
declare #id int
set #id = 1
while((select count(*) from userdetails
where parentid in (select userid from #temptab where id=#id ) and isactive = 1) > 0)
begin
insert into #temptab (id, userid, parentid)
select #id + 1, userid, #userid
from userdetails
where parentid in (select userid from #temptab where id = #id)
and isactive = 1
set #id= #id + 1
end
insert into Parent_Child_UserDetails(Parentid, Userid)
select parentid, userid
from #temptab
update #users
set valid = 0
where userid = #userid
end
drop table #temptab
drop table #users
Set nocount off
end
Please help me .....

Following our conversation in the comments, what you need is to insert to the Parent_Child_UserDetails table all the parent ids and user ids where both are active,
you can replace that RBAR nightmare of a code with a single insert....select statement, like this:
insert into Parent_Child_UserDetails(Parentid, Userid)
select parentid, userid
from userdetails as t0
where isactive = 1
and exists
(
select 1
from userdetails as t1
where t1.isavtice = 1
and t1.userId = t0.ParentId
)

Related

How to update string values with incrementing numbers

I have a table that has a column which needs to be updated with values like
dummy1
dummy2
dummy3
..
..
..
dummy6
I created the following while loop script
create table people_test(UserName varchar(40))
GO
INSERT INTO people_test (UserName) values ('admin'),('test'),('opq'),('mn'),('ijkl'),('efgh'),('abcd')
GO
Select * from people_test
GO
DECLARE #i int;
DECLARE #j int = 1;
BEGIN
SELECT #i = COUNT(*) FROM people_test WHERE username <> 'admin';
WHILE(#j <= #i)
BEGIN
print 'i='+CONVERT(VARCHAR,#i)+' j='+CONVERT(VARCHAR,#j);
UPDATE people_test
SET
UserName = 'dummy'+ CONVERT(VARCHAR,#j)
WHERE username <> 'admin';
print 'i='+CONVERT(VARCHAR,#i)+' j='+CONVERT(VARCHAR,#j);
SET #j = #j + 1;
END;
END;
GO
Expected result
UserName
admin
dummy1
dummy2
dummy3
Result that I am seeing
UserName
admin
dummy6
dummy6
dummy6
You can solve this task with the row_number() function and therefore without a loop. Something like this:
drop table if exists #people_test
go
create table #people_test (UserName varchar (1000))
go
INSERT INTO #people_test (UserName) values ('admin'),('test'),('opq'),('mn'),('ijkl'),('efgh'),('abcd')
GO
update t
set t.UserName = 'dummy' + cast(t.rn as varchar(10))
from (
select row_number() over (order by UserName ) rn, *
from #people_test
where UserName!='admin'
) t
select * from #people_test
order by UserName
IF OBJECT_ID('tempdb..#TEMPS','U') IS NOT NULL
DROP TABLE #TEMPS
create table #TEMPS (UserName varchar(40))
GO
INSERT INTO #TEMPS (UserName) values ('admin'),('test'),('opq'),('mn'),('ijkl'),('efgh'),('abcd')
GO
UPDATE T1 SET T1.USERNAME= 'DUMMY'+CAST(T2.ROW AS varchar)
FROM #TEMPS AS T1, (SELECT ROW_NUMBER() OVER (ORDER BY USERNAME) AS ROW,* FROM #TEMPS ) AS T2
WHERE T2.UserName=T1.UserName AND T1.UserName!='ADMIN'
SELECT * FROM #TEMPS ORDER BY 1
Your own solution might work, but this is the wrong century for a CURSOR/LOOP based solution for such an easy task. Try something along this:
--Create a test table and fill it with data.
CREATE TABLE people_test(USERID INT IDENTITY,UserName VARCHAR(40))
GO
INSERT INTO people_test (UserName) VALUES
('admin')
,('test')
,('opq')
,('mn')
,('ijkl')
,('efgh')
,('abcd');
GO
SELECT * FROM people_test;
GO
--This is an updateable CTE
WITH upd AS
(
SELECT UserName
,CONCAT(UserName,ROW_NUMBER() OVER(ORDER BY USERID)) AS NewUserName
FROM people_test
WHERE UserName <> 'admin'
)
UPDATE upd SET UserName=NewUserName;
GO
--See the result
SELECT * FROM people_test;
GO
--Clean-up (carefull with real data!)
DROP TABLE people_test;
GO
The idea in short:
The CTE upd will return two columns:
UserName is the column you want to change
NewUserName is a computed column returning the value you want to see in UserName.
We can simply use UPDATE upd to update the underlying table.
create table people_test(USERID int,UserName varchar(40))
GO
INSERT INTO people_test (USERID, UserName) values (1,'admin'),(2,'test'),(3,'opq'),(4,'mn'),(5,'ijkl'),(6,'efgh'),(7,'abcd')
GO
Select * from people_test
GO
DECLARE #i int;
DECLARE #j int = 1;
DECLARE #k int;
DECLARE c_id cursor for
Select USERID from PEOPLE_TEST
WHERE username <> 'admin'
BEGIN
open c_id;
FETCH NEXT FROM c_id into #k
SELECT #i = COUNT(*) FROM PEOPLE_TEST WHERE username <> 'admin';
WHILE(#j <= #i)
BEGIN
UPDATE PEOPLE_TEST
SET
UserName = concat('dummy',#j)
WHERE userid = #k;
print 'i='+CONVERT(VARCHAR,#i)+' j='+CONVERT(VARCHAR,#j)+' k='+CONVERT(VARCHAR,#k);
SET #j = #j + 1;
FETCH NEXT FROM c_id into #k
END;
close c_id
Deallocate c_id
END;
Select * from people_test
GO

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

How do I modify this T-SQL query so that its ouput can be joined with a table in my database?

I am using SQl Server 2014. I have the following T-SQL query (with its corresponding output given below):
USE mydatabase
declare #t table (StayID int)
insert into #t select distinct StayID from DateOfBirth
while ((select count(1) from #t) > 0)
begin
declare #Id int = (select top 1 StayID from #t)
declare #age_tab table (StayID int, Age int, Year1 date)
declare #cage1 smallint = 2, #cage2 smallint = 5
declare #i_a int = (select AdultCount from DateOfBirth where StayID = #Id)
declare #i_c1 int = (select [0-3] from DateOfBirth where StayID = #Id)
declare #i_c2 int = (select [4-6] from DateOfBirth where StayID = #Id)
declare #age int = (select Age from DateOfBirth where StayID = #Id)
declare #Year1 date = (select cast(datepart(year,ArrivalDate) as varchar) from DateOfBirth where StayID = #Id)
while (#i_a>1)
begin
insert into #age_tab select #Id, (#age + 2), #Year1
set #age-=2
set #i_a-=1
end
while (#i_c1>0)
begin
insert into #age_tab select #Id, #cage1, #Year1
set #i_c1-=1
end
while (#i_c2>0)
begin
insert into #age_tab select #Id, #cage2, #Year1
set #i_c2-=1
end
insert into #age_tab select StayID, Age, cast(datepart(year,ArrivalDate) as varchar) from DateOfBirth where StayID = #Id
delete #t where StayID = #Id
end
select * from #age_tab
order by StayID, Age desc
The output of the above query is as follows:
StayID Age Year1
101 54 2016-01-01
101 52 2016-01-01
102 42 2016-01-01
102 40 2016-01-01
102 14 2016-01-01
...
I want to join the output of this query with values from another table in my database. Given the above query uses temporary tables to perform its operation, I am unable to wrap it into a CTE.
The additional information I want to bring to my output is stored in, say, table "t2".
Let's say the output from the query above is temporarily stored in a table called "t1"
So, I want to do something like this:
Select a.*
, b.Cty
from t1 a
LEFT JOIN t2 b on b.StayID = a.StayID
How can I do this?

Subquery in ISNULL,IIF,CASE statements

Subquery that is give as a parameter in ISNULL or IIF or CASE gets executed irrespective of the condition
To explain what I mean, consider the below example. When I run the below query and look in the execution plan, I find that even if the variable #Id not NULL, the second condition gets executed always.
Can anyone explain Why does the subquery gets executed in Query 1,2,3?
--Create a Temp table
IF OBJECT_ID('tempdb..#TempTable') IS NOT NULL DROP TABLE #TempTable
CREATE TABLE #TempTable
(
ID INT
)
--Insert some data
INSERT INTO #TempTable VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
DECLARE #Id INT = 1
--Query 1: ISNULL
SET #Id= ISNULL(#Id, (SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1))
SELECT #Id AS ID
--Query 2: IIF
SET #Id= IIF(#Id IS NULL,(SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1),#Id)
SET #Id= IIF(#Id IS NOT NULL,#Id,(SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1))
SELECT #Id AS ID
--Query 3: CASE
SET #Id= CASE WHEN #Id IS NULL THEN (SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1) ELSE #Id END
SET #Id= CASE WHEN #Id IS NOT NULL THEN #Id ELSE (SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1) END
SELECT #Id AS ID
---Query 4: IF
IF #Id IS NULL
BEGIN
SET #Id = (SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1)
SELECT #Id AS ID
END
ELSE
BEGIN
SELECT #Id AS ID
END
If you want the subquery to execute only when the null condition is met, use an IF instead of ISNULL
if #Id is null
set #id = (SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1)
instead of
SET #Id= ISNULL(#Id, (SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1))

TSQL - While in While code

Can someone please tell me why this wont work. I want to loop inside a loop .....
BEGIN
SET NOCOUNT ON;
Declare #TempLocations Table (PK int Identity(1,1) not null Primary key, LocationID Int)
Declare #TempItems Table (PK1 int Identity(1,1) not null Primary key, ItemID int)
Declare #TempTable Table (ID Int Identity(1,1), LocationID int, ItemID int)
Declare #MaxLocationID int,
#MaxItemID Int,
#LocationID int,
#ItemID int
-- Load "Can be sold from" Locations into Temp Table
Insert Into #TempLocations (LocationID)
Select LocationID from WMS.Locations
Where CanBeSoldFrom = 'Checked'
Set #MaxItemID = (Select MAX(PK1) From #TempItems)
Set #LocationID = 1
-- Load "IsActive" Items into Temp Table
Insert Into #TempItems (ItemID)
Select ItemID from IMS.ItemDetails
Where IsActive = 'Checked'
Set #MaxLocationID = (Select MAX(PK) From #TempLocations)
Set #ItemID = 1
--Main Code
While #LocationID <= #MaxLocationID
Begin
While #ItemID <= #MaxItemID
Begin
Insert into #TempTable (LocationID, ItemID)
Values (#LocationID, #ItemID)
Set #ItemID = #ItemID + 1
end
Set #LocationID = #LocationID + 1
End
Select * from #TempTable
END
The result I am Tryinig to get is this
#tempTable =
LocationID = 1
ItemID = 1
ItemID = 2
ItemID = 3
ItemID = 4
LocationID = 2
ItemID = 1
ItemID = 2
ItemID = 3
ItemID = 4
and so on ......
This shouldn't be done in a procedural code at all. Use pure SQL and let the DB engine do it's job, it will perform much better, and less code = less bugs. I'm not sure I completely understand what results you want, but I think this does it:
select
LocationID,
ItemID
from
(
Select LocationID from WMS.Locations
Where CanBeSoldFrom = 'Checked'
)
cross join
(
Select ItemID from IMS.ItemDetails
Where IsActive = 'Checked'
)
order by
LocationID,
ItemID
Your query selects the #MaxItemID before anything is filled into #TempItems. Therefor #MaxItemID is null. You have to switch the Statements Set #MaxLocationID = (Select MAX(PK) From #TempLocations) and Set #MaxItemID = (Select MAX(PK1) From #TempItems).
I agree with Jeremy though, it would be better to do that with set-based-programming.