通过SQL查询获取随机N行,这将与不同部分中的行总数成正比

时间:2018-11-27 18:57:10

标签: sql sql-server

我有一个表,其中包含许多问题,每个问题都属于一个部分:

Id      Question        SectionId
 1      What is ...     3
 2      Who is...       3
 3      When is...      2
 4      Why is...       1
 5      How is...       3

大约有1000个问题,大约有50个部分。但是,我的查询很简单,我从特定部分的表格中选择了给定数量的问题,例如

SELECT TOP 10 [Id], [Question] FROM [Questions] 
WHERE [SectionId] IN (1,2)
ORDER BY NEWID()

这很简单并且可以正常工作,除了有时候我从只有6个问题的部分中从所请求的10个问题中得到5个问题,从有100个问题的部分中获得2个问题,从有20个问题的部分中获得3个问题。

如何使结果与每个部分中的问题数量成比例。例如,如果我请求10个问题,那么我会从具有更多问题的部分中获得更多问题,而从具有较少问题的部分中获得更少的问题。

我目前唯一能想到的就是进行多个查询,首先是获取每个部分中的问题数量,然后做一些数学运算并确定每个部分中有多少个问题,然后再进行几个查询来获得我想要的问题数量。这种声音非常密集,我希望有一种更实用的方法。

注意:可以使用SQL查询或EF Linq查询。

6 个答案:

答案 0 :(得分:2)

对于分层样本,请在订购时进行第n个样本。这有点棘手,但这应该可行:

SELECT TOP (10) q.*
FROM (SELECT q.*,
             ROW_NUMBER() OVER (ORDER BY section, NEWID()) as seqnum,
             COUNT(*) OVER (ORDER BY section, NEWID()) as cnt
      FROM [Questions] q
      WHERE [SectionId] IN (1, 2)
     ) q
ORDER BY seqnum % (cnt / 10);

这种逻辑上可能存在一些边界条件,但是随着问题数量的增加和样本足够大,它应该做您想要的。

答案 1 :(得分:1)

除非您事先知道部分的数量和每个部分的比例,否则我想不出一步来完成此操作的方法。

如果必须在查询时计算这些值,则需要运行查询以获取截面和比例,并使用它们来构建动态SQL查询。

使用GROUP BY查询来获取SectionID和每个Section中的问题数量,并根据您要包含的Section进行过滤。

遍历该结果以构建一个动态UNION ALL查询,该查询为每个部分获取TOP n(根据部分计数/总计数的百分比计算n)(每个部分一个查询),以便结束动态构建看起来像这样的东西:

SELECT TOP 5 ID, Question --because SectionID 1 is 50% of the questions
FROM Questions
WHERE SectionID=1
ORDER BY NEWID()
UNION ALL 
SELECT TOP 3 ID, Question  --because SectionID 2 is 30% of the questions
FROM Questions
WHERE SectionID=2
ORDER BY NEWID()
UNION ALL 
SELECT TOP 2 ID, Question  --because SectionID 3 is 20% of the questions
FROM Questions
WHERE SectionID=3
ORDER BY NEWID()

您可以考虑的另一种方法是创建一个由该部分的相对密度决定的人工排名列。

例如,我的意思是(超级简化)是假设第一部分是问题的75%,第二部分是问题的25%。

您将使用ROW_NUMBER(),由SectionID分区,由NEWID()排序,并进行分解,以便:

第1部分的值应为1,2,3,5,6,7等(每4个基数中就有3个)

第2节的值应为1、5、9、10等(每4个中的1个)

然后通过此人工列对查询结果进行排序。

答案 2 :(得分:1)

这在没有样本数据的情况下未经测试,但是,类似的事情可能会起作用:

WITH CTE AS(
    SELECT ID,
           Question,
           SectionID,
           ROW_NUMBER() OVER (ORDER BY NEWID()) AS RN,
           (COUNT(ID) OVER (PARTITION BY SectionID) / (COUNT(ID) OVER () *1.0)) *10 AS Perc 
    FROM YourTable
)
SELECT TOP 10
       ID,
       Question,
       SectionID
FROM CTE
WHERE RN <= CEILING(Perc)
ORDER BY RN ASC;

答案 3 :(得分:1)

另一种替代方法,例如...每节返回总行数的20%

DECLARE @percentage numeric(10,2)

SET @percentage = 0.20 --20% of total question for section

SELECT [SectionID],[ID],[Question]
FROM (  SELECT
            [ID],
            [Question],
            [SectionID],
            ROW_NUMBER() OVER(PARTITION BY SectionID ORDER BY NEWID()) [idx],
            COUNT(1) OVER(PARTITION BY SectionID) * @percentage AS [Proportional]
        FROM [Questions]) tbl
WHERE 
    (tbl.[SectionID] = 1 AND tbl.[idx] <= [Proportional])
OR (tbl.[SectionID] = 2 AND tbl.[idx] <= [Proportional])
OR (tbl.[SectionID] = 3 AND tbl.[idx] <= [Proportional])

答案 4 :(得分:1)

您可以将NTILE(100)函数与over子句按部分进行分区来获取

SELECT TOP 10 [Id], [Question] FROM [Questions] 
WHERE [SectionId] IN (1,2)
ORDER BY NEWID()

应该是

declare @limit int = 10;

;with data as (
   SELECT NTILE(100) over (partition by sectionid ORDER BY NEWID() ) as Centile, [Id], [Question] 
   FROM [Questions] 
   WHERE [SectionId] IN (1,2)
)
select * from data where centile <= @limit

https://docs.microsoft.com/en-us/sql/t-sql/functions/ntile-transact-sql

答案 5 :(得分:0)

您可以在任何部分中始终选择以下记录的10%:

SELECT TOP ( select CAST(( COUNT(*) * 0.1 ) AS INT ) 
FROM QUESTION WHERE SECTIONID IN ( 1,2)) * FROM QUESTION 
WHERE [SectionId] IN (1,2)
ORDER BY NEWID()