在 Oracle SQL 中使用替换/引导进行采样

问题描述 投票:0回答:1

这是我对堆栈溢出的第一个问题。

我们正在对模拟数据进行统计,我想在Oracle-SQL中实现引导程序(带替换的采样数据)。我的做法如下:

  1. 对查询中的所有行进行编号
  2. 创建行范围内的随机数列表
  3. 对于每个随机数,加入一行。 -> 即放回抽样

但是,连接似乎有问题。 Oracle 正在丢失一些行。

这是一个例子。当我执行以下查询时,我得到行的随机排序:

    WITH basis as (
         SELECT 'A' AS KPI_KEY, 2 AS KPI_VALUE FROM dual
        union all
        SELECT 'A' AS KPI_KEY, 5 AS KPI_VALUE FROM dual
        union all
        SELECT 'A' AS KPI_KEY, 3 AS KPI_VALUE FROM dual
    ), group_counts AS (
      SELECT KPI_KEY, COUNT(*) as total_count
      FROM basis
      GROUP BY KPI_KEY
    )
    , random_numbers AS (
      SELECT KPI_KEY, ceil(dbms_random.value(0, max(total_count))) AS rand_rn
      FROM group_counts
      GROUP BY KPI_KEY
      CONNECT BY LEVEL <= total_count AND PRIOR KPI_KEY = KPI_KEY AND PRIOR dbms_random.value IS NOT NULL
    )
    select * from random_numbers
    ;

结果(正确): KPI_KEY RAND_RN ‘A’1 'A2 ‘A’2

现在,当我像下面这样连接编号行时,结果是错误的:

    WITH basis as (
         SELECT 'A' AS KPI_KEY, 2 AS KPI_VALUE FROM dual
        union all
        SELECT 'A' AS KPI_KEY, 5 AS KPI_VALUE FROM dual
        union all
        SELECT 'A' AS KPI_KEY, 3 AS KPI_VALUE FROM dual
    ), group_counts AS (
      SELECT KPI_KEY, COUNT(*) as total_count
      FROM basis
      GROUP BY KPI_KEY
    )
    , numbered_rows AS (
      SELECT KPI_KEY, KPI_VALUE, ROW_NUMBER() 
        OVER (PARTITION BY KPI_KEY ORDER BY dbms_random.value) AS rn, total_count
      FROM basis
      JOIN group_counts USING (KPI_KEY)
    )
    , random_numbers AS (
      SELECT KPI_KEY, ceil(dbms_random.value(0, max(total_count))) AS rand_rn
      FROM group_counts
      GROUP BY KPI_KEY
      CONNECT BY LEVEL <= total_count AND PRIOR KPI_KEY = KPI_KEY AND PRIOR dbms_random.value IS NOT NULL
    )
    SELECT rn.KPI_KEY, nr.KPI_VALUE, nr.rn
    FROM random_numbers rn
    LEFT JOIN numbered_rows nr 
        ON rn.KPI_KEY = nr.KPI_KEY 
        and rn.rand_rn = nr.rn
    ;

结果(值是随机的,但连接中缺少行): KPI_KEY KPI_VALUE RN ‘A’5 3

有人遇到过类似的问题吗? 对我来说似乎是 Oracle 的错误,但也许我错过了一个重要的细节。

我尝试了物化提示,以及此处找到的 rownum 方法:Random join in oracle

oracle sampling statistics-bootstrap
1个回答
0
投票

错误在于,倒数第二个查询的

GROUP BY
将所有生成的行聚合到一行中,而
CONNECT BY
子句实际上做了不必要的工作,收益为零。

这似乎是因为 SQL 引擎在重写查询时,如果您从倒数第二个查询中获取输出,而没有最终查询,那么它会执行

GROUP BY
,然后
CONNECT BY
,如果您同时执行倒数第二个和最终查询,那么它先执行
CONNECT BY
,然后执行
GROUP BY
(以相反的顺序)。您可以在问题底部链接的小提琴中看到
EXPLAIN PLAN

可以修复查询并具体化倒数第二个查询的输出,以便

EXPLAIN PLAN
与查询的最终部分一致生成;然而,这似乎是一个脆弱的解决方案,您可能最好完全重写查询,以使用 (a) 更简单和 (b) 受编译器重写查询影响较小的东西。


假设你的逻辑是:

  1. 对于每个

    kpi_key

    1. 为每一行分配一个唯一的行号。
    2. 为每一行分配一个从 1 到总行数的随机行号
      kpi_key
      (使用
      CEIL(DBMS_RANDOM.VALUE(0, num_rows))
      的逻辑中存在一个错误,因为
      DBMS_RANDOM
      将包括下限并排除上限因此您可以获取从 0 开始的值(您不想包含该值),然后向上舍入到
      num_rows
      ,但概率不均匀,因为排除了上限;相反,您想使用
      FLOOR
      并加 1) .
  2. 自连接行,以便每一行都连接到其随机对应部分。

然后您可以使用分析函数重写查询:

WITH basis (kpi_key, kpi_value) as (
  SELECT 'A', 2 FROM dual union all
  SELECT 'A', 5 FROM dual union all
  SELECT 'A', 3 FROM dual
),
random_numbers AS (
  SELECT kpi_key,
         kpi_value,
         1 + FLOOR(DBMS_RANDOM.VALUE(0, COUNT(*) OVER (PARTITION BY kpi_key))) AS rand_rn,
         ROW_NUMBER() OVER (PARTITION BY kpi_key ORDER BY ROWNUM) AS rn
  FROM   basis
)
select original.kpi_key   AS o_kpi_key,
       original.kpi_value AS o_kpi_value,
       original.rn        AS o_rn,
       rand.kpi_key       AS r_kpi_key,
       rand.kpi_value     AS r_kpi_value,
       rand.rn            AS r_rn
from   random_numbers original
       INNER JOIN random_numbers rand
       ON original.rand_rn = rand.rn;

注意:当您将其与随机值进行比较时,没有必要对

ROW_NUMBER
输出进行随机排序,并且将静态排序列表与该列表中的随机值进行比较时,您将获得与比较随机值时相同的随机性。 -有序列表到该列表中的随机值。

可能会随机输出:

O_KPI_KEY O_KPI_VALUE O_RN R_KPI_KEY R_KPI_VALUE R_RN
A 5 2 A 3 3
A 2 1 A 3 3
A 3 3 A 2 1

注意:您可能只需要与随机行选择相对应的最后 3 列。

小提琴

© www.soinside.com 2019 - 2024. All rights reserved.