Oracle 11.2より前のバージョンでは、カスタム集計関数を使用して列を行に連結していました。 11.2 LISTAGG
関数を追加したので、代わりにそれを使用しようとしています。私の問題は、結果の重複を排除する必要があり、それを行うことができないようだということです。
例を示します。
CREATE TABLE ListAggTest AS (
SELECT rownum Num1, DECODE(rownum,1,'2',to_char(rownum)) Num2 FROM dual
CONNECT BY rownum<=6
);
SELECT * FROM ListAggTest;
NUM1 NUM2
---------- ---------------------
1 2
2 2 << Duplicate 2
3 3
4 4
5 5
6 6
私が見たいのはこれです:
NUM1 NUM2S
---------- --------------------
1 2-3-4-5-6
2 2-3-4-5-6
3 2-3-4-5-6
4 2-3-4-5-6
5 2-3-4-5-6
6 2-3-4-5-6
これは近いですが重複を排除しないlistagg
バージョンです。
SELECT Num1, listagg(Num2,'-') WITHIN GROUP (ORDER BY NULL) OVER () Num2s
FROM ListAggTest;
解決策はありますが、カスタム集計関数を使い続けるよりも悪いです。
正規表現と regexp_replace
listagg
との連結後に重複を削除するには:
SELECT Num1,
RTRIM(
REGEXP_REPLACE(
(listagg(Num2,'-') WITHIN GROUP (ORDER BY Num2) OVER ()),
'([^-]*)(-\1)+($|-)',
'\1\3'),
'-') Num2s
FROM ListAggTest;
Oracleのregexフレーバーが先読みグループまたは非キャプチャグループをサポートしている場合、これはより扱いやすくなる可能性があります ただし、サポートしていません 。
ただし、このソリューションでは、ソースを複数回スキャンすることは避けられます。
DBFiddle ここ
私が知る限り、現在利用可能な言語仕様では、これはあなたが望むものを達成するための最短の方法ですiflistagg
で実行する必要があります。
select distinct
a.Num1,
b.num2s
from listaggtest a cross join (
select listagg(num2d, '-') within group (order by num2d) num2s
from (
select distinct Num2 num2d from listaggtest
)
) b;
カスタム集計ソリューションよりも悪かったソリューションは何ですか?
これは受け入れられた回答を持つ古い投稿ですが、LAG()分析関数はこの場合にうまく機能し、注目に値すると思います。
これが提案されたコードです:
with nums as (
SELECT
num1,
num2,
decode( lag(num2) over (partition by null order by num2), --get last num2, if any
--if last num2 is same as this num2, then make it null
num2, null,
num2) newnum2
FROM ListAggTest
)
select
num1,
--listagg ignores NULL values, so duplicates are ignored
listagg( newnum2,'-') WITHIN GROUP (ORDER BY Num2) OVER () num2s
from nums;
以下の結果は、OPが望んでいるように見えます。
NUM1 NUM2S
1 2-3-4-5-6
2 2-3-4-5-6
3 2-3-4-5-6
4 2-3-4-5-6
5 2-3-4-5-6
6 2-3-4-5-6
これを行うには カスタム集計関数 を作成します。
Oracleデータベースは、レコードのセットに対して操作を実行するために、MAX、MIN、SUMなどのいくつかの事前定義された集約関数を提供します。これらの事前定義された集約関数は、スカラーデータでのみ使用できます。ただし、これらの関数の独自のカスタム実装を作成したり、完全に新しい集約関数を定義して、複雑なデータ(たとえば、オブジェクトタイプ、不透明タイプ、LOBを使用して保存されたマルチメディアデータ)で使用したりできます。
ユーザー定義の集計関数は、Oracleデータベースの組み込み集計関数と同様に、SQL DMLステートメントで使用されます。このような関数がサーバーに登録されると、データベースは、ネイティブルーチンではなく、指定した集計ルーチンを呼び出すだけです。
ユーザー定義の集計は、スカラーデータでも使用できます。たとえば、金融または科学アプリケーションに関連する複雑な統計データを処理するための特別な集計関数を実装することは価値があるかもしれません。
ユーザー定義の集計は、Extensibility Frameworkの機能です。 ODCIAggregateインターフェース・ルーチンを使用してそれらを実装します。
私の意見では、既存のカスタム集計関数を使用するほどうまくいかない問題に対する私の解決策がここにあります。
SELECT Num1, listagg(Num2,'-') WITHIN GROUP (ORDER BY NULL) OVER () Num2s FROM (
SELECT Num1, DECODE(ROW_NUMBER() OVER (PARTITION BY Num2 ORDER BY NULL),
1,Num2,NULL) Num2 FROM ListAggTest
);
また、collectステートメントを使用して、コレクションを文字列に変換するカスタムpl/sql関数を作成することもできます。
CREATE TYPE varchar2_ntt AS TABLE OF VARCHAR2(4000);
CREATE TYPE varchar2_ntt AS TABLE OF VARCHAR2(4000);
select cast(collect(distinct num2 order by num2) as varchar2_ntt)
from listaggtest
distinct
句でcollect
およびorder by
を使用できますが、distinct
を組み合わせた場合、11.2.0.2以降では機能しません。
回避策は副選択である可能性があります。
select collect(num2 order by num2)
from
(
select distinct num2
from listaggtest
)
代わりにWMSYS.WM_Concatを使用してください。
SELECT Num1, Replace(Wm_Concat(DISTINCT Num2) OVER (), ',', '-')
FROM ListAggTest;
注:この関数は文書化されておらず、サポートされていません。 https://forums.Oracle.com/forums/message.jspa?messageID=4372641#4372641 を参照してください。
ListAggに遭遇する前にこのソリューションを作成しましたが、この重複する値の問題などの状況がまだあるため、このツールは役立ちます。以下のバージョンには、結果を制御するための4つの引数があります。
説明CLOBlistは、コンストラクターCLOBlistParamをパラメーターとして受け取ります。 CLOBlistParamには4つの引数があります
string VARCHAR2(4000) - The variable to be aggregated
delimiter VARCHAR2(100) - The delimiting string
initiator VARCHAR2(100) - An initial string added before the first value only.
no_dup VARCHAR2(1) - A flag. Duplicates are suppressed if this is Y
使用例
--vertical list of comma separated values, no duplicates.
SELECT CLOBlist(CLOBlistParam(column_name,chr(10)||',','','Y')) FROM user_tab_columns
--simple csv
SELECT CLOBlist(CLOBlistParam(table_name,',','','N')) FROM user_tables
Gistへのリンクは以下のとおりです。
https://Gist.github.com/peter-genesys/d203bfb3d88d5a5664a86ea6ee34eeca]1
-- Program : CLOBlist
-- Name : CLOB list
-- Author : Peter Burgess
-- Purpose : CLOB list aggregation function for SQL
-- RETURNS CLOB - to allow for more than 4000 chars to be returned by SQL
-- NEW type CLOBlistParam - allows for definition of the delimiter, and initiator of sequence
------------------------------------------------------------------
--This is an aggregating function for use in SQL.
--It takes the argument and creates a comma delimited list of each instance.
WHENEVER SQLERROR CONTINUE
DROP TYPE CLOBlistImpl;
WHENEVER SQLERROR EXIT FAILURE ROLLBACK
create or replace type CLOBlistParam as object(
string VARCHAR2(4000)
,delimiter VARCHAR2(100)
,initiator VARCHAR2(100)
,no_dup VARCHAR2(1) )
/
show error
--Creating CLOBlist()
--Implement the type CLOBlistImpl to contain the ODCIAggregate routines.
create or replace type CLOBlistImpl as object
(
g_list CLOB, -- progressive concatenation
static function ODCIAggregateInitialize(sctx IN OUT CLOBlistImpl)
return number,
member function ODCIAggregateIterate(self IN OUT CLOBlistImpl
, value IN CLOBlistParam) return number,
member function ODCIAggregateTerminate(self IN CLOBlistImpl
, returnValue OUT CLOB
, flags IN number) return number,
member function ODCIAggregateMerge(self IN OUT CLOBlistImpl
, ctx2 IN CLOBlistImpl) return number
)
/
show error
--Implement the type body for CLOBlistImpl.
create or replace type body CLOBlistImpl is
static function ODCIAggregateInitialize(sctx IN OUT CLOBlistImpl)
return number is
begin
sctx := CLOBlistImpl(TO_CHAR(NULL));
return ODCIConst.Success;
end;
member function ODCIAggregateIterate(self IN OUT CLOBlistImpl
, value IN CLOBlistParam) return number is
begin
IF self.g_list IS NULL THEN
self.g_list := value.initiator||value.string;
ELSIF value.no_dup = 'Y' AND
value.delimiter||self.g_list||value.delimiter LIKE '%'||value.delimiter||value.string||value.delimiter||'%'
THEN
--Do not include duplicate value
NULL;
ELSE
self.g_list := self.g_list||value.delimiter||value.string;
END IF;
return ODCIConst.Success;
end;
member function ODCIAggregateTerminate(self IN CLOBlistImpl
, returnValue OUT CLOB
, flags IN number) return number is
begin
returnValue := self.g_list;
return ODCIConst.Success;
end;
member function ODCIAggregateMerge(self IN OUT CLOBlistImpl
, ctx2 IN CLOBlistImpl) return number is
begin
self.g_list := LTRIM( self.g_list||','||ctx2.g_list,',');
return ODCIConst.Success;
end;
end;
/
show error
--Using CLOBlist() to create a vertical list of comma separated values
-- SELECT CLOBlist(CLOBlistParam(product_code,chr(10)||',','','Y'))
-- FROM account
--DROP FUNCTION CLOBlist
--/
Prompt Create the user-defined aggregate.
CREATE OR REPLACE FUNCTION CLOBlist (input CLOBlistParam) RETURN CLOB
PARALLEL_ENABLE AGGREGATE USING CLOBlistImpl;
/
show error
私の考えは、次のようなストアド関数を実装することです。
CREATE TYPE LISTAGG_DISTINCT_PARAMS AS OBJECT (ELEMENTO VARCHAR2(2000), SEPARATORE VARCHAR2(10));
CREATE TYPE T_LISTA_ELEMENTI AS TABLE OF VARCHAR2(2000);
CREATE TYPE T_LISTAGG_DISTINCT AS OBJECT (
LISTA_ELEMENTI T_LISTA_ELEMENTI,
SEPARATORE VARCHAR2(10),
STATIC FUNCTION ODCIAGGREGATEINITIALIZE(SCTX IN OUT T_LISTAGG_DISTINCT)
RETURN NUMBER,
MEMBER FUNCTION ODCIAGGREGATEITERATE (SELF IN OUT T_LISTAGG_DISTINCT,
VALUE IN LISTAGG_DISTINCT_PARAMS )
RETURN NUMBER,
MEMBER FUNCTION ODCIAGGREGATETERMINATE (SELF IN T_LISTAGG_DISTINCT,
RETURN_VALUE OUT VARCHAR2,
FLAGS IN NUMBER )
RETURN NUMBER,
MEMBER FUNCTION ODCIAGGREGATEMERGE (SELF IN OUT T_LISTAGG_DISTINCT,
CTX2 IN T_LISTAGG_DISTINCT )
RETURN NUMBER
);
CREATE OR REPLACE TYPE BODY T_LISTAGG_DISTINCT IS
STATIC FUNCTION ODCIAGGREGATEINITIALIZE(SCTX IN OUT T_LISTAGG_DISTINCT) RETURN NUMBER IS
BEGIN
SCTX := T_LISTAGG_DISTINCT(T_LISTA_ELEMENTI() , ',');
RETURN ODCICONST.SUCCESS;
END;
MEMBER FUNCTION ODCIAGGREGATEITERATE(SELF IN OUT T_LISTAGG_DISTINCT, VALUE IN LISTAGG_DISTINCT_PARAMS) RETURN NUMBER IS
BEGIN
IF VALUE.ELEMENTO IS NOT NULL THEN
SELF.LISTA_ELEMENTI.EXTEND;
SELF.LISTA_ELEMENTI(SELF.LISTA_ELEMENTI.LAST) := TO_CHAR(VALUE.ELEMENTO);
SELF.LISTA_ELEMENTI:= SELF.LISTA_ELEMENTI MULTISET UNION DISTINCT SELF.LISTA_ELEMENTI;
SELF.SEPARATORE := VALUE.SEPARATORE;
END IF;
RETURN ODCICONST.SUCCESS;
END;
MEMBER FUNCTION ODCIAGGREGATETERMINATE(SELF IN T_LISTAGG_DISTINCT, RETURN_VALUE OUT VARCHAR2, FLAGS IN NUMBER) RETURN NUMBER IS
STRINGA_OUTPUT CLOB:='';
LISTA_OUTPUT T_LISTA_ELEMENTI;
TERMINATORE VARCHAR2(3):='...';
LUNGHEZZA_MAX NUMBER:=4000;
BEGIN
IF SELF.LISTA_ELEMENTI.EXISTS(1) THEN -- se esiste almeno un elemento nella lista
-- inizializza una nuova lista di appoggio
LISTA_OUTPUT := T_LISTA_ELEMENTI();
-- riversamento dei soli elementi in DISTINCT
LISTA_OUTPUT := SELF.LISTA_ELEMENTI MULTISET UNION DISTINCT SELF.LISTA_ELEMENTI;
-- ordinamento degli elementi
SELECT CAST(MULTISET(SELECT * FROM TABLE(LISTA_OUTPUT) ORDER BY 1 ) AS T_LISTA_ELEMENTI ) INTO LISTA_OUTPUT FROM DUAL;
-- concatenazione in una stringa
FOR I IN LISTA_OUTPUT.FIRST .. LISTA_OUTPUT.LAST - 1
LOOP
STRINGA_OUTPUT := STRINGA_OUTPUT || LISTA_OUTPUT(I) || SELF.SEPARATORE;
END LOOP;
STRINGA_OUTPUT := STRINGA_OUTPUT || LISTA_OUTPUT(LISTA_OUTPUT.LAST);
-- se la stringa supera la dimensione massima impostata, tronca e termina con un terminatore
IF LENGTH(STRINGA_OUTPUT) > LUNGHEZZA_MAX THEN
RETURN_VALUE := SUBSTR(STRINGA_OUTPUT, 0, LUNGHEZZA_MAX - LENGTH(TERMINATORE)) || TERMINATORE;
ELSE
RETURN_VALUE:=STRINGA_OUTPUT;
END IF;
ELSE -- se non esiste nessun elemento, restituisci NULL
RETURN_VALUE := NULL;
END IF;
RETURN ODCICONST.SUCCESS;
END;
MEMBER FUNCTION ODCIAGGREGATEMERGE(SELF IN OUT T_LISTAGG_DISTINCT, CTX2 IN T_LISTAGG_DISTINCT) RETURN NUMBER IS
BEGIN
RETURN ODCICONST.SUCCESS;
END;
END; -- fine corpo
CREATE
FUNCTION LISTAGG_DISTINCT (INPUT LISTAGG_DISTINCT_PARAMS) RETURN VARCHAR2
PARALLEL_ENABLE AGGREGATE USING T_LISTAGG_DISTINCT;
// Example
SELECT LISTAGG_DISTINCT(LISTAGG_DISTINCT_PARAMS(OWNER, ', ')) AS LISTA_OWNER
FROM SYS.ALL_OBJECTS;
申し訳ありませんが、場合によっては(非常に大きなセットの場合)、Oracleが次のエラーを返す可能性があります。
Object or Collection value was too large. The size of the value
might have exceeded 30k in a SORT context, or the size might be
too big for available memory.
しかし、これは良い出発点だと思います;)
元の投稿の後であることはわかっていますが、これは、グーグルの後で同じ問題の答えを見つけた最初の場所であり、ここに上陸した他の誰かが過度に複雑なクエリに依存しない簡潔な答えを見つけて喜んでいるのではないかと考えましたまたは正規表現。
これにより、望ましい結果が得られます。
with nums as (
select distinct num2 distinct_nums
from listaggtest
order by num2
) select num1,
(select listagg(distinct_nums, '-') within group (order by 1) from nums) nums2list
from listaggtest;
これを試してください:
select num1,listagg(Num2,'-') WITHIN GROUP (ORDER BY NULL) Num2s
from (
select distinct num1
,b.num2
from listaggtest a
,(
select num2
from listaggtest
) b
order by 1,2
)
group by num1
他の可能な解決策の問題は、列1と列2の結果の間に相関関係がないことです。これを回避するために、内部クエリはこの相関関係を作成し、その結果セットから重複を削除します。 listaggを実行すると、結果セットは既にクリーンです。問題は、データを使用可能な形式で取得することと関係がありました。