【问题标题】:Oracle:Comparing comma separated values from two columns of two different tables [duplicate]Oracle:比较来自两个不同表的两列的逗号分隔值[重复]
【发布时间】:2020-02-04 00:45:17
【问题描述】:

我想比较两个不同 Oracle 表的具有逗号分隔值的两列(差异表)的值。我想查找与所有值匹配的行(NAME1 所有值都应与 NAME2 值匹配)。

注意:逗号分隔值的顺序不同。

例子:

T1:
ID_T1             NAME1
===================================


1      ASCORBIC ACID, PARACETAMOL, POTASSIUM HYDROGEN CARBONATE
2      SODIUM HYDROGEN CARBONATE, SODIUM CARBONATE ANHYDROUS, CITRIC ACID
3      CAFFEINE, PARACETAMOL PH. EUR.
4      PSEUDOEPHEDRINE HYDROCHLORIDE,DEXCHLORPHENIRAMINE MALEATE
5      PARACETAMOL, DEXTROMETHORPHAN, PSEUDOEPHEDRINE, PYRILAMINE

T2:

ID_T2          NAME2
=================================

 4      POTASSIUM HYDROGEN CARBONATE, ASCORBIC ACID, PARACETAMOL
 5      SODIUM HYDROGEN CARBONATE, SODIUM CARBONATE ANHYDROUS
 6      PARACETAMOL PH. EUR.,CAFFEINE
 7      CODEINE PHOSPHATE, PARACETAMOL DC
 8      DEXCHLORPHENIRAMINE MALEATE, DEXTROMETHORPHAN HYDROBROMIDE 
10      DEXCHLORPHENIRAMINE MALEATE, PSEUDOEPHEDRINE HYDROCHLORIDE
11      PARACETAMOL, DEXTROMETHORPHAN, PSEUDOEPHEDRINE, PYRILAMINE1

MY RESULT 应该只显示基于两个表中的 ALL NAME 匹配的匹配行。

ID_T1    ID_T2    MATCHING NAME
    ==================================
    1            4    POTASSIUM HYDROGEN CARBONATE, ASCORBIC ACID, PARACETAMOL
    3            6    PARACETAMOL PH. EUR.,CAFFEINE
    4           10    PSEUDOEPHEDRINE HYDROCHLORIDE,DEXCHLORPHENIRAMINE MALEATE

现有成员@Goran 下面提供了部分解决方案,以下解决方案适用于除最后一行以外的所有值。下面的解决方案是找到 T1 的第 5 行与 T2 的第 11 行的匹配这是错误的,因为 T2 的最后一行值是“PYRILAMINE1”,它是 到 T1 最后一行值“PYRILAMINE”

部分解决方案:

SELECT
    T1.ID_T1,
    T2.ID_T2,
    T1.NAME1
FROM
    T1
    JOIN T2 ON TRIM('#' FROM TRANSLATE(T1.NAME1, T2.NAME2, '#')) IS NULL
               AND TRANSLATE(T1.NAME1, T2.NAME2, '#') IS NOT NULL
               AND REGEXP_COUNT(T1.NAME1, ',') = REGEXP_COUNT(T2.NAME2, ',');

【问题讨论】:

  • 所谓的“部分解决”是100%错误的。例如,它将匹配字符串'CALCIUM CARBONATE, SODIUM NITRATE' 和字符串'CALCIUM NITRATE, SODIUM CARBONATE'。你从哪里找到这个所谓的“解决方案”?
  • 澄清问题。我看到在大多数情况下,每个逗号后面都有一个空格,但也有一些例外。这是您的错字,还是您实际上在数据中观察到了这一点?如果您确实在数据中看到此类异常,“名称”是否也可能不是全大写?名字有可能有错别字吗?所有这些异常都可能导致查询中出现“不正确”的结果。这只是数据模型中的许多致命弱点之一(它违反了第一范式,这是一个好的数据模型的最基本要求)。
  • @mathguy: 1) 是的,大多数值都有空格。 2) 大多数名字都用大写字母 3) 这是我们拥有的数据模型,它不是我创建的。
  • 请不要发布重复的问题。如果您不喜欢对 your previous question 的回复,请对那里的答案发表评论和/或编辑您的问题以解释您还需要什么。
  • @APC2:对不起,下次我会按照你的指示去做。

标签: regex database oracle plsql


【解决方案1】:

这是一种方法,纯粹在 SQL 中,不利用 Oracle 12 及更高版本的任何功能(因为您没有告诉我们您的版本,所以我不想做出假设)。如果您发现自己多次需要这种比较,最好按照下面代码中显示的行编写“normalize_string”函数,并在需要时使用它。它会做什么(以及下面的代码做什么)是将逗号分隔的字符串拆分为标记,从每个标记的前面和结尾修剪空格,将每个标记转换为大写(允许输入小写字母),重复标记(允许相同的标记在输入中出现多次),然后重新创建逗号分隔的列表,标记按字母顺序出现。然后,这允许将结果字符串相互比较。

如果在您的能力范围内(无论是自己做还是影响老板去做),那就更好了,那就是改变数据模型。您应该有一个单独的小表用于各个成分,并带有一个主键(可能是一个数字,当然不是成分的名称)以供其他表参考。然后你需要一个单独的小药物表,也有一个主键(不是药物名称!)最后是一个多对多关系表,每个药物中的每种成分都有一行 - 两者都由它们各自的键标识(不是名字)。这将避免药物或成分名称中的拼写错误、小写而不是大写等,这将使您的所有代码更容易编写、测试和维护,并且速度更快。你能做到吗?

= = = = = = =

在下面的输出中,我显示出现在t1 中的“名称”(在您想要的输出中,您显示出现在t2 中的名称,但我假设您不在乎)。也许显示标准化版本会更好(norm1 而不是name1)。

select n1.id_t1, n2.id_t2, n1.name1
from
  ( select id_t1, name1, listagg(token, ', ') within group (order by token) norm1
    from   ( select  distinct id_t1, name1,
                     upper(trim(substr(str, instr(str, ',', 1, level) + 1,
                           instr(str, ',', 1, level + 1) 
                              - instr(str, ',', 1, level) - 1))) token
             from    (select id_t1, name1, ',' || name1 || ',' as str from t1)
             connect by  level <= length(str) - length(replace(str, ',')) - 1 
                     and prior id_t1 = id_t1
                     and prior sys_guid() is not null
           )
    group  by id_t1, name1
  ) n1
  inner join
  ( select id_t2, name2, listagg(token, ', ') within group (order by token) norm2
    from   ( select  distinct id_t2, name2, 
                     upper(trim(substr(str, instr(str, ',', 1, level) + 1,
                           instr(str, ',', 1, level + 1)
                              - instr(str, ',', 1, level) - 1))) token
             from    (select id_t2, name2, ',' || name2 || ',' as str from t2)
             connect by  level <= length(str) - length(replace(str, ',')) - 1 
                     and prior id_t2 = id_t2
                     and prior sys_guid() is not null
           )
    group  by id_t2, name2
  ) n2
  on n1.norm1 = n2. norm2
;

输出

 ID_T1  ID_T2 NAME1                                                             
------ ------ ------------------------------------------------------------------
     1      4 ASCORBIC ACID, PARACETAMOL, POTASSIUM HYDROGEN CARBONATE          
     3      6 CAFFEINE, PARACETAMOL PH. EUR.                                    
     4     10 PSEUDOEPHEDRINE HYDROCHLORIDE,DEXCHLORPHENIRAMINE MALEATE 

【讨论】:

  • 非常感谢您的工作,非常感谢。当我用数千条记录运行这个查询时,它需要很长时间。我们可以使用 REGEXP 来获得更好的性能吗?这些只是成分列表,不是药物。一种药物由多种成分组成。所以我可以有一张表,其中一个 DRUG_ID 我有多个 INGREDIENT_ID。这是你的建议吗?
  • @PramodKumar - 我想每个表中的每个 ID 都是药物的 ID,不是吗?是的,我建议的是一个多对多表,其中一个 DRUG_ID 可能出现在三到五个不同的行上,它具有的每种成分一次(由 INGREDIENT_ID 标识)。至于速度:REGEXP 会使事情变得更慢,而不是更快。标准字符串函数通常比正则表达式快很多。为了使查询更快,您可以利用 Oracle 12 或更高版本的功能,例如 LATERAL 子句。那么,您的 Oracle 版本是什么?运行SELECT * FROM V$VERSION 找出答案。
  • 是的,它们是 DRUG 的 ID。我明白你的意思,是的,计划是创建这样的表。我的版本是:Oracle Database 11g Enterprise Edition Release 11.2.0.4.0。我仍在努力为数千条记录运行您的查询。一旦我制作表格并在该表格上运行查询,我会告诉你一个。伟大的工作人,你为你的社区做着出色的工作。
猜你喜欢
  • 2020-02-02
  • 2012-10-29
  • 1970-01-01
  • 2020-02-01
  • 2010-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-28
相关资源
最近更新 更多