Reputation: 21182
I'm trying to write a stored procedure that will return two calculated values for each record according to the rules below, but I haven't figured out how to structure the SQL to make it happen. I'm using SQL Server 2008.
First, the relevant tables, and the fields that matter to the problem.
ProductionRuns
RunID (key, and RunID is given to the stored proc as its parameter)
ContainerName
ProductName
TemplateID
TemplateMeasurements
MeasurementTypeID
TemplateID
SimpleBounds
MeasurementTypeID
TemplateID
UpperBound
LowerBound
ContainerBounds
MeasurementTypeID
TemplateID
UpperBound
LowerBound
ContainerName
ProductBounds
MeasurementTypeID
TemplateID
UpperBound
LowerBound
ProductName
And this is what I'm trying to return. I want to return a calculated upper bound and lower bound value for each TemplateMeasurements record that has a matching TemplateID with the ProductionRuns record that has the supplied runID.
The calculated upper and lower bounds basically get the tightest bound that can be obtained as a result of the simple, container and product bounds, if they qualify.
If a SimpleBounds record exists with the correct MeasurementTypeID and TemplateID, then that becomes one of the qualifying bounds for a particular MeasurementTypeID and record of TemplateMeasurements.
For a ContainerBound record to qualify, the TemplateID and MeasurementTypeID must match, but also the ContainerName must match the value for ContainerName in the ProductionRuns record. Also for ProductBounds, the same is true, but for ProductName.
For a particular MeasurementTypeID, take all the qualifying bounds, and find the least Upper Bound, and that will be the calculated Upper Bound that is to be returned. Find the greatest Lower Bound of the qualifiers and that will be the returned Lower Bound.
I have no idea how to put together SQL to do this however.
Also, if none of the three bound tables qualify for a particular MeasurementTypeID, then null could be returned.
My thought would be some kind of left outer join, but I'm not sure how to extend that to three tables that could all have null in the results.
Thanks for the help.
Upvotes: 1
Views: 1054
Reputation: 21182
Thanks for leading me in the right direction. I had to fiddle with the problem for a while before I got it tweaked just right, but it works great now.
My final code and results:
ALTER PROCEDURE [dbo].[GetBounds]
@runID int
AS
BEGIN
SET NOCOUNT ON;
DECLARE @templateID int
SET @templateID = (SELECT TOP(1) TemplateID
FROM ProductionRuns WHERE RunID = @runID);
SELECT TM.MeasurementTypeID,
CASE
WHEN MIN(SB.UpperBound) < MIN(PB.UpperBound)
AND MIN(SB.UpperBound) < MIN(CB.UpperBound) THEN MIN(SB.UpperBound)
WHEN MIN(PB.UpperBound) < MIN(SB.UpperBound)
AND MIN(PB.UpperBound) < MIN(CB.UpperBound) THEN MIN(PB.UpperBound)
WHEN MIN(CB.UpperBound) < MIN(SB.UpperBound)
AND MIN(CB.UpperBound) < MIN(PB.UpperBound) THEN MIN(CB.UpperBound)
ELSE MIN(SB.UpperBound)
END AS 'UpperBound',
CASE
WHEN MAX(SB.LowerBound) > MAX(PB.LowerBound)
AND MAX(SB.LowerBound) > MAX(CB.LowerBound) THEN MAX(SB.LowerBound)
WHEN MAX(PB.LowerBound) > MAX(SB.LowerBound)
AND MAX(PB.LowerBound) > MAX(CB.LowerBound) THEN MAX(PB.LowerBound)
WHEN MAX(CB.LowerBound) > MAX(SB.LowerBound)
AND MAX(CB.LowerBound) > MAX(PB.LowerBound) THEN MAX(CB.LowerBound)
ELSE MAX(SB.LowerBound)
END AS 'LowerBound'
FROM
ProductionRuns PR
INNER JOIN TemplateMeasurements TM ON
TM.TemplateID = PR.TemplateID
LEFT OUTER JOIN SimpleBounds SB ON
SB.TemplateID = PR.TemplateID AND
SB.MeasurementTypeID = TM.MeasurementTypeID
LEFT OUTER JOIN ContainerBounds CB ON
CB.TemplateID = PR.TemplateID AND
CB.MeasurementTypeID = TM.MeasurementTypeID AND
CB.ContainerName = PR.ContainerName
LEFT OUTER JOIN ProductBounds PB ON
PB.TemplateID = PR.TemplateID AND
PB.MeasurementTypeID = TM.MeasurementTypeID AND
PB.ProductName = PR.ProductName
WHERE TM.TemplateID = @templateID
GROUP BY
TM.MeasurementTypeID
END
Partial results for a particular case, RunID = 3249 (TemplateID = 2)
MeasurementTypeID UpperBound LowerBound
2 NULL NULL
11 4 2.5
18 30 1
20 40 10
33 99 0
36 200 140
42 120 32
...
Upvotes: 0
Reputation: 47392
I don't have time to test this right now, but hopefully this will get you pretty close:
SELECT
PR.RunID,
PR.TemplateID,
CASE
WHEN MAX(SB.LowerBound) > MAX(CB.LowerBound) AND
MAX(SB.LowerBound) > MAX(PB.LowerBound) THEN MAX(SB.LowerBound)
WHEN MAX(CB.LowerBound) > MAX(PB.LowerBound) THEN MAX(CB.LowerBound)
ELSE MAX(PB.LowerBound)
END AS LowerBound,
CASE
WHEN MIN(SB.UpperBound) < MIN(CB. UpperBound) AND
MIN(SB. UpperBound) < MIN(PB. UpperBound) THEN MIN(SB. UpperBound)
WHEN MIN(CB. UpperBound) < MIN(PB. UpperBound) THEN MIN(CB. UpperBound)
ELSE MIN(PB. UpperBound)
END
FROM
ProductionRuns PR
INNER JOIN TemplateMeasurements TM ON
TM.TemplateID = PR.TemplateID
LEFT OUTER JOIN SimpleBounds SB ON
SB.TemplateID = PR.TemplateID AND
SB.MeasurementTypeID = TM.MeasurementTypeID
LEFT OUTER JOIN ContainerBounds CB ON
CB.TemplateID = PR.TemplateID AND
CB.MeasurementTypeID = TM.MeasurementTypeID AND
CB.ContainerName = PR.ContainerName
LEFT OUTER JOIN ProductBounds PB ON
PB.TemplateID = PR.TemplateID AND
PB.MeasurementTypeID = TM.MeasurementTypeID AND
PB.ProductName = PR.ProductName
GROUP BY
PR.RunID,
PR.TemplateID
Upvotes: 1
Reputation: 16578
You've got other answers already that should work, but in my opinion this type of UNIONed inner query can result in the cleanest-looking, most-maintainable way to collapse a horizontal hierarchy into a vertical one, which is basically your problem:
SELECT MIN(iq.upperbound), MAX(iq.lowerbound)
FROM TemplateMeasurements tm
INNER JOIN ProductionRuns pr ON tm.TemplateID = pr.TemplateID
LEFT JOIN
(
SELECT sb.UpperBound, sb.LowerBound, sb.MeasurementTypeID, '' as Name, 'sb' as Type, sb.TemplateID
FROM SimpleBounds sb
UNION ALL
SELECT cb.UpperBound, cb.LowerBound, cb.MeasurementTypeID, cb.ContainerName as Name, 'cb' as Type, cb.TemplateID
FROM ContainerBounds cb
UNION ALL
SELECT pb.UpperBound, pb.LowerBound, pb.MeasurementTypeID, pb.ProductName as Name, 'pb' as Type, pb.TemplateID
FROM ProductBounds pb
) iq ON iq.MeasurementTypeID = tm.MeasurementTypeID
AND iq.TemplateID = tm.TemplateID
AND iq.Name =
CASE iq.Type
WHEN 'sb' THEN iq.Name
WHEN 'cb' THEN pr.ContainerName
WHEN 'pb' THEN pr.ProductName
END
WHERE pr.RunID = @runid
GROUP BY tm.TemplateID, tm.MeasurementTypeID
Upvotes: 1
Reputation: 60438
Not to take away from Tom H.'s answer, but you might also consider approaching this problem with unions instead of joins to help split up the different upper/lower rules. It depends on how you think the queries will need to change (if at all) in the future.
The query ends up looking cleaner, especially without all the CASE rules, but it might not be as useful in cases when TemplateMeasurement rows don't exist.
SELECT RunID, TemplateID, MIN(UpperBound), MAX(LowerBound)
FROM
(SELECT PR.RunID, SB.TemplateID, SB.UpperBound, SB.LowerBound
FROM SimpleBounds SB
INNER JOIN TemplateMeasurements TM
ON SB.TemplateID = TM.TemplateID
AND SB.MeasurementTypeID = TM.MeasurementTypeID
INNER JOIN ProductionRuns PR
ON TM.TemplateID = PR.TemplateID)
UNION
(SELECT PR.RunID, CB.TemplateID, CB.UpperBound, CB.LowerBound
FROM ContainerBounds CB
INNER JOIN TemplateMeasurements TM
ON CB.TemplateID = TM.TemplateID
AND CB.MeasurementTypeID = TM.MeasurementTypeID
INNER JOIN ProductionRuns PR
ON TM.TemplateID = PR.TemplateID
AND CB.ContainerName = PR.ContainerName)
UNION
(SELECT PR.RunID, PB.TemplateID, PB.UpperBound, PB.LowerBound
FROM ProductBounds PB
INNER JOIN TemplateMeasurements TM
ON PB.TemplateID = TM.TemplateID
AND PB.MeasurementTypeID = TM.MeasurementTypeID
INNER JOIN ProductionRuns PR
ON TM.TemplateID = PR.TemplateID
AND PB.ProductName = PR.ProductName)
GROUP BY RunID, TemplateID
Upvotes: 1