【问题标题】:Oracle 12: Join on comma-separated list?Oracle 12:加入逗号分隔的列表?
【发布时间】:2018-01-19 00:38:18
【问题描述】:

假设我有一个无法修改的名为“公司”的视图:

+------------+--------------------+--------+--------+------------------------+
| company_id | company_name       | ceo    | cfo    | legal_contacts         |
+------------+--------------------+--------+--------+------------------------+
| 1          | johnson and son    | pid111 | pid333 | pid444, pid567, pid999 |
| 2          | Pepperville apples | pid777 |        | pid345                 |
| 3          | Cats LTD           | pid123 | pid321 |                        |
+------------+--------------------+--------+--------+------------------------+ 

还有一个叫做“联系人”的视图:

+-----------+-------+----------------------+
| person_id | name  | email                |
+-----------+-------+----------------------+
| pid111    | john  | john@gmail.com       |
| pid333    | steve | funkylover@mail.com  |
| pid444    | mary  | mar123@ymail.com     |
| pid999    | joe   | joe.bloggs@gmail.com |
| pid777    | louis |                      |
| pid345    | carol | carol@carolssite.com |
| pid321    | ellen | ellen.deg@gmail.com  |
+-----------+-------+----------------------+

我的最终目标是编写一个交叉引用人员 ID 并显示电子邮件和公司的查询,例如:

+---------+----------------+-----------------------------------------+
| company | ceo            | legal_contacts                          |
+---------+----------------+-----------------------------------------+
| 1       | john@gmail.com | mary123@ymail.com, joe.bloggs@gmail.com |
| 2       |                | carol@carossite.com                     |
| 3       |                |                                         |
+---------+----------------+-----------------------------------------+

有没有办法让我在不编写函数或进程的情况下在查询中加入或处理这个逗号分隔的标识符列表?

您可以假设“法律联系人”最多有 25 个标识符,始终采用相同格式,始终以逗号分隔

【问题讨论】:

  • 将值存储为 csv 是非常糟糕的数据库设计。规范化您的表格
  • “我有一个名为“公司”的视图,我无法修改”...
  • 我真的认为你应该看看视图的 ddl,companieslegal_contacts 的来源是 csv 列吗?如果不是这种情况,我会强烈建议查看其与contactscompany 表关联的外键(必须有)并重新考虑此要求的来源。
  • @PatrickBacon 是的,不幸的是,它的源代码是 CSV。它没有以标准化格式存储在数据库中的任何位置,我无法修改表或视图。真正的表格/视图实际上比我在问题中的简化示例要复杂得多。

标签: sql oracle


【解决方案1】:

这是一种糟糕的数据格式,但您似乎知道这一点。这是一种方法:

select c.company, c.ceo,
       listagg(co.email, ', ') within group (order by co.person_id) as emails
from companies c join
     contacts co
     on ', ' || legal_contacts || ', ' like '%, ' || co.person_id || ',%'
group by c.company, c.ceo ;

【讨论】:

  • 这是一种“快速而肮脏”的解决方案,如果查询是临时的,则效果很好。您不能忘记列表开头 (OR like co.person_id|| ',%')、列表末尾 (OR like '%, '|| co.person_id) 和列表中的单个值 (OR = co.person_id) 中的单词。我还会使用LEFT JOIN 来避免删除没有法律联系的公司。
【解决方案2】:

您可以使用正则表达式拆分companies.legal_contacts 的列表,然后将结果集与联系人加入以获取电子邮件地址(加入两次以获取ceo 邮件),然后使用listagg 重新连接电子邮件功能:

SELECT co.company_id, p1.email, LISTAGG(p2.email, ', ') WITHIN GROUP (ORDER BY p2.email)
  FROM (
        SELECT DISTINCT company_id, ceo, REGEXP_SUBSTR(legal_contacts, '[^, ]+', 1, LEVEL) AS single_contact   
          FROM COMPANIES
       CONNECT BY REGEXP_SUBSTR(legal_contacts, '[^, ]+', 1, LEVEL) IS NOT NULL) co
  LEFT JOIN CONTACTS p1 ON co.ceo = p1.person_id
  LEFT JOIN CONTACTS p2 ON co.single_contact = p2.person_id
 GROUP BY co.company_id, p1.email;

如果companies.legal_contacts 可以包含多个值,则出于性能原因,正则表达式的使用会有所改变,您必须使用 MULTISET。

【讨论】:

  • 完美!谢谢!通过复制您的示例,我也能够解决我的真正问题。我不熟悉您正在使用的这种“级别”构造(老实说,只是盲目地复制了您以使我的真正解决方案发挥作用。如果我想了解更多关于它的信息,我应该用谷歌搜索什么?
  • LEVEL 是一个伪列,可用于将更多记录相互关联。搜索“分层查询”(CONNECT BY 是关键字)。在这种特殊情况下,分层查询用于拆分字符串,但最常见的用途是在具有父节点和子节点的树结构中获取数据。
  • levelconnect by 查询中可用的伪列,表示层次结构中的级别。如果您使用connect by 只是因为它能够生成行,就像这里的情况一样,level(或rownum)将成为代表当前迭代的升序。
【解决方案3】:

您还可以编写一个函数来帮助您规范化数据(pehpaps 创建您自己的视图)

CREATE OR REPLACE TYPE VARCHAR_TABLE_TYPE AS TABLE OF VARCHAR2(1000);


CREATE OR REPLACE FUNCTION SplitString(LIST IN VARCHAR2, Separator IN VARCHAR2) RETURN VARCHAR_TABLE_TYPE IS
    OutTable VARCHAR_TABLE_TYPE;    
BEGIN    
    SELECT TRIM(REGEXP_SUBSTR(LIST, '[^'||Separator||']+', 1, LEVEL)) 
    BULK COLLECT INTO OutTable
    FROM dual                           
    CONNECT BY REGEXP_SUBSTR(LIST, '[^'||Separator||']+', 1, LEVEL) IS NOT NULL;
    RETURN OutTable;    
END SplitString;

那么查询将是这样的:

WITH com AS 
    (SELECT company_id, company_name, ceo, cfo, 
        imp_util.SplitString(legal_contacts, ', ') AS legal_contacts
    FROM companies)
SELECT company_id, ceo.email AS ceo, 
    LISTAGG(legal.email, ', ') WITHIN GROUP (ORDER BY legal.person_id) AS emails
FROM com
    LEFT OUTER JOIN contacts legal ON legal.person_id MEMBER OF legal_contacts
    LEFT OUTER JOIN contacts ceo ON ceo.person_id = ceo
GROUP BY company_id, ceo.email;

【讨论】:

    【解决方案4】:
    SQL> WITH companies (company_id, ceo, legal_contacts)
      2       AS (SELECT 1, 'pid111', 'pid444, pid567, pid999' FROM DUAL
      3           UNION
      4           SELECT 2, 'pid777', 'pid345' FROM DUAL
      5           UNION
      6           SELECT 3, 'pid123', NULL FROM DUAL),
      7       contacts (person_id, email)
      8       AS (SELECT 'pid111', 'john@gmail.com' FROM DUAL
      9           UNION
     10           SELECT 'pid333', 'funkylover@mail.com' FROM DUAL
     11           UNION
     12           SELECT 'pid444', 'mar123@ymail.com' FROM DUAL
     13           UNION
     14           SELECT 'pid999', 'joe.bloggs@gmail.com' FROM DUAL
     15           UNION
     16           SELECT 'pid777', NULL FROM DUAL
     17           UNION
     18           SELECT 'pid345', 'carol@carolssite.com' FROM DUAL
     19           UNION
     20           SELECT 'pid321', 'ellen.deg@gmail.com' FROM DUAL),
     21       sco
     22       AS (SELECT company_id,
     23                  ceo,
     24                  REGEXP_SUBSTR (REPLACE (legal_contacts, ' ', ''),
     25                                 '[^,]+',
     26                                 1,
     27                                 x.COLUMN_VALUE)
     28                     person_id
     29             FROM companies,
     30                  TABLE (
     31                     CAST (
     32                        MULTISET (
     33                               SELECT LEVEL
     34                                 FROM DUAL
     35                           CONNECT BY LEVEL <=
     36                                         REGEXP_COUNT (legal_contacts, ',') + 1) AS SYS.odcivarchar2list)) x)
     37    SELECT s.company_id,
     38           c1.email ceo,
     39           LISTAGG (c2.email, ', ') WITHIN GROUP (ORDER BY c2.email)
     40              legal_contacts
     41      FROM sco s
     42           LEFT OUTER JOIN contacts c1 ON c1.person_id = s.ceo
     43           LEFT OUTER JOIN contacts c2 ON c2.person_id = s.person_id
     44  GROUP BY s.company_id, c1.email;
    
    COMPANY_ID CEO                  LEGAL_CONTACTS
    ---------- -------------------- ----------------------------------------
             1 john@gmail.com       joe.bloggs@gmail.com, mar123@ymail.com
             2                      carol@carolssite.com
             3
    
    SQL>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-12-08
      • 1970-01-01
      • 1970-01-01
      • 2013-07-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多