我正在尝试创建一个查询,该查询将从一个表中找到最接近的值并将其 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)
是否有能够返回这种结果的查询?
测试和优化了几个查询。都返回一样,基本都是标准的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)
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)
这是完全可能的,尽管我能想到的解决这个问题的唯一方法效率相当低,而且扩展性也不是很好。
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)
哪种解决方案最适合您主要取决于您拥有的数据量。尝试不同的解决方案。
| 归档时间: |
|
| 查看次数: |
15381 次 |
| 最近记录: |