【问题标题】:How to select different percentages of data based in a column value?如何根据列值选择不同百分比的数据?
【发布时间】:2013-08-21 22:13:08
【问题描述】:

我需要查询具有“性别”列的表,如下所示:

|编号 |性别 |姓名 | ------------------------- | 1 |中号 |迈克尔 | ------------------------- | 2 | F |汉娜 | ------------------------- | 3 |中号 |路易 | -------------------------

我需要提取前 N 个结果,例如 80% 的男性和 20% 的女性。所以,如果我需要 1000 个结果,我会想要检索 800 个男性和 200 个女性。

  1. 是否可以在单个查询中完成?怎么样?

  2. 如果我没有足够的记录(假设我在上面的示例中只有 700 名男性)是否可以自动选择 700 / 300?

【问题讨论】:

  • 对于场景 2,应该发生什么?
  • 我已经编辑了我的答案以便更好地解释自己。
  • 不幸的是,我不知道足够的 SQL 来给出代码方面的答案,但我可以给出逻辑:我建议一个 SP 并有一个值 N(您正在选择的数字)和取 n*.8 并选择性别为 M,计算返回为 numResultsMale 的行,然后选择 N-(numResultsMale),其中性别为 F
  • 附带说明,gender 作为布尔值或M/F 迟早会让您或您的用户陷入麻烦。允许“其他”或“未指定”通常是个好主意。有些人不是 100% 的男性或 100% 的女性(生物学和/或心理上,出生或改变)。
  • @CraigRinger,也许他们想要这样。满足所有用户的所有要求并不总是一个目标。我理解您的评论并同意它在许多情况下都是有效的,但我相信如果他愿意,我们应该让他将性别存储为布尔值。

标签: sql postgresql


【解决方案1】:

基本上,您希望获得尽可能多的“M”,但不要超过您的百分比,然后获得足够的“F”,这样您总共有 1000 行:

with cte_m as (
    select * from Table1 where gender = 'M' limit (1000 * 0.8)
), cte as (
    select *, 0 as ord from cte_m
    union all
    select *, 1 as ord from Table1 where gender = 'F'
    order by ord
    limit 1000
)
select id, gender, name
from cte

sql fiddle demo

【讨论】:

  • 太完美了!谢谢!
【解决方案2】:

假设您提供的是行数(“lmt”),并为 M/F 分布浮动:

create table gen (
id     integer,
gender text,
name   text
);

-- inserts 75% males and 25% females into the source table ("gen")
insert into gen select n, case when mod(n,5) = 0 then 'F' else 'M' end, (case when mod(n,5) = 0 then 'F' else 'M' end)||'_'||n::text
from generate_series(1,20000) n


-- extract 80/20 M vs F
with conf as (select 1000 as lmt, .80::FLOAT as mpct, .20::FLOAT as fpct),
     g as (select id,gender,name,row_number() over (partition by gender order by gender) rn from gen)
select *
from g
where (gender = 'M' and rn <= (select lmt*mpct from conf))
or (gender = 'F' and rn <= (select lmt*fpct from conf));


-- Same query, to show the percent M vs F:
with conf as (select 1000 as lmt, .80::FLOAT as mpct, .20::FLOAT as fpct),
     g as (select id,gender,name,row_number() over (partition by gender order by gender) rn from gen)
select gender,count(*)
from (
    select *
    from g
    where (gender = 'M' and rn <= (select lmt*mpct from conf))
    or (gender = 'F' and rn <= (select lmt*fpct from conf))
    ) y
group by gender

【讨论】:

    【解决方案3】:

    我没有 postgresql,但第一个场景很容易在 MS SQL 2012 中使用联合。我假设您可以在 postgre 中类似地执行此操作:

    declare @MaxRows            INT
            ,@PercentageMale    INT
            ,@PercentageFemale  INT
    
    select      @MaxRows = 1000
                ,@PercentageMale = 80
                ,@PercentageFemale = 20
    
    select  top (@MaxRows*@PercentageMale/100)  *
    FROM        someTable
    WHERE       Gender = 'M'
    UNION
    select  top (@MaxRows*@PercentageFemale/100)    *
    FROM        someTable
    WHERE       Gender = 'F'
    

    第二点其实很简单。基本上你想选择男性的前 %,然后用女性填充列表的其余部分,直到总行数。女性的数量实际上并不相关:

    declare @MaxRows            INT
            ,@PercentageMale    INT
    
    select      @MaxRows = 1000
                ,@PercentageMale = 80
    
    SELECT TOP @MaxRows *
    FROM
    (
        select  top (@MaxRows*@PercentageMale/100)  *
        FROM        someTable
        WHERE       Gender = 'M'
        UNION
        select  top (@MaxRows)  * --we never want more than @MaxRows 
                                  --so no need to check for a %, 
                                  --just fill in the rest of the data set
        FROM        someTable
        WHERE       Gender = 'F'
    ) a
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-31
      • 2016-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-10
      相关资源
      最近更新 更多