SQL:如何多次在查询中选择一些(但不是全部)记录

时间:2015-07-09 17:57:48

标签: sql sql-server sql-server-2012

我们有一堆记录,我们按以下方式为每条记录分配一个随机数,其值介于1和记录总数之间:

SELECT personID, ROW_NUMBER()
OVER(ORDER BY NEWID()) as RowNumber
FROM folks

很容易就像馅饼一样。让我们假设一个LOWER(编辑:不高,对不起!)数字更适合客户的目的,并且他们喜欢'随机'元素在这里工作。麻烦的是,客户现在说,有些人很特别,我们希望他们有三次机会,然后将他们的最佳结果保存为他们的号码。'

由于我们不是连续发送数字,而是一次性发送数字,这里的方法似乎是在此查询中选择三次特殊人员,然后获取最高行数。

这类似于,但比这个问题(以及其他类似的人)更多地涉及一步:

Select Records multiple times from table

我不想三次选择所有记录;但我确实想一气呵成;也就是说,我无法指定特殊人数,然后为其他人分配数字 - 它必须是一个查询。

  1. 我如何构建一个JOIN(和/或CTE)来对此进行建模,假设我们可以在每条记录上依赖isSpecial = 1之类的字段?

  2. 我怎样才能获得最低数字' (即该记录的第一个row_number出现)来自我的SELECT声明中的结果?

  3. 平台:Microsoft SQL 2012

    示例数据(在输出查询中包括isSpecial仅用于演示) - 另外,我们希望此处的最小数量用于商业目的,而非最大值

    personID    isSpecial
    1           1
    2           0
    3           0
    4           0
    5           0
    6           0
    7           0
    8           0
    9           0
    10          0
    

    当前输出:

    SELECT personID, isSpecial, row_number
    OVER(ORDER BY NEWID()) as RowNumber
    FROM folks
    
    personID    RowNumber    isSpecial
    8           1            0
    2           2            0
    10          3            0
    1           4            1
    9           5            0
    3           6            0
    4           7            0
    6           8            0
    5           9            0
    7           10            0
    

    期望的输出:

    personID    MinRowNumber isSpecial rowNumber1 rowNumber2 rowNumber3
    8           1            0         1
    2           2            0         2
    1           3            1         4          7          3
    9           5            0         5
    3           6            0         6
    6           8            0         8
    5           9            0         9
    7           10           0         10
    4           11           0         11         
    10          12           0         12
    

4 个答案:

答案 0 :(得分:1)

您可以使用计数表和一些聚合来执行此操作。这些方面的东西。

WITH
cteTally(N) AS (select n from (values (1),(2),(3))dt(n))

select personID
    , MAX(RowNumber)
from 
(
    SELECT personID
        , ROW_NUMBER() OVER(ORDER BY NEWID()) as RowNumber
    FROM folks f
    join cteTally t on t.N <= case when f.IsSpecial = 1 then 3 else 1 end
) x
group by x.personID

- 编辑 -

你说你可能想要所有行而不仅仅是MAX。这是你如何做到的。

WITH
cteTally(N) AS (select n from (values (1),(2),(3))dt(n))

SELECT personID
    , ROW_NUMBER() OVER(ORDER BY NEWID()) as RowNumber
FROM folks f
join cteTally t on t.N <= case when f.IsSpecial = 1 then 3 else 1 end

答案 1 :(得分:1)

我认为您可以使用UNION方法,但只应用NEWID()一次:

create table folks (personID int, isSpecial int)

insert into folks values (1,1);
insert into folks values (2,0);
insert into folks values (3,0);
insert into folks values (4,0);
insert into folks values (5,0);
insert into folks values (6,0);
insert into folks values (7,0);
insert into folks values (8,0);
insert into folks values (9,0);
insert into folks values (10,0);

select * from folks;

select
   personID,
   min(rownumber) as min_rownumber
from
  (SELECT 
      personID, 
      ROW_NUMBER() OVER(ORDER BY NEWID()) as RowNumber
   FROM 
      (select personID from folks 
      union all
      select personID from folks where isSpecial = 1 
      union all
      select personID from folks where isSpecial = 1) u
   ) r
group by
   personID

SQLFiddle

答案 2 :(得分:1)

解决任务的正确方法是这样。

让我们有O普通人加上S特殊人士。每个普通人都有一次机会,每个特殊的人有3次机会。我们应该生成在O范围内均匀分布的S * 3[1 .. O+S*3]个随机数,然后根据他们获得的数字对所有人进行排序。特殊人物将在此有序列表中出现3次,普通人只会出现一次。

这是执行此操作的查询。使用示例数据创建表的代码如下所示。 CTE_Numbers只是一个包含三个数字的表格。如果您想为特殊人员提供不同的机会,请更改此查询。 CTE列出所有普通人一次加上所有特殊人物三次。 CTE_rn为每行分配一个随机数。每个特殊的人都有三个随机数。由于每个特殊人员在CTE_rn中有三行,因此PersonID为最终查询组,并为每个具有最小数量的特殊人员留下一行。为了更好地理解它的工作原理,请检查CTE_rn

的中间结果
WITH
CTE_Numbers
AS
(
    SELECT Number
    FROM (VALUES (1),(2),(3)) AS N(Number)
)
,CTE
AS
(
    -- list ordinary people only once
    SELECT PersonID,IsSpecial
    FROM @T
    WHERE IsSpecial = 0

    UNION ALL

    -- list each special person three times
    SELECT PersonID,IsSpecial
    FROM @T CROSS JOIN CTE_Numbers
    WHERE IsSpecial = 1
)
,CTE_rn
AS
(
    SELECT
        PersonID,IsSpecial
        ,ROW_NUMBER() OVER(ORDER BY CRYPT_GEN_RANDOM(4)) AS rn
    FROM CTE
)
SELECT
    PersonID,IsSpecial
    ,MIN(rn) AS FinalRank
FROM CTE_rn
GROUP BY PersonID,IsSpecial
ORDER BY FinalRank;

<强>结果

PersonID    IsSpecial    FinalRank
9           0            1
2           0            2
1           1            3
10          0            4
8           0            5
5           0            6
3           0            7
7           0            9
4           0            10
6           0            12

注意,FinalRank的值如何从1到12(不是10),值8和11没有显示。特别的人有他们。特殊的人得到随机数3,8,11,最终结果只包含这三个中的最小值。

第一个变种。它有效,但结果有偏差。

非常直截了当。生成随机行数三次,将它们连接在一起,并为普通人选择第一个随机数的结果,特殊人员选择最少三次运行。

没有人承诺为NEWID提供任何特定的随机数分布,所以在这种情况下你最好不要使用它。在此示例中,我使用了CRYPT_GEN_RANDOM

我使用相同的查询在三个单独的CTE中获取随机数,而不是在连接中使用相同的CTE,以确保计算三次。如果您使用单个CTE,服务器可能足够聪明,只能计算一次随机数,而不是三次,这不是我们在这里需要的。我们需要在此处拨打CRYPT_GEN_RANDOM 30次电话。

DECLARE @T TABLE (PersonID int, IsSpecial bit);

INSERT INTO @T(PersonID, IsSpecial) VALUES
(1 , 1),
(2 , 0),
(3 , 0),
(4 , 0),
(5 , 0),
(6 , 0),
(7 , 0),
(8 , 0),
(9 , 0),
(10, 0);

WITH
CTE1
AS
(
    SELECT PersonID, IsSpecial,
        ROW_NUMBER() OVER(ORDER BY CRYPT_GEN_RANDOM(4)) AS rn
    FROM @T
)
,CTE2
AS
(
    SELECT PersonID, IsSpecial,
        ROW_NUMBER() OVER(ORDER BY CRYPT_GEN_RANDOM(4)) AS rn
    FROM @T
)
,CTE3
AS
(
    SELECT PersonID, IsSpecial,
        ROW_NUMBER() OVER(ORDER BY CRYPT_GEN_RANDOM(4)) AS rn
    FROM @T
)
,CTE_All
AS
(
SELECT
    CTE1.PersonID
    ,CTE1.IsSpecial
    ,CTE1.rn AS rn1
    ,CTE2.rn AS rn2
    ,CTE3.rn AS rn3
    ,CA.MinRN
FROM
    CTE1
    INNER JOIN CTE2 ON CTE2.PersonID = CTE1.PersonID
    INNER JOIN CTE3 ON CTE3.PersonID = CTE1.PersonID
    CROSS APPLY
    (
        SELECT MIN(A.rn) AS MinRN
        FROM (VALUES (CTE1.rn), (CTE2.rn), (CTE3.rn)) AS A(rn)
    ) AS CA
)
SELECT
    PersonID
    ,IsSpecial
    ,CASE WHEN IsSpecial = 0 
    THEN rn1 -- a person is not special, he gets random rank from the first run only
    ELSE MinRN -- a special person, he gets a rank that is minimum of three runs
    END AS FinalRank
    ,rn1
    ,rn2
    ,rn3
    ,MinRN
FROM CTE_All
ORDER BY FinalRank;

结果集

PersonID    IsSpecial    FinalRank    rn1    rn2    rn3    MinRN
8           0            1            1      1      1      1
6           0            2            2      7      2      2
5           0            3            3      5      6      3
1           1            3            9      3      4      3
4           0            4            4      6      3      3
7           0            5            5      9      10     5
3           0            6            6      8      9      6
2           0            7            7      2      8      2
10          0            8            8      10     5      5
9           0            10           10     4      7      4

你可以看到特殊的人可以(偶然)获得与普通人相同的等级。你可以进一步支持特殊的人,并确保他们在这种情况下出现在普通人面前。只需将ORDER BY改为ORDER BY FinalRank, IsSpecial DESC

答案 3 :(得分:0)

如何使用UNION?

SELECT personID, ROW_NUMBER()
    OVER(ORDER BY NEWID()) as RowNumber
FROM folks
WHERE isSpecial = 0

UNION ALL

SELECT personID, MAX(RN)
FROM (    
    SELECT personID, ROW_NUMBER() AS 'RN'
        OVER(ORDER BY NEWID()) as RowNumber
    FROM folks
    WHERE isSpecial = 1

    UNION ALL

    SELECT personID, ROW_NUMBER()
        OVER(ORDER BY NEWID()) as RowNumber
    FROM folks
    WHERE isSpecial = 1

    UNION ALL

    SELECT personID, ROW_NUMBER()
        OVER(ORDER BY NEWID()) as RowNumber
    FROM folks
    WHERE isSpecial = 1    
    )
GROUP BY personID