अपने पोस्टग्रेज SQL प्रश्नों में थोड़े से बदलाव और सुधार के साथ, आप अपने डेटाबेस के साथ इंटरफेस करने के लिए आवश्यक दोहराव, त्रुटि-प्रवण एप्लिकेशन कोड की मात्रा में कटौती कर सकते हैं। अक्सर ऐसा नहीं होता है, इस तरह के बदलाव से एप्लिकेशन कोड के प्रदर्शन में भी सुधार होता है।
यहां कुछ युक्तियां और तरकीबें दी गई हैं जो आपके एप्लिकेशन कोड को PostgreSQL पर अधिक काम आउटसोर्स करने में मदद कर सकती हैं, और आपके एप्लिकेशन को पतला और तेज बना सकती हैं।
अप्सर्ट
पोस्टग्रेज v9.5 के बाद से, यह निर्दिष्ट करना संभव है कि "संघर्ष" के कारण इंसर्ट विफल होने पर क्या होना चाहिए। विरोध या तो एक अद्वितीय अनुक्रमणिका (प्राथमिक कुंजी सहित) या किसी बाधा का उल्लंघन हो सकता है (पहले CREATE CONSTRAINT का उपयोग करके बनाया गया था)।
इस सुविधा का उपयोग एकल SQL कथन में सम्मिलित-या-अद्यतन अनुप्रयोग तर्क को सरल बनाने के लिए किया जा सकता है। उदाहरण के लिए, एक तालिका दी गई है kv कुंजी . के साथ और मान कॉलम, नीचे दिया गया विवरण एक नई पंक्ति सम्मिलित करेगा (यदि तालिका में कुंजी ='होस्ट' के साथ एक पंक्ति नहीं है) या मान को अपडेट करें (यदि तालिका में कुंजी ='होस्ट' के साथ एक पंक्ति है):
CREATE TABLE kv (key TEXT PRIMARY KEY, value TEXT);
INSERT INTO kv (key, value)
VALUES ('host', '10.0.10.1')
ON CONFLICT (key) DO UPDATE SET value=EXCLUDED.value;
ध्यान दें कि कॉलम key
तालिका की एकल-स्तंभ प्राथमिक कुंजी है, और इसे संघर्ष खंड के रूप में निर्दिष्ट किया गया है। यदि आपके पास एकाधिक स्तंभों वाली प्राथमिक कुंजी है, तो इसके बजाय प्राथमिक कुंजी अनुक्रमणिका का नाम निर्दिष्ट करें।
आंशिक अनुक्रमणिका और बाधाओं को निर्दिष्ट करने सहित उन्नत उदाहरणों के लिए, पोस्टग्रेज़ डॉक्स देखें।
सम्मिलित करें .. लौटते हुए
INSERT विवरण वापस भी कर सकता है एक या अधिक पंक्तियाँ, जैसे SELECT कथन। यह फ़ंक्शन द्वारा उत्पन्न मान लौटा सकता है, कीवर्ड जैसे current_timestamp और धारावाहिक /अनुक्रम/पहचान कॉलम।
उदाहरण के लिए, यहां एक तालिका है जिसमें एक ऑटोजेनरेटेड पहचान कॉलम है और एक कॉलम है जिसमें पंक्ति के निर्माण का टाइमस्टैम्प है:
db=> CREATE TABLE t1 (id int GENERATED BY DEFAULT AS IDENTITY,
db(> at timestamptz DEFAULT CURRENT_TIMESTAMP,
db(> foo text);
हम foo कॉलम के लिए केवल मान निर्दिष्ट करने के लिए INSERT .. रिटर्निंग स्टेटमेंट का उपयोग कर सकते हैं , और Postgres को id . के लिए जेनरेट किए गए मानों को वापस करने दें और पर कॉलम:
db=> INSERT INTO t1 (foo) VALUES ('first'), ('second') RETURNING id, at, foo;
id | at | foo
----+----------------------------------+--------
1 | 2022-01-14 11:52:09.816787+01:00 | first
2 | 2022-01-14 11:52:09.816787+01:00 | second
(2 rows)
INSERT 0 2
एप्लिकेशन कोड से, उन्हीं पैटर्न/API का उपयोग करें जिनका उपयोग आप SELECT स्टेटमेंट चलाने और मानों में पढ़ने के लिए करते हैं (जैसे executeQuery() JDBC या db.Query() . में गो में)।
यहां एक और उदाहरण दिया गया है, इसमें एक स्वतः उत्पन्न UUID है:
CREATE TABLE t2 (id uuid PRIMARY KEY, foo text);
INSERT INTO t2 (id, foo) VALUES (gen_random_uuid(), ?) RETURNING id;
INSERT के समान, UPDATE और DELETE स्टेटमेंट में पोस्टग्रेज में रिटर्निंग क्लॉज भी हो सकते हैं। रिटर्निंग क्लॉज एक पोस्टग्रेज एक्सटेंशन है, और SQL मानक का हिस्सा नहीं है।
सेट में कोई भी
एप्लिकेशन कोड से, आप एक WHERE क्लॉज कैसे बनाएंगे, जिसे स्वीकार्य मानों के सेट के विरुद्ध कॉलम के मान से मेल खाना चाहिए? जब मानों की संख्या पहले से ज्ञात हो, तो SQL स्थिर होता है:
stmt = conn.prepareStatement("SELECT key, value FROM kv WHERE key IN (?, ?)");
stmt.setString(1, key[0]);
stmt.setString(2, key[1]);
लेकिन क्या होगा अगर चाबियों की संख्या 2 नहीं बल्कि कोई भी संख्या हो सकती है? क्या आप गतिशील रूप से SQL कथन का निर्माण करेंगे? Postgres सरणियों का उपयोग करना एक आसान विकल्प है:
SELECT key, value FROM kv WHERE key = ANY(?)
उपरोक्त कोई भी ऑपरेटर तर्क के रूप में एक सरणी लेता है। खंड कुंजी =कोई भी(?) उन सभी पंक्तियों का चयन करता है जहां कुंजी . का मान होता है आपूर्ति की गई सरणी के तत्वों में से एक है। इसके साथ, आवेदन कोड को सरल बनाया जा सकता है:
stmt = conn.prepareStatement("SELECT key, value FROM kv WHERE key = ANY(?)");
a = conn.createArrayOf("STRING", keys);
stmt.setArray(1, a);
यह दृष्टिकोण सीमित संख्या में मूल्यों के लिए व्यवहार्य है, यदि आपके पास मिलान करने के लिए बहुत सारे मूल्य हैं, तो अन्य विकल्पों पर विचार करें जैसे (अस्थायी) तालिकाओं या भौतिक विचारों से जुड़ना।
पंक्तियों को टेबल के बीच ले जाना
हाँ, आप एक तालिका से पंक्तियों को हटा सकते हैं और उन्हें एक एकल SQL कथन के साथ दूसरे में सम्मिलित कर सकते हैं! एक मुख्य INSERT कथन एक CTE का उपयोग करके सम्मिलित करने के लिए पंक्तियों में खींच सकता है, जो एक DELETE को लपेटता है।
WITH items AS (
DELETE FROM todos_2021
WHERE NOT done
RETURNING *
)
INSERT INTO todos_2021 SELECT * FROM items;
एप्लिकेशन कोड में समतुल्य करना बहुत ही क्रियात्मक हो सकता है, जिसमें मेमोरी में डिलीट के पूरे परिणाम को संग्रहीत करना और कई INSERTs का उपयोग करना शामिल है। माना जाता है कि चलती पंक्तियाँ शायद एक सामान्य उपयोग का मामला नहीं है, लेकिन अगर व्यावसायिक तर्क इसके लिए कहता है, तो इस दृष्टिकोण द्वारा प्रस्तुत एप्लिकेशन मेमोरी और डेटाबेस राउंड ट्रिप की बचत इसे आदर्श समाधान बनाती है।
स्रोत और गंतव्य तालिकाओं में स्तंभों का सेट समान नहीं होना चाहिए, आप निश्चित रूप से चयन/वापसी सूचियों में मानों में हेरफेर करने के लिए कार्यों को पुन:व्यवस्थित, पुनर्व्यवस्थित और उपयोग कर सकते हैं।
एक साथ होना
एप्लिकेशन कोड में NULL मान सौंपना आमतौर पर अतिरिक्त कदम उठाता है। उदाहरण के लिए, गो में, आपको sql.NullString . जैसे प्रकारों का उपयोग करना होगा; Java/JDBC में, resultSet.wasNull() . जैसे कार्य करता है . ये बोझिल और त्रुटि-प्रवण हैं।
यदि इसे संभालना संभव है, तो NULLs को खाली स्ट्रिंग्स के रूप में कहें, या NULL पूर्णांकों को 0 के रूप में कहें, किसी विशिष्ट क्वेरी के संदर्भ में, आप COALESCE फ़ंक्शन का उपयोग कर सकते हैं। COALESCE फ़ंक्शन NULL मानों को किसी विशिष्ट मान में बदल सकता है। उदाहरण के लिए इस प्रश्न पर विचार करें:
SELECT invoice_num, COALESCE(shipping_address, '')
FROM invoices
WHERE EXTRACT(month FROM raised_on) = 1 AND
EXTRACT(year FROM raised_on) = 2022
जिसे जनवरी 2022 में इनवॉइस नंबर और इनवॉइस के शिपिंग पते मिलते हैं। संभवतः, shipping_address यदि माल को भौतिक रूप से भेजना नहीं है तो NULL है। यदि एप्लिकेशन कोड ऐसे मामलों में कहीं खाली स्ट्रिंग प्रदर्शित करना चाहता है, तो कहें, COALESCE का उपयोग करना और एप्लिकेशन में NULL-हैंडलिंग कोड निकालना आसान है।
आप खाली स्ट्रिंग के बजाय अन्य स्ट्रिंग्स का भी उपयोग कर सकते हैं:
SELECT invoice_num, COALESCE(shipping_address, '* NOT SPECIFIED *') ...
आप सूची से पहला गैर-शून्य मान भी प्राप्त कर सकते हैं, या इसके बजाय निर्दिष्ट स्ट्रिंग का उपयोग कर सकते हैं। उदाहरण के लिए या तो बिलिंग पते या शिपिंग पते का उपयोग करने के लिए, आप इसका उपयोग कर सकते हैं:
SELECT invoice_num, COALESCE(billing_address, shipping_address, '* NO ADDRESS GIVEN *') ...
केस
वास्तविक जीवन, अपूर्ण डेटा से निपटने के लिए CASE एक और सहायक निर्माण है। मान लें कि shipping_address . में NULLs होने के बजाय गैर-शिप करने योग्य वस्तुओं के लिए, हमारे नॉट-सो-परफेक्ट इनवॉइस क्रिएशन सॉफ्टवेयर ने "नॉट-स्पेसिफाइड" में डाल दिया है। जब आप डेटा में पढ़ते हैं तो आप इसे NULL या खाली स्ट्रिंग में मैप करना चाहेंगे। आप केस का उपयोग कर सकते हैं:
-- map NOT-SPECIFIED to an empty string
SELECT invoice_num,
CASE shipping_address
WHEN 'NOT-SPECIFIED' THEN ''
ELSE shipping_address
END
FROM invoices;
-- same result, different syntax
SELECT invoice_num,
CASE
WHEN shipping_address = 'NOT-SPECIFIED' THEN ''
ELSE shipping_address
END
FROM invoices;
CASE में एक अस्पष्ट वाक्य रचना है, लेकिन कार्यात्मक रूप से C-जैसी भाषाओं में स्विच-केस कथनों के समान है। यहाँ एक और उदाहरण है:
SELECT invoice_num,
CASE
WHEN shipping_address IS NULL THEN 'NOT SHIPPING'
WHEN billing_address = shipping_address THEN 'SHIPPING TO PAYER'
ELSE 'SHIPPING TO ' || shipping_address
END
FROM invoices;
चुनें .. संघ
UNION का उपयोग करके दो (या अधिक) अलग-अलग SELECT स्टेटमेंट के डेटा को जोड़ा जा सकता है। उदाहरण के लिए यदि आपके पास दो टेबल हैं, एक में वर्तमान उपयोगकर्ता हैं और एक हटा दिया गया है, तो यहां बताया गया है कि उन दोनों को एक ही समय में कैसे क्वेरी करें:
SELECT id, name, address, FALSE AS is_deleted
FROM users
WHERE email = ?
UNION
SELECT id, name, address, TRUE AS is_deleted
FROM deleted_users
WHERE email = ?
दो प्रश्नों की एक ही चयन सूची होनी चाहिए, यानी एक ही संख्या और कॉलम के प्रकार को वापस करना चाहिए।
UNION डुप्लीकेट भी हटाता है। केवल अद्वितीय पंक्तियाँ लौटाई जाती हैं। यदि आप डुप्लिकेट पंक्तियों को बनाए रखना चाहते हैं, तो UNION के बजाय "UNION ALL" का उपयोग करें।
UNION की तारीफ करते हुए, INTERSECT और EXCEPT भी है, अधिक जानकारी के लिए PostgreSQL डॉक्स देखें।
चुनें .. पर अलग
SELECT द्वारा लौटाई गई डुप्लिकेट पंक्तियों को SELECT के बाद DISTINCT कीवर्ड जोड़कर जोड़ा जा सकता है (अर्थात केवल अनन्य पंक्तियाँ ही वापस आती हैं)। जबकि यह मानक SQL है, Postgres एक एक्सटेंशन प्रदान करता है, "DISTINCT ON"। इसका उपयोग करना थोड़ा मुश्किल है, लेकिन व्यवहार में यह आपके लिए आवश्यक परिणाम प्राप्त करने का सबसे संक्षिप्त तरीका है।
एक ग्राहकों . पर विचार करें प्रति ग्राहक एक पंक्ति वाली तालिका, और एक खरीदारी (कुछ) ग्राहकों द्वारा की गई प्रति खरीदारी एक पंक्ति के साथ तालिका। नीचे दी गई क्वेरी सभी ग्राहकों को उनकी प्रत्येक खरीदारी के साथ लौटाती है:
SELECT C.id, P.at
FROM customers C LEFT OUTER JOIN purchases P ON P.customer_id = C.id
ORDER BY C.id ASC, P.at ASC;
प्रत्येक ग्राहक पंक्ति को उनके द्वारा की गई प्रत्येक खरीदारी के लिए दोहराया जाता है। क्या होगा यदि हम ग्राहक की केवल पहली खरीद वापस करना चाहते हैं? हम मूल रूप से ग्राहक द्वारा पंक्तियों को क्रमबद्ध करना चाहते हैं, ग्राहक द्वारा पंक्तियों को समूहित करना चाहते हैं, प्रत्येक समूह के भीतर पंक्तियों को खरीद समय के अनुसार क्रमबद्ध करें, और अंत में प्रत्येक समूह से केवल पहली पंक्ति लौटाएं। SQL में DISTINCT ON के साथ लिखना वास्तव में छोटा है:
SELECT DISTINCT ON (C.id) C.id, P.at
FROM customers C LEFT OUTER JOIN purchases P ON P.customer_id = C.id
ORDER BY C.id ASC, P.at ASC;
जोड़ा गया "DISTINCT ON (C.id)" क्लॉज वही करता है जो ऊपर वर्णित किया गया था। बस कुछ अतिरिक्त अक्षरों के साथ यह बहुत काम है!
संख्याओं का खंड के अनुसार क्रम में उपयोग करना
एक टेबल से ग्राहकों के नामों की सूची और उनके फोन नंबरों का एरिया कोड लाने पर विचार करें। हम मान लेंगे कि यूएस फ़ोन नंबर (123) 456-7890
. के रूप में स्वरूपित संग्रहीत हैं . अन्य देशों के लिए, हम क्षेत्र कोड के रूप में केवल "NON-US" कहेंगे।
SELECT last_name, first_name,
CASE country_code
WHEN 'US' THEN substr(phone, 2, 3)
ELSE 'NON-US'
END
FROM customers;
यह सब ठीक है, और हमारे पास CASE का निर्माण भी है, लेकिन क्या होगा यदि हमें इसे अभी क्षेत्र कोड द्वारा क्रमबद्ध करने की आवश्यकता है?
यह काम करता है:
SELECT last_name, first_name,
CASE country_code
WHEN 'US' THEN substr(phone, 2, 3)
ELSE 'NON-US'
END
FROM customers
ORDER BY
CASE country_code
WHEN 'US' THEN substr(phone, 2, 3)
ELSE 'NON-US'
END ASC;
लेकिन उह! केस क्लॉज को दोहराना बदसूरत और त्रुटि-प्रवण है। हम एक संग्रहीत फ़ंक्शन लिख सकते हैं जो देश कोड और फोन लेता है और क्षेत्र कोड देता है, लेकिन वास्तव में एक अच्छा विकल्प है:
SELECT last_name, first_name,
CASE country_code
WHEN 'US' THEN substr(phone, 2, 3)
ELSE 'NON-US'
END
FROM customers
ORDER BY 3 ASC;
"ऑर्डर बाय 3" कहता है कि तीसरे क्षेत्र द्वारा आदेश दिया गया है! जब आप चयन सूची को पुनर्व्यवस्थित करते हैं तो आपको संख्या को अपडेट करना याद रखना होगा, लेकिन यह आमतौर पर इसके लायक है।