Given a table of OriginalValues, I would like to return both the first #Conversion record where ValueFrom is greater than or equal to the OriginalValue (nearest equal to or above), and the first #Conversion record where ValueFrom is less than the OriginalValue (nearest below), to the one output record for each OriginalValue. (eg. OriginalValue, ValueFromAbove, ValueToAbove, ValueFromBelow, ValueToBelow).

Despite having the below single result query, I can't quite figure out how to join the #Values and #Conversion tables together for the desired result. Does anyone have any ideas on how to solve this?

Also, please let me know if a cursor or any other method would be faster, I'm dealing with very large data sets (#Value will be much larger than #Conversion).

Example Table structures:

CREATE TABLE #Conversion (ConversionPeriodId int, ValueFrom float, ValueTo float)
INSERT INTO #Conversion 
VALUES (1, 0, 0.001), (1, 1, 0.05), (1, 1.5, 0.5), (1, 2, 1),
(2,0,0),(2,1,1),(2,2,4)

CREATE TABLE #Values (PeriodId int, OriginalValue float)
insert into #Values 
VALUES (1, 0.01),(1, 2), (1, 1.89625), (1, 1.3), (1, 7), (1, -1)

Example query for a single OriginalValue (returns nothing for outside of range eg. @OrigValue = 7, but would be nicer if it could return (7) 0, 0, 2, 1 )

DECLARE @OrigValue float = 1.89625

SELECT @OrigValue as OriginalValue, a.ValueFrom AS ValueFromAbove, a.ValueTo AS ValueToAbove, 
b.ValueFrom AS ValueFromBelow, b.ValueTo AS ValueToBelow
  FROM (SELECT TOP 1 * FROM #Conversion 
  WHERE ConversionPeriodId = 1
  AND ValueFrom >= @OrigValue
  ORDER BY ValueFrom ) a
FULL OUTER JOIN
  (SELECT TOP 1 * FROM #Conversion WHERE ConversionPeriodId = 1
  AND ValueFrom < (SELECT TOP 1 ValueFrom FROM #Conversion 
    WHERE ConversionPeriodId = 1
    AND ValueFrom >= @OrigValue
    ORDER BY ValueFrom )
  ORDER BY ValueFrom DESC) b
  ON a.ConversionPeriodId = b.ConversionPeriodId
  AND a.ValueFrom = (SELECT TOP 1 x.ValueFrom FROM #Conversion x 
    WHERE x.ValueFrom > b.ValueFrom ORDER BY ValueFrom )

--OriginalValue   ValueFromAbove    ValueToAbove    ValueFromBelow  ValueToBelow
--1.89625         2                 1               1.5             0.5

DROP TABLE #Conversion

DROP TABLE #Values

(I'm not sure the above query is the best way to go about this. Any optimisation suggestions would be appreciated!)

My goal is to get the entire result for #Values in one go.

Example desired result:

PeriodId    OriginalValue   ValueFromAbove  ValueToAbove    ValueFromBelow      ValueToBelow
1           0.01            1               0.05            0                   0.001
1           2               2               1               1.5                 0.5
1           1.89625         2               1               1.5                 0.5
1           1.3             1.5             0.5             1                   0.05
1           7               0               0               0                   0
1           -1              0               0.001           0                   0

The result for 7 could instead be = 1, 7, 0, 0, 2, 1.

The PeriodId column would not be necessary unless in the query it is possible to provide multiple PeriodIds. eg. insert into #Values VALUES (1, 0.01),(2, 0.1) (I don't mind too much if it isn't at this point).

有帮助吗?

解决方案 2

Got it working for additional PeriodIds, in MSSQL 2008, and returning correctly for

insert into c_Values (2,1.5)

Also now returning min and max values instead of null or zero when values head out of range.

SELECT cv.PeriodID, cv.OriginalValue,
    c1.ValueFrom ValueFromAbove,
    c1.ValueTo ValueToAbove,
    c2.ValueFrom ValueFromBelow,
    c2.ValueTo ValueToBelow
FROM c_values cv 
LEFT JOIN Conversion  c1 ON
    cv.PeriodId = c1.ConversionPeriodId 
    AND c1.ValueFrom = ISNULL(
      (select top 1 ValueFrom FROM Conversion WHERE ConversionPeriodId = cv.PeriodId 
       AND ValueFrom >= cv.OriginalValue order by ValueFrom), 
      (select top 1 ValueFrom From Conversion WHERE ConversionPeriodId = cv.PeriodId 
       ORDER BY ValueFrom desc))
    --if c1.ValueFrom IS NULL (above given range),  return highest value.
LEFT JOIN Conversion  c2
    ON
    cv.PeriodId = c2.ConversionPeriodId 
    AND c2.ValueFrom = ISNULL(
      (select top 1 ValueFrom FROM Conversion WHERE ConversionPeriodId = cv.PeriodId 
      AND ValueFrom < cv.OriginalValue order by ValueFrom desc), 
      c1.ValueFrom)
    --if c2.ValueFrom IS NULL (below given range) return lowest value.

Will work when copied into the above Fiddle and set to 'MS SQL Server 2008'

其他提示

Try this query (Fiddle):

SELECT cv.PeriodID, cv.OriginalValue,
c1.ValueFrom ValueFromAbove,
c1.ValueTo ValueToAbove,
c2.ValueFrom ValueFromBelow,
c2.ValueTo ValueToBelow
FROM c_values cv LEFT JOIN Conversion c1 ON
cv.PeriodId = c1.ConversionPeriodId 
AND c1.ValueFrom >= cv.OriginalValue
LEFT JOIN Conversion c2 ON
cv.PeriodId = c2.ConversionPeriodId
AND c2.ValueFrom <= cv.OriginalValue
WHERE
CASE WHEN c1.ValueFrom IS NULL THEN NULL 
ELSE c1.ValueFrom END <= ALL (SELECT ValueFrom from Conversion
                     WHERE ValueFrom >= cv.OriginalValue)
AND
CASE WHEN c2.ValueFrom IS NOT NULL THEN c2.ValueFrom
ELSE NULL END >= ALL (SELECT ValueFrom from Conversion
                     WHERE ValueFrom <= cv.OriginalValue)
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top