【问题标题】:Oracle 11g: Need to get the longest matching string that starts with another stringOracle 11g:需要获取以另一个字符串开头的最长匹配字符串
【发布时间】:2016-04-20 02:49:55
【问题描述】:

我有两个数据表:

CREATE TABLE tbl1 (
  ID NUMBER,
  LABEL VARCHAR2(50)
);
CREATE TABLE tbl2 (
  ID NUMBER,
  SRC VARCHAR2(400)
);

INSERT INTO tbl1 VALUES (1, 'foobar');
INSERT INTO tbl1 VALUES (2, 'foo');
INSERT INTO tbl1 VALUES (3, 'bar');

INSERT INTO tbl2 (SRC) VALUES ('foo: yeah');
INSERT INTO tbl2 (SRC) VALUES ('foobar: nope');

我正在尝试通过匹配 tbl1 中最长的匹配字符串来更新 tbl2 的 ID 字段。我的意图是“foo: yes”条目的 ID 应为 2,而“foobar: nope”条目的 ID 应为 1:

UPDATE tbl2 t2
SET t2.ID = (SELECT t1.ID
             FROM tbl1 t1
             WHERE t2.SRC LIKE t1.LABEL || '%');

这样做会导致错误:“单行子查询返回多行”。这对我来说很有意义,所以我尝试了这个:

UPDATE tbl2 t2
SET t2.ID = (SELECT t1.ID
             FROM tbl1 t1
             WHERE t2.SRC LIKE t1.LABEL || '%'
             AND ROWNUM=1
             ORDER BY LENGTH(t1.LABEL) DESC);

但现在我收到此错误:“缺少右括号”。 在这种情况下我不明白这个错误,括号格式正确。

【问题讨论】:

    标签: oracle oracle11g


    【解决方案1】:

    “缺少右括号”是因为子查询中的order by 子句;它在那里无效。

    您还将获得与您订购的同一级别的ROWNUM。这不会完全符合您的预期。排序最后发生,因此您实际上会得到一行 - 任何行,哪一行是不确定的 - 然后您将按长度排序该单行,这不会做任何事情。

    您需要另一个级别的子查询来获取您感兴趣的行:

    UPDATE tbl2 t2
    SET t2.ID = (
      SELECT ID FROM (
        SELECT t1.ID
        FROM tbl1 t1
        WHERE t2.SRC LIKE t1.LABEL || '%'
        ORDER BY LENGTH(t1.LABEL) DESC
      )
      WHERE ROWNUM=1
    );
    

    ...但不幸的是,这也不起作用,并且会出现另一个错误:ORA-00904: "T2"."SRC": invalid identifier

    那是因为你不能引用表或别名两个级别的子查询,至少在 12c 之前(可能;有一个 recent question 似乎表明它现在可以工作,还有 this slightly older one)。

    您可以使用the FIRST functionKEEP DENSE_RANK 来实现:

    UPDATE tbl2 t2
    SET t2.ID = (
      SELECT MIN(t1.ID) KEEP (DENSE_RANK FIRST ORDER BY LENGTH(t1.LABEL) DESC)
      FROM tbl1 t1
      WHERE t2.SRC LIKE t1.LABEL || '%'
    );
    
    2 rows updated.
    
    select * from tbl2;
    
            ID SRC                
    ---------- --------------------
             2 foo: yeah           
             1 foobar: nope        
    

    您也可以使用KEEP (DENSE_RANK LAST ORDER BY LENGTH(t1.LABEL)),即LAST 代替FIRST,并且不使用DESC,如果没有空值,这在逻辑上是相同的。

    【讨论】:

    • 效果很好,让我可以使用一些我以前从未使用过的东西(KEEP、DENSE_RANK 和 FIRST)。谢谢!
    • 使用 MIN(t1.ID)(或 MAX(t1.ID))似乎只是因为结果必须汇总,对吧? ID应该只有一个值,并不是真的选择较小(或最大)的ID值,对吧?
    【解决方案2】:
    update tbl2 set tbl2.id =
     (
        with a as (select src, label, 
                          row_number() over (partition by src order by length(label) desc) rn 
                   from tbl2 join tbl1 on src like label || '%')
        select id from tbl1 join a using (label) where tbl2.src = a.src and rn = 1
     )
    

    【讨论】:

    • 这也很好用,但我把答案交给了 Alex Poole,因为他给出了更详细的解释。谢谢!
    • 哦,不用担心 - 很乐意为您提供帮助。如果 SO 完全取消了这些“声誉点”,我一点也不在乎——它们只是鼓励仓促的、没有经过深思熟虑的答案。在数学方面更是如此。祝你好运!
    【解决方案3】:

    您可以使用MAX( ... ) KEEP ( DENSE_RANK [FIRST|LAST] ORDER BY ... ) 获取ORDER BY 中第一个(或最后一个)值的最大值。

    UPDATE tbl2 t2
    SET t2.id = ( SELECT MAX( id ) KEEP ( DENSE_RANK LAST
                                          ORDER BY LENGTH( LABEL ) )
                  FROM tbl1
                  WHERE t2.src LIKE label || '%' );
    

    结果

    SELECT * FROM tbl2;
    
            ID SRC 
    ---------- -------------
             2 foo: yeah
             1 foobar: nope
    

    【讨论】:

    • 是的,这和亚历克斯的回答一样。亚历克斯第一个回答,并获得了荣誉。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-01
    • 2014-01-15
    相关资源
    最近更新 更多