2013-03-22 3 views
5

여기 내 시나리오가 있습니다. 두 개의 테이블 "Car"와 "CarPart"가 있다고 가정 해 봅시다. 자동차는 많은 부품으로 구성되며 각 부품은 여러 자동차에 속할 수 있습니다. 제 경우의 한 가지 복잡한 점은 각 부품이 동일한 부품 이름이더라도 새 부품 ID를 얻지 만 간단히 다른 자동차에 속하는 것입니다. 이것은 내가 통제 할 수없는 일이기 때문에 나와 함께 감내하십시오. 여기에 일을 설정하는 스크립트가 있습니다.어려운 재귀 T-SQL 쿼리

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) 

당신이 볼 수 있듯이, 부분 "엔진"에 모두 '시보레'와 '포드'에 속하는 다른 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 

나는 모든 포인터를 사용합니다.

감사합니다.

+1

예제 데이터로 실행 가능한 스크립트를 제공하는 경우 +1 –

답변

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 
+0

감사합니다, 스티브,이 작품은 아름답게하고 내가 필요한 것을 해낸다! –

0

junction table이 필요하므로 일련 번호가있는 자동차 -> 자동차 부품 -> 자동차 부품 이름이 필요합니다. CarPart 테이블에는 부품 일련 번호가 없어야합니다.

2

SQL Fiddle

검색어 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 

Results : 계층 구조를 정의하지 않기 때문에

| CAR_NAME | PART_NAME | 
------------------------ 
| Toyota |  Door | 
| Toyota | Window | 
| Chevy | Engine | 
| Chevy |  Door | 
|  Hugo | Window | 
|  Ford | Engine | 
|  Ford |  Wheel | 
+0

대단히 감사합니다! 이것은 제대로 작동하고 내가 필요한 결과를 만들어냅니다. CTE 솔루션이 좀 더 우아하다고 생각하기 때문에 대답으로 선택해야합니다. –

2

당신의 CTE가 무한 루프에가는 이유입니다. 당신이 관계 다이어그램을 그리면 당신은 많은 둘러보기가 영원히 반복하도록하는 많은 서클을 보게 될 것입니다.

먼저 해결해야 할 것은 계층 구조를 만드는 것입니다. 내 코드에서 첫 번째 cte car_hierarchy은 모두 CarID 쌍을 찾음으로써 수행하지만 왼쪽 하나는 오른쪽보다 작아야한다는 것을 제한합니다. 이제 원이없는 직접 관계 그래프가 생겼습니다. (방향을 무시하면 여전히 원을 찾을 수 있지만 알고리즘에는 문제가되지 않습니다.)

두 번째 단계는 주어진 차량의 모든 친척을 찾는 것입니다. 주어진 자동차가 계층 구조의 끝에 앉지 않을 수 있기 때문에 이것은 2 단계 프로세스입니다. 먼저 가장 연결된 가장 왼쪽의 차를 찾으십시오. 거기에서 연결된 모든 연결된 차를 찾으십시오. 아래의 쿼리에서 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 위해 일하는 것을 보여주기 위해 도요타 시보레에서 시작 차를 투입합니다. 당신은 도요타에서만 바깥쪽으로 걸 으면 당신은 포드를 그리워합니다.) 당신은 기본적으로 통과하는

+0

감사합니다, Sebastian! 이것은 잘 작동합니다. 위의 Steve 솔루션이 조금 더 간단하다고 생각하지만 여전히 동일한 결과를 얻습니다. 문제가 있습니까? –