如何根据查找表检索最接近的值?

pjb*_*ger 8 table

我正在尝试创建一个查询,该查询将从一个表中找到最接近的值并将其 ID 返回到结果表中。

下面是一个应该更好地描述情况的例子。

样本数据

这两个表将存在于 SQL 数据库中。

主表

+----+-------------+
| ID | Measurement |
+----+-------------+
|  1 | 0.24        |
|  2 | 0.5         |
|  3 | 0.14        |
|  4 | 0.68        |
+----+-------------+
Run Code Online (Sandbox Code Playgroud)

查找表

+----+---------------+
| ID | Nominal Value |
+----+---------------+
|  1 | 0.1           |
|  2 | 0.2           |
|  3 | 0.3           |
|  4 | 0.4           |
|  5 | 0.5           |
|  6 | 0.6           |
|  7 | 0.7           |
|  8 | 0.8           |
|  9 | 0.9           |
+----+---------------+
Run Code Online (Sandbox Code Playgroud)

目标

这将是查询的结果。测量值不应在边界上(例如 0.25)。

+----+-------------+-----------+
| ID | Measurement | Lookup ID |
+----+-------------+-----------+
|  1 | 0.24        |         2 |
|  2 | 0.5         |         5 |
|  3 | 0.14        |         1 |
|  4 | 0.68        |         7 |
+----+-------------+-----------+
Run Code Online (Sandbox Code Playgroud)

是否有能够返回这种结果的查询?

Erw*_*ter 7

测试和优化了几个查询。都返回一样,基本都是标准的SQL。(但没有 RDBMS 完全支持该标准。)

第一个使用一个LATERAL JOIN这是在甲骨文12C或MySQL前失踪。测试自己哪个表现最好。

他们都对lookupPostgres 中的表使用仅索引扫描。显然,lookup.nominal_value需要被索引。我建议制作它,UNIQUE因为看起来该列应该是独一无二的。这会自动创建最重要的索引。

LATERAL 加入

SELECT m.id, m.measurement, l.nominal_value
FROM   measurement m
JOIN   LATERAL (
   (
   SELECT nominal_value - m.measurement AS diff, nominal_value
   FROM   lookup
   WHERE  nominal_value >= m.measurement
   ORDER  BY nominal_value
   LIMIT  1
   )
   UNION  ALL
   (
   SELECT m.measurement - nominal_value, nominal_value
   FROM   lookup
   WHERE  nominal_value <= m.measurement
   ORDER  by nominal_value DESC
   LIMIT  1
   )
   ORDER  BY 1
   LIMIT  1
   ) l ON true;
Run Code Online (Sandbox Code Playgroud)

需要的所有括号UNION。看:

子查询中的相关子查询

SELECT id, measurement
     , CASE WHEN hi - measurement > measurement - lo
            THEN lo
            ELSE hi
       END AS nominal_value
FROM  (
   SELECT id, measurement
        , ( SELECT nominal_value
            FROM   lookup
            WHERE  nominal_value >= m.measurement
            ORDER  BY nominal_value
            LIMIT  1) AS hi
         , COALESCE((
            SELECT nominal_value
            FROM   lookup
            WHERE  nominal_value <= m.measurement
            ORDER  by nominal_value DESC
            LIMIT  1), 0) AS lo   -- cover possible NULL values
   FROM   measurement m
   ) sub;
Run Code Online (Sandbox Code Playgroud)

CTE 中的相关子查询

WITH cte AS (
   SELECT id, measurement
        , ( SELECT nominal_value
            FROM   lookup
            WHERE  nominal_value >= m.measurement
            ORDER  BY nominal_value
            LIMIT  1) AS hi
        , COALESCE((
            SELECT nominal_value
            FROM   lookup
            WHERE  nominal_value <= m.measurement
            ORDER  by nominal_value DESC
            LIMIT  1), 0) AS lo   -- cover possible NULL values
   FROM   measurement m
   )
SELECT id, measurement
     , CASE WHEN hi - measurement > measurement - lo
            THEN lo
            ELSE hi
       END AS nominal_value
FROM   cte;
Run Code Online (Sandbox Code Playgroud)

嵌套相关子查询

SELECT id, measurement
     , (SELECT nominal_value FROM (
         (
         SELECT nominal_value - m.measurement, nominal_value
         FROM   lookup
         WHERE  nominal_value >= m.measurement
         ORDER  BY nominal_value
         LIMIT  1
         )
         UNION  ALL
         (
         SELECT m.measurement - nominal_value, nominal_value
         FROM   lookup
         WHERE  nominal_value <= m.measurement
         ORDER  by nominal_value DESC
         LIMIT  1
         )
         ORDER  BY 1
         LIMIT  1
         ) sub
         ) AS nominal_value
FROM   measurement m;
Run Code Online (Sandbox Code Playgroud)

db<>fiddle here
旧的sqlfiddle


Dan*_*her 1

这是完全可能的,尽管我能想到的解决这个问题的唯一方法效率相当低,而且扩展性也不是很好。

SELECT t.ID, t.Measurement,
    (SELECT TOP 1 lkp.ID
     FROM lookupTable AS lkp
     ORDER BY ABS(lkp.NominalValue-t.Measurement)) AS LookupID
FROM mainTable AS t
Run Code Online (Sandbox Code Playgroud)

另一种解决方案可能会扩展/性能更好,它使用有序窗口函数(可在 SQL Server 2012 和 2014 以及其他一些数据库平台上使用,但不可在 Azure 上使用)。

WITH lkp AS (
    SELECT ID,
           --- fromValue is the average of the previous NominalValue and this one:
           (NominalValue+LAG(NominalValue, 1) OVER (ORDER BY NominalValue))/2.0 AS fromValue,
           --- toValue is the average of the next NominalValue and this one:
           (NominalValue+LEAD(NominalValue, 1) OVER (ORDER BY NominalValue))/2.0 AS toValue
    FROM dbo.LookupTable)

SELECT t.ID, t.Measurement, lkp.ID AS LookupID
FROM MainTable AS t
LEFT JOIN lkp ON
    --- The first lookup value will have fromValue=NULL
    (t.Measurement>=lkp.fromValue OR lkp.fromValue IS NULL) AND
    --- The last lookup value will have toValue=NULL
    (t.Measurement<lkp.toValue OR lkp.toValue IS NULL);
Run Code Online (Sandbox Code Playgroud)

如果此查询仍然给您带来性能问题,请尝试创建一个临时查找表,用“lkp”中的行填充它,然后如上所述连接“t”和“lkp”。我可能会给临时表一个像这样的索引

CREATE UNIQUE INDEX IX_temptable ON #temptable (fromValue) INCLUDE (toValue, ID);
Run Code Online (Sandbox Code Playgroud)

哪种解决方案最适合您主要取决于您拥有的数据量。尝试不同的解决方案。