Làm thế nào để lấy giá trị gần nhất dựa trên bảng tra cứu?


7

Tôi đang cố gắng tạo một truy vấn sẽ tìm giá trị gần nhất từ ​​một bảng và trả lại ID của nó vào bảng kết quả.

Dưới đây là một ví dụ nên mô tả tình huống tốt hơn.

Dữ liệu mẫu

Hai bảng này sẽ tồn tại trong cơ sở dữ liệu SQL.

Bảng chính

+----+-------------+
| ID | Measurement |
+----+-------------+
|  1 | 0.24        |
|  2 | 0.5         |
|  3 | 0.14        |
|  4 | 0.68        |
+----+-------------+

Bảng tra cứu

+----+---------------+
| 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           |
+----+---------------+

Mục tiêu

Đây sẽ là kết quả của một truy vấn. Các phép đo không nên ở biên giới (ví dụ 0,25).

+----+-------------+-----------+
| ID | Measurement | Lookup ID |
+----+-------------+-----------+
|  1 | 0.24        |         2 |
|  2 | 0.5         |         5 |
|  3 | 0.14        |         1 |
|  4 | 0.68        |         7 |
+----+-------------+-----------+

Có một truy vấn sẽ có thể trả về loại kết quả này?


Điều này nghe có vẻ giống như một tìm kiếm "gần nhất" không gian, có thể được thực hiện để sử dụng một số loại chỉ mục đặc biệt.
Colin 't Hart

Bạn có cần điều này cho cả một bảng cùng một lúc hoặc chỉ cho một hàng hoặc một hàng được chọn không? Ngoài ra, luôn luôn khai báo RDBMS và phiên bản của bạn.
Erwin Brandstetter

Rất vui khi thấy tất cả các cách tiếp cận khác nhau ở đây. Và thật tuyệt khi có giải pháp cho các DBMS khác nhau.
Colin 't Hart

Tôi sẽ chỉ định RDBMS và phiên bản, nếu tôi có thể. Tôi đang làm việc để di chuyển một tệp Excel có nhiều bảng sang một số loại cơ sở dữ liệu. Tuy nhiên, đây là một thực tập và nghi ngờ RDBMS sẽ được chọn trước khi tôi rời đi. Hiện tại, tôi đang thực hiện mô phỏng các cấu trúc và truy vấn bảng trong Microsoft Access 2010
pjbollinger

Câu trả lời:


5

Một vài truy vấn được kiểm tra và tối ưu hóa cho Postgres 9.3. Tất cả đều trả về như nhau, tất cả về cơ bản là SQL tiêu chuẩn, nhưng không có RDBMS nào hỗ trợ tiêu chuẩn hoàn toàn.

Cụ thể, cái đầu tiên sử dụng a LATERAL JOIN, cái bị thiếu trong Oracle hoặc MySQL. Kiểm tra mà thực hiện tốt nhất.
Tất cả đều sử dụng quét chỉ mục trên lookupbảng trong Postgres. Rõ ràng, lookup.nominal_valuecần phải được lập chỉ mục. Tôi đề nghị làm cho nó UNIQUEbởi vì có vẻ như cột phải là duy nhất và bởi vì điều đó cũng tự động tạo ra chỉ mục quan trọng.

THAM GIA

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  -- NULLS LAST is default
   LIMIT  1
   ) l ON TRUE;

Tất cả các dấu ngoặc đơn cần thiết cho UNION. Câu trả lời liên quan:
Postgres 9.2 chọn nhiều hàng cụ thể trong một truy vấn

Truy vấn con tương quan trong một truy vấn con

SELECT id, measurement
      ,CASE WHEN hi - measurement > measurement - lo
         THEN lo
         ELSE COALESCE(hi, lo)  -- cover all possible NULL values
       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
         ,(SELECT nominal_value
           FROM   lookup
           WHERE  nominal_value <= m.measurement
           ORDER  by nominal_value DESC
           LIMIT  1) AS lo   -- cover possible NULL values
   FROM   measurement m
   ) sub;

Các truy vấn con tương quan trong 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
         ,(SELECT nominal_value
           FROM   lookup
           WHERE  nominal_value <= m.measurement
           ORDER  by nominal_value DESC
           LIMIT  1) AS lo
   FROM   measurement m
   )
SELECT id, measurement
      ,CASE WHEN hi - measurement > measurement - lo
         THEN lo
         ELSE COALESCE(hi, lo)  -- cover all possible NULL values
       END AS nominal_value
FROM cte;

Các truy vấn con tương quan lồng nhau

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;

Câu đố SQL.


1
Oracle hỗ trợ các bên tham gia kể từ 12c (bao gồm từ khóa của Microsoft sử dụng apply) docs.oracle.com/database/121/QueryRF/ợi
a_horse_with_no_name

4

Không chắc chắn bạn sử dụng DBMS nào, nhưng khá nhiều chức năng cửa sổ hỗ trợ hiện nay:

SELECT id, measurement, lookupid
FROM (
    SELECT t1.id, t1.measurement, t2.id as lookupid
         , row_number() over (partition by t1.id
                              order by abs(t1.measurement - t2.nominal) desc
                             ) as rn
    FROM main t1
    CROSS JOIN lookup t2
) AS T
WHERE rn = 1;

1

Điều này là hoàn toàn có thể, mặc dù cách duy nhất tôi có thể nghĩ ra để giải quyết điều này khá kém hiệu quả và thực sự không có quy mô rất tốt.

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

Một giải pháp khác, có thể mở rộng / hoạt động tốt hơn, sử dụng các hàm cửa sổ được sắp xếp (có sẵn trên SQL Server 2012 và 2014 cũng như một vài nền tảng cơ sở dữ liệu khác, nhưng không phải 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);

Nếu truy vấn này vẫn cung cấp cho bạn các vấn đề về hiệu suất, hãy thử tạo bảng tra cứu tạm thời, điền vào đó các hàng từ "lkp", sau đó tham gia "t" và "lkp" như trên. Tôi có thể sẽ cung cấp cho bảng tạm thời một chỉ số như

CREATE UNIQUE INDEX IX_temptable ON #temptable (fromValue) INCLUDE (toValue, ID);

Giải pháp nào là tốt nhất cho bạn phụ thuộc chủ yếu vào lượng dữ liệu bạn có. Hãy thử các giải pháp khác nhau.


1

Tôi hy vọng tôi không thiếu thứ gì rõ ràng nhưng cách tôi sẽ truy vấn cái này để nó có thể mở rộng cho một bảng tra cứu rất lớn bằng cách quan sát những điều sau:

Có thể có được một DBMS có thẩm quyền (tôi biết PostgreSQL có thể làm điều này) để sử dụng một chỉ mục để

  • tìm kiếm giá trị tra cứu lớn nhất nhỏ hơn số đo của chúng tôi và
  • tìm kiếm giá trị tra cứu nhỏ nhất lớn hơn phép đo của chúng tôi.

Khi chúng ta có hai giá trị này, chúng ta có thể xác định giá trị nào trong hai giá trị này gần hơn.

Vì vậy, một cái gì đó như, chưa được kiểm tra:

with candidates as (
  select id, nominal_value
  from lookup_table
  where nominal_value >= measurement
  order by nominal_value
  limit 1
  union
  select id, nominal_value
  from lookup_table
  where nominal_value <= measurement
  order by nominal_value desc
  limit 1
)
select id
from candidates
order by abs(nominal_value - measurement)
limit 1;

nên nhanh như chớp - về cơ bản luôn là hai tra cứu chỉ mục và không có gì nữa.

Đã viết tất cả những điều này, có thể sử dụng chức năng cửa sổ để thực hiện chỉ một lần quét chỉ mục cho hai giá trị ứng cử viên ở hai bên của giá trị "đo lường", nhưng cách tiếp cận trên không yêu cầu chức năng cửa sổ và nên hoạt động trên bất kỳ DBMS có thể "đi" một chỉ mục thay vì thực hiện một order by.


Ở đây muộn rồi. Tôi hy vọng ở trên không phải là não.
Colin 't Hart

0

Tôi đã sử dụng câu trả lời của Lennart, chỉ cần thay đổi desc thành asc theo thứ tự. Nó hoạt động rất đẹp và không quá phức tạp.

SEL

ECT id, measurement, lookupid 
FROM (
    SELECT t1.id, t1.measurement, t2.id as lookupid
         , row_number() over (partition by t1.id
                              order by abs(t1.measurement - t2.nominal) asc
                             ) as rn
    FROM main t1
    CROSS JOIN lookup t2
) AS T
WHERE rn = 1
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.