我正在使用两个实体:Item
和Attribute
,它们类似于以下内容:
Item
----
itemId
Attribute
---------
attributeId
name
Item
具有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
目前,我们每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
子句基于用户输入。它通常是一个列,因此排序很简单,处理结果集的app端循环将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)以这种方式对数据进行排序?
答案 0 :(得分:4)
这里确实有两个问题:
汇总如此多的数据并将其显示在一列中是否明智?
无论如何,您需要某种大型结构才能显示超过4000个字符,例如CLOB
。您可以按照Tom Kyte's thread之一中描述的一般准则编写自己的聚合方法(显然您需要修改它以使最终输出为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
不幸的是,您无法按Oracle中任意大的列进行排序:相对于排序键的类型和长度存在已知的限制。
ORA-00932
:不一致的数据类型:expected - 得到CLOB。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
答案 1 :(得分:1)
正如Vincent所说,排序键是有限的(没有CLOB,最大块大小)。
我可以提供一个稍微不同的解决方案,它在10g及更新版本中开箱即用,无需使用XMLAgg自定义函数和类型:
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)
;
答案 2 :(得分:0)
清洁是主观的,需要检查效率(但它仍然只能击中表格,所以可能不会更糟),但如果你对任何项目的属性数量有一个有限的上限 - 或者在线索中您需要考虑多少订购 - 然后您可以使用多个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;
因此,内部查询获取了您关注的两个值,并使用了四个lead
调用(仅作为示例,因此可以根据前五个属性名称的最大值进行排序,但可以当然可以通过添加更多!)来扩展每个项目的其他内容。根据您的数据,这给出了:
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
下一个查询对这五个有序属性名称进行dense_rank
,为每个itemID
和name
分配一个等级,给出:
ITEMID NAME DR
---------- ---------- ----------
1 A 1
1 B 4
1 C 6
2 A 3
2 C 6
3 A 2
3 B 5
下一个查询输出使用min
的分析版本找到每个dr
计算的itemId
值的最小值,因此每个itemID=1
获取min(dr) = 1
},itemId=2
获取3
,itemId=3
获得2
。 (您可以通过选择min(dense_rank(...))
来结合这两个级别,但这样(甚至)不那么明确。)
最终的外部查询使用每个项目的最小等级来进行实际排序,给出:
ITEMID NAME
---------- ----------
1 A
1 B
1 C
3 A
3 B
2 A
2 C