I'm new in working with SQL queries and I'm trying to check if for each parent from the Parents table I have the age for all children from Children table. For example:
Parents
Name Id
John Smith 7
Children
Name Age ParentId
Sasha Smith 10 7
Johnny Smith 0 7
This is what I have so far:
create function fnCheckChildren(
#parentId int
)
returns int
begin
declare #allOk int
if(select count(1) from Children where ParentId=#parentId ) = 0
BEGIN
SET #allOk =1 --default all are ok - parent has no children
END
else
BEGIN
--here is my missing part
END
return #allOk
end
go
If I'll call the function for John Smith (#parentId = 7) #allOk should have the value 0 because one of his children (Johnny) has Age 0. If all had Age > 0 then #allOk should have 1.
Can anyone help me with it? As I understand, T-SQL doesn't support For Each...Next, and so on, so what alternatives do I have?
Here is a relative simple and efficient method:
select p.*,
(case when exists (select 1 from children c where c.parentid = p.id and c.age = 0
then 1 else 0
end) as flag
from p;
You can make this a function by simply doing:
create function fnCheckChildren(
#parentId int
)
returns int
begin
if (exists (select 1 from children where parentid = #parentid and age = 0)
)
begin
return(0);
end;
return(1);
end;
There is no need for CURSOR at all:
SELECT *
FROM Parents p
OUTER APPLY (SELECT CASE WHEN
COUNT(*) = COUNT(CASE WHEN c.Age > 0 THEN 1 END) THEN 1
ELSE 0
END AS res
FROM Children c
WHERE p.id = c.parent_id
) sub
-- WHERE ....;
DBFiddle Demo
As I understand, T-SQL doesn't support For Each.
This is not true, For-Each is kind of correlated subquery for instance: using inline syntax like in Gordon's answer or OUTER APPLY.
You can try the following query
CREATE FUNCTION fnCheckChildren(
#parentId INT
)
RETURNS INT
BEGIN
DECLARE #allOk INT
SET #allOk = CASE WHEN (select count(*) from Children where ParentId=#parentId AND Age > 0) > 1 THEN 1
ELSE 0 END
RETURN #allOk
END
Thanks
You could also have a case of no children at all. This will indicate that.
declare #P table (id int identity primary key, name varchar(20));
insert into #P (name) values ('John Smith'), ('Pat Jones'), ('Will Smith');
select * from #P;
declare #C table (id int identity primary key, parID int, name varchar(20), age int);
insert into #C (parID, name, age) values (1, 'jim', 12), (1, 'sally', 0), (2, 'bruce', 10);
select * from #C;
select p.id, P.name
, count(c.id) as [child count]
, case when MIN(c.age) <= 0 then 'childen with zero' else 'no children with zero' end as status
from #P p
left join #C c
on p.id = c.parID
group by p.id, p.name;
Related
I have a recursive stored procedure running on SQL server 2005. I tested it and it works well. But when I tried to add another capability to it I get the message:
There is already an object named '#BOM' in the database.
The problem is that I need to create the initial temp table 2 different ways depending on parameters. The temp table is used for a set of reports the expand a BOM (Bill Of Materials). The BOM can consist of parts that are sub-assemblies that have thier own BOMs. In the simplest form I need this to work:
create PROCEDURE [dbo].[ExpandBOMTestError2]
(
#Similiar bit = 0
)AS
BEGIN
IF #Similiar = 0
SELECT '1' As Col INTO #BOM
ELSE
SELECT '2' As Col INTO #BOM
SELECT * FROM #BOM
END
I even tried this, although I would prefer to avoid something this ugly and cluttering I would accept it if it would work:
create PROCEDURE [dbo].[ExpandBOMTestError]
(
#Similiar bit = 0
)AS
BEGIN
IF #Similiar = 0
BEGIN
IF (SELECT object_id('TempDB..#BOM')) IS NOT NULL
DROP TABLE #BOM
SELECT '1' As Col INTO #BOM
END
ELSE
BEGIN
IF (SELECT object_id('TempDB..#BOM')) IS NOT NULL
DROP TABLE #BOM
SELECT '2' As Col INTO #BOM
END
SELECT * FROM #BOM
END
I am also enclosing the original procedure incase anyone wants to provide alternate solutions that may work in my stripped down example but not in real production code.
create PROCEDURE [dbo].[ExpandBOMList]
(
#ItemID varchar(100),
#Level int,
#EffectiveDate datetime = null,
#ExcludeIgnoreCost bit = 0,
#MaxBOMLevel int = 99,
#Similiar bit = 0
)AS
BEGIN
DECLARE #NewLevel int
IF #Level = 0
BEGIN
IF #Similiar = 0
SELECT #ItemID AS SubAssembly, #ItemID AS Component, Null AS EffectiveDate, 1 AS QPA, 0 AS BOMLevel INTO #BOM
ELSE
SELECT IMA_ItemID AS SubAssembly, IMA_ItemID AS Component, Null AS EffectiveDate, 1 AS QPA, 0 AS BOMLevel INTO #BOM FROM Item WHERE IMA_ItemID LIKE #ItemID + '%'
SET #NewLevel = #Level + 1
EXEC ExpandBOMList #ItemID, #NewLevel, #ExcludeIgnoreCost, #MaxBOMLevel
END
ELSE
BEGIN
INSERT #BOM SELECT ItemParent.IMA_ItemID, ItemChild.IMA_ItemID, Null, PST_QtyPerAssy, #Level
FROM ProductStructureHeader
JOIN ProductStructure ON PST_PSH_RecordID = PSH_RecordID
LEFT OUTER JOIN Item AS ItemParent ON PSH_IMA_RecordID = ItemParent.IMA_RecordID
LEFT OUTER JOIN Item AS ItemChild ON PST_IMA_RecordID = ItemChild.IMA_RecordID
JOIN #BOM ON ItemParent.IMA_ItemID = Component AND BOMLevel = #Level - 1
WHERE PST_QtyPerAssy <> 0 AND PST_EffStopDate IS NULL AND BOMLevel <= #MaxBOMLevel
IF ##rowcount > 0
BEGIN
SET #Level = #Level + 1
EXEC ExpandBOMList #ItemID, #Level, #ExcludeIgnoreCost, #MaxBOMLevel
END
END
IF #Level = 0
SELECT Component AS ItemID, IMA_ItemName AS ItemName, BOMLevel
FROM #BOM
JOIN Item on IMA_ItemID = Component
END
I hope someone has a good idea before I have to make this real ugly.
For the simple example I would do the following. I will look at the original procedure you posted and update my answer if I can offer any advice on that. It looks like some pretty complicated stuff you are doing. I would need time and data to try to figure something out other than what you are doing. Gut feeling is that using hierarchy might be appropriate.
Editing my original answer to account for recursive nature.
create PROCEDURE [dbo].[ExpandBOMTestError2]
(
#Similiar bit = 0
)AS
BEGIN
IF (SELECT object_id('TempDB..#BOM')) IS NULL
Create Table #BOM (Col Int)
IF #Similiar = 0
Insert #BOM SELECT '1'
ELSE
Insert #BOM SELECT '2'
SELECT * FROM #BOM
END
i have a query with 3 variable tables: #result, #order and #stock.
the logic is the stock qty must be allocated by lotsize (here i set=1) to all order based on priority (FIFO). the stock qty must be allocated till zero and the allocateqty must <= orderqty. the problem is one of orders, its allocateqty is over orderqty (priority=7) while other are correct.
DECLARE #RESULT TABLE (priority int,partcode nvarchar(50),orderqty int, runningstock int, allocateqty int)
DECLARE #ORDER TABLE(priority int,partcode nvarchar(50),orderqty int)
DECLARE #STOCK TABLE(partcode nvarchar(50),stockqty int)
INSERT INTO #ORDER (priority,partcode,orderqty)
VALUES (1,'A',10),
(2,'A',50),
(3,'A',10),
(4,'A',40),
(5,'A',3),
(6,'A',5),
(7,'A',11),
(8,'A',10),
(9,'A',10),
(10,'A',10);
INSERT INTO #STOCK(partcode,stockqty)
VALUES('A',120)
IF (SELECT SUM(orderqty)FROM #ORDER)<(SELECT stockqty FROM #STOCK)
BEGIN
INSERT INTO #RESULT(priority,partcode,orderqty,allocateqty)
SELECT priority, partcode,orderqty,orderqty
FROM #ORDER
END
ELSE
BEGIN
DECLARE #allocatedqty int = 0
DECLARE #Lotsize int=1
DECLARE #allocateqty int = #Lotsize
DECLARE #runningstock int = (SELECT stockqty FROM #stock)
WHILE #runningstock>=0
BEGIN
DECLARE #priority int
SELECT TOP 1 #priority = priority FROM #order ORDER BY priority ASC
WHILE #priority <= (SELECT MAX(priority) FROM #order)
BEGIN
DECLARE #orderqty int
SELECT #orderqty = orderqty - #allocatedqty FROM #order WHERE priority = #priority
SELECT #allocateqty = CASE WHEN #runningstock > 0 AND #orderqty > 0 THEN #Lotsize ELSE 0 END
INSERT INTO #RESULT(priority,partcode,orderqty,runningstock,allocateqty)
SELECT #priority,
partcode,
CASE WHEN #orderqty >= 0 THEN #orderqty ELSE 0 END AS orderqty,
#runningstock,
#allocateqty
FROM #order
WHERE priority = #priority
SET #priority += 1
SET #runningstock = #runningstock - #allocateqty
END
SET #allocatedqty += #allocateqty
IF (#runningstock <= 0) BREAK
END
END
select * from #RESULT where priority=7;
SELECT priority,
sum(allocateqty) as allocated
from #RESULT
group by priority
the result:
my reputation not reach 50 so cant add comment.
you said your other order is correct then priority = 7 is also correct. you can compare priority 2 and 4 with 7. its the same thing. i think all of your loop for orderqty only reach 10 times where priority 7 got 11 so it will left 1.
Either everything is correct or everything is wrong =x
EDIT:
Hi, I found the answer.
Change
SET #allocatedqty += #allocateqty
to
SET #allocatedqty += 1
because when using SET #allocatedqty += #allocateqty, the last order #allocateqty is 0 then it will always make #allocatedqty = 0 then it will not increase.
Hope this really help you.
EDIT based on #Jesuraja given answer it should be:
SET #allocatedqty += #Lotsize
As I'm not quite sure what you try to achieve with records which will set your stock to 0 or beyond I just can provide this. But it is much better than to run all your orders in a loop. Maybe you'll want to replace your loop.
DECLARE #RESULT TABLE (priority int,partcode nvarchar(50),orderqty int, runningstock int, allocateqty int)
DECLARE #ORDER TABLE(priority int,partcode nvarchar(50),orderqty int)
DECLARE #STOCK TABLE(partcode nvarchar(50),stockqty int)
INSERT INTO #ORDER (priority,partcode,orderqty)
--VALUES (1,'A',10),(2,'A',50),(3,'A',10),(4,'A',40),(5,'A',3),(6,'A',5),(7,'A',11),(8,'A',10),(9,'A',10),(10,'A',10); --your orders
VALUES (1,'A',1),(2,'A',2),(3,'A',3),(4,'A',4),(5,'A',5),(6,'A',6),(7,'A',7),(8,'A',8),(9,'A',9),(10,'A',10);
INSERT INTO #STOCK(partcode,stockqty)
--VALUES('A',50) -- your stock
VALUES('A',50)
IF (SELECT SUM(orderqty) FROM #ORDER)<(SELECT stockqty FROM #STOCK)
BEGIN
INSERT INTO #RESULT(priority,partcode,orderqty,allocateqty)
SELECT priority, partcode,orderqty,orderqty
FROM #ORDER
END
ELSE
BEGIN
;WITH dat AS(
SELECT s.partcode, s.stockqty, o.priority, o.orderqty,
ROW_NUMBER() OVER(PARTITION BY s.partcode ORDER BY o.priority DESC) as runningOrder
FROM #Stock as s
INNER JOIN #ORDER as o
ON s.partcode = o.partcode
)
INSERT INTO #RESULT(priority,partcode,orderqty,runningstock,allocateqty)
SELECT d1.priority, d1.partcode, d1.orderqty,
d1.stockqty - SUM(d2.orderqty) OVER(PARTITION BY d1.runningOrder) as runningstock,
CASE WHEN d1.stockqty - SUM(d2.orderqty) OVER(PARTITION BY d1.runningOrder) > 0 AND d1.orderqty > 0 THEN 1 ELSE 0 END
FROM dat as d1
INNER JOIN dat as d2
ON d1.partcode = d2.partcode
AND d1.runningOrder >= d2.runningOrder
END
select * from #RESULT where priority=7;
SELECT priority,
sum(allocateqty) as allocated
from #RESULT
group by priority
I am upgrading a legacy application and I need to find the number of parents certain rows in a table have.
I was thinking of using a declared procedure for this, but i'm having trouble figuring it out how to make it work.
Basically you have a table with id and parent
= id = parent =
= 0 = 0 =
= 1 = 0 =
= 2 = 1 =
= 3 = 1 =
= 4 = 2 =
ID is unique, and parent is variable depending on what was used to create that entry, but it will always match a row with matching ID
What i'd like to achieve is calling one procedure that returns all matching parent numbers as a simple iterable result set.
So if i were to do getAllParents(4) it should return me 2, 1, 0
My failed attempts at looping have brought me so far
CREATE PROCEDURE getNumberOfParents #start int #current int
as
begin
SELECT parent FROM test where id=#start
if(parent > 0)
begin
set #current = #current + 1;
set #current = #current + getNumberOfParents(parent,#current);
end
end
Due to restrictions I cannot use an extra table to achieve this, otherwise i'd be easy heh. i can however make temptables that can be cleaned up after the method exits.
You can do it without a while loop by the use of a recursive CTE:
DECLARE #lookupId INT = 4
;WITH ParentsCTE AS (
SELECT id, parent
FROM #mytable
WHERE id = #lookupId
UNION ALL
SELECT m.id, m.parent
FROM #mytable AS m
INNER JOIN ParentsCTE AS p ON m.id = p.parent
WHERE m.id <> m.parent
)
SELECT parent
FROM ParentsCTE
The anchor member of the above CTE:
SELECT id, parent
FROM #mytable
WHERE id = #lookupId
returns the immediate parent of the 'lookup id'.
The recursive member:
SELECT m.id, m.parent
FROM #mytable AS m
INNER JOIN ParentsCTE AS p ON m.id = p.parent
WHERE m.id <> m.parent
keeps adding parents up the hierarchy until a root node (m.id <> m.parent predicate detects this) has been reached.
SQL Fiddle Demo
Test Data
DECLARE #Table TABLE (id INT, parent INT)
INSERT INTO #Table VALUES
(0 , 0),
(1 , 0),
(2 , 1),
(3 , 1),
(4 , 2)
Query
-- Id you want all the parents for
DECLARE #ParentsFor INT = 4
;with parents as
(
select ID, parent
from #Table
where parent IS NOT NULL
union all
select p.ID, t.parent
from parents p
inner join #Table t on p.parent = t.ID
and t.ID <> t.parent
)
select Distinct
STUFF((SELECT ',' + Cast(parent AS VarChar(10))
FROM parents
WHERE ID = #ParentsFor
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,'')
FROM parents
Result
2,1,0
SQL FIDDLE
Fiddle Demo Here
try:
declare #id varchar(max)=''
declare #getid int=4
while #getid>0
begin
select #id=#id+cast(parent as varchar(10))+',' from tab_1
where id=#getid
select #getid=parent from tab_1 where id=#getid
end
select #id
Try to use while loop as presented in this example:
http://blog.sqlauthority.com/2007/10/24/sql-server-simple-example-of-while-loop-with-continue-and-break-keywords/
declare #temp int,
#temp1 int,
#temp2 int,
#temp3 int,
#temp4 int,
#temp5 int,
#modid int,
#supid int,
#sid varchar(50)
begin tran
select * from StudentSupervisor;
select #temp = count(*) from Students s where s.IsLockedOut = '0' and s.IsGraduated = '0';
select #temp1 = count(*) from staffs st where st.IsLockedOut ='0';
set #temp5 = round(#temp/#temp1,0);
WHILE (select count(*) from students s where s.IsLockedOut ='0' and s.StudentId not in (select ss.StudentId from StudentSupervisor ss where s.StudentId = ss.StudentId and ss.IsApproved = 1)) != 0
BEGIN
select top 1 #sid = s.studentid from students s, StudentSupervisor ss where s.IsLockedOut ='0' and s.StudentId not in (select s.StudentId where s.StudentId = ss.StudentId and ss.IsApproved = 1);
select top 1 #supid = st.Staffid, #modid = st.moderatorid from Staffs st where st.IsLockedOut =0 and Quota <=#temp5;
insert into StudentSupervisor
(StudentId,SupervisorId,ModeratorId,IsApproved)
values
(#sid,#supid,#modid,1)
update Staffs set quota +=1 where staffs.StaffID = #supid;
END
select * from StudentSupervisor;
ROLLBACK tran
Hi all, i am quite stuck on this logic and i did search for solution but i get nothing after overnight work, let me clear with my situation now, first i would like to take count of student that not in studentsupervisor table or in studentsupervisor table but isapproved !=1, then i take the count of staff that quota is not more than total student / total staff,after that i would like to pump student together with staff into studentsupervisor table while they still available.. please let me know what wrong with my dynamic query, thanks
I am having trouble understanding your question. I think your loop condition should be:
WHILE (SELECT COUNT(*) from students WHERE StudentID NOT IN (SELECT StudentID FROM StudentSupervisor WHERE IsAppoved = 1)) <> 0
Depending on what you are trying to do in the loop, it will probably be possible to remove the loop. If you can update your question with more detail on what is happening in the loop and an example I will try to provide a more complete answer.
Update:
In that case I would also update the first line in the loop to:
select top 1 #sid = studentid from students WHERE IsLockedOut = 0 and StudentId NOT IN (SELECT StudentID FROM StudentSupervisor WHERE IsAppoved = 1);
Update2 Table variable option:
declare #temp int,
#temp1 int,
#temp2 int,
#temp5 int,
#PairCount int, --The number of students paired with a staff member.
#modid int,
#supid int,
#StaffQuotaNeed int, --The number of students the staff needs to be paired with to meet quota.
#sid varchar(50)
DECLARE #StaffToPair table
(
StaffID int NOT NULL,
ModeratorID int NOT NULL,
QuotaNeed int NOT NULL --The number of students the staff needs to be paired with to meet quota.
);
DECLARE #StudentsToPair table (StudentID int NOT NULL);
DECLARE #StudentsPaired table (StudentID int NOT NULL);
begin tran
select * from StudentSupervisor;
select #temp = count(*) from Students where IsLockedOut = '0' and IsGraduated = '0';
select #temp1 = count(*) from staffs where IsLockedOut ='0';
set #temp5 = round(#temp/#temp1 + .5, 0); --Round Up
INSERT INTO #StaffToPair (StaffID, ModeratorID, QuotaNeed)
SELECT StaffID, ModeratorID, #temp5 - Quota FROM Staffs WHERE IsLockedOut =0 AND Quota <#temp5;
INSERT INTO #StudentsToPair (StudentID)
SELECT StudentID from students WHERE IsLockedOut = 0 AND StudentId NOT IN (SELECT StudentID FROM StudentSupervisor WHERE IsAppoved = 1);
WHILE (SELECT COUNT(*) from #StudentsToPair) > 0 AND (SELECT COUNT(*) from #StaffToPair) > 0
BEGIN
SELECT TOP 1 #supid = Staffid, #modid = ModeratorID, #StaffQuotaNeed = QuotaNeed FROM #StaffToPair;
INSERT INTO StudentSupervisor (StudentID, SupervisorId, ModeratorId, IsApproved)
OUTPUT INSERTED.StudentID INTO #StudentsPaired(StudentID)
SELECT TOP (#StaffQuotaNeed) StudentID, #supid, #modid, 1 FROM #StudentsToPair;
DELETE FROM #StudentsToPair WHERE StudentID IN (SELECT StudentID FROM #StudentsPaired); --Delete paired students from table variable.
DELETE FROM #StaffToPair WHERE StaffID = #supid; --Delete paired staff from table variable.
SELECT #PairCount = COUNT(*) FROM #StudentsPaired;
UPDATE Staffs set Quota += #PairCount where staffs.StaffID = #supid;
END
select * from StudentSupervisor;
ROLLBACK tran
If the above does not work then please update your question to contain the table schema's so I can test it on my system.
This is my table structure:
CREATE table Credit(id integer, organisationid int, availableCredit int)
INSERT INTO Credit VALUES (1, 1, 1000)
INSERT INTO Credit VALUES (2, 1, 100)
INSERT INTO Credit VALUES (3, 2, 600)
INSERT INTO Credit VALUES (4, 2, 400)
I have to reduce the available credit column value, I have amount 1050 with me. I need to reduce 1050 from credit table where organisation id = 1. Here the organisation Id 1 have 1100 available credit in total. The condition is the first inserted credit row should be updated first and then the rest (FIFO model), also the updation should happen only for organisationId = 1.
How do we update this using a single or multiple update statement?
Any suggestions?
Unfortunately, this is a rather messy thing to do in T-SQL - you'll need something like a loop (cursor or WHILE statement).
With this code here, I can get it to run - it's not pretty, but it works.
-- you want to reduce the total credits by this amount
DECLARE #AmountToReduce INT = 1050
-- temporary variables
DECLARE #ID INT, #AvailableCredit INT
-- get the first row from dbo.Credit that still has availableCredit - ordered by id
SELECT TOP 1 #ID = id, #AvailableCredit = availableCredit
FROM dbo.Credit
WHERE availableCredit > 0 AND organisationId = 1
ORDER BY id
-- as long as we still have credit to reduce - loop..
WHILE #AmountToReduce > 0 AND #ID IS NOT NULL
BEGIN
-- if we need to remove the complete availableCredit - do this UPDATE
IF #AmountToReduce > #AvailableCredit
BEGIN
UPDATE dbo.Credit
SET availableCredit = 0
WHERE id = #ID
SET #AmountToReduce = #AmountToReduce - #AvailableCredit
END
ELSE BEGIN
-- if the amount to reduce left is less than the availableCredit - do this UPDATE
UPDATE dbo.Credit
SET availableCredit = availableCredit - #AmountToReduce
WHERE id = #ID
SET #AmountToReduce = 0
END
-- set #ID to NULL to be able to detect that there's no more rows left
SET #ID = NULL
-- select the next "first" row with availableCredit > 0 to process
SELECT TOP 1 #ID = id, #AvailableCredit = availableCredit
FROM dbo.Credit
WHERE availableCredit > 0 AND organisationId = 1
ORDER BY id
END
This script reduces "Id 1" availableCredit by 1000, and reduces "Id 2" availableCredit by 50.
UPDATE Credit
SET availableCredit=(availableCredit-1000)
WHERE id=1
UPDATE Credit
SET availableCredit=(availableCredit-50)
WHERE id=2
Previously I had given only select query,Here is the final UPDATE query which does the task.
DECLARE #balance int=1050
;WITH CTE as (
select id,organisationid,CASE when #balance>availableCredit then 0 else availableCredit-#balance end as availableCredit,
CASE when #balance>availableCredit then #balance-availableCredit else 0 end as balamt from Credit where id=1 and organisationid=1
union all
select t.id,t.organisationid,CASE when c.balamt>t.availableCredit then 0 else t.availableCredit-c.balamt end as availableCredit,
CASE when c.balamt>t.availableCredit then c.balamt-t.availableCredit else 0 end as balamt1
from Credit t inner join CTE c on t.id-1=c.id --and t.organisationid=1
)
Update c SET c.availableCredit = ct.availableCredit
FROM Credit c inner join CTE ct
on c.id=ct.id
SELECT * FROM Credit
Try This query:
CREATE table Credit(id integer, organisationid int, availableCredit int)
INSERT INTO Credit VALUES (1, 1, 1000)
INSERT INTO Credit VALUES (2, 1, 100)
INSERT INTO Credit VALUES (3, 2, 600)
INSERT INTO Credit VALUES (4, 2, 400)
DECLARE #balance int=1050
;WITH CTE as (
select id,organisationid,CASE when #balance>availableCredit then 0 else availableCredit-#balance end as availableCredit,
CASE when #balance>availableCredit then #balance-availableCredit else 0 end as balamt from Credit where id=1 and organisationid=1
union all
select t.id,t.organisationid,CASE when c.balamt>t.availableCredit then 0 else t.availableCredit-c.balamt end as availableCredit,
CASE when c.balamt>t.availableCredit then c.balamt-t.availableCredit else 0 end as balamt1
from Credit t inner join CTE c on t.id-1=c.id and t.organisationid=1
)
SELECT id,organisationid,availableCredit FROM CTE