簡體   English   中英

使用SQL查詢生成時間表的所有可能組合

[英]Generating all possible combinations of a timetable using an SQL Query

我有一個尷尬的SQL拼圖,它擊敗了我。

我正在嘗試生成學生單元的可能配置列表,以便我可以將他們的課程選擇納入時間表。 學生可能的資格和學習列表如下:

Biology A
Biology C 
Biology D 
Biology E 

Chemistry B 
Chemistry C 
Chemistry D 
Chemistry E 
Chemistry F 

Computing D
Computing F 

Tutorial A 
Tutorial B 
Tutorial E 

可以為學生提供可能的積木解決方案

Biology D
Chemistry C 
Computing F 
Tutorial E 

如何查詢上述數據集,為學生生成課程和塊的所有可能組合? 然后,我可以削減列表,刪除那些沖突並選擇一個有效的列表。 我估計在這種情況下總共會有大約120種組合。

我可以想象它會是某種交叉連接。 我已經嘗試了使用窗口函數和交叉應用等各種解決方案,但它們都有一些缺陷。 他們都往往被絆倒,因為每個學生都有不同數量的課程,每門課程都有不同數量的課程。

歡呼為您提供任何幫助! 如果有必要,我可以粘貼在我的查詢的粗糙混亂中!

亞歷克斯

對於固定數量的資格,答案相對簡單 - 以前答案中的CROSS JOIN選項將完美地運作。

但是,如果資格數量未知,或者將來可能發生變化,硬編碼四個CROSS JOIN操作將無法正常工作。 在這種情況下,答案變得更加復雜。

對於少量行,您可以在DBA上使用此答案的變體,它使用2的冪和比特比較來生成組合。 但是,這將限於非常少的行。

對於較大數量的行,您可以使用函數從“N”行生成“M”數字的每個組合。 然后,您可以將其連接回源數據上計算的ROW_NUMBER值以獲取原始行。

生成組合的功能可以寫在TSQL的,但它會使盡可能使用SQLCLR更有意義:

[SqlFunction(
    DataAccess = DataAccessKind.None,
    SystemDataAccess = SystemDataAccessKind.None,
    IsDeterministic = true,
    IsPrecise = true,
    FillRowMethodName = "FillRow",
    TableDefinition = "CombinationId bigint, Value int"
)]
public static IEnumerable Combinations(SqlInt32 TotalCount, SqlInt32 ItemsToPick)
{
    if (TotalCount.IsNull || ItemsToPick.IsNull) yield break;

    int totalCount = TotalCount.Value;
    int itemsToPick = ItemsToPick.Value;
    if (0 >= totalCount || 0 >= itemsToPick) yield break;

    long combinationId = 1;
    var result = new int[itemsToPick];
    var stack = new Stack<int>();
    stack.Push(0);

    while (stack.Count > 0)
    {
        int index = stack.Count - 1;
        int value = stack.Pop();

        while (value < totalCount)
        {
            result[index++] = value++;
            stack.Push(value);

            if (index == itemsToPick)
            {
                for (int i = 0; i < result.Length; i++)
                {
                    yield return new KeyValuePair<long, int>(
                        combinationId, result[i]);
                }

                combinationId++;
                break;
            }
        }
    }
}

public static void FillRow(object row, out long CombinationId, out int Value)
{
    var pair = (KeyValuePair<long, int>)row;
    CombinationId = pair.Key;
    Value = pair.Value;
}

(基於此功能 。)

一旦功能到位,生成有效組合列表相當容易:

DECLARE @Blocks TABLE 
(
    Qualification varchar(10) NOT NULL, 
    Block char(1) NOT NULL, 
    UNIQUE (Qualification, Block)
);

INSERT INTO @Blocks 
VALUES
    ('Biology', 'A'),
    ('Biology', 'C'), 
    ('Biology', 'D'), 
    ('Biology', 'E'),
    ('Chemistry', 'B'), 
    ('Chemistry', 'C'), 
    ('Chemistry', 'D'), 
    ('Chemistry', 'E'), 
    ('Chemistry', 'F'), 
    ('Computing', 'D'),
    ('Computing', 'F'), 
    ('Tutorial', 'A'), 
    ('Tutorial', 'B'), 
    ('Tutorial', 'E') 
;

DECLARE @Count int, @QualificationCount int;

SELECT
    @Count = Count(1),
    @QualificationCount = Count(DISTINCT Qualification)
FROM
    @Blocks
;

WITH cteNumberedBlocks As
(
    SELECT
        ROW_NUMBER() OVER (ORDER BY Qualification, Block) - 1 As RowNumber,
        Qualification,
        Block
    FROM
        @Blocks
),
cteAllCombinations As
(
    SELECT
        C.CombinationId,
        B.Qualification,
        B.Block
    FROM
        dbo.Combinations(@Count, @QualificationCount) As C
        INNER JOIN cteNumberedBlocks As B
        ON B.RowNumber = C.Value
),
cteMatchingCombinations As
(
    SELECT
        CombinationId
    FROM
        cteAllCombinations
    GROUP BY
        CombinationId
    HAVING
        Count(DISTINCT Qualification) = @QualificationCount
    And
        Count(DISTINCT Block) = @QualificationCount
)
SELECT
    DENSE_RANK() OVER(ORDER BY C.CombinationId) As CombinationNumber,
    C.Qualification,
    C.Block
FROM
    cteAllCombinations As C
    INNER JOIN cteMatchingCombinations As MC
    ON MC.CombinationId = C.CombinationId
ORDER BY
    CombinationNumber,
    Qualification
;

此查詢將生成172行的列表,表示43個有效組合:

1  Biology    A
1  Chemistry  B
1  Computing  D
1  Tutorial   E

2  Biology    A
2  Chemistry  B
2  Computing  F
2  Tutorial   E
...

如果您需要TSQL版本的Combinations功能:

CREATE FUNCTION dbo.Combinations
(
    @TotalCount int,
    @ItemsToPick int
)
Returns @Result TABLE
(
    CombinationId bigint NOT NULL,
    ItemNumber int NOT NULL,
    Unique (CombinationId, ItemNumber)
)
As
BEGIN
DECLARE @CombinationId bigint;
DECLARE @StackPointer int, @Index int, @Value int;
DECLARE @Stack TABLE 
( 
    ID int NOT NULL Primary Key,
    Value int NOT NULL
);
DECLARE @Temp TABLE
(
    ID int NOT NULL Primary Key,
    Value int NOT NULL Unique
);

    SET @CombinationId = 1;

    SET @StackPointer = 1;
    INSERT INTO @Stack (ID, Value) VALUES (1, 0);

    WHILE @StackPointer > 0
    BEGIN
        SET @Index = @StackPointer - 1;
        DELETE FROM @Temp WHERE ID >= @Index;

        -- Pop:
        SELECT @Value = Value FROM @Stack WHERE ID = @StackPointer;
        DELETE FROM @Stack WHERE ID = @StackPointer;
        SET @StackPointer -= 1;

        WHILE @Value < @TotalCount
        BEGIN
            INSERT INTO @Temp (ID, Value) VALUES (@Index, @Value);
            SET @Index += 1;
            SET @Value += 1;

            -- Push:
            SET @StackPointer += 1;
            INSERT INTO @Stack (ID, Value) VALUES (@StackPointer, @Value);

            If @Index = @ItemsToPick
            BEGIN
                INSERT INTO @Result (CombinationId, ItemNumber)
                SELECT @CombinationId, Value
                FROM @Temp;

                SET @CombinationId += 1;
                SET @Value = @TotalCount;
            END;
        END;
    END;

    Return;
END

它與SQLCLR版本幾乎相同,除了TSQL沒有堆棧或數組這一事實,所以我不得不用表變量偽造它們。

一個巨大的交叉加入?

select * from tablea,tableb,tablec,tabled

這實際上適用於你需要的東西,其中tablea是生物學條目,b是chem,c是計算,d是教程。 您可以更好地指定連接:

select * from tablea cross join tableb cross join tablec cross join tabled.

從技術上講,這兩個語句是相同的...這是所有交叉連接,所以上面的逗號版本更簡單,在更復雜的查詢中,你將要使用第二個語句,這樣你就可以非常清楚你在哪里交叉加入vs內/左連接。

您可以使用select union語句替換'table'條目,以便以查詢形式提供您要查找的值:

select * from  
(select 'biology' as 'course','a' as 'class' union all  select 'biology','c'  union all select 'biology','d' union all select 'biology','e') a cross join
(select 'Chemistry' as 'course','b' as 'class' union all  select 'Chemistry','c'  union all select 'Chemistry','d' union all select 'Chemistry','e' union all select 'Chemistry','f') b cross join
(select 'Computing' as 'course','a' as 'class' union all  select 'Computing','c') c cross join
(select 'Tutorial ' as 'course','a' as 'class' union all  select 'Tutorial ','b'  union all select 'Tutorial ','e') d

有你的120結果(4 * 5 * 3 * 2)

沒有真正看到問題,但這個sqlFiddle工作嗎?

你應該可以使用一個簡單的聯合,但是每個聯盟的選擇只有一個類型的過濾器,所以你沒有得到BIO,BIO,BIO,BIO,BIO BIO,CHEM,BIO,BIO,BIO等等...

select
      b.course as BioCourse,
      c.course as ChemCourse,
      co.course as CompCourse,
      t.course as Tutorial
  from
      YourTable b,
      YourTable c,
      YourTable co,
      YourTable t
  where
          b.course like 'Biology%'
      AND c.course like 'Chemistry%'
      AND co.course like 'Computing%'
      AND t.course like 'Tutorial%'

讓我們使用Table1是Biology的范例,Table2是化學,Table3是計算,Table4是教程。 每個表都有1列,這是該表或課程的可能塊。 為了獲得所有可能的組合,我們希望將所有表格中的笛卡爾積相合,然后過濾掉具有重復字母的行。

最終結果中的每一列將代表其各自的課程。 這意味着完成的表中的第1列將是生物學的塊字母,即Table1。

所以答案的SQL看起來像這樣。

SELECT * FROM Table1,Table2,Table3,Table4 
WHERE col1 != col2
AND col1 != col3
AND col1 != col4
AND col2 != col3
AND col2 != col4
AND col3 != col4;

注意:這很簡單,可以擴展到每個表有2列的情況,第一列是主題,第二列是塊。 替換只需要在where子句中完成,但如果我忽略這種情況,代碼就更容易遵循。

這有點冗長,但是如果每個學生必須擁有每個表中的一個類,並且最多類的數量是4個類,則這是有效的。 如果學生不必有4個班級,這個解決方案就會崩潰。

所需的確切SQL可能會有所不同,具體取決於您使用的數據庫。 例如!=可能是<>。

希望這可以幫助!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM