2013-02-02 32 views
6

मैंने this answer देखा और मुझे उम्मीद है कि वह गलत है, जैसे कोई गलत कह रहा था कि प्राथमिक कुंजी कॉलम पर हैं और मैं इसे एकाधिक कॉलम पर सेट नहीं कर सकता।रिकर्सिव mysql का चयन करें?

यहाँ मेरी मेज

create table Users(id INT primary key AUTO_INCREMENT, 
    parent INT, 
    name TEXT NOT NULL, 
    FOREIGN KEY(parent) 
    REFERENCES Users(id) 
); 


+----+--------+---------+ 
| id | parent | name | 
+----+--------+---------+ 
| 1 | NULL | root | 
| 2 |  1 | one  | 
| 3 |  1 | 1down | 
| 4 |  2 | one_a | 
| 5 |  4 | one_a_b | 
+----+--------+---------+ 

मैं प्रयोक्ता आईडी 2 का चयन करें और recurse इसलिए मैं सभी इसके प्रत्यक्ष और अप्रत्यक्ष बच्चे प्राप्त करना चाहते हैं है (ताकि आईडी 4 और 5)।

मैं इसे कैसे लिखूं इस तरह से काम करता हूं? मैंने postgresql और sqlserver में रिकर्सन देखा।

+0

विधेयक Karwin सही है। MySQL में 'SQL सर्वर' जैसी रिकर्सिव क्वेरी के लिए कोई फ़ंक्शन नहीं है क्योंकि इसमें 'CTE' है। लेकिन पुनरावृत्ति का व्यवहार अभी भी अनुकरण किया जा सकता है। ': डी' –

+0

मुझे नहीं लगता कि आप एक प्रश्न से MySQL में रिकर्सन कर सकते हैं, लेकिन मैंने संग्रहित प्रक्रियाओं के माध्यम से समान पेरेंट पदानुक्रम पूछताछ की है जो माता-पिता स्तर की तलाश में रहते हैं जब तक कोई और अभिभावक प्रविष्टियां नहीं मिलतीं ... लेकिन किया जाता है समाप्त होने पर हटाए गए एक अस्थायी तालिका के माध्यम से ... क्या यह आपके लिए काम करेगा? – DRapp

+0

@DRapp: शायद यह स्वीकार्य होगा। किसी भी तरह से सीखना मजेदार होगा –

उत्तर

14
CREATE DEFINER = 'root'@'localhost' 
PROCEDURE test.GetHierarchyUsers(IN StartKey INT) 
BEGIN 
    -- prepare a hierarchy level variable 
    SET @hierlevel := 00000; 

    -- prepare a variable for total rows so we know when no more rows found 
    SET @lastRowCount := 0; 

    -- pre-drop temp table 
    DROP TABLE IF EXISTS MyHierarchy; 

    -- now, create it as the first level you want... 
    -- ie: a specific top level of all "no parent" entries 
    -- or parameterize the function and ask for a specific "ID". 
    -- add extra column as flag for next set of ID's to load into this. 
    CREATE TABLE MyHierarchy AS 
    SELECT U.ID 
     , U.Parent 
     , U.`name` 
     , 00 AS IDHierLevel 
     , 00 AS AlreadyProcessed 
    FROM 
    Users U 
    WHERE 
    U.ID = StartKey; 

    -- how many rows are we starting with at this tier level 
    -- START the cycle, only IF we found rows... 
    SET @lastRowCount := FOUND_ROWS(); 

    -- we need to have a "key" for updates to be applied against, 
    -- otherwise our UPDATE statement will nag about an unsafe update command 
    CREATE INDEX MyHier_Idx1 ON MyHierarchy (IDHierLevel); 


    -- NOW, keep cycling through until we get no more records 
    WHILE @lastRowCount > 0 
    DO 

    UPDATE MyHierarchy 
    SET 
     AlreadyProcessed = 1 
    WHERE 
     IDHierLevel = @hierLevel; 

    -- NOW, load in all entries found from full-set NOT already processed 
    INSERT INTO MyHierarchy 
    SELECT DISTINCT U.ID 
        , U.Parent 
        , U.`name` 
        , @hierLevel + 1 AS IDHierLevel 
        , 0 AS AlreadyProcessed 
    FROM 
     MyHierarchy mh 
    JOIN Users U 
    ON mh.Parent = U.ID 
    WHERE 
     mh.IDHierLevel = @hierLevel; 

    -- preserve latest count of records accounted for from above query 
    -- now, how many acrual rows DID we insert from the select query 
    SET @lastRowCount := ROW_COUNT(); 


    -- only mark the LOWER level we just joined against as processed, 
    -- and NOT the new records we just inserted 
    UPDATE MyHierarchy 
    SET 
     AlreadyProcessed = 1 
    WHERE 
     IDHierLevel = @hierLevel; 

    -- now, update the hierarchy level 
    SET @hierLevel := @hierLevel + 1; 

    END WHILE; 


    -- return the final set now 
    SELECT * 
    FROM 
    MyHierarchy; 

-- and we can clean-up after the query of data has been selected/returned. 
-- drop table if exists MyHierarchy; 


END 

यह बोझिल प्रकट हो सकता है, लेकिन यह उपयोग करने के लिए, कर

call GetHierarchyUsers(5); 

(या जो कुछ भी कुंजी आईडी आप के लिए श्रेणीबद्ध पेड़ लगाना चाहते हैं)।

आधार एक कुंजी के साथ शुरू करना है जिसके साथ आप काम कर रहे हैं। फिर, उपयोगकर्ता तालिका में फिर से जुड़ने के आधार के रूप में इसका उपयोग करें, लेकिन पहली प्रविष्टि के माता-पिता आईडी पर आधारित है। एक बार मिलने के बाद, अस्थायी तालिका को अद्यतन करें और उस चक्र के लिए अगले चक्र पर फिर से शामिल न हों। फिर तब तक जारी रखें जब तक कोई और "पैरेंट" आईडी कुंजी नहीं मिल पाती।

यह अभिभावकों तक रिकॉर्ड के पूरे पदानुक्रम को वापस कर देगा चाहे कितना घोंसला घोंसला हो। हालांकि, अगर आप केवल अंतिम माता-पिता चाहते हैं, तो आप @hierlevel वैरिएबल का उपयोग केवल फ़ाइल में जोड़े गए नवीनतम संस्करण को वापस करने के लिए कर सकते हैं, या ऑर्डर बाय और LIMIT 1

+0

वाह जो दयालु है बहुत से: | +1 और स्वीकृत –

+0

यह एक अच्छा समाधान है, लेकिन यदि पहली बार पूरा होने से पहले क्वेरी चलती है तो अस्थायी MyHeirarchy तालिका हटा दी जाएगी और पहली क्वेरी विफल हो जाएगी। नाम में टाइमस्टैम्प के साथ अस्थायी तालिका बनाना और प्रक्रिया के अंत में इसे शुरू करना (शुरुआत के बजाए) उस समस्या को हल करेगा। –

+2

@andrewlorien, हाँ, यह सच है, लेकिन फिर आप गतिशील-एसक्यूएल से निपट रहे हैं। एक अन्य विकल्प #tempTable नाम (या ## temp) टेबल का उपयोग करना है जो प्रति कनेक्शन और/या उपयोगकर्ता अद्वितीय हैं। इससे एक उपयोगकर्ता से दूसरे उपयोगकर्ता को आकस्मिक हटा दिया जाएगा। – DRapp

4

मुझे पता है कि शायद ऊपर बेहतर और अधिक कुशल उत्तर है लेकिन यह स्निपेट थोड़ा अलग दृष्टिकोण देता है और दोनों - पूर्वजों और बच्चों को प्रदान करता है।

विचार है कि रिश्तेदार पंक्तियों को अस्थायी तालिका में लगातार डालें, फिर अपने रिश्तेदारों को देखने के लिए एक पंक्ति प्राप्त करें, सभी पंक्तियों को संसाधित होने तक दोहराएं। क्वेरी को केवल 1 अस्थायी तालिका का उपयोग करने के लिए अनुकूलित किया जा सकता है।

यहां एक काम कर रहे sqlfiddle उदाहरण है।

CREATE TABLE Users 
     (`id` int, `parent` int,`name` VARCHAR(10))// 

    INSERT INTO Users 
     (`parent`, `name`) 
    VALUES 
     (1, NULL, 'root'), 
     (2, 1, 'one'), 
     (3, 1, '1down'), 
     (4, 2, 'one_a'), 
     (5, 4, 'one_a_b')// 

    CREATE PROCEDURE getAncestors (in ParRowId int) 
    BEGIN 
     DECLARE tmp_parentId int; 
     CREATE TEMPORARY TABLE tmp (parentId INT NOT NULL); 
     CREATE TEMPORARY TABLE results (parentId INT NOT NULL); 
     INSERT INTO tmp SELECT ParRowId; 
     WHILE (SELECT COUNT(*) FROM tmp) > 0 DO 
     SET tmp_parentId = (SELECT MIN(parentId) FROM tmp); 
     DELETE FROM tmp WHERE parentId = tmp_parentId; 
     INSERT INTO results SELECT parent FROM Users WHERE id = tmp_parentId AND parent IS NOT NULL; 
     INSERT INTO tmp SELECT parent FROM Users WHERE id = tmp_parentId AND parent IS NOT NULL; 
     END WHILE; 
     SELECT * FROM Users WHERE id IN (SELECT * FROM results); 
    END// 

    CREATE PROCEDURE getChildren (in ParRowId int) 
    BEGIN 
     DECLARE tmp_childId int; 
     CREATE TEMPORARY TABLE tmp (childId INT NOT NULL); 
     CREATE TEMPORARY TABLE results (childId INT NOT NULL); 
     INSERT INTO tmp SELECT ParRowId; 
     WHILE (SELECT COUNT(*) FROM tmp) > 0 DO 
     SET tmp_childId = (SELECT MIN(childId) FROM tmp); 
     DELETE FROM tmp WHERE childId = tmp_childId; 
     INSERT INTO results SELECT id FROM Users WHERE parent = tmp_childId; 
     INSERT INTO tmp SELECT id FROM Users WHERE parent = tmp_childId; 
     END WHILE; 
     SELECT * FROM Users WHERE id IN (SELECT * FROM results); 
    END// 

उपयोग:

CALL getChildren(2); 

    -- returns 
    id parent name 
    4 2 one_a 
    5 4 one_a_b 


CALL getAncestors(5); 

    -- returns 
    id parent name 
    1 (null) root 
    2 1 one 
    4 2 one_a