动态将行溢出到多个逗号分隔的列表中

时间:2018-11-07 13:02:23

标签: sql oracle

我有一个包含用户列表的表。

USER_TABLE

USER_ID   DEPT
-------   ----
USER1     HR
USER2     FINANCE
USER3     IT`

使用SQL语句,我需要获取用户列表作为以varchar2返回的定界字符串-这是我可以使用的唯一数据类型,例如我所使用的应用程序所指定的数据类型。

USER1, USER2, USER3

我的问题是列表将超过4000个字符。我有以下内容,它们将手动将用户一次分块到150个用户的列表中(基于user_id的最大大小为20个字符,加上安全地适合4000个字符的分隔符)。

SELECT  LISTAGG(USER_ID, ',') WITHIN GROUP (ORDER BY USER_ID) 
FROM (SELECT DISTINCT USER_ID  AS USER_ID, ROW_NUMBER() OVER (ORDER BY USER_ID) RN FROM TABLE_NAME)
WHERE RN <= 150 
START WITH RN = 1
CONNECT BY PRIOR RN = RN - 1
UNION
SELECT  LISTAGG(USER_ID, ',') WITHIN GROUP (ORDER BY USER_ID) 
FROM (SELECT DISTINCT USER_ID  AS USER_ID, ROW_NUMBER() OVER (ORDER BY USER_ID) RN FROM TABLE_NAME)
WHERE RN > 150 AND RN <= 300 
START WITH RN = 1
CONNECT BY PRIOR RN = RN - 1

这是手动操作,每150个用户块中的每个用户都需要一个额外的UNION,以后用户总数可能会增加。

是否可以这样做,以便动态生成user_id的定界字符串,使其适合4000个字符的多个块,并且不会将user_id拆分为多个字符串?

理想情况下,我希望输出看起来像这样:

USER1, USER2, USER3 (to) USER149
USER150, USER151, USER152 (to) USER300
USER301, USER302, USER303 (to) USER450`

该解决方案需要为SELECT语句,因为该模式是只读的,并且我们无法在数据库上创建任何对象。我们正在使用Oracle 11g。

3 个答案:

答案 0 :(得分:0)

您可以使用流水线函数来做到这一点:

create or replace function get_user_ids
    return sys.dbms_debug_vc2coll pipelined
is
    rv varchar2(4000) := null;
begin
    for r in ( select user_id, length(user_id) as lng
               from user_table
               order by user_id )
    loop
        if length(rv) + r.lng + 1 > 4000
        then
            rv := rtrim(rv, ','); -- remove trailing comma
            pipe row (rv);
            rv := null;
        end if;
        rv := rv || r.user_id || ',';
    end loop;
    return;
end;
/

您会这样称呼它:

select column_value as user_id_csv
from table(get_user_ids);

答案 1 :(得分:0)

使用以下功能的替代方法:

create or replace FUNCTION my_agg_user 
RETURN CLOB IS
  l_string CLOB;

  TYPE t_bulk_collect_test_tab IS TABLE OF VARCHAR2(4000);
  l_tab    t_bulk_collect_test_tab;

CURSOR user_list IS
SELECT  USER_ID
FROM USER_TABLE ;

BEGIN

  OPEN user_list;
  LOOP
   FETCH user_list
    BULK COLLECT INTO l_tab LIMIT 1000;
  FOR indx IN 1 .. l_tab.COUNT 
   LOOP
   l_string := l_string || l_tab(indx);
   l_string := l_string || ','; 
   END LOOP;
      EXIT WHEN user_list%NOTFOUND;
  END LOOP;
  CLOSE user_list;
  RETURN l_string;
END my_agg_user;

创建函数后,

select my_agg_user from dual;

答案 2 :(得分:0)

我相信下面的SQL在大多数情况下都可以使用。我已经对SQL进行了硬编码,以将字符串分解为150个用户ID条目,但其余都是动态的。

中间部分产生重复项,这需要额外的区别来消除,但是我不确定是否有更好的方法来做到这一点。

WITH POSITION AS ( SELECT  ((LEVEL-1) * 150 + 1) FROM_POS, LEVEL * 150 TO_POS
  FROM DUAL
CONNECT BY LEVEL <= (SELECT COUNT(DISTINCT( USER_ID)) / 150 FROM TABLE_NAME)
)
SELECT  DISTINCT
LISTAGG(USER_ID, ',') WITHIN GROUP (ORDER BY USER_ID) OVER (PARTITION BY FROM_POS, TO_POS)
FROM 
(SELECT DISTINCT USER_ID  AS USER_ID, ROW_NUMBER() OVER (ORDER BY USER_ID) RN FROM TABLE_NAME) V0 , 
POSITION 
WHERE V0.RN >= POSITION.FROM_POS 
AND V0.RN <=  POSITION.TO_POS