2013-03-22 11 views
5

Ecco il mio scenario. Diciamo che ho due tavoli "Car" e "CarPart". L'auto è composta da molte parti e ogni parte può appartenere a più auto. Una delle complicazioni nel mio caso è che ogni parte riceve un nuovo PartID, anche se è lo stesso nome di parte, ma appartiene semplicemente a un'auto diversa. Questo è qualcosa su cui non ho controllo, quindi portami con me. Ecco la sceneggiatura per impostare le cose.Quota T-SQL ricorsiva impegnativa

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) 

Come si può vedere, la parte "Motore" appartiene a entrambi "Chevy" e "Ford" e è elencato due volte con diversi ID. Ancora una volta, questa è una limitazione del design con cui devo convivere.

Ecco ciò che devo realizzare: data una macchina, ho bisogno di trovare tutte le parti per questa macchina e tutte le altre auto a cui queste parti appartengono. Devo continuare a trovare parti e auto in modo ricorsivo fino a raggiungere la fine della catena. La logica può essere delineata come segue: @StartCar -> Parti di una @StartCar -> Altre parti con lo stesso nome -> ottieni Id di quelle "altre" parti -> Ottieni macchine che "possiedono" quelle parti - -> ricomincia e ripeti fino al raggiungimento della fine della catena.

per risolvere il mio problema, ho provato questa query:

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 

Tuttavia, si ottiene in un ciclo infinito e termina. Mi aspetto di vedere l'output simile a questo:

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

Apprezzo qualsiasi suggerimento.

Grazie!

+1

+1 per fornire script eseguibili con dati di esempio –

risposta

2

Si sono fondamentalmente di traslazione un grafico che non è aciclico, quindi è necessario evitare esplicitamente i cicli. Un modo è quello di tenere traccia del percorso nel grafico. Ecco il codice che dovrebbe funzionare. È possibile utilizzare il tipo di dati HIERARCHYID di SQL Server per contenere anche i percorsi.

Ho scelto di fare del CTE un tavolo di macchine invece di un tavolo di parti. La tua regola non risulta mai in alcune parti, ma non in tutte, per un'auto specifica, quindi questo sembrava più semplice.

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

Grazie, Steve, questo funziona magnificamente e fa quello che mi serve! –

0

Sembra che tu abbia bisogno di un junction table in modo che tu abbia auto-> parti di auto con numeri di serie-> nomi di parti di auto. La tabella CarPart non deve contenere il numero di serie della parte.

2

SQL Fiddle

Query 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

Grazie mille! Funziona correttamente e produce il risultato che mi serve. Penso solo che la soluzione CTE sia più elegante, quindi devo scegliere quella come risposta. –

2

Il motivo per il vostro CTE entra in un ciclo infinito è perché non si definisce una gerarchia. Se disegni il tuo diagramma delle relazioni vedrai molte circonferenze che causano il loop continuo di ogni ciclo.

Per risolvere ciò, la prima cosa è creare una gerarchia. Nel mio codice la prima cte car_hierarchy lo fa trovando tutte le coppie CarID ma limitando che quella di sinistra deve essere più piccola di quella di destra. Con questo ora hai un grafico di relazioni dirette senza cerchio. (Se ignori la direzione potresti ancora trovare cerchi, ma non importa per l'algoritmo.)

Il secondo passo è trovare tutti i parenti per una determinata macchina. Poiché l'auto fornita potrebbe non sedersi alla fine della gerarchia, questa è una procedura in due fasi. Prima trova l'auto più connessa a sinistra, poi trova tutte le auto collegate da lì. car_left e car_right nella query qui sotto fanno proprio questo.

Il passo finale è quello di prendere gli ID e tirare i nomi di auto e parte posteriore in:

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

Questo dovrebbe risolvere il problema. Tuttavia non sono sicuro che funzionerà bene. Con un set di dati di grandi dimensioni si potrebbe effettivamente essere meglio utilizzare un ciclo. Ma con l'indicizzazione appropriata potrebbe funzionare. Quindi provalo.

(I acceso la macchina inizio da Chevy a Toyota per dimostrare che funziona per ars nel mezzo della gerarchia troppo. Se si cammina solo verso l'esterno da Toyota vi mancherà Ford.)

+0

Grazie, Sebastian! Funziona alla grande Penso che la soluzione di Steve sopra sia un po 'più semplice, ma raggiunge comunque lo stesso risultato. Vedi problemi con esso? –