Oracle SQL:计算分组数据的Z分数的最有效方法

时间:2013-01-10 21:24:09

标签: sql oracle plsql statistics

我有两张桌子: DATA

DATA_ID  |  SAMPLE_ID  |  ASSAY_ID  |  SIGNAL
101      |  201        |  301       |  2.87964
102      |  201        |  302       |  7.64623
103      |  202        |  301       |  1.98473
...

SAMPLES

SAMPLE_ID  |  SAMPLE_NAME  |  CATEGORY
201        |  SAMP0001     |  CAT A  
202        |  SAMP0002     |  CAT B
203        |  SAMP0003     |  CAT A
...

SAMPLES中约有20,000行。对于每个样本,DATA中有大约40,000行。 ASSAY_ID中每个样本只出现一次DATA。我需要在SAMPLE中获取一部分样本,并计算DATA中每个信号值的标准/ z分数值,按ASSAY_ID进行分组。我正在尝试创建一个将被重复调用的存储过程,它将接受单个ASSAY_ID值并返回预定义样本子集中所有样本的SAMPLE_IDZSCORE对。

给定一组给定化验的样本信号值(X = [3.21, 4.56, 1.12, ..]),本例中的标准/ z分数计算为

(X[i] - median(X))/(K * MAD)

其中K是一个等于1.4826的比例因子而MAD是中位数调整后的偏差,等于:

median(|X[i]-median(X)|)

知道了吗?好:)现在,使用SQL查询执行此计算的最有效方法是什么?执行时间是关键,因为DATA中有近10亿行,并且几乎每个SIGNAL值都需要计算z分数。

这是迄今为止我能够提出的最佳查询:

WITH BASE AS (
    SELECT 
        S.SAMPLE_ID,
        D.SIGNAL
    FROM
        DATA D
        JOIN SAMPLES S
            ON D.SAMPLE_ID = S.SAMPLE_ID
    WHERE 
        S.CATEGORY IN ('CAT A', 'CAT B')
        AND D.ASSAY_ID = 12345
        AND S.SAMPLE_NAME NOT IN ('SAMP0003', 'SAMP0005', 'SAMP0008')          
)
SELECT  
    A.SAMPLE_ID,
    (A.SIGNAL-B.MED)/(1.4826*C.MAD) AS ZSCORE
FROM 
    BASE A,
    (
        SELECT MEDIAN(X.SIGNAL) AS MED 
        FROM BASE X
    ) B,
    (
        SELECT MEDIAN(ABS(Y.SIGNAL-YY.MED)) AS MAD 
        FROM BASE Y, 
        (SELECT MEDIAN(SIGNAL) AS MED FROM BASE) YY
    ) C 

是否有更有效的方法来执行此查询?

奖金问题:我可以编写一个SQL查询,在一次执行中为每个ASSAY_ID执行此计算吗?

1 个答案:

答案 0 :(得分:2)

你能看一下:

SELECT ASSAY_ID, SAMPLE_ID, 
       (SIGNAL - MED)/(1.4826F * MAD) AS ZSCORE
  FROM (
        SELECT ASSAY_ID, SAMPLE_ID, SIGNAL, MED,
               MEDIAN(ABS(SIGNAL - MED)) OVER (PARTITION BY ASSAY_ID) AS MAD
          FROM (
                SELECT ASSAY_ID, SAMPLE_ID, SIGNAL,
                       MEDIAN(SIGNAL) OVER (PARTITION BY ASSAY_ID) AS MED
                  FROM DATA    D
                  JOIN SAMPLES S USING (SAMPLE_ID)
                 WHERE S.CATEGORY IN ('CAT A', 'CAT B')
                   AND S.SAMPLE_NAME NOT IN ('SAMP0003', 'SAMP0005', 'SAMP0008')  
                   AND D.ASSAY_ID = 301
               )
       );

这是对的吗?它更快吗?如果是,只需删除红利问题AND D.ASSAY_ID = 301条款: - )

在物理方面,我会研究信号的数据类型(BINARY_FLOATBINARY_DOUBLE应该比NUMBER更快)。而且,如果这是一个选项,我会尝试将分析与分区进行物理配置。