DB2 将唯一的列值连接成一行,以逗号分隔

DB2 concatenate unique column values into one row, comma seperated

两个表:

Parts Table:
Part_Number   Load_Date   TQTY
m-123         19940102    32
1234Cf        20010809    3
wf9-2         20160421    14


Locations Table:
PartNo     Condition     Location   QTY
m-123      U             A02        2
1234Cf     S             A02        3
m-123      U             B01        1
wf9-2      S             A06        7
m-123      S             A18        29
wf9-2      U             F16        7

Result:
Part_Number   Load_Date  TQTY   U_LOC    UQTY    S_LOC   SQTY
m-123         19940102   32     A02,B01  3       A18     29
1234Cf        20010809   3                       A02     3
wf9-2         20160421   14     F16      7       A06     7 

我在使用我当前的 DB2 版本找到此问题的解决方案时遇到了麻烦。我不太确定如何找到版本,但它在 AS400 系统上是 运行,而且 DB2 的版本似乎与 OS 版本相关联。使用的盒子:操作系统:i5/OS版本:V5R4M0 (我使用这些建议 Here 尝试了一些命令来获取 DB2 版本,但其中 none 有效,就像大多数人所说的那样)。

关于将多行列数据连接成一行,我看到很多文章都说要使用 XMLAGGxmlserializeHere and, Here 但我收到一条错误消息,指出命令无法识别。

不知道从哪里开始,因为似乎有解决方案,但我无法使那些已经建议的功能起作用。


编辑: 使用公认的答案和解释,以及示例 HERE通过一个简单的例子来了解递归的基本概念,它是
HERE 使用“SELECT rownumber() over(partition by category)”语句确实有助于将它们整合在一起。一旦我理解了那句话。

我还学会了确保递归中使用的数据尽可能地缩小,然后再与额外的数据结合起来。这使得结果呈指数级增长。 <-- 这看起来很明显,但是当试图弄清楚所有这些时,它并不明显而且我的查询非常慢。一旦我更好地了解了实际发生的情况,就可以更轻松地进行调整以获得真正快速的结果。

不知道 UQTY、S_LOC、SQTY 的规则是什么,但这是您询问的专栏 ---

SELECT 
  P.Part_Number,
  P.Load_Date,
  P.TQTY,
  LISTAGG(L.Location, ', ') WITHIN GROUP (ORDER BY L.Location) AS U_LOC    
FROM "Parts Table" AS P
LEFT JOIN "Locations Table" AS L ON P.Part_Number = L.Part_Number
GROUP BY P.Part_Number, P.Load_Date, P.TQTY

这个比较复杂,所以我会展示我所有的工作:

Table 定义

create table parts
  (part_number      Varchar(64),
   load_date        Date,
   total_qty        Dec(5,0));
create table locations
  (part_number      Varchar(64),
   condition        Char(1),
   location         Char(3),
   qty              Dec(5,0));
insert into parts
  values ('m-123',  '1994-01-02', 32),
         ('1234Cf', '2001-08-09',  3),
         ('wf9-2',  '2016-04-21', 14);
insert into locations
  values ('m-123',  'U', 'A02', 2),
         ('1234Cf', 'S', 'A02', 3),
         ('m-123',  'U', 'B01', 1),
         ('wf9-2',  'S', 'A06', 7),
         ('m-123',  'S', 'A18', 29),
         ('wf9-2',  'U', 'F16', 7);

查询:

with -- CTE's
  -- This collects locations into a comma seperated list
  tmp (part_number, condition, location, csv, level) as (
    select part_number, condition, min(location), 
           cast(min(location) as varchar(128)), 1
      from locations
      group by part_number, condition
    union all
    select a.part_number, a.condition, b.location, 
           a.csv || ',' || b.location, a.level + 1
      from tmp a
        join locations b using (part_number, condition)
      where a.csv not like '%' || b.location || '%'
        and b.location > a.location),
  -- This chooses the correct csv list, and adds quantity for the condition
  tmp2 (part_number, condition, csv, qty) as (    
    select t.part_number, t.condition, t.csv, 
           (select sum(qty) qty
              from locations 
              where part_number = t.part_number
                and condition = t.condition)
      from tmp t
      where level = (select max(level)
              from tmp 
              where part_number = t.part_number
                and condition = t.condition))
-- This is the final select that combines the parts file with
-- the second stage CTE and arranges things horizontally by condition
select p.part_number, p.load_date, 
       (select sum(qty) 
          from locations 
          where part_number = p.part_number) as total_qty, 
       coalesce(u.csv, '') as u_loc,
       coalesce(u.qty, 0) as uqty,
       coalesce(s.csv, '') as s_loc, 
       coalesce(s.qty, 0) as sqty
  from parts p
    left outer join tmp2 u
      on u.part_number = p.part_number and u.condition = 'U'
    left outer join tmp2 s
      on s.part_number = p.part_number and s.condition = 'S'
  order by p.load_date;



EDIT 我不得不在此处添加一些额外的位以支持 part/condition 的两个以上位置,并且我在 CTE 中进行了列命名更一致。好的,让我稍微解释一下,这个问题有 3 个部分,2 个 CTE 和查询,您可以看到这三个部分由注释分隔。第一个 CTE 是递归 CTE。它的目的是生成逗号分隔的位置列表。您应该能够 运行 和 select 本身来查看它的作用。 tmp 是 table 名称,part_number、条件、csv 和级别是列名称。递归 CTE 需要一个 SELECT 来填充 CTE,需要一个 UNION ALL 和一个 SELECT 来填充下一个细节。在这种情况下,启动 SELECT 检索部件号、条件和该组合的第一个位置(按字母顺序)。级别设置为 1。如果您 运行 只是启动 select,您将得到:

part_number  condition  location  csv  level
-----------  ---------  --------  ---  -----
1234Cf          S       A01       A02    1
m-123           S       A18       A18    1
m-123           U       A02       A02    1
wf9-2           U       F16       F16    1
wf9-2           S       A06       A06    1

每个 part/condition 记一行。递归 CTE 的剩余部分将填充 csv 中的剩余位置,但它实际上会添加额外的记录,因此我们需要在此处和以后过滤结果。所以记录在添加时被处理。上面列出的第一行与位置文件连接 关于 part_number 和条件。请注意,在启动 select 中,我将第二个 min(location) 转换为 varchar(128)。这为 CSV 列留下了扩展空间。没有这个,它仍然会扩展,但不足以容纳超过 2 个位置。

递归 CTE 中的第二个 select 将逗号和下一个位置连接到 CSV 的末尾。执行此操作的特定位是 a.csv || ',' || b.location。它还会增加级别列。这有助于我们跟踪我们在查询中的位置。最终,具有最高 level 的行就是我们要使用的行。我们还有一种方法可以结束递归,还有一些过滤器可以减少添加到临时结果集中的行数。如果我们有 2 个位置,A02B02,未选中,我们将得到以下行:A02A02,A02A02,B02A02,A02,A02, A02,B02,A02, A02,A02,B02, A02,B02,B02, ... 无穷无尽。反重复过滤器 where a.csv not like '%' || b.location || '%' 足以让两个位置结束递归,并最小化行,如上,对于位置 A02B02,使用反重复过滤器,我们将获取行 A02A02,B02。请注意,返回第一个示例中具有重复位置的其他结果中的 none。添加第三个位置 C02 将产生以下行:A02A02,B02A02,C02A02,B02,C02A02,C02,B02.这里没有重复,但我们确实有多余的行,并且当您添加位置时,情况会变得更糟。这是我们需要一种方法来检测这些冗余行的地方。由于我们从最低位置编号开始,因此我们始终可以确保添加到 CSV 的位置大于之前添加的位置。为此,我们需要做的就是在结果集中包含一列,指示添加了哪一列(我们可以询问 CSV,但这更难)。这就是为什么我们需要 tmp 中的 location 列。然后我们可以写过滤器b.location > a.location。在上面的 3 个位置示例中,此过滤器阻止行 A02,C02,B02 只留下包含所有三个位置的一行。向位置文件添加三个以上的位置将导致 TMP 中的行数增加更多,但对于每个零件和条件,只有一行包含所有位置,它将包含所有位置升序。

第二个 CTE 做了两件事。首先,它过滤 TMP 以删除除包含给定 part/condition 的所有位置的行以外的所有行。其次,它累加每个 part/condition 的总数量。

执行过滤的位在where子句中:

where level = (select max(level)
        from tmp 
        where part_number = t.part_number
          and condition = t.condition))

非常简单。 part/condition累计总数量的位也是一个容易理解的子查询:

(select sum(qty) qty
   from locations 
   where part_number = t.part_number
     and condition = t.condition)

这个怪物查询的最后一部分是主要的 select。它将零件文件与第二个 CTE 的结果结合起来形成最终结果集:

select p.part_number, p.load_date, 
       (select sum(qty) from locations where part_number = p.part_number) as total_qty,
       coalesce(u.csv, '') as u_loc, coalesce(u.qty, 0) as uqty,
       coalesce(s.csv, '') as s_loc, coalesce(s.qty, 0) as sqty
  from parts p
    left outer join tmp2 u
      on u.part_number = p.part_number and u.condition = 'U'
    left outer join tmp2 s
      on s.part_number = p.part_number and s.condition = 'S'
  order by p.load_date

需要注意的是从 locations table 中检索总量的子查询。您可以在 parts 中使用 tqty 字段,但这可能与 locations table 中的实际数量不同步。此外还有两个左外连接 tmp2,一个用于条件 U,另一个用于条件 S。这些在结果行中构造 Location/Quantity 的水平数组。最后一件事是 coalesce 函数。这些给 null 值(当缺少外部连接的结果时)一个默认值。

编辑结束


最后的结果是:

part_number  load_date   tqty  u_loc    uqty  s_loc  sqty
-----------  ----------  ----  -------  ----  -----  ----
m-123        1994-01-02   32   A02,B01    3   A18     29
1234Cf       2001-08-09    3              0   A02      3
wf9-2        2016-04-21   14   F16        7   A06      7

注意 XMLAGGXMLSERIALIZE 在 DB2 for i v7.1 中可用,LISTAGG 在 DB2 for i v7 中可用。 2.截至 2017 年 8 月 9 日的最新版本是 v7.3。由于您使用的是 v5r4,很可能您不仅需要软件,还需要硬件升级才能获得最新版本。