2011-09-12 22 views
8

के साथ डेटा डालें और विदेशी कुंजी सेट करें स्कीमा परिवर्तन के बाद मुझे पोस्टग्रेस डीबी में मौजूदा डेटा की बड़ी मात्रा माइग्रेट करना होगा।पोस्टग्रेस

पुरानी स्कीमा में एक देश विशेषता उपयोगकर्ता तालिका में संग्रहीत की जाएगी। अब देश विशेषता एक अलग पते तालिका में ले जाया गया है:

users: 
    country # OLD 
    address_id # NEW [1:1 relation] 

addresses: 
    id 
    country 

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

जब डेटा को माइग्रेट, मैं समस्याओं के पते डालने के बाद उन तालिका में विदेशी कुंजी की स्थापना हो रही है:

INSERT INTO addresses (country) 
    SELECT country FROM users WHERE address_id IS NULL 
    RETURNING id; 

मैं डाला पंक्तियों की आईडी कैसे प्रचार और विदेशी कुंजी सेट करूँ उपयोगकर्ता तालिका में संदर्भ?

एकमात्र समाधान मैं अब तक के पते तालिका में एक अस्थायी user_id स्तंभ बनाने और फिर address_id अपडेट कर रहा है के साथ आ सकता है:

UPDATE users SET address_id = a.id FROM addresses AS a 
    WHERE users.id = a.user_id; 

हालांकि, इस के बावजूद निकला बेहद धीमी गति से होने के लिए (दोनों उपयोगकर्ताओं.आईडी और address.user_id पर सूचकांक का उपयोग कर)।

उपयोगकर्ता तालिका में लगभग 3 मिलियन पंक्तियां हैं जिनमें 300k संबंधित पते हैं।

क्या किसी तालिका में व्युत्पन्न डेटा डालने और दूसरे में डाले गए डेटा के लिए विदेशी कुंजी संदर्भ सेट करने का कोई अन्य तरीका है (स्कीमा को स्वयं किए बिना)?

मैं पोस्टग्रेस 8.3.14 का उपयोग कर रहा हूं।

धन्यवाद

अब मैं एक अजगर/SQLAlchemy स्क्रिप्ट के साथ डेटा को माइग्रेट करने से समस्या हल कर दिया है। एसक्यूएल के साथ कोशिश करने की तुलना में यह बहुत आसान (मेरे लिए) साबित हुआ। फिर भी, अगर कोई पोस्टग्रेस एसक्यूएल में एक इंसर्ट स्टेटमेंट के रिटर्निंग परिणाम को संसाधित करने का तरीका जानता है तो मुझे दिलचस्पी होगी।

+0

यह पुराना है और आपने इसे हल किया है। लेकिन 1: 1 संबंध इस मामले में कोई समझ नहीं आता है। क्या आप इसके बजाय एक देश तालिका नहीं बना रहे हैं? –

+0

एक पते में वास्तव में सड़क, शहर, ज़िप कोड, ... और प्रत्येक उपयोगकर्ता के लिए देश शामिल है। मैंने इसे और अधिक पठनीय बनाने के लिए इसे सरल बना दिया। – Pankrat

+0

देश, ज़िप कोड, शहर, काउंटी इत्यादि सभी की अपनी टेबल होगी। यह सड़क, संख्या इत्यादि छोड़ देता है। फिर भी इनके लिए एक अलग तालिका में कोई बिंदु नहीं है जब तक कि प्रत्येक उपयोगकर्ता के लिए एक से अधिक पता संभव न हो। –

उत्तर

10

तालिका users में कुछ प्राथमिक कुंजी होना चाहिए जिसका आपने खुलासा नहीं किया था। इस उत्तर के प्रयोजन के लिए मैं इसे users_id नाम दूंगा।

आप data-modifying CTEs PostgreSQL 9.1 के साथ पेश किया साथ नहीं बल्कि सुंदर ढंग से इस का समाधान कर सकते हैं:

हम यह मान सकते हैं कि country अद्वितीय है, पूरे ऑपरेशन नहीं बल्कि मामूली बात है:

WITH i AS (
    INSERT INTO addresses (country) 
    SELECT country 
    FROM users 
    WHERE address_id IS NULL 
    RETURNING id, country 
    ) 
UPDATE users u 
SET address_id = i.id 
FROM i 
WHERE i.country = u.country; 

आप उल्लेख आपके प्रश्न में संस्करण 8.3। यदि आप इस दौरान अपग्रेड करने के लिए चारों ओर नहीं गए थे, तो आप उन्नयन पर विचार करना चाहेंगे। End of life is coming soon for 8.3.

जैसा भी हो सकता है, यह संस्करण 8.3 के साथ काफी आसान है।तुम सिर्फ दो बयानों की जरूरत है:

INSERT INTO addresses (country) 
SELECT country 
FROM users 
WHERE address_id IS NULL; 

UPDATE users u 
SET address_id = a.id 
FROM addresses a 
WHERE address_id IS NULL 
AND a.country = u.country; 

तो country अद्वितीय नहीं है, यह अधिक चुनौतीपूर्ण हो जाता है। आप बस एक पता बना सकते हैं और इसे कई बार लिंक कर सकते हैं। लेकिन आपने 1: 1 रिश्ते का जिक्र किया है जो इस तरह के एक सुविधाजनक समाधान का नियम है।

संस्करण 9.1 के लिए:

WITH s AS (
    SELECT users_id, country 
     , row_number() OVER (PARTITION BY country) AS rn 
    FROM users 
    WHERE address_id IS NULL 
    ) 
    , i AS (
    INSERT INTO addresses (country) 
    SELECT country 
    FROM s 
    RETURNING id, country 
    ) 
    , r AS (
    SELECT * 
     , row_number() OVER (PARTITION BY country) AS rn 
    FROM i 
    ) 
UPDATE users u 
SET address_id = r.id 
FROM r 
JOIN s USING (country, rn) -- select exactly one id for every user 
WHERE u.users_id = s.users_id 
AND u.address_id IS NULL; 

कोई रास्ता नहीं स्पष्ट रूप से ठीक एक id समान country के साथ एक सेट के प्रत्येक उपयोगकर्ता को INSERT से लौटे आवंटित करने के लिए, मैं बनाने के लिए खिड़की समारोह row_number() का उपयोग नहीं है के रूप में उन्हें अद्वितीय।

संस्करण 8.3 के साथ सीधे आगे नहीं है। एक संभव तरीका:

INSERT INTO addresses (country) 
SELECT DISTINCT country -- pick just one per set of dupes 
FROM users 
WHERE address_id IS NULL; 

UPDATE users u 
SET address_id = a.id 
FROM addresses a 
WHERE a.country = u.country 
AND u.address_id IS NULL 
AND NOT EXISTS (
    SELECT * FROM addresses b 
    WHERE b.country = a.country 
    AND b.users_id < a.users_id 
    ); -- effectively picking the smallest users_id per set of dupes 

दोहराएँ इस जब तक पिछले NULL मूल्य users.address_id से चला गया है।

+0

बहुत धन्यवाद! आपके उत्तर से कुछ नई चीजें सीख लीं। और हां, इस दौरान हमने पोस्टग्रेस 9.1 में अपग्रेड किया। चीयर्स – Pankrat

+0

@Pankrat: यह अच्छी खबर है - दोनों, इससे मदद मिली और आप 9.1 तक अपग्रेड कर सकते थे। –

संबंधित मुद्दे