Sea*_*n U 5 sql t-sql sql-server
我试图弄清楚如何最好地查询由一个中央表以及记录一对多关系的多个“属性”表(抱歉,这里不确定最好的术语)组成的架构。在业务层中,这些表中的每一个对应于一个可能包含零个或多个元素的集合。
现在,我正在查看的代码通过从主表中获取值列表来检索数据,然后对其进行循环并查询每个“附件”表以填充这些集合。
如果可以的话,我想尝试将其简化为单个查询。我尝试使用多个LEFT JOIN
。但这有效地连接了附件表中值的叉积,从而导致行激增-尤其是当您添加更多连接时。该表包括五个此类关系,因此每个记录返回的行数可能很大,并且几乎完全由冗余数据组成。
这是一些表,数据,我正在使用的查询结构和结果的较小综合示例:
数据库结构和数据:
create table Containers (
Id int not null primary key,
Name nvarchar(8) not null);
create table Containers_Animals (
Container int not null references Containers(Id),
Animal nvarchar(8) not null,
primary key (Container, Animal)
);
create table Containers_Foods (
Container int not null references Containers(Id),
Food nvarchar(8) not null,
primary key (Container, Food)
);
insert into Containers (Id, Name)
values (0, 'box'), (1, 'sack'), (2, 'bucket');
insert into Containers_Animals (Container, Animal)
values (1, 'monkey'), (2, 'dog'), (2, 'whale'), (2, 'lemur');
insert into Containers_Foods (Container, Food)
values (1, 'lime'), (2, 'bread'), (2, 'chips'), (2, 'apple'), (2, 'grape');
Run Code Online (Sandbox Code Playgroud)
耦合到这样的业务对象:
class Container {
public string Name;
public string[] Animals; // may be empty
public string[] Foods; // may be empty
}
Run Code Online (Sandbox Code Playgroud)
这是我针对它构造查询的方式:
select c.Name container, a.Animal animal, f.Food food from Containers c
left join Containers_Animals a on a.Container = c.Id
left join Containers_Foods f on f.Container = c.Id;
Run Code Online (Sandbox Code Playgroud)
得到以下结果:
container animal food
--------- -------- --------
box NULL NULL
sack monkey lime
bucket dog apple
bucket dog bread
bucket dog chips
bucket dog grape
bucket lemur apple
bucket lemur bread
bucket lemur chips
bucket lemur grape
bucket whale apple
bucket whale bread
bucket whale chips
bucket whale grape
Run Code Online (Sandbox Code Playgroud)
我想看到的是,行数等于与任何关系上的根表关联的值的最大数量,并用NULL填充空白空间。这样可以使返回的行数,返回值,返回值保持向下,同时仍易于转换为对象。像这样:
container animal food
--------- -------- --------
box NULL NULL
sack monkey lime
bucket dog apple
bucket lemur bread
bucket whale chips
bucket NULL grape
Run Code Online (Sandbox Code Playgroud)
能做到吗
为什么不直接返回按容器排序的两个数据集,然后在客户端对它们进行逻辑合并连接呢?您所要求的是让数据库引擎做更多的工作,进行更复杂的查询,(对我来说)有一点好处。
它看起来像这样。使用两个左连接确保每个数据集至少具有所有容器名称的一个实例,然后同时循环遍历它们。这是一些粗略的伪代码:
Dim CurrentContainer
If Not Animals.Eof Then
CurrentContainer = Animals.Container
End If
Do While Not Animals.Eof Or Not Foods.Eof
Row = New Couplet(AnimalType, FoodType);
If Animals.Animal = CurrentContainer Then
Row.AnimalType = Animals.Animal
Animals.MoveNext
End If
If Foods.Container = CurrentContainer Then
Row.FoodType = Foods.Food
Foods.MoveNext
End If
If Not Animals.Eof AndAlso Animals.Container <> CurrentContainer _
AndAlso Not Foods.Eof AndAlso Foods.Container <> CurrentContainer Then
CurrentContainer = [Container from either non-Eof recordset]
EndIf
'Process the row, output it, put it in a stack, build a new recordset, whatever.
Loop
Run Code Online (Sandbox Code Playgroud)
但是,您所要求的当然是可能的!这里有两种方法。
单独处理输入并连接它们的位置:
WITH CA AS (
SELECT *,
Row_Number() OVER (PARTITION BY Container ORDER BY Animal) Pos
FROM Containers_Animals
), CF AS (
SELECT *,
Row_Number() OVER (PARTITION BY Container ORDER BY Food) Pos
FROM Containers_Foods
)
SELECT
C.Name,
CA.Animal,
CF.Food
FROM
Containers C
LEFT JOIN (
SELECT Container, Pos FROM CA
UNION SELECT Container, Pos FROM CF
) P ON C.Id = P.Container
LEFT JOIN CA
ON C.Id = CA.Container
AND P.Pos = CA.Pos
LEFT JOIN CF
ON C.Id = CF.Container
AND P.Pos = CF.Pos;
Run Code Online (Sandbox Code Playgroud)垂直连接输入并旋转它们:
WITH FoodAnimals AS (
SELECT
C.Name,
1 Which,
CA.Animal Item,
Row_Number() OVER (PARTITION BY C.Id ORDER BY (CA.Animal)) Pos
FROM
Containers C
LEFT JOIN Containers_Animals CA
ON C.Id = CA.Container
UNION
SELECT
C.Name,
2 Which,
CF.Food,
Row_Number() OVER (PARTITION BY C.Id ORDER BY (CF.Food)) Pos
FROM
Containers C
LEFT JOIN Containers_Foods CF
ON C.Id = CF.Container
)
SELECT
P.Name,
P.[1] Animal,
P.[2] Food
FROM
FoodAnimals FA
PIVOT (Max(Item) FOR Which IN ([1], [2])) P;
Run Code Online (Sandbox Code Playgroud)