【问题标题】:how to do an sql join on the closest approximation如何在最接近的近似值上进行 sql 连接
【发布时间】:2022-01-17 22:44:09
【问题描述】:

我有一个数据表,其中相关列包含一个数字。我想将其加入到包含数字顺序列表的参考表中,并且我想将数据表中的每一行与参考表中最接近的数字(接近最小差异)匹配。

我可以做类似的事情

Select top 1 ref_number 
from reference 
where ref_number < data_number
order by ref_number desc

ref_number &gt; data_number 做类似的事情,找到最小的差异然后加入。这会起作用,但对于看似简单的操作来说,这将是一大堆代码,因为它需要为数据表的每一行遍历整个参考表两次,我认为它也会非常慢(参考表有大约 25000 个条目,数据表有大约 100 万个)。

所以问题:有没有一种更有效的方法可以将参考表中最接近的数字与数据表中的每个条目相匹配?如果两个表都按它们的数字排序,那么进行匹配应该会容易得多,但我看不到 SQL 代码来做到这一点。

这是我想要得到的简化示例。

参考表

ref_number  other reference columns 
1           ..
3
6
10

数据表

data_number 
1           
7
9.2

目标表

data_number ref_number other reference columns
1           1          ..
7           6
9.2         10

【问题讨论】:

  • 可以使用表表达式,也可以使用横向查询。这取决于具体的数据库。您使用的是哪个数据库?
  • 请添加几行样本数据和预期的结果。

标签: sql optimization query-optimization hana


【解决方案1】:

您可以使用RANK window function 来实现这种连接。请找到具有相关模式的 sn-p:

CREATE TABLE tab_ref (id integer, value double);
INSERT INTO tab_ref VALUES (1, 1.0);
INSERT INTO tab_ref VALUES (2, 3.0);
INSERT INTO tab_ref VALUES (3, 6.0);
INSERT INTO tab_ref VALUES (4, 10.0);

CREATE TABLE tab_data (id integer, value double);
INSERT INTO tab_data VALUES (1, 1.0);
INSERT INTO tab_data VALUES (2, 7.0);
INSERT INTO tab_data VALUES (3, 9.2);
    
    
SELECT *
FROM
(
    SELECT *, RANK() OVER (PARTITION BY b.id ORDER BY ABS(a.value - b.value)) AS rnk
    FROM tab_ref a, tab_data b
)
WHERE rnk = 1

【讨论】:

    【解决方案2】:

    您可以为每个参考值生成两个区间:一个区间低于当前值,另一个区间高于当前值。然后between 谓词的两个连接将为每个data_number 分配两个引用值:一个值高于当前data_number,另一个值低于当前值。然后决定哪一个更接近。

    代码如下(基于 Postgres fiddle,但大多数现代 DBMS 的语法相同)。

    insert into base_tab (data_number)
    values (0), (1), (4.5), (7), (9.2)
    
    insert into ref_tab (ref_number)
    values (1), (3), (6), (9)
    
    with fromto as (
      select
        ref_number as num
        /*
          Identify the first and the last row
          in the data set to manage values below
          the lowest (in the reference data set)
          and above the highest
        */
        , lag(0, 1, 1)
          over(order by ref_number asc)
          as first_row
        , lead(0, 1, 1)
          over(order by ref_number asc)
          as last_row
        /*
          And add value ranges (before and after current ref_num)
          to match data_number as closest above
          or closest below
        */
        , lead(ref_number, 1, 999999999999999.0)
          over(order by ref_number asc)
          as next_num
        , lag(ref_number, 1, -999999999999999.0)
          over(order by ref_number asc)
          as prev_num
      from ref_tab
    )
    
    select
      b.*
      , case
          /*
            When the data_number is below the lowest
            in the reference data set, then use the lowest
            value from reference data set
          */
          when up_.first_row = 1
          then up_.num
          /*
            When the data_number is above the highest
            in the reference data set, then use the highest
            value from reference data set
          */
          when down_.last_row = 1
          then down_.num
          /*
            Assign the closest value from the reference
            data set with minimal difference.
            Lower value has higher priority
          */
          when b.data_number - down_.num
            <= up_.num - b.data_number
          then down_.num
          when up_.num - b.data_number
            > b.data_number - down_.num
          then up_.num
        end as ref_num
    from base_tab as b
      /*Test if data_number is below current ref_num*/
      left join fromto as up_
        on
          b.data_number >= up_.prev_num
          and b.data_number < up_.num
      /*Test if data_number is above current ref_num*/
      left join fromto as down_
        on
          b.data_number >= down_.num
          and b.data_number < down_.next_num
    
    数据编号 | ref_num ----------: | ------: 0 | 1 1 | 1 4.5 | 3 7 | 6 9.2 | 9

    db小提琴here

    UPD:请注意,范围连接无论如何都需要循环,因为没有任何相等性,并且 DBMS 需要测试每一行(整个表或从排序数据集中的某个起点)。您可以尝试在程序扩展(HANA 的 SQL 脚本)中对已排序数据(排序合并连接)进行显式循环来实现一些改进,但我认为这是优化器的工作,应该在幕后隐式完成。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多