4

我正在实施一个具有多个优先级的排队系统。
我想要一个查询,它可以返回 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

4 回答 4

0

假设您使用的是 SQL Server 2005 或更高版本,嵌套CTEs 和排名函数的组合应该能够做到这一点:

;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
于 2013-05-21T01:44:29.920 回答
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

编辑:对不起,我刚刚注意到你想要最高优先级来填补空白。修改应该不会太难,但是如果您有问题,请发表评论,我会帮助您。

于 2013-05-21T01:48:31.880 回答
0

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

动态我的意思是:

SELECT TOP (@count) * FROM SomeTable

count如果有必要在之前的迭代中调整了哪里。

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

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

但是这里有许多变量起作用:您获取的单个行的大小、您搜索的表及其索引的大小、您的程序架构、网络配置、计数器值、优先级的数量等等。

于 2013-05-21T01:09:36.373 回答
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 个高优先级项目并不重要 - 没关系,因为如果没有足够的低优先级值可以找到,高优先级项目将用作填充物。

于 2013-05-21T06:42:09.873 回答