Click here to Skip to main content
15,888,270 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Dear experts, I'm lost, please help me.

I have a table with BOM Positions:
BomId   MaterialId   MaterialQuantity   Note
-----   ----------   ----------------   -----
Bom_A   Mat_A        20                 BOM1
Bom_A   Mat_B        30                 BOM1
Bom_B   Mat_A        20                 BOM2
Bom_B   Mat_B        35                 BOM2
Bom_C   Mat_A        20                 BOM1
Bom_C   Mat_B        30                 BOM1
Bom_D   Mat_A        10                 BOM3
Bom_D   Mat_B        20                 BOM3
Bom_D   Mat_C        30                 BOM3
Bom_E   Mat_A        10                 BOM3
Bom_E   Mat_B        20                 BOM3
Bom_E   Mat_C        30                 BOM3

Based on that table I like to select distinct the equal BOMs.
Equal here means the BomDetails have same material in same quantities.

Note: The column 'Note' is only for the explanation here and does not exists in my db.

Therefore the result I'm looking for is something like this:
MaterialId	 MaterialQuantity   BOMGRP   BOMCOUNT
----------   ----------------   ------   ---------
Mat_A        20                 1        2
Mat_B        30                 1        2
Mat_A        20                 2        1
Mat_B        35                 2        1
Mat_A        10                 3        2
Mat_B        20                 3        2
Mat_C        30                 3        2

'BOMGRP' should be a dynamically created Id.
'BOMCOUNT' would be nice, but I can live without it

I hope my question makes sense.
Thank you very much in advance.

What I have tried:

I tried to solve it with the help of ChatGPT, but without success.
Btw. I'm on MS SQL Server 2014 :(
[Edit]: Upgraded to MS SQL Server 2019 :)

DROP TABLE BomPositions;
CREATE TABLE BomPositions
(
   BomId             VARCHAR(30),
   MaterialId        VARCHAR(30),
   MaterialQuantity  DOUBLE PRECISION,
   Note				 VARCHAR(30)
);


INSERT INTO BomPositions(BomId, MaterialId, MaterialQuantity, Note) VALUES
   ('Bom_A', 'Mat_A', 20, 'BOM1'),
   ('Bom_A', 'Mat_B', 30, 'BOM1'),
   ('Bom_B', 'Mat_A', 20, 'BOM2'),
   ('Bom_B', 'Mat_B', 35, 'BOM2'),
   ('Bom_C', 'Mat_A', 20, 'BOM1'),
   ('Bom_C', 'Mat_B', 30, 'BOM1'),
   ('Bom_D', 'Mat_A', 10, 'BOM3'),
   ('Bom_D', 'Mat_B', 20, 'BOM3'),
   ('Bom_D', 'Mat_C', 30, 'BOM3'),
   ('Bom_E', 'Mat_A', 10, 'BOM3'),
   ('Bom_E', 'Mat_B', 20, 'BOM3'),
   ('Bom_E', 'Mat_C', 30, 'BOM3');

SELECT * FROM BomPositions ORDER BY BomId, MaterialId;
Posted
Updated 28-Mar-24 23:44pm
v6

The query should look like -
SQL
WITH BOMGroups AS (
    SELECT DISTINCT BomId, ROW_NUMBER() OVER (ORDER BY BomId) AS BOMGRP
    FROM BomPositions
),
BOMCounts AS (
    SELECT BomId, COUNT(DISTINCT MaterialId) AS BOMCOUNT
    FROM BomPositions
    GROUP BY BomId
)
SELECT MaterialId,
       MaterialQuantity,
       BOMGRP,
       BOMCOUNT
FROM (
    SELECT MaterialId,
           MAX(MaterialQuantity) AS MaterialQuantity,
           BomId,
           ROW_NUMBER() OVER (PARTITION BY BomId ORDER BY MaterialId) AS RowNum
    FROM BomPositions
    GROUP BY MaterialId, BomId
) AS SubQuery
JOIN BOMGroups ON SubQuery.BomId = BOMGroups.BomId
JOIN BOMCounts ON SubQuery.BomId = BOMCounts.BomId
ORDER BY BOMGRP, MaterialId;


I have not tested though.

[EDIT]
SQL
WITH BOMGroups AS (
    SELECT
        BomId,
        ROW_NUMBER() OVER (ORDER BY BomId) AS BOMGRP
    FROM
        (SELECT DISTINCT BomId FROM BomPositions) AS DistinctBomIds
),
BOMCounts AS (
    SELECT
        BomId,
        COUNT(DISTINCT MaterialId) AS BOMCOUNT
    FROM
        BomPositions
    GROUP BY
        BomId
),
MaterialRanks AS (
    SELECT
        BomId,
        MaterialId,
        MAX(MaterialQuantity) AS MaterialQuantity,
        DENSE_RANK() OVER (PARTITION BY BomId ORDER BY MaterialId) AS MaterialRank
    FROM
        BomPositions
    GROUP BY
        BomId,
        MaterialId
)
SELECT
    MaterialId,
    MaterialQuantity,
    BOMGRP,
    BOMCOUNT
FROM
    MaterialRanks
    JOIN BOMGroups ON MaterialRanks.BomId = BOMGroups.BomId
    JOIN BOMCounts ON MaterialRanks.BomId = BOMCounts.BomId
ORDER BY
    BOMGRP,
    MaterialRank;

[END EDIT]
 
Share this answer
 
v2
Comments
0x01AA 29-Mar-24 8:38am    
Thank you for your interest .
My 5 for your help, it gives me a direction and ideas.

The result is unfortunatelly not what I expect.
It ends in 12 Groups wehere I expect 3 and also 30 reuslt lines instead of 7.
Anyway wow! Without testing, no syntax error in your sql statement ;)

Btw. My current appraoch is to create a 'Set Id' with the help of 'STRING_AGG'. It works, but I don' like it. It is far away from SQL's set approach.
Andre Oosthuizen 29-Mar-24 8:43am    
Ouch, let me test the query and see where I got it wrong, should have returned the expected 7 lines... :)

"Anyway wow! Without testing, no syntax error in your sql statement ;)" - I have a little self written helper file that checks for syntax errors as I have been bitten before. :)
Andre Oosthuizen 29-Mar-24 9:25am    
I played with the query, still not 100% but much closer. I think the issue is with the 'MaterialRank' if you want to look into this. The result now is -

MaterialId	MaterialQuantity	BOMGRP	BOMCOUNT
Mat_A	20	1	2
Mat_B	30	1	2
Mat_A	20	2	2
Mat_B	35	2	2
Mat_A	20	3	2
Mat_B	30	3	2
Mat_A	10	4	3
Mat_B	20	4	3
Mat_C	30	4	3
Mat_A	10	5	3
Mat_B	20	5	3
Mat_C	30	5	3


See fiddle here to play on - BOM Fiddle[^]
0x01AA 29-Mar-24 10:01am    
Thank you very much again! Unfortunately I can't upvote again :)
At the moment, I let the question as 'not solved' to have a chance, that others will participate.
Later on, I will accept your answer, because you gave me very good inputs.
0x01AA 29-Mar-24 11:12am    
FYI, below my ugly approach,at least to determine the Groups with a thing like SetId.

As mentioned, I don't like it. It works, but I assume only by chance. This because it is not possible to have a 'order by' in the select statement for the 'STRING_AGG' and furthermore it is simply ugly and has many restrictions and does not really follows the set theory on which SQL is based.

WITH Bom AS (
SELECT  DISTINCT
	BomId
  FROM BomPositions
),
BomSetId AS (
   SELECT
      Bom.BomId,
	  (SELECT STRING_AGG(BomPos.MaterialId + '_' + FORMAT(BomPos.MaterialQuantity, 'N2'), ',') 
      FROM BomPositions BomPos 
	  WHERE  (BomPos.BomId = Bom.BomId)
	  AS SetId
   FROM Bom 
)

SELECT 
	DISTINCT SetId 
FROM BomSetId
ORDER BY SetId;
Below my solution.
I don't like it, because it is a way too clumsy.
I would never use that in production code. But for an analysis of an existing database it is just about acceptable.

Many thanks to @AndreOosthuizen[^]
WITH Boms AS (
SELECT  DISTINCT
	BomId
  FROM BomPositions
),
BomSetId AS (
   SELECT
      Boms.BomId,
	  (SELECT STRING_AGG(BomPos.MaterialId + '_' + FORMAT(BomPos.MaterialQuantity, 'N2'), ',') WITHIN GROUP (ORDER BY BomPos.MaterialId, BomPos.MaterialQuantity)
      FROM BomPositions BomPos 
	  WHERE  (BomPos.BomId = Boms.BomId)
	  )  AS SetId
   FROM Boms 
),
BomSets AS (
SELECT 
	SetId, 
	MIN(BomId) MinBomId,
	COUNT(*) BomCount
FROM BomSetId
GROUP BY SetId
)

SELECT DISTINCT * 
FROM BomSets
LEFT JOIN BomPositions ON BomPositions.BomId = MinBomId
ORDER BY SetId,MaterialId


[Edit]
ChatGPT documented our collaborative solution (90%ChatGPT, 10% me) as follows, not bad from my point of view:

Explanation:
- **Boms CTE:** Selects distinct BomIds from the BomPositions table.
- **BomSetId CTE:** For each BomId, creates a SetId by concatenating MaterialId and MaterialQuantity with an underscore `_`.
- **BomSets CTE:** Groups the BomSetId by SetId, selects the minimum BomId (as a representative Bom), and counts the number of BOMs with the same SetId.
- **Main Query:** Joins the BomSets with BomPositions to retrieve the unique BOMs and their details.
 
Share this answer
 
v3
Comments
0x01AA 31-Mar-24 11:28am    
And no, it was not me who accpeted that as a solution .... ?
See also: Bugs and Suggestions[^]

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900