SQL 中的优先级队列
Posted
技术标签:
【中文标题】SQL 中的优先级队列【英文标题】:Priority queue in SQL 【发布时间】:2013-05-21 00:53:11 【问题描述】:我正在实施一个具有多个优先级的排队系统。 我想要一个查询,它可以返回 X 行,每个优先级至少 Y 行。
例如:
假设队列有 3 个优先级(高、中和低),我希望每个优先级分别有 3、2 和 1 行。
如果表格如下所示:
-----------------
| Id | Priority |
-----------------
| 1 | High |
| 2 | High |
| 3 | High |
| 4 | High |
| 5 | Medium |
| 6 | Medium |
| 7 | Low |
-----------------
联合起来的三个简单查询将返回 (1, 2, 3, 5, 6, 7)。
SELECT TOP 3 Id FROM tbl WHERE Priority = 'High' UNION
SELECT TOP 2 Id FROM tbl WHERE Priority = 'Medium' UNION
SELECT TOP 1 Id FROM tbl WHERE Priority = 'Low'
但是,当表没有包含足够的特定优先级时,就会出现问题:
-----------------
| Id | Priority |
-----------------
| 1 | High |
| 2 | High |
| 3 | High |
| 4 | High |
| 5 | Medium |
| 6 | Low |
| 7 | Low |
-----------------
我想让它返回 (1, 2, 3, 4, 5, 6)。 使用最高优先级来填补空白(在这种情况下,使用第 4 高行,因为没有足够的媒介)。
是否有可以适应这种情况的查询,或者我会在我的应用程序内部而不是在 SQL 级别进行更好的过滤?
【问题讨论】:
您会一直分别需要 3、2、1,还是会以某种方式对其进行参数化? @Damien_The_Unbeliever:数量将是静态的(不一定是 3、2、1),因此不需要将它们作为参数。 【参考方案1】:您始终可以编写一个在 for 循环中执行此操作的存储过程(3 次,每个优先级一个,从最低优先级开始),并且在每次迭代时,动态调整要在下一次迭代中返回的值的数量(更高优先级)如果这一项不够。
动态地我的意思是:
SELECT TOP (@count) * FROM SomeTable
count
在之前的迭代中根据需要进行了调整。
在应用程序逻辑中使用它的问题是,您要么必须获取更多数据(TOP
中使用的最大计数器的 3 倍),这样您才能有足够的数据来填充您的插槽,或者您必须再次与您的数据库通信。
对于小数字,预防性的额外提取并不重要。我的偏好是将它放在存储过程中。
但这里有许多变量起作用:您获取的单个行的大小、您搜索的表及其索引的大小、您的程序架构、网络配置、计数器值、优先级数等。等。
【讨论】:
【参考方案2】:假设您使用的是 SQL Server 2005 或更高版本,嵌套 CTE
s 和排名函数的组合应该能够解决问题:
;with A as
(
select top 3 * from tbl where priority = 'High'
),
A1 as
(
select id, priority from (
select * from A
union all
select top 2 id, priority from (select *,
ROW_NUMBER() over (order by case when priority = 'Medium' then 1 else 2 end) as ranker
from tbl where priority in ('High', 'Medium') and id
not in (select id from A))Z order by ranker asc
) X
),
A2 as (
select id, priority from (
select * from A1
union all
select top 1 id, priority from (select *,
ROW_NUMBER() over (order by case when priority = 'Low' then 1 else 2 end) as ranker
from tbl where priority in ('High', 'Low') and id
not in (select id from A1))Z order by ranker asc
) X
)
select * from A2
order by id
【讨论】:
【参考方案3】:此查询将为您提供最多 3 个低优先级、最多 6 个中等优先级和最多 9 个高优先级。如果没有足够的低优先级项目来填满队列,则使用下一个最高优先级。
;WITH PriorityRanks AS (
SELECT
ID,
Priority,
ROW_NUMBER() OVER (PARTITION BY Priority ORDER BY ID ASC) as [Rank]
FROM PriorityQueue
)
, LowPriority AS (
SELECT
ID,
Priority,
ROW_NUMBER() OVER (ORDER BY ID ASC) as [Rank]
FROM PriorityRanks
WHERE Priority = 'Low'
AND [Rank] <= 3
)
, MediumPriority AS (
SELECT
ID,
Priority,
ROW_NUMBER() OVER (ORDER BY ID ASC) as [Rank]
FROM PriorityRanks pq
WHERE pq.Priority = 'Medium'
AND [Rank] <= 6
- (SELECT ISNULL(MAX([Rank]), 0) FROM LowPriority)
)
, HighPriority AS (
SELECT
ID,
Priority
FROM PriorityRanks pq
WHERE pq.Priority = 'High'
AND [Rank] <= 9
- (SELECT ISNULL(MAX([Rank]), 0) FROM LowPriority)
- (SELECT ISNULL(MAX([Rank]), 0) FROM MediumPriority)
)
SELECT ID, Priority FROM LowPriority
UNION ALL
SELECT ID, Priority FROM MediumPriority
UNION ALL
SELECT ID, Priority FROM HighPriority
编辑:抱歉,我刚刚注意到您希望最高优先级来填补空白。修改应该不会太难,但是如果您有问题,请发表评论,我会帮助您。
【讨论】:
【参考方案4】:我采用了基于 CTE 的方法,希望每个步骤都能展示我所遵循的思考过程:
declare @t table (Id int not null, Priority varchar(6) not null)
insert into @t (Id,Priority) values
(1,'High'),
(2,'High'),
(3,'High'),
(4,'High'),
(5,'Medium'),
(6,'Low'),
(7,'Low')
--We want 6 rows. We'd like to get 1 low, if available, and 2 mediums, if available
; with NumberedRows as (
select
Id,Priority,
ROW_NUMBER() OVER (PARTITION BY Priority ORDER BY Id) as rn,
CASE Priority WHEN 'High' then 1 WHEN 'Medium' THEN 2 ELSE 3 END as NumPri
from @t
), Selection as (
select Id, Priority, NumPri,
CASE
WHEN NumPri = 3 and rn <= 1 THEN 1
WHEN NumPri = 2 and rn <= 2 THEN 2
WHEN NumPri = 1 THEN 3
WHEN NumPri = 2 THEN 4
ELSE 5 --Low, rn>1
END as Preference
from NumberedRows
), Chosen as (
select top 6 * from Selection order by Preference
)
select * from Chosen order by NumPri,Id
(附带说明,请注意,我的代码顶部的示例数据所采用的格式与您问题中的表格一样多 - 但它实际上可以在脚本中使用)
如果要选择的项目数量不同,那么您需要更改:
WHEN NumPri = 3 and rn <= 1 THEN 1
WHEN NumPri = 2 and rn <= 2 THEN 2
(更改 rn
值)和:
select top 6 * from Selection order by Preference
(将其更改为总共需要多少个)
另外请注意,您说想要 3 个高优先级项目并不重要 - 没关系,因为如果没有足够的低优先级值可以找到,高优先级项目将用作填充物。
【讨论】:
以上是关于SQL 中的优先级队列的主要内容,如果未能解决你的问题,请参考以下文章