MySQL查询以获得具有限制的众多查询的"交集"

the*_*way 43 mysql sql database select inner-join

假设我有一个mySQL表(用户),其中包含以下字段:

userid  
gender  
region  
age  
ethnicity  
income
Run Code Online (Sandbox Code Playgroud)

我希望能够根据用户输入的数量返回总记录数.此外,他们还将提供额外的标准.

在最简单的例子中,他们可能会要求1000条记录,其中600条记录应该是性别="男性",400条记录中性别="女性".这很简单.

现在,再往前走一步.假设他们现在想要指定Region:

GENDER  
    Male:   600 records  
    Female: 400 records  

REGION  
    North:  100 records  
    South:  200 records  
    East:   300 records  
    West:   400 records
Run Code Online (Sandbox Code Playgroud)

同样,只应返回1000条记录,但最终必须有600名男性,400名女性,100名北方人,200名南方人,300名东方人和400名西方人.

我知道这不是有效的语法,但使用伪mySQL代码,它有希望说明我正在尝试做什么:

(SELECT * FROM users WHERE gender = 'Male' LIMIT 600  
UNION  
SELECT * FROM users WHERE gender = 'Female' LIMIT 400)

INTERSECT

(SELECT * FROM users WHERE region = 'North' LIMIT 100  
UNION  
SELECT * FROM users WHERE region = 'South' LIMIT 200  
UNION  
SELECT * FROM users WHERE region = 'East' LIMIT 300  
UNION  
SELECT * FROM users WHERE region = 'West' LIMIT 400)
Run Code Online (Sandbox Code Playgroud)

请注意,我不是在寻找一次性查询.每个标准中的记录总数和记录数将根据用户的输入不断变化.所以,我试图想出一个可以反复使用的通用解决方案,而不是硬编码的解决方案.

为了使事情变得更复杂,现在添加更多标准.也可能有年龄,种族和收入,每个都有自己设定的每组记录数,附加的代码如上:

INTERSECT

(SELECT * FROM users WHERE age >= 18 and age <= 24 LIMIT 300  
UNION  
SELECT * FROM users WHERE age >= 25 and age <= 36 LIMIT 200  
UNION  
SELECT * FROM users WHERE age >= 37 and age <= 54 LIMIT 200  
UNION  
SELECT * FROM users WHERE age >= 55 LIMIT 300)  

INTERSECT

etc.
Run Code Online (Sandbox Code Playgroud)

我不确定是否可以在一个查询中写入,或者这是否需要多个语句和迭代.

inv*_*sal 16

展平你的标准


您可以将多维标准展平为单一级别标准

在此输入图像描述

现在,可以在一个查询中实现此标准,如下所示

(SELECT * FROM users WHERE gender = 'Male' AND region = 'North' LIMIT 40) UNION ALL
(SELECT * FROM users WHERE gender = 'Male' AND region = 'South' LIMIT 80) UNION ALL
(SELECT * FROM users WHERE gender = 'Male' AND region = 'East' LIMIT 120) UNION ALL
(SELECT * FROM users WHERE gender = 'Male' AND region = 'West' LIMIT 160) UNION ALL
(SELECT * FROM users WHERE gender = 'Female' AND region = 'North' LIMIT 60) UNION ALL
(SELECT * FROM users WHERE gender = 'Female' AND region = 'South' LIMIT 120) UNION ALL
(SELECT * FROM users WHERE gender = 'Female' AND region = 'East' LIMIT 180) UNION ALL
(SELECT * FROM users WHERE gender = 'Female' AND region = 'West' LIMIT 240)
Run Code Online (Sandbox Code Playgroud)

问题

  • 它并不总是返回正确的结果.例如,如果少于40个用户是男性且来自北方,则查询将返回少于1,000条记录.

调整您的标准


假设有少于40个用户是男性和北方用户.然后,您需要调整其他标准数量以涵盖"男性"和"北部"中缺少的数量.我相信用裸SQL做这件事是不可能的.这是我想到的伪代码.为简化起见,我认为我们只会查询男性,女性,北方和南方

conditions.add({ gender: 'Male',   region: 'North', limit: 40  })
conditions.add({ gender: 'Male',   region: 'South', limit: 80  })
conditions.add({ gender: 'Female', region: 'North', limit: 60  })
conditions.add({ gender: 'Female', region: 'South', limit: 120  })

foreach(conditions as condition) {
    temp = getResultFromDatabaseByCondition(condition)
    conditions.remove(condition)

    // there is not enough result for this condition,
    // increase other condition quantity
    if (temp.length < condition.limit) {
        adjust(...);
    }
}
Run Code Online (Sandbox Code Playgroud)

假设只有30名北方男性.所以我们需要调整+10男性和+10北方人.

To Adjust
---------------------------------------------------
Male        +10
North       +10

Remain Conditions
----------------------------------------------------
{ gender: 'Male',   region: 'South', limit: 80 }
{ gender: 'Female', region: 'North', limit: 60  }
{ gender: 'Female', region: 'South', limit: 120  }
Run Code Online (Sandbox Code Playgroud)

'男'+'南'是第一个符合'男'调整条件的条件.将其增加+10,并将其从"保留条件"列表中删除.因为,我们增加南方,我们需要在其他条件下减少它.因此,将"South"条件添加到"To Adjust"列表中

To Adjust
---------------------------------------------------
South       -10
North       +10

Remain Conditions
----------------------------------------------------
{ gender: 'Female', region: 'North', limit: 60  }
{ gender: 'Female', region: 'South', limit: 120  }

Final Conditions
----------------------------------------------------
{ gender: 'Male',   region: 'South', limit: 90 }
Run Code Online (Sandbox Code Playgroud)

找到与"南方"匹配的条件并重复相同的过程.

To Adjust
---------------------------------------------------
Female      +10
North       +10

Remain Conditions
----------------------------------------------------
{ gender: 'Female', region: 'North', limit: 60  }

Final Conditions
----------------------------------------------------
{ gender: 'Female', region: 'South', limit: 110  }
{ gender: 'Male',   region: 'South', limit: 90 }
Run Code Online (Sandbox Code Playgroud)

最后

{ gender: 'Female', region: 'North', limit: 70  }
{ gender: 'Female', region: 'South', limit: 110  }
{ gender: 'Male',   region: 'South', limit: 90 }
Run Code Online (Sandbox Code Playgroud)

我还没有提出调整的确切实施.这比我想象的要困难得多.一旦我弄清楚如何实现它,我会更新.


Gor*_*off 10

您描述的问题是多维建模问题.特别是,您试图同时沿多个维度获取分层样本.关键是要降低到最小的粒度级别并从那里构建样本.

我进一步假设您希望样本在所有级别都具有代表性.也就是说,您不希望"North"的所有用户都是女性.或者所有"男性"都来自"西方",即使这符合最终标准.

首先考虑每个维度的记录,维度和分配总数.例如,对于第一个样本,将其视为:

  • 1000条记录
  • 2个维度:性别,地区
  • 性别分裂:60%,40%
  • 区域分割:10%,20%,30%,40%

然后,您想要将这些数字分配给每个性别/地区组合.数字是:

  • 北,马累:60
  • 北,女:40
  • 南,马累:120
  • 南,女:80
  • 东,男:180
  • 东,女:120
  • 西部,男性:240
  • 西,女:160

你会看到这些在尺寸上加起来.

每个单元格中的数字计算非常简单.它是百分比乘以总数的乘积.所以,"东方,女性"是30%*40%*1000...瞧!值为120.

这是解决方案:

  1. 将每个维度的输入作为总数的百分比.并确保它们在每个维度上加起来达到100%.
  2. 创建每个单元格的预期百分比表.这是每个维度百分比的乘积.
  3. 预期百分比乘以总数.
  4. 最终查询概述如下.

假设您有一个cells包含预期计数和原始数据(users)的表.

select enumerated.*
from (select u.*,
             (@rn := if(@dims = concat_ws(':', dim1, dim2, dim3), @rn + 1,
                        if(@dims := concat_ws(':', dim1, dim2, dim3), 1, 1)
                       )
             ) as seqnum
      from users u cross join
           (select @dims = '', @rn := '') vars
      order by dim1, dim2, dim3, rand()
     ) enumerated join
     cells
     on enumerated.dims = cells.dims
where enuemrated.seqnum <= cells.expectedcount;
Run Code Online (Sandbox Code Playgroud)

请注意,这是解决方案的草图.您必须填写有关尺寸的详细信息.

只要您有足够的数据用于所有单元格,这将有效.

在实践中,当进行这种类型的多维分层抽样时,您确实存在细胞空或太小的风险.发生这种情况时,您可以经常通过额外的传递来解决此问题.从足够大的细胞中获取尽可能多的东西.这些通常占所需数据的大部分.然后添加记录以满足最终计数.要添加的记录是其值与最需要的维度所需的值匹配的记录.但是,此解决方案只是假设有足够的数据来满足您的标准.