EDIT - The problem was that the same item showed up multiple times in different spots in the BOM which then produced a result row for every instance instead of just one. This has been resolved. Thanks
I have the following code
WITH tBomCTE (ParentItem, ChildItem, WorkCentre, Operation, Quantity, ActualQuantity, ParentUnitWeight, ParentWeightUnitOfMeasure, ChildUnitWeight, ChildWeightUnitOfMeasure, BomLevel, MaterialClass, ParentItemSource) AS
(
SELECT
id.parentitem, id.ChildItem, id.WorkCentre, id.Operation,
id.Quantity, id.Quantity, id.ParentUnitWeight,
id.ParentWeightUnitOfMeasure, id.ChildUnitWeight,
id.ChildWeightUnitOfMeasure,
0 as BomLevel, id.MaterialClassCode, ParentItemSource
FROM
#tItemDenomalized id
WHERE
id.parentitem = '10054471'
UNION ALL
SELECT
id.parentitem, id.ChildItem, id.WorkCentre, id.Operation,
id.Quantity, CAST((id.Quantity * b.ActualQuantity) AS DECIMAL(19,8)), id.ParentUnitWeight,
id.ParentWeightUnitOfMeasure, id.ChildUnitWeight,
id.ChildWeightUnitOfMeasure,
BomLevel + 1,
id.MaterialClassCode, id.ParentItemSource
FROM
tBomCTE b
JOIN
#tItemDenomalized id ON b.ChildItem = id.parentitem
)
SELECT DISTINCT
'T1', ParentItem, ChildItem, WorkCentre, Operation, Quantity,
ActualQuantity,
COALESCE(ParentUnitWeight, 0), ParentWeightUnitOfMeasure,
COALESCE(ChildUnitWeight, 0), ChildWeightUnitOfMeasure, BomLevel,
MaterialClass, ParentItemSource
FROM
tBomCTE
The problem is that this code is producing multiple result rows. I have isolated it down to the cast((id.Quantity * b.ActualQuantity) as decimal(19,8))line.
Basically i am trying to build a Bill Of Materials (BOM) and we had a problem with the quantity not being added up appropriately. For example if we needed 2 of the parent item, the child item quantity only reflected what we needed for 1. Which was messing up costs.
So that line was added. It has never caused a problem but we just ran a test and now it is causing issues.
Specifically. I have a parent item, then child 1, then child 2. When I run this code i get 3 results for child 2 that all have the same path from the parent. So that doesn't make sense. And then the quantities are 22, 44, 66 for the 3 child 2 items respectively.
If i had to guess it looks like whats happening is that the quantity does get multiplied to the parent. Which then turns into 22. Then i gets multiplied by the next parent and instead of multiplying, it is creating a new row entirely.
Right now my solution is to update the quantities with code and then to delete all duplicate rows to get rid of the extra rows. But this is bad practice.
Why is it producing multiple rows instead of multiplying the parent to the current item?
Edit.
Here is my entire stored procedure that is causing the problem:
IF EXISTS (SELECT *
FROM sysobjects
WHERE id = object_id(N'[dbo].[spSAL_BomRecursive]')
AND OBJECTPROPERTY(id, N'IsProcedure') = 1 )
BEGIN
DROP PROCEDURE [dbo].[spSAL_BomRecursive];
END
GO
CREATE PROCEDURE [dbo].[spSAL_BomRecursive]
(
#SessionId varchar(50)
,#Item [dbo].[ItemType] = NULL
,#DebugLevel BIT = 0
,#CurrentOrStandardBOM nvarchar(1) = 'C'
)
AS
BEGIN
SET NOCOUNT ON;
--declare #Item varchar(30);
--set #item = '10029554';
Declare #tItemDenomalized TABLE (ParentItem nvarchar(30),
ChildItem nvarchar(30),
WorkCentre nvarchar(30) ,
Operation nvarchar(30),
Quantity decimal(19,8),
ParentUnitWeight decimal(18,9),
ParentWeightUnitOfMeasure nvarchar(3),
ChildUnitWeight decimal(18,9),
ChildWeightUnitOfMeasure nvarchar(3),
MaterialClassCode nvarchar(30),
ParentItemSource nvarchar(30));
Declare #CurrentOrStandardSuffix int
Set #CurrentOrStandardSuffix = Case when #CurrentOrStandardBOM = 'C' then 0 else 1 end
-- populate a table with all of the items, merging data to make the recursive SQL easier
Insert into #tItemDenomalized
select distinct i.item, coalesce(jm.item, ''), jr.wc, jr.oper_num, coalesce(jm.matl_qty, 0) as qty ,i.unit_weight as ParentUnitWeight, i.weight_units as ParentWeightUnitOfMeasure, i2.unit_weight as ChildUnitWeight, i2.weight_units as ChildWeightUnitOfMeasure, i.Uf_SalMaterialClassCode as MaterialClass, i.p_m_t_code
from item_mst i
left join jobroute_mst jr on i.job = jr.job and jr.suffix = #CurrentOrStandardSuffix
left join jobmatl_mst jm on jr.job = jm.job and jr.oper_num = jm.oper_num and jr.suffix = jm.suffix
left join item_mst i2 on coalesce(jm.item, '') = i2.item;
WITH tBomCTE ( ParentItem, ChildItem, WorkCentre, Operation, Quantity, ActualQuantity, ParentUnitWeight, ParentWeightUnitOfMeasure, ChildUnitWeight, ChildWeightUnitOfMeasure, BomLevel, MaterialClass, ParentItemSource )
AS
(
select id.parentitem, id.ChildItem, id.WorkCentre, id.Operation, id.Quantity, id.Quantity, id.ParentUnitWeight, id.ParentWeightUnitOfMeasure, id.ChildUnitWeight, id.ChildWeightUnitOfMeasure, 0 as BomLevel, id.MaterialClassCode, ParentItemSource
from #tItemDenomalized id
where id.parentitem = #item
UNION ALL
select id.parentitem, id.ChildItem, id.WorkCentre, id.Operation, id.Quantity, cast((id.Quantity * b.ActualQuantity) as decimal(19,8)) , id.ParentUnitWeight, id.ParentWeightUnitOfMeasure, id.ChildUnitWeight, id.ChildWeightUnitOfMeasure, BomLevel+1, id.MaterialClassCode, id.ParentItemSource
from tBomCTE b
join #tItemDenomalized id on b.ChildItem = id.parentitem
)
insert into tSAL_Bom
([SessionId],[ParentItem],[ChildItem],[WorkCentre],[Operation],[Quantity],[ActualQuantity],[ParentUnitWeight],[ParentWeightUnitOfMeasure],[ChildUnitWeight],[ChildWeightUnitOfMeasure],[BomLevel],[MaterialClassCode],[ParentItemSource])
SELECT distinct #SessionId, ParentItem, ChildItem, WorkCentre, Operation, Quantity, ActualQuantity, coalesce(ParentUnitWeight, 0), ParentWeightUnitOfMeasure, coalesce(ChildUnitWeight, 0), ChildWeightUnitOfMeasure, BomLevel, MaterialClass, ParentItemSource
FROM tBomCTE
-- cleanup the table from yesterday
delete from tSAL_Bom
where CreatedOn < Getdate()-1
RETURN 0;
END
GO
The problematic data rows are the following
SortingOrder DepthLevel ItemOrWorkCenterNumber BaseQuantity Quantity
[10054471] 0 10054471 1 1
[10054471][1605][10008773] 1 10008773 1 2
[10054471][1605][10008773][1100][10024306] 2 10024306 2 4
[10054471][1605][10008773][1100][10024306][1005][10030273] 3 10030273 11 22
[10054471][1605][10008773][1100][10024306][1005][10030273] 3 10030273 11 44
[10054471][1605][10008773][1100][10024306][1005][10030273] 3 10030273 11 66
So as you can hopefully see. There should be 1 row that looks like this
[10054471][1605][10008773][1100][10024306][1005][10030273] 3 10030273 11 88
Because the main parent requires 1. The first child requires 2. So our multiplier is 2. Our next parent requires 2 normally but with the multiplier requires 4 which makes our multiplier now 8. This line is correct. Then the next line should be the base quantity of 11 times the multiplier of 8. So 88. But instead i am getting a row that is multiplied by 2, and 4 and 6.
This isn't really an answer to your question, but an attempt to break down the massive amount of logic into something reproducible.
I wrote a very quick, self-encapsulated query that "sort of" does what I think you are trying to do. Maybe you could do something similar to explain what the differences between my logic and your logic are?
WITH Base AS (
SELECT 1 AS id, NULL AS parent, 1 AS multiplier
UNION ALL
SELECT 2 AS id, 1 AS parent, 2 AS multiplier
UNION ALL
SELECT 3 AS id, 2 AS parent, 4 AS multiplier),
Recurs AS (
SELECT
id,
1 AS depth,
multiplier
FROM
Base
WHERE
id = 1
UNION ALL
SELECT
b.id,
depth + 1 AS depth,
b.multiplier * r.multiplier AS multiplier
FROM
Base b
INNER JOIN Recurs r ON r.id = b.parent),
SecondRecurs AS (
SELECT
id,
depth,
multiplier
FROM
Recurs
UNION ALL
SELECT
p.parent,
s.depth,
s.multiplier
FROM
SecondRecurs s
INNER JOIN Base b ON b.id = s.id
INNER JOIN Base p ON p.id = b.parent),
Ordered AS (
SELECT
*,
ROW_NUMBER() OVER (ORDER BY depth DESC, id) AS order_id
FROM
SecondRecurs)
SELECT
id,
depth,
multiplier
FROM
Ordered
WHERE
order_id = 1;
So how does this work?
First I make some test data:
id parent multiplier
1 NULL 1
2 1 2
3 2 4
Then I use a recursive CTE to get the depth/ multiplier, using similar logic to your example:
id depth multiplier
1 1 1
2 2 2
3 3 8
But I deliberately let this run without worrying about rolling up children into parents, so now I have a second stage to get this into some sort of order:
id depth multiplier order_id
1 3 8 1
3 3 8 2
NULL 2 2 3
2 2 2 4
1 1 1 5
Finally I can pick out the row I want, ignoring the "partial" results that you seem to be getting in your query?
id depth multiplier
1 3 8
Does this help at all?
I've two tables like this.
create table teams (
"ID" Integer NOT NULL ,
"STADIUM_ID" Integer NOT NULL ,
"NAME" Varchar2 (50) NOT NULL ,
primary key ("ID")
) ;
create table matches (
"ID" Integer NOT NULL ,
"WINNER_ID" Integer NOT NULL ,
"OPPONENT_ID" Integer NOT NULL ,
"WINNERSCORE" Integer,
"OPPONENTSCORE" Integer,
primary key ("ID","WINNER_ID","OPPONENT_ID")
) ;
They have the following data :
select * from matches;
ID WINNER_ID OPPONENT_ID WINNERSCORE OPPONENTSCORE
---------- ---------- ----------- ----------- -------------
1 5 2 5 2
2 4 5 1 0
3 3 2 1 0
4 3 2 1 0
5 1 2 2 0
6 3 1 2 1
select * from teams;
ID STADIUM_ID NAME
---------- ---------- -----------
1 1 Team1
2 3 Team2
3 4 Team3
4 2 Team4
5 5 Team5
I need to get the sum of the goals for each team.
For this aim, tried the following query and got the results below :
select name,
(select sum(opponentscore)
from matches
where opponent_id = teams.id) +
(select sum(winnerscore) from matches where winner_id = teams.id) sum
from teams;
NAME SUM
-------------------------------------------------- ----------
Team1 3
Team2
Team3
Team4
Team5 5
Do you have any suggestion ?
All you need is to calculate seperately opponentscore and winnerscore by each individual team, and combine them with UNION ALL :
select name, sum(score) total_score
from
(
select name, sum(winnerscore) score
from teams t join matches m on ( t.id = m.winner_id )
group by name
union all
select name, sum(opponentscore) score
from teams t join matches m on ( t.id = m.opponent_id )
group by name
)
group by name
order by 1;
SQL Fiddle Demo
you should use join and group by
select name, sum(matches.opponentscore) + sum(matches.winnerscore) my_sum
from matches
inner join teams on teams.id = matches.winner_id
group by teams.name
You could join table teams twice with table matches:
SELECT name, SUM(wonMatches.WINNERSCORE + lostMatches.OPPONENTSCORE) as goals
FROM (teams INNER JOIN matches as wonMatches ON teams.ID = wonMatches.WINNER_ID)
INNER JOIN matches as lostMatches ON teams.ID = lostMatches.OPPONENT_ID
GROUP BY name
My solution is : change your database schema. Restart thinking your app's requirements. This schema does not answer the value your user are expecting from you.
From what I see, I would say that you're trying to build an app for fans that want to track their team / favorite player progress so they can brag.
That being said, I would have, at the end, those tables :
fan
team (id_team)
player (id_player, id_team)
tournament (id_tournament)
match (id_match, id_tournament, start_on, id_team_home, id_team_visitor)
goals (id_match, id_player, goaled_on)
So now, I believe that writing your query would be much more simple. You'll just have to join team, player, count over goals and group by team.
The problem is with NULLs - the subqueries return NULL when no result is found, and NULL + anything == NULL.
Most straightforward fix is:
select name,
nvl(
(select sum(opponentscore) from matches where opponent_id = teams.id),
0
)
+
nvl(
(select sum(winnerscore) from matches where winner_id = teams.id),
0
) sum
from teams;
For performance reasons tohugh, you might want to consider using a joined query with GROUP BY as suggested by others.
I really am not even sure which direction to go with this...
I'm trying to select a list of customers based on the following rules:
Select all rows from Customer where Ranking = 1,
OR if Ranking = 1 AND Type = Store then Rank 1 and return the row with Rank 2.
OR if the customer only has 1 row, return it even if the type = Store.
The Ranking is not assigned with a Rank statement in the query. Rather it is an actual column in the Customer table (populated by a stored proc that does the ranking).
Using the example below I'd want rows 1, 4, 6, and 10 returned.
Customer Table
RowID CustID Type Ranking
----- ------ ---- -------
1 9 Web 1
2 9 Catalog 2
3 9 Store 3
4 10 Store 1
5 11 Store 1
6 11 Web 2
7 12 Store 1
8 12 Web 2
9 12 Catalog 3
10 13 Web 1
I feel like this task is more difficult BECAUSE the Ranking is already done when the table is created! Any suggestions are most welcome!
You could try something like this (I haven't tested it!):
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE (c.Ranking = 1 AND c.Type != 'Store')
OR (c.Type = 'Store' AND Ranking = 2)
OR (c.Type = 'Store' AND Ranking = 1 AND NOT EXISTS (SELECT 1 FROM Customer WHERE CustId = c.CustId AND Ranking = 2))
If the customer table is large, you might find that the query is a bit slow and something like this would be faster:
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE c.Ranking = 1 AND c.Type != 'Store'
UNION ALL
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE c.Type = 'Store' AND Ranking = 2
UNION ALL
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE c.Type = 'Store' AND Ranking = 1 AND NOT EXISTS (SELECT 1 FROM Customer WHERE CustId = c.CustId AND Ranking = 2)
As with the other answer, I haven't done a lot of thorough testing, but here's what I'd look at. The idea here is to build a row_number over the data set prioritizing type:store to the top, and then using rank as the secondary sort condition.
select *
from (
select
rid = row_number() over (partition by CustID, order by case when type = 'Store' then 0 else 1 end, Rank desc),
rowid,
CustID,
Type,
Ranking
from customer)
where RID = 1
Try:
SELECT *
FROM Customer c
WHERE
-- There's only one row for this customer
(
SELECT COUNT(*)
FROM Customer
WHERE CustID = c.CustID
) = 1
-- There's a row with Ranking = 1 and Type = 'Store', so select Ranking = 2
OR (Ranking = 2 AND EXISTS (SELECT 1 FROM Customer WHERE CustID = c.CustID AND Ranking = 1 AND Type = 'Store'))
-- There's a row with Ranking = 1 that's not Type = 'Store'
OR (Ranking = 1 AND Type <> 'Store')
I am using SQL Server 2008. I have a table like this:
UnitId ParentId UnitName
---------------------------
1 0 FirstUnit
2 1 SecondUnit One
3 1 SecondUnit Two
4 3 B
5 2 C
6 4 D
7 6 E
8 5 F
I want to get second parent of the record. For example:
If I choose unit id that equal to 8, It will bring unit id is equal to 2 to me. It needs to be SecondUnit One. or If I choose unit id that equal to 7, It will bring unit id is equal to 3 to me. It needs to be SecondUnit Two.
How can I write a SQL query this way?
It took me a while, but here it is :)
with tmp as (
select unitId, parentId, unitName, 0 as iteration
from t
where unitId = 7
union all
select parent.unitId, parent.parentId, parent.unitName, child.iteration + 1
from tmp child
join t parent on child.parentId = parent.unitId
where parent.parentId != 0
)
select top 1 unitId, parentId, unitName from tmp
order by iteration desc
Here is also a fiddle to play with.
SELECT t.*, tParent1.UnitId [FirstParent], tParent2.UnitId [SecondParent]
FROM Table t
LEFT JOIN Table tParent1 ON t.ParentId = tParent1.UnitId
LEFT JOIN Table tParent2 ON tParent1.ParentId = tParent2.UnitId
WHERE t.UnitId = <Unit ID search here>
AND NOT tParent2.UnitId IS NULL
Edit: And leave out second part of the WHERE clause if you want results returned even if they don't have a second parent.
I have a details table and I want to get the record that exactly matches the list of values in another table.
Here is a scenario:
OrderDetailTable
OrderID ItemID
1 1
1 2
1 3
1 4
2 1
2 2
2 4
3 1
3 2
3 3
4 1
4 2
OrderedTable
ItemID
1
2
Now I want to get the OrderID that has the exact ItemID matches with OrderedTable ItemID. In the above scenario OrderID 1 is valid since ItemID 1,2,3 is exactly matched with OrderedTable ItemID.
I used the join but it did not work. It gave me both OrderID 1,2 . How do I do it any ideas??
Try this:
SELECT OrderID
FROM OrderDetailTable JOIN OrderedTable USING (ItemID)
GROUP BY OrderID
HAVING COUNT(DISTINCT ItemID) = (SELECT COUNT(DISTINCT ItemID) FROM OrderedTable)
The idea, in a nutshell, is as follows:
Count how many OrderDetailTable rows match OrderedTable by ItemID,
and then compare that to the total number of ItemIDs from OrderedTable.
If these two numbers are equal, the given OrderID "contains" all ItemIDs. If the one is smaller than the other, there is at least one ItemID not contained in the given OrderID.
Depending on your primary keys, the DISTINCT may not be necessary (though it doesn't hurt).
try
SELECT * FROM OrderDetailTable WHERE OrderID NOT IN
(
SELECT A.OrderID FROM
(
SELECT
Y.OrderID
, OT.ItemID
, (SELECT Z.ItemID
FROM OrderDetailTable Z
WHERE Z.ItemID = OT.ItemID AND Z.OrderID = Y.OrderID
) I
FROM OrderDetailTable Y, OrderedTable OT
) A
WHERE A.I IS NULL);
EDIT - as per request the better syntax:
SELECT * FROM
OrderDetailTable Z WHERE Z.ORDERID NOT IN
(
SELECT O1 FROM
(SELECT Y.ORDERID O1, YY.ORDERID O2 FROM
OrderDetailTable Y CROSS JOIN OrderedTable OT
LEFT OUTER JOIN OrderDetailTable YY ON
YY.ORDERID = Y.ORDERID AND YY.ITEMID = OT.ITEMID ) ZZ WHERE ZZ.O2 IS NULL);