我们有一堆记录,我们按以下方式为每条记录分配一个随机数,其值介于1和记录总数之间:
SELECT personID, ROW_NUMBER()
OVER(ORDER BY NEWID()) as RowNumber
FROM folks
很容易就像馅饼一样。让我们假设一个LOWER(编辑:不高,对不起!)数字更适合客户的目的,并且他们喜欢'随机'元素在这里工作。麻烦的是,客户现在说,有些人很特别,我们希望他们有三次机会,然后将他们的最佳结果保存为他们的号码。'
由于我们不是连续发送数字,而是一次性发送数字,这里的方法似乎是在此查询中选择三次特殊人员,然后获取最高行数。
这类似于,但比这个问题(以及其他类似的人)更多地涉及一步:
Select Records multiple times from table
我不想三次选择所有记录;但我确实想一气呵成;也就是说,我无法指定特殊人数,然后为其他人分配数字 - 它必须是一个查询。
我如何构建一个JOIN(和/或CTE)来对此进行建模,假设我们可以在每条记录上依赖isSpecial = 1
之类的字段?
我怎样才能获得最低数字' (即该记录的第一个row_number
出现)来自我的SELECT
声明中的结果?
平台: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
答案 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
答案 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