5

这是我的场景。假设我有两个表“Car”和“CarPart”。汽车由许多零件组成,每个零件可以属于多辆汽车。在我的例子中,一个复杂的问题是每个部件都有一个新的 PartID,即使它是相同的部件名称,但它只是属于不同的汽车。这是我无法控制的事情,所以请耐心等待。这是设置内容的脚本。

IF OBJECT_ID('Car') IS NOT NULL DROP TABLE Car
CREATE TABLE Car (
CarID INT,
CarName VARCHAR(16)
)

IF OBJECT_ID('CarPart') IS NOT NULL DROP TABLE CarPart
CREATE TABLE CarPart (
PartID INT,
PartName VARCHAR(16),
CarID INT
)

INSERT INTO Car
VALUES (1, 'Chevy'),
    (2, 'Ford'),
    (3, 'Toyota'),
    (4, 'Honda'),
    (5, 'Nissan'),
    (6, 'Hugo')

INSERT INTO CarPart 
VALUES  (110, 'Engine', 1),
  (120, 'Engine', 2),
  (210, 'Door', 1),
  (220, 'Door', 3),
  (310, 'Seat', 4),
  (320, 'Seat', 5),
  (410, 'Window', 3),
  (510, 'Wheel', 2),
  (420, 'Window', 6)

如您所见,“Engine”部分属于“Chevy”和“Ford”,并以不同的 ID 列出了两次。再一次,这是我必须忍受的设计限制。

这是我需要完成的:给定一辆车,我需要找到这辆车的所有零件以及这些零件所属的所有其他汽车。我必须以递归方式继续寻找零件和汽车,直到到达链条的末端。逻辑可以概括如下:@StartCar --> @StartCar 的部分 --> 同名的其他部分 --> 获取那些“其他”部分的 ID --> 获取“拥有”这些部分的汽车 - -> 重新开始并重复,直到到达链的末端。

为了解决我的问题,我尝试了这个查询:

DECLARE @StartCar VARCHAR(16) = 'Chevy'

;WITH cte (CarName, PartName)
AS
(
SELECT c.CarName,
       cp.PartName
FROM CarPart cp
JOIN Car c ON cp.CarID = c.CarID
WHERE c.CarName = @StartCar
UNION ALL
SELECT c.CarName,
       cp.PartName
FROM CarPart cp
JOIN Car c ON cp.CarID = c.CarID
JOIN cte cte ON cp.PartName = cte.PartName
)
SELECT CarName, PartName
FROM cte

但是,它进入无限循环并终止。我希望看到与此类似的输出:

CarName PartName
Chevy Engine
Chevy Door
Ford Engine
Ford Wheel
Toyota Door
Toyota Window
Hugo Window

我很感激任何指示。

谢谢!

4

4 回答 4

2

SQL小提琴

查询 1

declare @t table (
  car_name varchar(100), 
  part_name varchar(100)
  )

declare @car int = 3

insert @t
select c.CarName, p.PartName
from Car c join CarPart p on c.CarID = p.CarID
where c.CarID = @car


while exists(
  select c.CarName, p.PartName
  from Car c join CarPart p on c.CarID = p.CarID
  where c.CarName in (
    select c.CarName
    from Car c join CarPart p on c.CarID = p.CarID
    where p.PartName in (select part_name from @t)
      and c.CarName not in (select car_name from @t)
    )
) 
insert @t
  select c.CarName, p.PartName
  from Car c join CarPart p on c.CarID = p.CarID
  where c.CarName in (
    select c.CarName
    from Car c join CarPart p on c.CarID = p.CarID
    where p.PartName in (select part_name from @t)
      and c.CarName not in (select car_name from @t)
    )

select * from @t

结果

| CAR_NAME | PART_NAME |
------------------------
|   Toyota |      Door |
|   Toyota |    Window |
|    Chevy |    Engine |
|    Chevy |      Door |
|     Hugo |    Window |
|     Ford |    Engine |
|     Ford |     Wheel |
于 2013-03-23T00:21:28.663 回答
2

您的 cte 进入无限循环的原因是您没有定义层次结构。如果您绘制关系图,您将看到许多圆圈导致任何演练永远循环。

要解决这个问题,首先要做的是创建层次结构。在我的代码中,第一个 ctecar_hierarchy通过查找所有CarID对但限制左侧必须小于右侧来做到这一点。有了这个,你现在有了一个无圆有向关系图。(如果您忽略方向,您可能仍然会找到圆圈,但这对算法无关紧要。)

第二步是找到给定汽车的所有亲属。因为给定的汽车可能不在层次结构的末尾,所以这是一个两步过程。首先找到最左边的连接汽车,然后找到所有连接的汽车从那里开始。car_leftcar_right在下面的查询中做到这一点。

最后一步是获取 ID 并将汽车和零件名称拉回:

IF OBJECT_ID('dbo.Car') IS NOT NULL DROP TABLE dbo.Car
CREATE TABLE dbo.Car (
CarID INT,
CarName VARCHAR(16)
)

IF OBJECT_ID('dbo.CarPart') IS NOT NULL DROP TABLE dbo.CarPart
CREATE TABLE dbo.CarPart (
PartID INT,
PartName VARCHAR(16),
CarID INT
)

INSERT INTO dbo.Car
VALUES (1, 'Chevy'),
    (2, 'Ford'),
    (3, 'Toyota'),
    (4, 'Honda'),
    (5, 'Nissan'),
    (6, 'Hugo')

INSERT INTO dbo.CarPart 
VALUES  (110, 'Engine', 1),
  (120, 'Engine', 2),
  (210, 'Door', 1),
  (220, 'Door', 3),
  (310, 'Seat', 4),
  (320, 'Seat', 5),
  (410, 'Window', 3),
  (510, 'Wheel', 2),
  (420, 'Window', 6)


DECLARE @StartCarID INT = 1;

WITH 
car_hierachy (CarID1, CarID2) AS (
  SELECT DISTINCT
         cp1.CarID CarID1,
         cp2.CarID CarID2
  FROM dbo.CarPart cp1
  JOIN dbo.CarPart cp2
  ON cp1.PartName = cp2.PartName
  AND cp1.CarID < cp2.CarID
),
car_left(CarID) AS (
  SELECT @StartCarID 
  UNION ALL
  SELECT ch.CarID1
  FROM car_hierachy ch
  JOIN car_left cl
  ON cl.CarID = ch.CarID2
),
car_right(CarID) AS (
  SELECT MIN(CarID) 
  FROM car_left
  UNION ALL
  SELECT ch.CarID2
  FROM car_hierachy ch
  JOIN car_right cr
  ON cr.CarID = ch.CarID1
)
SELECT *
FROM car_right ac
JOIN dbo.Car c
ON ac.CarID = c.CarID
JOIN dbo.CarPart cp
ON c.CarID = cp.CarID
ORDER BY c.CarId, cp.PartId; 

SQLFiddle

这应该可以解决您的问题。但是我不确定它会表现良好。对于大型数据集,您实际上可能会更好地使用循环。但是通过适当的索引,它可能会起作用。所以试试吧。

(我将启动车从雪佛兰切换到丰田,以表明它也适用于层次结构中间的 ars。如果你只从丰田向外走,你会错过福特。)

于 2013-03-23T01:00:16.663 回答
2

您基本上是在遍历一个非循环的图,因此您必须明确避免循环。一种方法是跟踪图中的路径。这是应该工作的代码。您也可以使用 SQL Server 的 HIERARCHYID 数据类型来保存路径。

我选择让 CTE 成为汽车表而不是零件表。您的规则永远不会导致特定汽车的某些(但不是全部)零件,所以这似乎更简单。

WITH cte(CarID,hier) AS (
  SELECT CarID, CAST('/'+LTRIM(CarID)+'/' AS varchar(max))
  FROM Car
  WHERE CarName = @StartCar

  UNION ALL

  SELECT c2.CarID, hier+LTRIM(c2.CarID)+'/'
  FROM Car AS c
  JOIN cte ON cte.CarID = c.CarID
  JOIN CarPart AS c1 ON c.CarID = c1.CarID
  JOIN CarPart AS c2 ON c2.PartName = c1.PartName
  WHERE hier NOT LIKE '%/'+LTRIM(c2.CarID)+'/%'
)

SELECT
  c.CarName, cp.PartName
FROM Car AS c
JOIN CarPart AS cp ON cp.CarID = c.CarID
JOIN cte on cte.CarID = c.CarID
于 2013-03-23T01:42:48.177 回答
0

看起来您需要一个连接表,以便您拥有带有序列号的汽车->汽车零件->汽车零件名称。CarPart 表中不应包含零件序列号。

于 2013-03-22T23:34:09.350 回答