【问题标题】:Extracting specific number of records so that certain overall conditions are met提取特定数量的记录以满足特定的总体条件
【发布时间】:2016-03-11 16:25:56
【问题描述】:

我在这里描述了一个抽象案例,但它与我现在试图解决的案例相似。我知道如何使用 PL/SQL 块获得粗略的结果,但我想知道是否有人可以建议使用单个选择查询的解决方案。

假设我们有一个表t_people,其中包含数千条记录,这些记录描述了具有以下一组属性的某些人:

  • id
  • age,号码
  • height 厘米,数字
  • gender, varchar2 ('male' or 'female')

并且我们需要抽取N条记录,使得结果集满足以下条件:

  • 30% 的入选者身高超过 180 厘米
  • 60% 的选定人员是男性
  • 40% 的选定人员年龄超过 40 岁

我们还可以假设 N 远小于表中的总行数,并且问题是可以解决的。

您如何建议使用单个选择查询来执行此操作?

谢谢

【问题讨论】:

  • 您能否使用 8 个查询的 UNION ALL - 第一个返回 N*.3*.6*.4 个身高超过 180 厘米、男性、40 岁以上的人,下一个返回 N*。 3*.6*.6 身高180cm以上、男性、40以下等?

标签: sql oracle


【解决方案1】:

您可以将数据分成 8 组,然后从每组中按比例抽取样本以满足您的要求。一种粗略的方法是将条件转换为组,例如:

  • 300 人高于 180,不是男性,不是老年人
  • 100 人矮,不是男性,不是老年人
  • 400 人更矮,男性,更年长
  • 200 人矮,男性,不老

那么你可以这样处理:

with p as (
      select p.*,
             row_number() over (partition by height, male, age order by height) as seqnum
      from (select p.*,
                   (case when height > 180 then 1 else 0 end) as height,
                   (case when gender = 'male' then 1 else 0 end) as male,
                   (case when age > 40 then 1 else 0 end) as age
            from people p
           ) p
     )
select p.*
from p
where (height = 1 and male = 0 and age = 0 and seqnum <= 300) or
      (height = 0 and male = 0 and age = 0 and seqnum <= 100) or
      (height = 0 and male = 1 and age = 1 and seqnum <= 400) or
      (height = 0 and male = 1 and age = 0 and seqnum <= 200);

您可以使用另一种方法来均匀地填充 8 个桶,跟踪每个维度的数字(年轻/年长、男性/女性、更矮/更高)。然后当第一个维度被填充时停止填充并继续填充 4 个互补单元格。重复该过程,直到获得所需的数字。

【讨论】:

  • 非常感谢,您的第一种方法很适合我。虽然在我的真实案例中我有 9 个分组属性,但我认为你的想法仍然适用。至于第二种方法,我想过类似的东西,但略有不同。首先,我要随机选择 N 行并查看比例。虽然比例不够好,但我会用剩下的一组中的更好的记录来替换坏记录。如果它使集合更接近所需的比例,则记录会更好。再次感谢,祝您周末愉快!
【解决方案2】:

我最终选择了Gordon Linoff 的第一种方法suggested,并进行了一些小的修改。我保留了最初的想法,但还引入了几个额外的子查询来指定组内所需的记录分布,并构建了一个矩阵,其中包含每组所需的记录数。还有一个全局参数部分,其中包含指定总记录数的唯一参数。

查询产生了非常有用的结果:

with 
    people as (
        select  id,
                floor(months_between(sysdate, date_birth)/12) age,
                195 - least(floor(months_between(sysdate, date_birth)/12), 50) height,
                decode(sex, 1, 'male', 'female') gender
        from    my_people_table
        where   date_birth is not null and rownum < 100000
    ),
    params as ( /* Global params */
        select  100 rec_count   -- total record count 
        from dual
    ),
    age_groups as (     /* distribution by height */
        select  'group 1' age_group, .7 prc from dual union
        select  'group 2' age_group, .3 prc from dual  
    ),
    height_groups as ( /* distribution by height */
        select  'group 1' height_group, .6 prc from dual union
        select  'group 2' height_group, .4 prc from dual  
    ),
    genders as (       /* distribution by gender */
        select  'male'   gender, .6 prc from dual union
        select  'female' gender, .4 prc from dual  
    ),
    mx as (            /* a matrix with record counts per group */
        select  age_group, height_group, gender,
                ceil(
                    age_groups.prc * 
                    height_groups.prc * 
                    genders.prc * 
                    rec_count
                )  rec_count       
        from    age_groups, height_groups, genders, params
    ),
    xpeople as (       /* Minor transformations - groups and group counters */
        select  p.*, 
                row_number() over (
                    partition by age_group, height_group, gender
                        order by age_group, height_group, gender
                ) rec_num
        from (                             
                select  people.*,
                        case 
                            when age    <=  40 then 'group 1' 
                                               else 'group 2' 
                        end age_group,
                        case 
                            when height <= 180 then 'group 1' 
                                               else 'group 2' 
                        end height_group
                from    people
        ) p
    )
/* the resulting query uses the matrix to filter the records */    
select  xpeople.*
from    xpeople join mx 
            on  xpeople.age_group = mx.age_group 
            and xpeople.height_group = mx.height_group      
            and xpeople.gender = mx.gender
            and xpeople.rec_num <= mx.rec_count 

感谢您的帮助!

【讨论】:

    猜你喜欢
    • 2020-03-30
    • 2018-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-17
    相关资源
    最近更新 更多