5

私は次のような 2 つのエンティティを使用しています:Itemと。Attribute

Item
----
itemId

Attribute
---------
attributeId
name

関連付けテーブルで指定されているItemhas :Attributes

ItemAttribute
--------------
itemId
attributeId

このデータがクライアントに到達するとItem、 ごとに行が表示され、各行には のリストがAttribute名前別に表示されます。例えば:

Item  Attributes
----  ----------
1     A, B, C
2     A, C
3     A, B

ユーザーはAttributes列を並べ替えるオプションを使用できるため、次のようにデータを並べ替える機能が必要です。

Item  Attributes
----  ----------
3     A, B
1     A, B, C
2     A, C

現在、1 行につき 1 行のデータを取得していますItemAttribute。基本的:

  SELECT Item.itemId,
         Attribute.name
    FROM Item
    JOIN ItemAttribute
      ON ItemAttribute.itemId = Item.itemId
    JOIN Attribute
      ON Attribute.attributeId = ItemAttribute.attributeId
ORDER BY Item.itemId;

次のような結果が生成されます。

itemId  name
------  ----
1       A
1       B
1       C
2       A
2       C
3       A
3       B

実際のORDER BY句は、ユーザー入力に基づいています。通常は 1 つの列であるため、順序付けは簡単です。結果セットを処理するアプリ側のループは、Attribute名前をコンマ区切りのリストに結合してクライアントに表示します。しかし、ユーザーがそのリストを並べ替えるように要求した場合、Oracle に結果を並べ替えてもらい、上記の例を使用すると、次のようになります。

itemId  name
------  ----
3       A
3       B
1       A
1       B
1       C
2       A
2       C

Oracle のLISTAGG関数を使用して、並べ替えの前に属性リストを生成できます。ただしAttribute.name、非常に長い文字列になる可能性があり、組み合わせたリストが 4000 文字を超える可能性があり、クエリが失敗する原因となります。

Oracle SQL (11gR2) を使用して、この方法でデータをソートするクリーンで効率的な方法はありますか?

4

3 に答える 3

4

ここには実際には 2 つの質問があります。

1) 4000 文字を超えるデータを集計する方法

これほど多くのデータを集約して 1 つの列に表示するのは理にかなっていますか?

CLOBとにかく、たとえばのように、4000文字を超える文字を表示するには、ある種の大きな構造が必要になります。Tom Kyte のスレッドの1 つで説明されている一般的なガイドラインに従って、独自の集約メソッドを作成できます(最終出力が CLOB になるように変更する必要があることは明らかです)。

ネストされたテーブルとカスタム関数 (10g で動作) を使用した簡単な方法を示します。

SQL> CREATE TYPE tab_varchar2 AS TABLE OF VARCHAR2(4000);
  2  /

Type created.

SQL> CREATE OR REPLACE FUNCTION concat_array(p tab_varchar2) RETURN CLOB IS
  2     l_result CLOB;
  3  BEGIN
  4     FOR cc IN (SELECT column_value FROM TABLE(p) ORDER BY column_value) LOOP
  5        l_result := l_result ||' '|| cc.column_value;
  6     END LOOP;
  7     return l_result;
  8  END;
  9  /

Function created.

SQL> SELECT item,
  2         concat_array(CAST (collect(attribute) AS tab_varchar2)) attributes
  3    FROM data
  4   GROUP BY item;

ITEM ATTRIBUTES
1    a b c
2    a c
3    a b

2) 大量のデータをソートする方法

残念ながら、Oracle では任意に大きな列でソートすることはできません。ソート キーの型と長さに関して既知の制限があります。

  • CLOBでソートしようとすると、次のようになりますORA-00932: inconsistent datatypes: expected - got CLOB.
  • データベースのブロックサイズより大きいキーでソートしようとすると (たとえば、大きなデータを多数の VARCHAR2 に分割する場合)、次のようになりORA-06502ます: PL/SQL: 数値または値のエラー: 文字列バッファが小さすぎます

属性列の最初の 4000 バイトでソートすることをお勧めします。

SQL> SELECT * FROM (
  2     SELECT item,
  3            concat_array(CAST (collect(attribute) AS tab_varchar2)) attributes
  4       FROM data
  5      GROUP BY item
  6  ) order by dbms_lob.substr(attributes, 4000, 1);

ITEM ATTRIBUTES
3    a b
1    a b c
2    a c
于 2012-07-18T14:12:40.097 に答える
1

Vincent が既に述べたように、ソート キーは制限されています (CLOB なし、最大ブロック サイズ)。

XMLAgg を使用したカスタム関数と型を必要とせずに、10g 以降ですぐに使用できる、少し異なるソリューションを提供できます。

with ItemAttribute  as (
 select 'name'||level name
        ,mod(level,3) itemid
   from dual
  connect by level < 2000
)
,ItemAttributeGrouped as (
 select xmlagg(xmlparse(content name||' ' wellformed) order by name).getclobval() attributes
       ,itemid
   from ItemAttribute
  group by itemid
 )
select itemid
      ,attributes
      ,dbms_lob.substr(attributes,4000,1) sortkey
  from ItemAttributeGrouped
order by dbms_lob.substr(attributes,4000,1)
;  
于 2012-07-18T15:51:19.853 に答える
0

クリーンは主観的であり、効率をチェックする必要があります(ただし、テーブルにヒットするのは1回だけなので、おそらくこれ以上悪化することはありません)が、属性の数に有限の上限がある場合は、アイテムが-またはリードで注文するために考慮しなければならない数-それなら、これを行うために複数のlead呼び出しを使用できます:

SELECT itemId, name
  FROM (
    SELECT itemId, name, min(dr) over (partition by itemId) as dr
      FROM (
        SELECT itemId, name,
            dense_rank() over (order by name, name1, name2, name3, name4) as dr
          FROM (
            SELECT Item.itemId,
                     Attribute.name,
                     LEAD(Attribute.name, 1)
                         OVER (PARTITION BY Item.itemId
                             ORDER BY Attribute.name) AS name1,
                     LEAD(Attribute.name, 2)
                         OVER (PARTITION BY Item.itemId
                             ORDER BY Attribute.name) AS name2,
                     LEAD(Attribute.name, 3)
                         OVER (PARTITION BY Item.itemId
                             ORDER BY Attribute.name) AS name3,
                     LEAD(Attribute.name, 4)
                         OVER (PARTITION BY Item.itemId
                             ORDER BY Attribute.name) AS name4
                FROM Item
                JOIN ItemAttribute
                  ON ItemAttribute.itemId = Item.itemId
                JOIN Attribute
                  ON Attribute.attributeId = ItemAttribute.attributeId
               )
          )
      )
ORDER BY dr, name;

したがって、内部クエリは、気になる2つの値を取得し、4つのlead呼び出しを使用します(例として、これは最初の5つの属性名の最大値に基づいて並べ替えることができますが、もちろんさらに追加することで拡張できます!)各アイテムが他に何を持っているかを把握するため。あなたのデータでこれは与えます:

    ITEMID NAME       NAME1      NAME2      NAME3      NAME4
---------- ---------- ---------- ---------- ---------- ----------
         1 A          B          C
         1 B          C
         1 C
         2 A          C
         2 C
         3 A          B
         3 B

次のクエリアウトは、これらの5つの順序付けられた属性名を実行し、それぞれにdense_rankランクを割り当て、次のようにします。itemIDname

    ITEMID NAME               DR
---------- ---------- ----------
         1 A                   1
         1 B                   4
         1 C                   6
         2 A                   3
         2 C                   6
         3 A                   2
         3 B                   5

次のクエリは、の分析バージョンを使用して、drそれぞれの計算値の最小値を見つけるため、それぞれが、を取得し、を取得し、を取得します。(選択することでこれらの2つのレベルを組み合わせることができますが、それは(さらに)明確ではありません)。itemIdminitemID=1min(dr) = 1itemId=23itemId=32min(dense_rank(...))

最後の外部クエリは、各アイテムの最小ランクを使用して実際の順序付けを行い、次のようにします。

    ITEMID NAME
---------- ----------
         1 A
         1 B
         1 C
         3 A
         3 B
         2 A
         2 C
于 2012-07-18T14:48:29.663 に答える