【问题标题】:SQL Server : return min number of rowsSQL Server:返回最小行数
【发布时间】:2018-01-26 13:50:34
【问题描述】:

在我们构建新版本时,我正在寻找一种快速解决方法来解决旧系统的问题。

概述

清单页面检查数据库中每个用户的一定数量的条目。然后页面根据登录的用户类型检查每个引用表的 count 条目,该引用表包含 必需 条目数(“RequiredRows”)。然后页面迭代用户的RequiredRows 的条目。

问题

当返回的条目数小于RequiredRows 时,显然会失败。

很明显

我知道你们中的大多数人,就像我一样,会说更新页面以不以这种方式进行迭代。不幸的是,目前这是不可能的,而且如果不详细说明,总体上要复杂得多。

我需要什么

我需要一个可以根据RequiredCount 返回相同数量或更多记录的查询。

这是一个例子:

  • User1 以公共用户身份登录
  • User2 以私人用户身份登录
  • 公众用户需提交3个参赛作品
  • 私人用户需提交 2 个条目

场景

  1. 如果 User1 有 0 个条目,则查询应该什么也不返回,没有行。
  2. 如果 User1 有 1 个条目,则查询应返回 1 行数据,2 行 NULL
  3. 如果 User1 有 2 个条目,则查询应返回 2 行数据和 1 行 NULL
  4. 如果 User1 有 3 个条目,则查询应返回 3 行数据。
  5. 如果 User2 有 0 个条目,则查询不应返回任何内容,不返回任何行。
  6. 如果 User2 有 1 个条目,则查询应返回 1 行数据和 1 行 NULL
  7. 如果 User2 有 2 个条目,则查询应返回 2 行数据。
  8. 如果 User2 有 3 个条目,则查询应返回 3 行数据。

数据库结构

Id    User    Type
-------------------
1     User1   1
2     User2   2

Id    UserType    RequiredRows
--------------------------------
1     Public      3
2     Private     2

Id    UserId    Entry
----------------------
1     1         Test

期望的结果

当查询 User1 的条目数时,我需要以下结果。

EntryId
--------
1
null
null

查询User2时:

EntryId
--------
null
null

注意事项

虽然这只是一个小样本数据,但所需条目的数量在 0-50 之间变化,具体取决于不同的用户类型。

【问题讨论】:

  • 使用IFUNION 获得您需要的结果。
  • @Sami 谢谢。我试图弄清楚这一点。不知道如何在我的场景中使用UNION。您可以提供的任何小代码都会有所帮助。同时我也会搜索。
  • 是否有超过 2 个用户?还是只有那两个?
  • @Sami 还有很多,这只是一个小样本
  • 这是一个数据库问题。参考(和定义)表格(不是页面)。

标签: sql sql-server tsql


【解决方案1】:

这是一种关系较少但冗长的方法:

DECLARE @User TABLE (
Id INT, [User] VARCHAR(255), [UserTypeId] INT
)
INSERT INTO @User
VALUES (1,'User1',1),(2,'User2',1),(3,'User3',2),(4,'User4',1),(5,'User5',2),(6,'User6',3)

DECLARE @UserType TABLE (
Id INT, UserType VARCHAR(20), RequiredRows INT
)
INSERT INTO @UserType
VALUES (1,'Public',3),(2,'Private',2),(3,'Untrusted',50)

DECLARE @Entries TABLE (
    Id INT IDENTITY(1,1), UserId INT,  [Entry] VARCHAR(255)
)
INSERT INTO @Entries
VALUES (1,'Test'),(2,'Test'),(2,'Test 2'),(3,'Test')
, (5,'MoreTests1'),(5,'Test2'),(5,'Test 3'),(5,'Test 4')
, (6,'SomeTest1'),(6,'SomeTest2'),(6,'SomeTest3'),(6,'SomeTest4'),(6,'SomeTest5')
, (6,'SomeTest6'),(6,'SomeTest7'),(6,'SomeTest8'),(6,'SomeTest9'),(6,'SomeTest10')

DECLARE @UserId INT = 1

DECLARE @RequiredRows INT = (
    SELECT RequiredRows
    FROM @User u
    INNER JOIN @UserType ut
    ON u.UserTypeId = ut.Id
    WHERE u.Id = @UserId)

DECLARE @ExistingRows INT = (SELECT COUNT(*) FROM @Entries WHERE UserId = @UserId)
DECLARE @MissingRows INT = (SELECT CASE WHEN @RequiredRows < @ExistingRows OR @ExistingRows = 0 THEN 0 ELSE @RequiredRows - @ExistingRows END)

;WITH fill AS (
    SELECT EmptyId FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) gen(EmptyId)
)
    SELECT Id as EntryId
    FROM @Entries WHERE UserId = @UserId
    UNION ALL
    SELECT TOP(@MissingRows) a.EmptyId
    FROM fill a, fill b, fill c

基本上它确定所需的行,如果不存在条目,则为 0 和现有行。剩下的就是选择条目并填充,如有必要,使用空元组。

您可以将场景 2@UserId 更改为 1,场景 3@UserId = 2,场景 6@UserId = 3 @UserId = 4 用于场景 1,但您可以轻松添加数据以覆盖其他测试用例。

编辑:添加了更多测试数据并重写了查询以最大限度地减少数据读取量。

编辑2: 这是一个紧凑的recursive cte,它使用ROW_NUMBER 枚举条目并确保它具有所需的行数。

DECLARE @User TABLE (
Id INT, [User] VARCHAR(255), [UserTypeId] INT
)
INSERT INTO @User
VALUES (1,'User1',1),(2,'User2',1),(3,'User3',2),(4,'User4',1),(5,'User5',2),(6,'User6',3)

DECLARE @UserType TABLE (
Id INT, UserType VARCHAR(20), RequiredRows INT
)
INSERT INTO @UserType
VALUES (1,'Public',3),(2,'Private',2),(3,'Untrusted',50)

DECLARE @Entries TABLE (
    Id INT IDENTITY(1,1), UserId INT,  [Entry] VARCHAR(255)
)
INSERT INTO @Entries
VALUES (1,'Test'),(2,'Test'),(2,'Test 2'),(3,'Test')
, (5,'MoreTests1'),(5,'Test2'),(5,'Test 3'),(5,'Test 4')
, (6,'SomeTest1'),(6,'SomeTest2'),(6,'SomeTest3'),(6,'SomeTest4'),(6,'SomeTest5')
, (6,'SomeTest6'),(6,'SomeTest7'),(6,'SomeTest8'),(6,'SomeTest9'),(6,'SomeTest10')

DECLARE @UserId INT = 1

;WITH rcte AS (
    SELECT ROW_NUMBER() OVER (ORDER BY e.Id) AS RN
        , e.Id
        , ut.RequiredRows
    FROM @Entries e 
    INNER JOIN @User AS u ON e.UserId = u.Id
    INNER JOIN @UserType AS ut ON u.UserTypeId = ut.Id
    WHERE UserId = @UserId
    UNION ALL
    SELECT RN + 1, NULL, RequiredRows
    FROM rcte r
    WHERE r.RN + 1 <= RequiredRows
)
SELECT RN, MAX(Id) AS EntryId
FROM rcte 
GROUP BY RN

【讨论】:

  • +1!谢谢!这也可以根据需要工作!虽然以我目前的“专业知识”水平来看,这似乎更清晰,但我仍然更喜欢@RossBush 的CTE 版本。
  • 初学者将开始编写简单但有缺陷的代码。随着他的进步,他学习了原理、模式和对框架的深入了解,并开始过度设计事物。编写干净、简单、工作和可维护的解决方案需要一些经验。上面的语句不需要弄清楚递归 cte 是什么,它的用途或外连接如何生成空值。
  • 我并不是说罗斯解决方案不好,坦率地说,我也是从递归开始的,但它变得复杂,可以处理所有场景。我承认他的查询本身很优雅,但它在某些情况下失败了。使用更多数据,至少在我的机器上,他的查询为多个用户返回错误的条目 ID。
  • 你是对的!我也对此进行了测试。虽然对于我们的案例,不需要确切的 ID,但我会说 @RossBush 的解决方案不好,除非已修复。
  • 这里是另一个测试用例:rextester.com/CZBEF67902 其中 userId 5 应该有 4 个条目
【解决方案2】:

这可以工作。

DECLARE @User TABLE(UserID INT,UserName NVARCHAR(20), UserTypeID INT)
DECLARE @UserType TABLE(UserTypeID INT, UserTypeName NVARCHAR(50), RequiredEntries INT)
DECLARE @UserEntry TABLE(UserEntryID INT, UserID INT, EntryName NVARCHAR(50))
INSERT @User SELECT 1 , 'User1', 1
INSERT @User SELECT 2 , 'User2', 2
INSERT @UserType SELECT 1, 'Public', 3
INSERT @UserType SELECT 2, 'Private', 2
INSERT @UserEntry SELECT 1,1,'Test'


DECLARE  @UserID INT = 1
;
WITH RequiredEntries AS(
   SELECT UserTypeID, RowNumber=1, RequiredEntries FROM @UserType UT
   UNION ALL
   SELECT UserTypeID,RowNumber=RowNumber + 1,RequiredEntries FROM RequiredEntries IR  WHERE IR.RowNumber < IR.RequiredEntries
),
UserEntries AS(
    SELECT UserID,EntryNumber=COUNT(*)
    FROM @UserEntry UE
    GROUP BY UserID,EntryName
),
UserTotals AS(
    SELECT UserID,TotalEntries=COUNT(*)
    FROM @UserEntry UE
    GROUP BY UserID
)
SELECT 
    EntryNumber
FROM   
    @User U 
    INNER JOIN RequiredEntries RE ON RE.UserTypeID=U.UserTypeID
    LEFT OUTER JOIN UserEntries UE ON UE.UserID=U.UserID AND EntryNumber=RE.RowNumber
    INNER JOIN UserTotals UT ON UT.UserID=U.UserID AND UT.TotalEntries > 0 --1 and 5
WHERE
    U.UserID=@UserID
ORDER BY 
    RE.RowNumber

【讨论】:

  • 哇!我刚试过这个,它似乎工作得很好!我将根据我的真实数据对此进行测试。
  • 您先生,真是个天才!我也尝试过使用CTE,但我没那么聪明……但……希望!
  • 劳尔·塞巴斯蒂安-Doh!
  • 为了解决 1 和 5,我将创建另一个按用户分组的 cte 来汇总条目。然后将其与主查询进行内部连接并粘贴 where 子句以限制 where entrycount> 0。我现在太累了。
  • 有趣,当我测试时,我认为我确实看到 #1 和 #5 是正确的。可能我也太累了吧! ;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-08
  • 2012-12-15
  • 1970-01-01
  • 2010-12-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多