需要帮助编写SQL以基于复杂规则检索列表

时间:2011-04-10 04:15:51

标签: sql join

似乎此标题之前已被多次使用,遗憾的是我不知道如何描述我的问题。所以,首先,如果你有一个更好的标题建议,这将有助于未来的搜索者,那就开火了!

无论如何,我的问题是尝试编写SQL以根据应用于以下模式的一组规则返回结果集:

TABLE: Tests
COLUMNS: ID (PK), Name

TABLE: TestVersions    
COLUMNS: TestID (PK), Version (PK), IsActive

TABLE: TestSessions
COLUMNS: TestID (PK), TestVersion (PK), UserID (PK), Iteration (PK), Completed, CompletionDate

[Tests]上的[Tests]和[TestVersions]之间存在关系。[ID] = [TestVersions]。[TestID]。在[TestSessions]上[TestSessions]和[TestVersions]之间也存在关系。[TestID] = [TestVersions]。[TestID]和[T​​estSessions]。[TestVersion] = [TestVersions]。[Version]

结果集应返回[Tests]。[ID],[Tests]。[Name]和[TestVersions]。[Version]基于以下规则:

  1. 任何在TestSessions中具有关联记录的测试,其中Complete为false。
  2. TestSessions中没有关联记录但IsActive的任何测试的最大版本为真。
  3. 这个是复杂的。如果Test在TestSessions中关联了所有已完成的记录(Complete为true)并且TestVersions中至少有一条关联记录且IsActive为true,我需要验证最近的CompletionDate至少是30天前,如果是,从TestVersions返回最高版本。
  4. 希望这是有道理的。

2 个答案:

答案 0 :(得分:0)

这是一个创建临时表的快速脚本,用数据填充它们,并向您展示我如何处理您遇到的问题。让我先说这种东西有时最好留给业务逻辑层。此外,我不确定您的描述是否非常准确,因为似乎存在一些不一致。话虽如此,我希望你能带走如何处理你的问题,把它分解成部分并解决它。不要担心预先优化,只需编写干净和逻辑代码。优化器会告诉您是否犯了任何错误。这假定SQL Server 2008:

declare @Tests table
(
    ID int,
    Name varchar(100)
)

declare @TestVersions table
(
    TestID int,
    Version int,
    IsActive bit
)

declare @TestSessions table
(
    TestID int,
    TestVersion int,
    UserID varchar(100),
    Iteration int,
    Completed bit,
    CompletionDate date
)

insert into @Tests
select 1, 'one'     union all
select 2, 'two'     union all
select 3, 'three'   union all
select 4, 'four'    union all
select 5, 'five'

insert into @TestVersions
select 1, 1, 0      union all
select 2, 1, 1      union all
select 3, 1, 0      union all
select 4, 1, 0      union all
select 5, 1, 1      union all
select 1, 2, 0      union all
select 2, 2, 0      union all
select 3, 2, 1      union all
select 4, 2, 0      union all
select 1, 3, 1      union all
select 2, 3, 1

insert into @TestSessions
select 1, 1, 'a', 1, 1, GETDATE()-101       union all
select 2, 1, 'b', 1, 1, GETDATE()-11        union all
select 3, 1, 'c', 1, 0, null                union all
select 4, 1, 'd', 1, 1, GETDATE()-103       union all
select 5, 1, 'e', 1, 1, GETDATE()-101       union all
select 1, 2, 'f', 1, 1, GETDATE()-15        union all
select 2, 2, 'g', 1, 0, null                union all
select 3, 2, 'h', 1, 1, GETDATE()-17        union all
select 4, 2, 'i', 1, 0, null                union all
select 1, 3, 'j', 2, 1, GETDATE()-109       union all
select 2, 3, 'k', 2, 1, GETDATE()-101       union all
select 2, 1, 'l', 3, 1, GETDATE()-120       union all
select 3, 1, 'm', 1, 1, GETDATE()-11        union all
select 4, 1, 'n', 1, 1, GETDATE()-140       union all
select 5, 1, 'a', 1, 0, null                union all
select 1, 2, 'b', 1, 1, GETDATE()-160       union all
select 2, 2, 'c', 2, 0, null                union all
select 3, 2, 'd', 1, 1, GETDATE()-17        union all
select 4, 2, 'e', 1, 0, null                union all
select 1, 3, 'f', 2, 1, GETDATE()-4         union all
select 2, 3, 'g', 3, 1, GETDATE()-101


 select Id
        ,Name
        ,Version
   from @Tests t
            inner join
        @TestVersions v on t.ID = v.TestID

  -- any test with a session with completed = false
  where exists
        (select *
           from @TestSessions s1
          where s1.Completed = 0
            and s1.TestID = t.ID
            and s1.TestVersion = v.Version
        )
     or (
  -- any max version of a test which has no sessions
            not exists
            (select *
               from @TestSessions s1
              where s1.TestID = t.ID
                and s1.TestVersion = v.Version 
            )
            and v.Version =
            (select MAX(Version)
               from @TestVersions v2
              where v2.TestID = t.ID
            )
        )
     or (
     -- no uncompleted sessions
            not exists
            (select *
               from @TestSessions s1
              where s1.Completed = 0
                and s1.TestID = t.ID
                and s1.TestVersion = v.Version
            )
     -- we're already inner joining to versions, so we just need to check IsActive
            and IsActive = 1
     -- the most recent completion date is at least 30 days ago (not quite 30 days ago but i'm lazy)
            and GETDATE() - 30 <=
            (select MAX(CompletionDate)
               from @TestSessions s2
              where s2.TestID = t.ID
                and s2.TestVersion = v.Version
            )
     -- not sure what you mean by "return", but I'm sure you can figure out how to project what you need
        )

答案 1 :(得分:0)

这是我最终得到的结果(基于Milimetric的建议):

SELECT   Tests.ID, Tests.Name, TestVersions.Version

FROM     Tests INNER JOIN
         TestVersions ON Tests.ID = TestVersions.TestID

WHERE    (
         EXISTS (SELECT *
                 FROM TestSessions
                 WHERE Complete = 0
                       AND TestID = Test.ID
                       AND TestVersion = TestVersions.Version)
         )
         OR
         (
         NOT EXISTS (SELECT *
                     FROM TestSessions
                     WHERE TestID = Tests.ID
                           AND TestVersion = TestVersions.Version)
         AND (TestVersions.Version = (SELECT MAX(Version)
                                      FROM TestVersions
                                      WHERE TestID = Tests.ID))
         AND TestVersions.IsActive = 1
         AND TestVersions.StartDate <= GetDate()
         AND (TestVersions.EndDate IS NULL OR TestVersions.EndDate >= GetDate())
         )
         OR
         (
         EXISTS (SELECT *
                 FROM TestSessions
                 WHERE Complete = 1
                       AND TestID = Tests.ID
                       AND TestVersion = TestVersions.Version)
         AND (TestVersions.Version = (SELECT MAX(Version)
                                      FROM TestVersions
                                      WHERE TestID = Tests.ID))
         AND (TestVersions.IsActive = 1)
         AND (TestVersions.StartDate <= GetDate())
         AND (TestVersions.EndDate IS NULL OR TestVersions.EndDate >= GetDate())
         AND (GetDate() >= (SELECT DATEADD(day, 30, MAX(TestSessions.CompletionDate))
                            FROM TestSessions
                            WHERE TestSessions.TestID = Tests.ID
                                  AND TestSessions.TestVersion = TestVersions.Version))