[英]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.