【问题标题】:A better way to aggregate into a default value聚合成默认值的更好方法
【发布时间】:2020-07-28 23:49:21
【问题描述】:

对于此示例,我有三个表(个人、业务和 ind_to_business)。个人拥有关于人的信息。商业有关于商业的信息。 ind_to_business 包含有关哪些人与哪些业务相关联的信息。以下是他们的 DDL:

CREATE TABLE individual
(
 ID INTEGER PRIMARY KEY,
 NAME VARCHAR2(100) NOT NULL,
 ENTERPRISE_ID VARCHAR2(25) NOT NULL UNIQUE
);
CREATE TABLE business
(
 ID INTEGER PRIMARY KEY,
 NAME VARCHAR2(100) NOT NULL,
 ENTERPRISE_ID VARCHAR2(25) NOT NULL UNIQUE
);
CREATE TABLE ind_to_business
(
  ID INTEGER PRIMARY KEY,
  IND_ID REFERENCES individual(id),
  BUS_ID REFERENCES business(id),
  START_DT DATE NOT NULL,
  END_DT DATE
);

我正在寻找为每个人显示一行的最佳方式。如果它们与一项业务相关联,我想显示该业务的ENTERPRISE_ID。如果它们与多个业务相关联,我想显示默认值“多个”。它们将始终与企业相关联,因此不需要LEFT JOIN。它们也可以多次与企业相关联(离开和回来)。同一业务的多条记录将被聚合。

所以对于以下示例数据:

个人:

+----+------------+---------------+
| ID |    NAME    | ENTERPRISE_ID |
+----+------------+---------------+
|  1 | John Smith | 53a23B7       |
|  2 | Jane Doe   | 63f2a35       |
+----+------------+---------------+

业务:

+----+----------+---------------+
| ID |   NAME   | ENTERPRISE_ID |
+----+----------+---------------+
|  3 | ABC Corp | 2a34d9b       |
|  4 | XYZ Inc  | 34bf21e       |
+----+----------+---------------+

ind_to_business

+----+--------+--------+-------------+-------------+
| ID | IND_ID | BUS_ID |  START_DT   |   END_DT    |
+----+--------+--------+-------------+-------------+
|  5 |      1 |      3 | 01-JAN-2000 | 31-DEC-2002 |
|  6 |      1 |      3 | 01-JAN-2015 |             |
|  7 |      2 |      3 | 01-JAN-2000 |             |
|  8 |      2 |      4 | 01-MAR-2006 | 05-JUN-2010 |
|  9 |      2 |      4 | 15-DEC-2019 |             |
+----+--------+--------+-------------+-------------+

我希望得到以下输出:

+---------+------------+------------+
| IND_ID  |    NAME    | LINKED_BUS |
+---------+------------+------------+
| 53a23B7 | John Smith | 2a34d9b    |
| 63f2a35 | Jane Doe   | Multiple   |
+---------+------------+------------+

这是我当前的查询:

SELECT DISTINCT
       sub.ind_id,
       sub.name,
       DECODE(sub.bus_count, 1, sub.bus_id, 'Multiple') AS LINKED_BUS
FROM (SELECT i.enterprise_id AS IND_ID, 
             i.name,
             b.enterprise_id AS BUS_ID,
             COUNT(DISTINCT b.enterprise_id) OVER (PARTITION BY i.id) AS BUS_COUNT
      FROM individual i
      INNER JOIN ind_to_business i2b ON i.id = i2b.ind_id
      INNER JOIN business b ON i2b.bus_id = b.id) sub;

我的查询有效,但这是在大型数据集上运行并且需要很长时间才能运行。我想知道是否有人对如何改进这一点有任何想法,以免浪费太多处理(即需要对最终结果执行 DISTINCT 或在内联视图中执行 COUNT(DISTINCT) 仅使用该值在上面的DECODE 中)。

我还为这个问题创建了一个 DBFiddle。 (Link)

提前感谢您的任何意见。

【问题讨论】:

  • 无需使用 DISTINCT 两次,您可以在子查询本身中获取不同的数据,然后聚合行。有关详细信息,请参阅我的答案。

标签: sql oracle join group-by query-optimization


【解决方案1】:

无需使用DISTINCT 两次。您可以使用subquery factoring 并将内联视图放在WITH 子句中,并将数据集DISTINCT 放在子查询本身中。

WITH data AS
(
  SELECT distinct 
       i.enterprise_id AS IND_ID, 
       i.name,
       b.enterprise_id AS BUS_ID
  FROM individual i
  JOIN ind_to_business i2b ON i.id = i2b.ind_id
  JOIN business b ON i2b.bus_id = b.id
)
SELECT ind_id,
       name,
       case 
         when count(*) = 1 then MIN(bus_id)
         else 'Multiple' 
       end AS LINKED_BUS
FROM data
GROUP BY ind_id, name;

IND_ID     NAME       LINKED_BUS               
---------- ---------- -------------------------
53a23B7    John Smith 2a34d9b                  
63f2a35    Jane Doe   Multiple

【讨论】:

    【解决方案2】:

    您可以尝试使用相关子查询。这消除了对外部distinct 的需要:

    SELECT 
        i.enterprise_id ind_id,
        i.name,
        (
            SELECT DECODE(COUNT(DISTINCT b.enterprise_id), 1, MIN(bus_id), 'Multiple')
            FROM ind_to_business i2b
            INNER JOIN business b ON i2b.bus_id = b.id
            WHERE i2b.ind_id = i.id
        ) linked_bus
    FROM individual i
    

    【讨论】:

    • 我认为DECODE 表达式中必须是MIN(bus_id)MAX(bus_id)
    • @LalitKumarB:我们在 select 子句中有聚合表达式,所以这是隐式聚合。很像你这样做的时候:SELECT COUNT(*) FROM mytable.
    • @GMB 好的,现在您添加了MIN,当我发表评论时它不存在 :-) 是的,我在查询中做了同样的事情,只是在子查询中做了 DISTINCT并在顶部聚合。你有它相关,另一个好方法!
    • @GMB 我通常不喜欢相关子查询,因为我看到开发人员经常使用它们而不是了解数据模型。但我认为您可能正在做某事,我想这完全是关于正确使用该模式。
    【解决方案3】:

    您可以加入每个人的聚合 ind_to_business。一种方法:

    select i.id, i.name, coalesce(b.enterprise_id, 'Multiple')
    from individual i
    join
    (
      select
        ind_id,
        case when min(bus_id) = max(bus_id) then min(bus_id) else null end as bus_id
      from ind_to_business
      group by ind_id
    ) ib on ib.ind_id = i.id
    left join business b on b.id = ib.bus_id
    order by i.id;
    

    【讨论】:

    • 这种模式似乎在所有答案中效果最好。但是,我确实做了一项似乎可以提高性能的更改。我没有使用CASE 语句,而是使用DECODE。增加的幅度很小,但是当查询要运行多次时,每一点都很重要。
    • 我很高兴该查询对您很有效。至于CASEDECODE:这不太可能产生可衡量的差异。您看到的时间差异更有可能有其他原因(数据已经在缓存中等)。 CASE 是标准 SQL,DECODE 不是,但只需使用您认为更具可读性的内容即可。
    【解决方案4】:

    首先您应该进行子查询以获取所有需要的维度,然后使用CASE 语句进行所有最终聚合。

    select
        ind_id,
        name,
        case
            when count(*) > 1 then 'Multiple'
            else ind_id
        end as linked_bus
    from
    (
        select 
            distinct i.enterprise_id as ind_id, 
            i.name,
            b.enterprise_id as bus_id
        from individual i
    
        join ind_to_business i2b 
        on i.id = i2b.ind_id
    
        join business b 
        on i2b.bus_id = b.id
    ) vals
    
    group by
        ind_id,
        name
    order by
        ind_id
    

    【讨论】:

    • 这不是 OP 所要求的。
    猜你喜欢
    • 1970-01-01
    • 2014-04-03
    • 2018-06-22
    • 1970-01-01
    • 2016-04-29
    • 2015-06-30
    • 2011-12-22
    • 2015-02-18
    • 2020-08-27
    相关资源
    最近更新 更多