Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2005 Forums
 Transact-SQL (2005)
 Combination Calculation

Author  Topic 

notmyrealname

98 Posts

Posted - 2010-09-14 : 13:11:22
Hi,

This is not exactly a SQL question but i wanted to post it here because this is the smartest forum i've used. I know it can be accomplished in SQL so i would be happy to see what ideas anyone has.

I need to list all possible sums of any combination of values in a given list.

For example...

Let's say i have the following numbers...

1 2 3 4 5

... the resulting list would include everything between 1 (1 by itself) through 15 (1+2+3+4+5).

I am looking for a list of sums for everything from each individual number to any combination of the numbers all the way to the sum of every number.

1=1
1+2=3
1+3=4
1+4=5
1+5=6
1+2+3=6
1+2+4=7
1+4+5=10
2+3+5=10
and so on...

I am trying to come up with a batching algorithm. Given a certain length of material and a list of n number of part lengths, i want to batch the parts on the material in the most efficient way.

Say we have a piece of material that is 20' long and we have the following parts...

10'
7'
7'
6'
4'
2'

In this case there are three combinations that would fit perfectly onto the material...

10+6+4=20
7+7+6+20
7+7+4+2=20

Once i have a list of all possible combinations i can then select the combination that is the closest to the desired length.

I hope this makes enough sense to everybody.

Any suggestion would be greatly appreciated.

Thanks,
Joel

rohitkumar
Constraint Violating Yak Guru

472 Posts

Posted - 2010-09-14 : 13:40:00
I would build a dynamic SQL to get something like this?


SELECT *
FROM (SELECT *
FROM (SELECT 0 AS col1
UNION ALL
SELECT 10 AS col1) a
CROSS JOIN (SELECT 0 AS col2
UNION ALL
SELECT 7 AS col2) b
CROSS JOIN (SELECT 0 AS col3
UNION ALL
SELECT 7 AS col3) c
CROSS JOIN (SELECT 0 AS col4
UNION ALL
SELECT 6 AS col4) d
CROSS JOIN (SELECT 0 AS col5
UNION ALL
SELECT 4 AS col5) e
CROSS JOIN (SELECT 0 AS col6
UNION ALL
SELECT 2 AS col6) f) x
WHERE col1 + col2 + col3 + col4 + col5 + col6 = 20
Go to Top of Page

kunal.mehta
Yak Posting Veteran

83 Posts

Posted - 2010-09-15 : 05:24:32
perfect solution. but need to make it dynamic to fulfil the reqt
Go to Top of Page

SwePeso
Patron Saint of Lost Yaks

30421 Posts

Posted - 2010-09-15 : 08:56:07
Dynamic SQL? Why?
DECLARE	@Sample TABLE
(
PieceID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
PieceLength INT NOT NULL
)

INSERT @Sample
(
PieceLength
)
SELECT 10 UNION ALL
SELECT 7 UNION ALL
SELECT 7 UNION ALL
SELECT 6 UNION ALL
SELECT 4 UNION ALL
SELECT 2

DECLARE @WantedLength INT

SET @WantedLength = 20

;WITH ctePermutations(PieceGroup, PieceLength, PiecePath, PieceTotal)
AS (
SELECT PieceID AS PieceGroup,
PieceLength,
'<p>' + CAST(PieceID AS VARCHAR(MAX)) + '</p>' AS PiecePath,
PieceLength AS PieceTotal
FROM @Sample

UNION ALL

SELECT p.PieceGroup,
s.PieceLength,
p.PiecePath + '<p>' + CAST(s.PieceID AS VARCHAR(MAX)) + '</p>' AS PiecePath,
p.PieceTotal + s.PieceLength AS PieceTotal
FROM @Sample AS s
INNER JOIN ctePermutations AS p ON p.PiecePath NOT LIKE '%<p>' + CAST(s.PieceID AS VARCHAR(MAX)) + '</p>%'
WHERE s.PieceLength >= p.PieceLength
), cteResult(PieceGroup, PieceID)
AS (
SELECT DISTINCT x.PieceGroup,
f.a.value('.', 'INT') AS PieceID
FROM (
SELECT PieceGroup,
CAST(PiecePath AS XML) AS w
FROM ctePermutations
WHERE PieceTotal = @WantedLength
) AS x
CROSS APPLY x.w.nodes('p') AS f(a)
)
SELECT DENSE_RANK() OVER (ORDER BY r.PieceGroup) AS Alternative,
s.PieceLength
FROM cteResult AS r
INNER JOIN @Sample AS s ON s.PieceID = r.PieceID
ORDER BY r.PieceGroup,
s.PieceLength



N 56°04'39.26"
E 12°55'05.63"
Go to Top of Page

notmyrealname

98 Posts

Posted - 2010-09-15 : 09:10:23
Thanks guys. I like the ideas. Not a huge fan of dynamic SQL so i think Peso takes this one. I'm on the fence between handling this in SQL versus handling it in VB. Your solution, Peso, is so simple that i might just stick with SQL.

Thanks again everyone. I knew i could count on SQL Team.
Go to Top of Page

rohitkumar
Constraint Violating Yak Guru

472 Posts

Posted - 2010-09-15 : 10:20:05
quote:
Originally posted by Peso

Dynamic SQL? Why?



good solution Peter, needs some tweaking, try 19, 21... etc. instead of 20.
Go to Top of Page

notmyrealname

98 Posts

Posted - 2010-09-15 : 10:24:44
Hey Peso,

I forgot to mention one thing. The pieces may not always add up to the @WantedLength. You might have a @WantedLength of 20 but no combination actually adds up to an even 20. In this case i need to know what pieces will add up closest to 20 without exceeding 20.

What i would really love to see would be a complete list of all poosible combinations with their corresponding Alternative ID. The list that i provided would yield 63 possible combinations. This is what's in my head. Imagine each length representing a digit in a binary number as shown below.

10: 7: 7: 6: 4: 2
-- -- -- -- -- --
1 0 1 0 1 1

We could have any combination from 000000 (total length = 0) to 111111 (total length = 10+7+7+6+4+2 = 36). The above example 101011 would add up to 10+7+4+2=23. Because there are six lengths we are working with, the corresponding binary number would be 6 digits long meaning we could have 63 possible combinations. Clearly the number of possibilities could become quite large depending on how many lengths we are working with. I can add some rules later that will restrict certain combinations from ever being included.

At the end of the day i am looking for a list that i can compare to the actual material in our shop and determine the most efficient placement of the pieces. I am trying to put something together in VB but am also interested in seeing if this could be done in SQL.

Thanks again for your help.
Joel
Go to Top of Page

SwePeso
Patron Saint of Lost Yaks

30421 Posts

Posted - 2010-09-15 : 10:29:51
21 gives
Alternative	PieceLength
1 4
1 7
1 7
1 10



N 56°04'39.26"
E 12°55'05.63"
Go to Top of Page

rohitkumar
Constraint Violating Yak Guru

472 Posts

Posted - 2010-09-15 : 10:37:20
19 gives

Alternative PieceLength
1 2
1 4
1 6
1 7
1 7
1 10
Go to Top of Page

SwePeso
Patron Saint of Lost Yaks

30421 Posts

Posted - 2010-09-15 : 10:56:29
Here is a slightly new algorithm
DECLARE	@Sample TABLE
(
PieceID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
PieceLength INT NOT NULL
)

INSERT @Sample
(
PieceLength
)
SELECT 10 UNION ALL
SELECT 7 UNION ALL
SELECT 7 UNION ALL
SELECT 6 UNION ALL
SELECT 4 UNION ALL
SELECT 2

DECLARE @WantedLength INT

SET @WantedLength = 20

;WITH ctePermutations(PieceLength, PiecePath, PieceTotal)
AS (
SELECT PieceLength,
'<p>' + CAST(PieceID AS VARCHAR(MAX)) + '</p>' AS PiecePath,
PieceLength AS PieceTotal
FROM @Sample

UNION ALL

SELECT s.PieceLength,
p.PiecePath + '<p>' + CAST(s.PieceID AS VARCHAR(MAX)) + '</p>' AS PiecePath,
p.PieceTotal + s.PieceLength AS PieceTotal
FROM @Sample AS s
INNER JOIN ctePermutations AS p ON p.PiecePath NOT LIKE '%<p>' + CAST(s.PieceID AS VARCHAR(MAX)) + '</p>%'
WHERE s.PieceLength >= p.PieceLength
), cteIntermediate(PiecePath, PieceLength, RecID)
AS (
SELECT x.PiecePath,
s.PieceLength,
RANK() OVER (ORDER BY x.PieceTotal DESC) AS RecID
FROM (
SELECT PiecePath,
CAST(PiecePath AS XML) AS w,
PieceTotal
FROM ctePermutations
WHERE PieceTotal <= @WantedLength
) AS x
CROSS APPLY x.w.nodes('p') AS f(a)
INNER JOIN @Sample AS s ON s.PieceID = f.a.value('.', 'INT')
), cteResult(RecID, t)
AS (
SELECT ROW_NUMBER() OVER (PARTITION BY f.t ORDER BY i.PiecePath) AS RecID,
f.t
FROM cteIntermediate AS i
CROSS APPLY (
SELECT CAST('<i>' + CAST(e.PieceLength AS VARCHAR(MAX)) + '</i>' AS XML)
FROM cteIntermediate AS e
WHERE e.PiecePath = i.PiecePath
AND e.RecID = 1
ORDER BY e.PieceLength
FOR XML PATH('')
) AS f(t)
WHERE i.RecID = 1
)
SELECT r.Alternative,
f.a.value('.', 'INT') AS PieceLength,
SUM(f.a.value('.', 'INT')) OVER (PARTITION BY r.Alternative) AS Total
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY t) AS Alternative,
CAST(t AS XML) AS w
FROM cteResult
WHERE RecID = 1
) AS r
CROSS APPLY r.w.nodes('i') AS f(a)


N 56°04'39.26"
E 12°55'05.63"
Go to Top of Page

rohitkumar
Constraint Violating Yak Guru

472 Posts

Posted - 2010-09-15 : 10:59:56
check 5
Go to Top of Page

SwePeso
Patron Saint of Lost Yaks

30421 Posts

Posted - 2010-09-15 : 11:02:29
Yes? It gives you 4 as requested.
quote:
Originally posted by notmyrealname

Once i have a list of all possible combinations i can then select the combination that is the closest to the desired length.


N 56°04'39.26"
E 12°55'05.63"
Go to Top of Page

rohitkumar
Constraint Violating Yak Guru

472 Posts

Posted - 2010-09-15 : 11:17:32
quote:
Originally posted by Peso

Yes? It gives you 4 as requested.



shouldn't it give 6?
Go to Top of Page

SwePeso
Patron Saint of Lost Yaks

30421 Posts

Posted - 2010-09-15 : 11:49:45
No. See op last response with "without exceeding" part.


N 56°04'39.26"
E 12°55'05.63"
Go to Top of Page

notmyrealname

98 Posts

Posted - 2010-09-15 : 11:50:09
Try adding some more pieces. Add about 20 random lengths between 1 and 10 and then try it. It looks like i is going to slow down too much
Go to Top of Page

notmyrealname

98 Posts

Posted - 2010-09-15 : 12:00:41
I'm not sure this is going to work in my actual scenario where the @WantedLength would be something like 12192 millimeters (20'-0") and there could be a hundred different piece lengths or more.
Go to Top of Page

SwePeso
Patron Saint of Lost Yaks

30421 Posts

Posted - 2010-09-15 : 12:14:49
Do you want all permutations or just one?


N 56°04'39.26"
E 12°55'05.63"
Go to Top of Page

notmyrealname

98 Posts

Posted - 2010-09-15 : 12:48:12
If there is a perfect match then there is no reason to continue. Those parts will be removed from the queue and the next @WantedLength will be processed. Imagine there are 100 parts that have to be batched onto 10 lengths of material (either constant or varying length).

Right now what i am doing is sorting my queue by [Length] DESC and iterating through the queue. If the piece fits on the length of material then it is assigned to that piece. If it doesn't then it is skipped and we move on to the next piece in the queue. What this does is fill up each batch with the the longer pieces at the start and the shorter pieces at the end. this works pretty efficiently but it's not perfect.

Take for instance my original numbers...

10'
7'
7'
6'
4'
2'

Using this system it is determined that...

10' fits so it is added
7' fits so it is added
7' does not fit so it is skipped
6' does not fit so it is skipped
4' does not fit so it is skipped
2' fits so it is added

resulting in a batch of three piece (10+7+2) adding up to 19' with 1' of waste. The remaining pieces are then added to additional batches using the same principal.

The waste is pretty low but you can clearly see that we can make an exact batch by using the 10+6+4 (or any of the other perfect combinations). This is why i was thinking of creating a list of all possible combinations (that don't exceed the @WantedLength) and then using that list to determine which parts go on each batch. I think it might take quite a processor to handle it unforetunately.

Thanks again. You're a big help.
Go to Top of Page

robvolk
Most Valuable Yak

15732 Posts

Posted - 2010-09-15 : 13:04:36
You wouldn't be applying for a job, would you?

http://www.dropbox.com/jobs/challenges

If not, maybe you should.
Go to Top of Page

notmyrealname

98 Posts

Posted - 2010-09-15 : 13:25:05
No thanks. After working in a distribution center for a few years (and moving several times) i have decided that i never want to pack another box!
Go to Top of Page

SwePeso
Patron Saint of Lost Yaks

30421 Posts

Posted - 2010-09-15 : 16:53:57
Have you tried this algorithm http://weblogs.sqlteam.com/peterl/archive/2008/08/12/How-to-sum-up-an-unknown-number-of-records.aspx ?
-- Initialize the search parameter
DECLARE @WantedValue INT = 19

-- Stage the source data
DECLARE @Data TABLE
(
RecID INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
MaxItems INT,
CurrentItems INT DEFAULT 0,
FaceValue INT,
BestUnder INT DEFAULT 0,
BestOver INT DEFAULT 1
)

-- Aggregate the source data
INSERT @Data
(
MaxItems,
FaceValue
)
SELECT COUNT(*),
Qty
FROM (
VALUES (10),
(7),
(7),
(6),
(4),
(2)
) AS d(Qty)
GROUP BY Qty
ORDER BY Qty DESC

-- Declare some control variables
DECLARE @CurrentSum INT,
@BestUnder INT,
@BestOver INT,
@RecID INT

-- If exact single wanted sum, select that item!
IF EXISTS (SELECT * FROM @Data WHERE FaceValue = @WantedValue)
BEGIN
SELECT 0 AS SumType,
1 AS Items,
FaceValue
FROM @Data
WHERE FaceValue = @WantedValue

RETURN
END

-- If productsum is less to the wanted sum, select all items!
IF (SELECT SUM(MaxItems * FaceValue) FROM @Data) < @WantedValue
BEGIN
SELECT -1 AS SumType,
MaxItems AS Items,
FaceValue
FROM @Data

RETURN
END

-- If productsum is equal to the wanted sum, select all items!
IF (SELECT SUM(MaxItems * FaceValue) FROM @Data) = @WantedValue
BEGIN
SELECT 0 AS SumType,
MaxItems AS Items,
FaceValue
FROM @Data

RETURN
END

-- Delete all unworkable FaceValues, keep one greater FaceValue because of oversum.
DELETE
FROM @Data
WHERE FaceValue > (SELECT MIN(FaceValue) FROM @Data WHERE FaceValue >= @WantedValue)

-- Update MaxItems to a proper value
UPDATE @Data
SET MaxItems = CASE
WHEN 1 + (@WantedValue - 1) / FaceValue < MaxItems THEN 1 + (@WantedValue - 1) / FaceValue
ELSE MaxItems
END

-- Update BestOver to a proper value
UPDATE @Data
SET BestOver = MaxItems

-- Initialize the control mechanism
SELECT @RecID = MIN(RecID),
@BestUnder = 0,
@BestOver = SUM(BestOver * FaceValue)
FROM @Data

-- Do the loop!
WHILE @RecID IS NOT NULL
BEGIN
-- Reset all "bits" not incremented
UPDATE @Data
SET CurrentItems = 0
WHERE RecID < @RecID

-- Increment the current "bit"
UPDATE @Data
SET CurrentItems = CurrentItems + 1
WHERE RecID = @RecID

-- Get the current sum
SELECT @CurrentSum = SUM(CurrentItems * FaceValue)
FROM @Data
WHERE CurrentItems > 0

-- Stop here if the current sum is equal to the sum we want
IF @CurrentSum = @WantedValue
BREAK
ELSE
-- Update the current BestUnder if previous BestUnder is less
IF @CurrentSum > @BestUnder AND @CurrentSum < @WantedValue
BEGIN
UPDATE @Data
SET BestUnder = CurrentItems

SET @BestUnder = @CurrentSum
END
ELSE
-- Update the current BestOver if previous BestOver is more
IF @CurrentSum > @WantedValue AND @CurrentSum < @BestOver
BEGIN
UPDATE @Data
SET BestOver = CurrentItems

SET @BestOver = @CurrentSum
END

-- Find the next proper "bit" to increment
SELECT @RecID = MIN(RecID)
FROM @Data
WHERE CurrentItems < MaxItems
END

-- Now we have to investigate which type of sum to return
IF @RecID IS NULL
IF @WantedValue - @BestUnder < @BestOver - @WantedValue
-- If BestUnder is closer to the sum we want, choose that
SELECT -1 AS SumType,
BestUnder AS Items,
FaceValue
FROM @Data
WHERE BestUnder > 0
ELSE
-- If BestOver is closer to the sum we want, choose that
SELECT 1 AS SumType,
BestOver AS Items,
FaceValue
FROM @Data
WHERE BestOver > 0
ELSE
-- We have an exact match
SELECT 0 AS SumType,
CurrentItems AS Items,
FaceValue
FROM @Data
WHERE CurrentItems > 0



N 56°04'39.26"
E 12°55'05.63"
Go to Top of Page
    Next Page

- Advertisement -