SQL查询:如何为表2中的每个值获取上下的最近记录

时间:2013-07-02 07:36:06

标签: sql sql-server-2008 join closest

给定一个OriginalValues表,我想返回第一个#Conversion记录,其中ValueFrom大于或等于OriginalValue(最接近等于或大于),以及第一个#Conversion记录,其中ValueFrom小于OriginalValue(最近),每个OriginalValue的一个输出记录。 (例如,OriginalValue,ValueFromAbove,ValueToAbove,ValueFromBelow,ValueToBelow)。

尽管有下面的单个结果查询,但我无法弄清楚如何将#Values和#Conversion表连接在一起以获得所需的结果。有没有人对如何解决这个问题有任何想法?

另外,如果光标或任何其他方法更快,请告诉我,我正在处理非常大的数据集(#Value将比#Conversion大得多)。

示例表结构:

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)

单个OriginalValue的示例查询(在范围之外不返回任何内容,例如@OrigValue = 7,但如果它可以返回(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

(我不确定以上查询是解决此问题的最佳方式。任何优化建议都将不胜感激!)

我的目标是一次性获得#Values的整个结果。

示例所需结果:

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

7的结果可能是= 1,7,7,0,2,1。

除非在查询中可以提供多个PeriodId,否则不需要PeriodId列。例如。插入#Values VALUES(1,0.01),(2,0.1)(如果不是在这一点,我不介意太多。)

2 个答案:

答案 0 :(得分:1)

尝试此查询(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)

答案 1 :(得分:1)

在MSSQL 2008中使用其他PeriodIds,并正确返回

insert into c_Values (2,1.5)

当值超出范围时,现在还返回min和max值而不是null或零。

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.

复制到上面的Fiddle并设置为“MS SQL Server 2008”

时可以正常工作