Need help with recursive query

  • Hello,

    Here is my scenario. Let's say I have two tables "Car" and "CarPart". The car consist of many parts and each part can belong to mutliple cars. One complication in my case is that each part gets a new PartID, even if it's the same part, which simply belongs to a different car. This is something I have no control over, so just bear with me. Here is the script to set things up.

    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')

    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)

    As you can see, the part "Engine" belongs to both "Chevy" and "Ford" and is listed twice with different IDs. Once again, this is a design limitation I have to live with. Here is what I need to accomplish. Given a car, I need to find all of the parts for this car and all of the other cars that these parts belong to. I have to continue finding parts and cars in a recursive manner until I reach the end of the chain. I tried this 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

    However, it gets into an infinite loop and terminates. I would expect see the output similar to this:

    CarName PartName

    Chevy Engine

    Chevy Door

    Ford Engine

    Ford Wheel

    Toyota Door

    Toyota Window

    I appreciante any pointers.

    Thank you!

  • Hi

    Does this do what you require?

    ;with originalCar AS (

    SELECT CarName, PartName

    FROM Car c INNER JOIN CarPart cp ON c.CarID = cp.CarID

    WHERE CarName = @StartCar

    ),

    otherCars AS (

    select carName, partName

    FROM Car c INNER JOIN CarPart cp ON c.CarID = cp.CarID

    WHERE carName in (

    SELECT carName

    FROM Car c INNER JOIN CarPart cp ON c.CarID = cp.CarID

    WHERE PartName in (SELECT PartName FROM originalCar)

    and CarName != @startCar

    )

    )

    select carname, partname

    from originalCar

    union all

    select carname, partname

    from otherCars

    order by CarName

  • First of all thank you for replying! It's close, but not quite. As I said, I need to get the entire chain Car -> CarPart -> Car -> CarPart, etc. The query you wrote gets the cars and their parts, which are directly related to the @StartCar and its parts. However, it doesn't get the rest of the chain. If I were to revise my setup code to this:

    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)

    As you can see, I added "Hugo", which will not get returned because it's not related to the @StartCar "Chevy" or its parts.

    I still think it has to be some sort of recursive CTE, but I can't figure out how to stop it and prevent infinite loop.

  • I don't see a need for a recursive query:

    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);

    select

    c.CarName,

    cp.PartName

    from

    dbo.Car c

    inner join dbo.CarPart cp

    on (c.CarID = cp.CarID)

    order by

    c.CarName;

    IF OBJECT_ID('Car') IS NOT NULL DROP TABLE Car;

    IF OBJECT_ID('CarPart') IS NOT NULL DROP TABLE CarPart;

  • Lynn,

    Thank you for your reply! The query you listed will return all of the cars and their parts, but this is not what I am after. My goal is as follows: given the initial "StartCar" find all of its parts, next find all of the cars which have the same parts (note in my original post that the parts get new numbers when they belong to different cars, so I have to detect the fact that the part is "shared" based on the PartName), repeat the process finding cars and their parts until I reach the end of the chain.

    In my example above, I would expect "Honda" and "Nissan" NOT to be returned since they don't share any parts with cars, which share parts with my @StartCar of "Chevy".

    Thank you!

  • mishaluba (3/22/2013)


    Lynn,

    Thank you for your reply! The query you listed will return all of the cars and their parts, but this is not what I am after. My goal is as follows: given the initial "StartCar" find all of its parts, next find all of the cars which have the same parts (note in my original post that the parts get new numbers when they belong to different cars, so I have to detect the fact that the part is "shared" based on the PartName), repeat the process finding cars and their parts until I reach the end of the chain.

    In my example above, I would expect "Honda" and "Nissan" NOT to be returned since they don't share any parts with cars, which share parts with my @StartCar of "Chevy".

    Thank you!

    I'm not sure what you mean by share parts. The only thing they share in your sample data is the same name (Engine, for example). None of the parts listed have the same part number.

  • Yes, that is correct. By "share parts" I mean that they share a part by the same name. That is why my "attempted" recursive query does a JOIN on PartName rather than PartID. Unfortunately this is a limitation of the design I have to live with. Also note that this is just an example to illustrate the problem (the real database is not about cars or parts). So once again, what I need to do is as follows:

    @StartCar --> Parts of a @StartCar --> Other parts by the same name --> get Id's of those "other" parts --> Get cars which "own" those parts --> start over and repeat until the end of the chain is reached.

    Does this help?

  • I would expect, considering the number of parts in a car and the number of shared parts across cars that your recursive query is probably going to return all cars and all car parts when fully explored anyway.

  • Thank you, but as I said, the Cars and CarParts was used by me simply to illustrate the problem. The actual database has nothing to do with either and the query will only return a fraction of avaialbe data.

  • Here is the solution to this problem, which works correctly (credit to Steve Kass: http://stackoverflow.com/users/139164/steve-kass):

    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

Viewing 10 posts - 1 through 10 (of 10 total)

You must be logged in to reply to this topic. Login to reply