我有一个包含以下列的窄表:
<Customer ID> <Field ID> <Value>
,所有这些都是数字。
我想将此表重新整理为宽格式:
<Customer ID> <Field1> <Field2> <Field3> ...
我有一个单独的字典表DIC_FIELDS
,它将字段ID转换为字段名称。
我在EXADATA服务器上工作。窄表有25亿条记录,我们有大约200个字段。
下面明显的简单解决方案严重填满了EXADATA服务器上的所有临时空间。
create table WIDE_ADS as (
CUSTOMERID
,max(case when FIELDID = 1 then VALUE end) as GENDER
,max(case when FIELDID = 2 then VALUE end) as AGE
,max(case when FIELDID = 3 then VALUE end) as EDUCATION
from NARROW_ADS
group by CUSTOMERID
);
我们还尝试了一种更聪明且手动的方法:
create index index1
on SZEROKI_ADS(CUSTOMERID);
DECLARE
rowidWide rowid;
type tColNames is table of STRING(32000) index by pls_integer ;
arrColNames tColNames;
x_CustomerID number;
strColName varchar2(32);
strColvalue varchar2(32000);
strSQL varchar2(200);
lngCounter pls_integer;
lngFieldID pls_integer;
BEGIN
lngCounter := 0;
-- we pre-load the dictionary arrColNames to speedup lookup.
for DIC_EL in (select * from DIC_FIELDS order by FIELDID) LOOP
lngFieldID := to_number(DIC_EL.FIELDID);
arrColNames(lngFieldID) := DIC_EL.FIELDNAME;
END LOOP;
FOR NARROW_REC IN (SELECT * FROM NARROW_ADS where VALUE is not null ) LOOP
strColName := arrColNames(NARROW_REC.FIELDID);
strColvalue := NARROW_REC.VALUE;
x_IDKlienta := NARROW_REC.CUSTOMERID;
BEGIN
select rowid into rowidWide from WIDE_ADS
where CUSTOMERID = NARROW_REC.CUSTOMERID;
strSQL := 'update :1 set :2 = :3 where rowid = :4';
execute immediate strSQL using WIDE_ADS, strColName, strColvalue, rowidWide;
EXCEPTION
WHEN NO_DATA_FOUND THEN
strSQL :=
'insert into '|| WIDE_ADS ||' (CUSTOMERID, '|| strColName ||')
values
(:1, :2)';
execute immediate strSQL using x_CustomerID, to_number(strColvalue) ;
END;
IF lngCounter=10000 THEN
COMMIT;
lngCounter:=0;
dbms_output.put_line('Clik...');
ELSE
lngCounter:=lngCounter+1;
END IF;
END LOOP;
END;
虽然它不需要温度,但它在性能方面却失败了;它在50秒内处理10 000条记录 - 比预期的慢1000倍。
我们可以做些什么来加快这个过程?
答案 0 :(得分:1)
正如Lalit所述,请尝试以CUSTOMERID
为基础的块进行。
首先,在CUSTOMERID
上创建一个索引(如果它不存在):
CREATE INDEX INDNARROWADS ON NARROW_ADS(CUSTOMERID);
其次,我们将创建一个辅助表来计算基于CUSTOMERID
的桶(在本例中我们创建1000个桶,1个桶代表1个块插入语句):
CREATE TABLE BUCKETS(MINCUSTOMER, MAXCUSTOMER, BUCKETNUM) AS
SELECT MIN(CUSTOMERID), MAX(CUSTOMERID), BUCKET
FROM (SELECT CUSTOMERID,
WIDTH_BUCKET(CUSTOMERID,
(SELECT MIN(CUSTOMERID) FROM NARROW_ADS),
(SELECT MAX(CUSTOMERID) FROM NARROW_ADS),
1000) BUCKET
FROM NARROW_ADS)
GROUP BY BUCKET;
您可以使用更多/更少的存储桶来修改WIDTH_BUCKET函数的第四个参数。
第三,创建WIDE_ADS
表(没有数据的结构)。您应该手动执行此操作(特别注意存储参数),但您也可以使用WHERE
错误条件的自己的查询:
create table WIDE_ADS as select
CUSTOMERID
,max(case when FIELDID = 1 then VALUE end) as GENDER
,max(case when FIELDID = 2 then VALUE end) as AGE
,max(case when FIELDID = 3 then VALUE end) as EDUCATION
from NARROW_ADS
where 1=0;
第四,对每个桶执行查询(1个桶意味着1个插入语句):
BEGIN
FOR B IN (SELECT * FROM BUCKETS ORDER BY BUCKETNUM) LOOP
INSERT INTO WIDE_ADS
SELECT
CUSTOMERID
,max(case when FIELDID = 1 then VALUE end) as GENDER
,max(case when FIELDID = 2 then VALUE end) as AGE
,max(case when FIELDID = 3 then VALUE end) as EDUCATION
FROM NARROW_ADS
WHERE CUSTOMERID BETWEEN B.MINCUSTOMER AND B.MAXCUSTOMER
GROUP by CUSTOMERID;
COMMIT;
END LOOP;
END;
最后,删除辅助表(如果没有必要,则删除索引)。
Oracle优化器应使用CUSTOMERID
上的索引对NARROW_ADS
执行“索引范围扫描”。因此,每个INSERT
应该有效地找到相应的间隔。
请注意,WIDTH_BUCKETS
根据CUSTOMERID
上指定时间间隔内的统一划分创建存储区(从最小值到最大值)。它不会根据统一的行数创建存储桶。另请注意,在执行此过程时,不得修改NARROW_ADS
。
当PL / SQL块在每次迭代时执行COMMIT
并且循环使用BUCKETNUM
顺序迭代桶时,您可以看到WIDE_ADS如何增长以及正在处理哪个桶(检索最大值)来自CUSTOMERID
的{{1}}并在WIDE_ADS
表上找到相应的存储分区。
如果临时空间使用率很高,则增加桶数(每个插入量都会更小)。