我有一个尴尬的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种组合。
我可以想象它会是某种交叉连接。我已经尝试了使用窗口函数和交叉应用等各种解决方案,但它们都有一些缺陷。他们都往往被绊倒,因为每个学生都有不同数量的课程,每门课程都有不同数量的课程。
欢呼您提供任何帮助!如果有必要的话,我可以粘贴在我查询的粗糙的混乱中!
亚历
答案 0 :(得分:5)
对于固定数量的资格,答案相对简单 - 以前答案中的CROSS JOIN
选项将完美运作。
但是,如果资格数量未知,或将来可能会发生变化,则硬编码四个CROSS JOIN
操作将无效。在这种情况下,答案会变得更复杂。
对于少量行,您可以使用this answer on 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;
}
(基于this function。)
一旦功能到位,生成有效组合列表相当容易:
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
;
此查询将生成一个包含43个有效组合的172行列表:
1 Biology A
1 Chemistry B
1 Computing D
1 Tutorial E
2 Biology A
2 Chemistry B
2 Computing F
2 Tutorial E
...
如果您需要Combinations
函数的TSQL版本:
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没有堆栈或数组,所以我不得不用表变量伪造它们。
答案 1 :(得分:0)
一个巨大的交叉加入?
select * from tablea,tableb,tablec,tabled
这实际上适用于你需要的东西,其中tablea是生物学条目,b是chem,c是计算,d是教程。您可以更好地指定连接:
select * from tablea cross join tableb cross join tablec cross join tabled.
从技术上讲,两个语句都是相同的...这是所有交叉连接,所以上面的逗号版本更简单,在更复杂的查询中,您将要使用第二个语句,这样您就可以非常明确地指向哪里你是交叉加入vs内/左连接。
您可以替换&#39;表格&#39;具有select union语句的条目,用于以查询形式提供您要查找的值:
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)
答案 2 :(得分:0)
没有真正看到问题,但此sqlFiddle是否有效?
答案 3 :(得分:0)
你应该可以使用一个简单的联合,但是每个联合选择只有一个类类型的过滤器,所以你不能得到 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%'
答案 4 :(得分:0)
让我们使用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可能会有所不同,具体取决于您使用的数据库。例如!=可能是&lt;&gt;。
希望这有帮助!