2013-03-22 8 views
5

यहाँ मेरी परिदृश्य है। मान लें कि मेरे पास दो टेबल "कार" और "कारपार्ट" हैं। कार में कई हिस्सों होते हैं और प्रत्येक भाग कई कारों से संबंधित हो सकता है। मेरे मामले में एक जटिलता यह है कि प्रत्येक भाग को एक नया पार्टिड मिलता है, भले ही यह एक ही भाग का नाम हो, लेकिन यह केवल एक अलग कार से संबंधित है। यह ऐसा कुछ है जिस पर मेरा कोई नियंत्रण नहीं है, इसलिए बस मेरे साथ सहन करें। चीजों को सेट करने के लिए स्क्रिप्ट यहां दी गई है।चुनौतीपूर्ण पुनरावर्ती टी 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 के पार्ट्स -> इसी नाम से अन्य भागों -> मिल ईद उन "अन्य" भागों के के -> कार जो "स्वयं" उन भागों प्राप्त करें - -> शुरू होने और श्रृंखला के अंत तक पहुंचने तक दोहराना।

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 उदाहरण डेटा के साथ निष्पादन योग्य स्क्रिप्ट प्रदान करने के लिए +1 –

उत्तर

2

आप मूल रूप से कर रहे हैं traversing एक ग्राफ जो कि विश्वकोश नहीं है, इसलिए आपको स्पष्ट रूप से चक्र से बचना चाहिए। ग्राफ में पथ का ट्रैक रखने का एक तरीका है। यहां कोड है जो काम करना चाहिए। पथों को पकड़ने के लिए आप SQL सर्वर के HIERARCHYID डेटा प्रकार का उपयोग भी कर सकते हैं।

मैंने सीटीई को भागों की एक मेज के बजाय कारों की एक टेबल बनाने का फैसला किया। आपके नियम का परिणाम कभी-कभी कुछ नहीं होता है, लेकिन सभी नहीं, एक विशिष्ट कार के लिए भाग, इसलिए यह आसान लग रहा था।

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 इसलिए जरूरत है आप सीरियल numbers-> कार हिस्सा नाम के साथ कार-> कार भागों है। कारपार्ट टेबल में भाग सीरियल नंबर नहीं होना चाहिए।

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

बहुत बहुत धन्यवाद! यह ठीक से काम करता है और मुझे जिस परिणाम की आवश्यकता है उसका उत्पादन करता है। मुझे लगता है कि सीटीई समाधान अधिक सुरुचिपूर्ण है, इसलिए मुझे इसे एक जवाब के रूप में चुनना है। –

2

कारण अपने CTE एक अंतहीन लूप में चला जाता है, क्योंकि आप एक पदानुक्रम परिभाषित नहीं करते। यदि आप रिश्ते के आरेख को आकर्षित करते हैं तो आपको कई मंडलियां दिखाई देगी जो हमेशा के लिए लूप के लिए चलती हैं।

कि हल करने के लिए, पहली बात यह है एक पदानुक्रम बनाने के लिए है। मेरी कोड में पहली CTE car_hierarchy करता है कि सभी CarID जोड़े खोजने लेकिन सीमित है कि छोड़ दिया पर सही से छोटी होनी चाहिए द्वारा। इसके साथ अब आपके पास एक सर्कल-मुक्त निर्देशित रिलेशनशिप ग्राफ है। (आप दिशा आप अभी भी हलकों मिल सकती है पर ध्यान न दें, लेकिन अगर वह एल्गोरिथ्म के लिए कोई फर्क नहीं पड़ता।)

दूसरे चरण के लिए किसी दिए गए कार के लिए सारे रिश्तेदारों को मिल रहा है। क्योंकि दी गई कार पदानुक्रम के अंत में नहीं बैठ सकती है, यह एक दो कदम प्रक्रिया है। वहां से जुड़े सभी जुड़े कारों की तुलना में सबसे पहले बाएं सबसे अधिक जुड़े हुए कार को ढूंढें। नीचे दिए गए प्रश्न में car_left और car_right बस यही करें।

अंतिम चरण आईडी लेने के लिए और कार और भाग नामों में वापस खींचने के लिए है:

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

यह आपकी समस्या का समाधान करना चाहिए। हालांकि मुझे यकीन नहीं है कि यह अच्छा प्रदर्शन करेगा। एक बड़े डेटासेट के साथ आप वास्तव में एक लूप का उपयोग करने के लिए बेहतर हो सकता है। लेकिन उपयुक्त अनुक्रमण के साथ यह सिर्फ काम कर सकता है। कोशिश करके देखें।

(मैं टोयोटा के चेवी से शुरू कार बंद है कि यह पदानुक्रम भी के बीच में आर्स लिए काम करता है दिखाने के लिए। आप टोयोटा से केवल बाहर की तरफ चलना यदि आप फोर्ड याद करेंगे।)

+0

धन्यवाद, सेबेस्टियन! यह बहुत अच्छा काम करता है। मुझे लगता है कि ऊपर स्टीव का समाधान थोड़ा आसान है, लेकिन फिर भी वही परिणाम पूरा करता है। क्या आप इसके साथ मुद्दों को देखते हैं? –