SQL中的优先级队列

时间:2013-05-21 00:53:11

标签: sql sql-server

我正在实施一个有多个优先级的排队系统 我想要一个可以返回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级别进行过滤?

4 个答案:

答案 0 :(得分:0)

您总是可以编写一个存储过程,在for循环中执行此操作(3次,每个优先级一个,从最低开始),并在每次迭代时动态调整下一次迭代中返回的值的数量(更高的优先级)如果这个中没有足够的。

动态我的意思是:

SELECT TOP (@count) * FROM SomeTable

如果在上一次迭代中有必要调整count

在应用程序逻辑中使用它的问题是你要么需要获取更多数据(是TOP中使用的最大计数器的3倍),这样你就可以有足够的数据来填充你的插槽,或者你必须再次与您的数据库通信。

对于小数字,预防性的额外提取并不重要。我的偏好是将它放在存储过程中。

但是有很多变量在这里起作用:你获取的各个行的大小,你搜索的表的大小及其索引,程序体系结构,网络配置,计数器值,优先级数等等。等等。

答案 1 :(得分:0)

假设您使用的是SQL Server 2005或更高版本,嵌套CTE和排名函数的组合应该能够解决问题:

;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

答案 2 :(得分:0)

此查询将为您提供最多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

编辑:对不起,我刚注意到你想要填写空白的最高优先级。不应该太难修改,但如果你有问题留下评论,我会帮你解决。

答案 3 :(得分:0)

我已经采用了基于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个高优先级项并不重要 - 这无关紧要,因为如果没有足够的低优先级值可以找到高优先级项目。