PostgreSQL फ़ंक्शन पैरामीटर के रूप में तालिका का नाम


85

मैं एक Postgres फ़ंक्शन में एक पैरामीटर के रूप में एक तालिका नाम पारित करना चाहता हूं। मैंने इस कोड की कोशिश की:

CREATE OR REPLACE FUNCTION some_f(param character varying) RETURNS integer 
AS $$
    BEGIN
    IF EXISTS (select * from quote_ident($1) where quote_ident($1).id=1) THEN
     return 1;
    END IF;
    return 0;
    END;
$$ LANGUAGE plpgsql;

select some_f('table_name');

और मुझे यह मिल गया:

ERROR:  syntax error at or near "."
LINE 4: ...elect * from quote_ident($1) where quote_ident($1).id=1)...
                                                             ^

********** Error **********

ERROR: syntax error at or near "."

और यहां त्रुटि तब हुई जब मुझे इसे बदल दिया गया select * from quote_ident($1) tab where tab.id=1:

ERROR:  column tab.id does not exist
LINE 1: ...T EXISTS (select * from quote_ident($1) tab where tab.id...

शायद, quote_ident($1)काम करता है, क्योंकि बिना where quote_ident($1).id=1भाग के मुझे मिलता है 1, जिसका अर्थ है कि कुछ चुना गया है। पहला quote_ident($1)काम और दूसरा एक ही समय में क्यों नहीं हो सकता ? और इसे कैसे हल किया जा सकता है?


मुझे पता है कि यह प्रश्न पुराने तरह का है, लेकिन मैंने इसे किसी अन्य मुद्दे के उत्तर की खोज करते हुए पाया। क्या आपका कार्य सिर्फ सूचनात्मक_चेम को क्वेरी नहीं कर सकता है? मेरा मतलब है, यह एक तरह से इसके लिए है - आपको क्वेरी करने और यह देखने के लिए कि डेटाबेस में क्या वस्तुएं मौजूद हैं। एक विचार है।
डेविड एस

@ दाविद एक टिप्पणी के लिए धन्यवाद, मैं कोशिश करूँगा।
जॉन डो

जवाबों:


124

इसे और सरल और बेहतर बनाया जा सकता है:

CREATE OR REPLACE FUNCTION some_f(_tbl regclass, OUT result integer)
    LANGUAGE plpgsql AS
$func$
BEGIN
   EXECUTE format('SELECT (EXISTS (SELECT FROM %s WHERE id = 1))::int', _tbl)
   INTO result;
END
$func$;

स्कीमा-योग्य नाम के साथ कॉल करें (नीचे देखें):

SELECT some_f('myschema.mytable');  -- would fail with quote_ident()

या:

SELECT some_f('"my very uncommon table name"');

प्रमुख बिंदु

  • फ़ंक्शन को सरल बनाने के लिए एक OUTपैरामीटर का उपयोग करें । आप सीधे डायनामिक SQL के परिणाम को इसमें चुन सकते हैं और किया जा सकता है। अतिरिक्त चर और कोड के लिए कोई ज़रूरत नहीं है।

  • EXISTSवास्तव में आप क्या चाहते हैं। आप प्राप्त trueपंक्ति मौजूद है या अगर falseनहीं तो। ऐसा करने के लिए विभिन्न तरीके हैं, EXISTSआमतौर पर सबसे कुशल है।

  • तुम एक चाहते करने लगते हैं पूर्णांक , वापस तो मैं डाली booleanसे परिणाम EXISTSके लिए integerहै, जो पैदावार आप वास्तव में क्या था। मैं बदले में बूलियन लौटाऊंगा

  • मैं regclassइनपुट प्रकार के रूप में ऑब्जेक्ट पहचानकर्ता प्रकार का उपयोग करता हूं _tbl। वह सब कुछ करता है quote_ident(_tbl)या format('%I', _tbl)करेगा, लेकिन बेहतर है, क्योंकि:

  • .. यह SQL इंजेक्शन को भी रोकता है ।

  • .. यह तुरंत और अधिक सुंदर रूप से विफल हो जाता है यदि तालिका का नाम अमान्य है / मौजूद नहीं है / वर्तमान उपयोगकर्ता के लिए अदृश्य है। (एक regclassपैरामीटर केवल मौजूदा तालिकाओं के लिए लागू है ।)

  • .. यह स्कीमा-योग्य तालिका नामों के साथ काम करता है, जहां एक सादा quote_ident(_tbl)या format(%I)विफल होगा क्योंकि वे अस्पष्टता को हल नहीं कर सकते हैं। आपको स्कीमा और टेबल नामों को अलग-अलग पास और बचाना होगा।

  • मैं अभी भी उपयोग करता हूं format(), क्योंकि यह सिंटैक्स को सरल करता है (और यह प्रदर्शित करने के लिए कि इसका उपयोग कैसे किया जाता है), लेकिन %sइसके बजाय %I। आमतौर पर, प्रश्न अधिक जटिल होते हैं इसलिए format()अधिक मदद मिलती है। सरल उदाहरण के लिए हम सिर्फ संक्षिप्त कर सकते हैं:

      EXECUTE 'SELECT (EXISTS (SELECT FROM ' || _tbl || ' WHERE id = 1))::int'
    
  • सूची idमें केवल एक ही तालिका है, जबकि स्तंभ को तालिका-अर्हता प्राप्त करने की आवश्यकता नहीं है FROM। इस उदाहरण में कोई अस्पष्टता संभव नहीं है। (डायनेमिक) SQL कमांड के अंदर EXECUTEएक अलग स्कोप होता है , फंक्शन वैरिएबल या पैरामीटर वहां दिखाई नहीं देते हैं - जैसा कि फंक्शन बॉडी में प्लेन SQL कमांड्स के विपरीत होता है।

यहां बताया गया है कि आप डायनेमिक SQL के लिए उपयोगकर्ता इनपुट को हमेशा सही तरीके से क्यों छोड़ते हैं:

db <> यहाँ पर SQL इंजेक्शन
Old sqlfield दिखाते हुए फिडल किया गया है


2
@suhprano: ज़रूर। इसे DO $$BEGIN EXECUTE 'ANALYZE mytbl'; END$$;
आज़माइए

% s और% L क्यों नहीं?
लोटस

3
@ लोटस: स्पष्टीकरण उत्तर में है। regclassपाठ के रूप में आउटपुट होने पर मान स्वचालित रूप से बच जाते हैं। इस मामले में गलत%L होगा ।
एरविन ब्रैंडस्टैटर

CREATE OR REPLACE FUNCTION table_rows(_tbl regclass, OUT result integer) AS $func$ BEGIN EXECUTE 'SELECT (SELECT count(1) FROM ' || _tbl || ' )::int' INTO result; END $func$ LANGUAGE plpgsql; तालिका पंक्ति गणना फ़ंक्शन बनाएं,select table_rows('nf_part1');
l mingzhi

हम सभी कॉलम कैसे प्राप्त कर सकते हैं?
आशीष

12

यदि संभव हो तो, ऐसा न करें।

इसका उत्तर है- यह एक प्रतिमान है। यदि क्लाइंट को उस तालिका का पता है, जिससे वह डेटा चाहता है, तो SELECT FROM ThatTable। यदि एक डेटाबेस को इस तरह से डिज़ाइन किया गया है कि यह आवश्यक है, तो यह उप-आशावादी रूप से डिज़ाइन किया गया लगता है। यदि डेटा एक्सेस लेयर को यह जानना होगा कि क्या किसी टेबल में कोई वैल्यू मौजूद है, तो एसक्यूएल को उस कोड में बनाना आसान है, और इस कोड को डेटाबेस में पुश करना अच्छा नहीं है।

मेरे लिए यह एक एलेवेटर के अंदर एक उपकरण स्थापित करने जैसा लगता है जहां कोई वांछित मंजिल की संख्या में टाइप कर सकता है। गो बटन को दबाए जाने के बाद, यह वांछित तल के लिए सही बटन पर एक यांत्रिक हाथ ले जाता है और इसे दबाता है। यह कई संभावित मुद्दों का परिचय देता है।

कृपया ध्यान दें: यहाँ मजाक का कोई इरादा नहीं है। मेरा मूर्खतापूर्ण लिफ्ट उदाहरण था * इस तकनीक के साथ मुद्दों को इंगित करने के लिए मैं सबसे अच्छी डिवाइस * कल्पना कर सकता था। यह अस्पष्ट / विचित्र सर्वर-साइड SQL कोड का उपयोग करके एक कॉलर स्पेस (एक मजबूत और अच्छी तरह से समझे गए डीएसएल, एसक्यूएल का उपयोग करके) से अप्रत्यक्ष की एक बेकार परत चलती है।

डायनेमिक SQL में क्वेरी कंस्ट्रक्शन लॉजिक के मूवमेंट के माध्यम से ऐसी जिम्मेदारी-बंटवारा कोड को समझने में कठिन बनाता है। यह एक मानक और विश्वसनीय कन्वेंशन का उल्लंघन करता है (कैसे एक SQL क्वेरी चुनता है कि क्या चुनना है) कस्टम कोड के नाम पर त्रुटि के लिए संभावित।

इस दृष्टिकोण के साथ कुछ संभावित समस्याओं पर यहां विस्तृत बिंदु दिए गए हैं:

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

  • संग्रहीत कार्यविधियाँ और फ़ंक्शन उन संसाधनों तक पहुँच सकते हैं जो SP / फ़ंक्शन स्वामी के पास अधिकार हैं, लेकिन कॉलर नहीं करता है। जहां तक ​​मैं समझता हूं, विशेष देखभाल के बिना, तब डिफ़ॉल्ट रूप से जब आप डायनेमिक SQL बनाने वाले कोड का उपयोग करते हैं और इसे चलाते हैं, तो डेटाबेस कॉलर के अधिकारों के तहत डायनेमिक SQL निष्पादित करता है। इसका मतलब है कि आप या तो विशेषाधिकार प्राप्त वस्तुओं का उपयोग करने में सक्षम नहीं होंगे, या आपको उन्हें सभी क्लाइंट तक खोलना होगा, जिससे विशेषाधिकारित डेटा पर संभावित हमले की सतह क्षेत्र बढ़ेगा। निर्माण समय पर SP / फ़ंक्शन को सेट करना हमेशा किसी विशेष उपयोगकर्ता (SQL सर्वर में EXECUTE AS) के रूप में चलता है, जो उस समस्या को हल कर सकता है, लेकिन चीजों को और अधिक जटिल बनाता है। यह पिछले बिंदु में वर्णित SQL इंजेक्शन के जोखिम को बढ़ाता है, गतिशील SQL को बहुत मोहक हमला वेक्टर बनाता है।

  • जब एक डेवलपर को यह समझना चाहिए कि इसे संशोधित करने या बग को ठीक करने के लिए एप्लिकेशन कोड क्या कर रहा है, तो उसे सटीक SQL क्वेरी को निष्पादित करने में मुश्किल होगी। SQL प्रोफाइलर का उपयोग किया जा सकता है, लेकिन यह विशेष विशेषाधिकार लेता है और उत्पादन प्रणालियों पर नकारात्मक प्रदर्शन प्रभाव डाल सकता है। निष्पादित क्वेरी को एसपी द्वारा लॉग किया जा सकता है लेकिन इससे संदिग्ध लाभ के लिए जटिलता बढ़ जाती है (नए तालिकाओं को समायोजित करने, पुराने डेटा को शुद्ध करने आदि की आवश्यकता होती है) और यह काफी गैर-स्पष्ट है। वास्तव में, कुछ एप्लिकेशन ऐसे होते हैं जैसे कि डेवलपर के पास डेटाबेस क्रेडेंशियल्स नहीं होते हैं, इसलिए उसके लिए वास्तव में क्वेरी सबमिट किए जाने को देखना लगभग असंभव हो जाता है।

  • जब कोई त्रुटि होती है, जैसे कि जब आप ऐसी तालिका का चयन करने का प्रयास करते हैं जो मौजूद नहीं है, तो आपको डेटाबेस से "अमान्य ऑब्जेक्ट नाम" की तर्ज पर एक संदेश मिलेगा। यह ठीक वैसा ही होगा कि आप एसक्यूएल को बैक एंड या डेटाबेस में कंपोज कर रहे हैं, लेकिन अंतर यह है कि सिस्टम के समस्या निवारण की कोशिश कर रहे कुछ खराब डेवलपर को एक लेवल और गहरा करना पड़ता है, जहां नीचे एक और गुफा होती है। समस्या मौजूद है, आश्चर्य-प्रक्रिया में खुदाई करने के लिए जो यह सब पता लगाने की कोशिश करता है कि समस्या क्या है। लॉग "GetWidget में त्रुटि" नहीं दिखाएंगे, यह "OneProcedureToRuleThemAllRunner में त्रुटि" दिखाएगा। यह अमूर्तता आमतौर पर एक प्रणाली को बदतर बना देगी ।

एक पैरामीटर के आधार पर स्विचिंग टेबल नामों के छद्म सी # में एक उदाहरण:

string sql = $"SELECT * FROM {EscapeSqlIdentifier(tableName)};"
results = connection.Execute(sql);

हालांकि यह कल्पना करने योग्य हर संभव मुद्दे को समाप्त नहीं करता है, लेकिन दूसरी तकनीक के साथ मैं जिन खामियों को रेखांकित करता हूं, वे इस उदाहरण से अनुपस्थित हैं।


4
मैं इससे पूरी तरह सहमत नहीं हूं। कहते हैं, आप इस "गो" बटन को दबाते हैं और फिर कुछ तंत्र जांचता है, अगर मंजिल मौजूद है। ट्रिगर्स में फ़ंक्शंस का उपयोग किया जा सकता है, जो बदले में कुछ शर्तों की जांच कर सकता है। यह निराशा सबसे सुंदर नहीं हो सकती है, लेकिन अगर सिस्टम पहले से ही बड़ा है और आपको इसके तर्क में कुछ सुधार करने की आवश्यकता है, तो ठीक है, यह पसंद इतनी नाटकीय नहीं है, मुझे लगता है।
जॉन डो

1
लेकिन विचार करें कि एक बटन दबाने की कोशिश करने की क्रिया जो मौजूद नहीं है, बस एक अपवाद पैदा करेगा, चाहे आप इसे कैसे भी संभाल लें। आप वास्तव में एक noxistent बटन को धक्का नहीं दे सकते हैं, इसलिए जोड़ने के लिए कोई लाभ नहीं है, बटन-पुश के शीर्ष पर, nonexistent नंबरों की जांच करने के लिए एक परत, क्योंकि आपके द्वारा बनाई गई लेयर से पहले ऐसी संख्या प्रविष्टि मौजूद नहीं थी! अमूर्त मेरी राय में प्रोग्रामिंग में सबसे शक्तिशाली उपकरण है। हालाँकि, एक परत को जोड़ना जो केवल मौजूदा अमूर्तता को खराब करता है, गलत है । डेटाबेस खुद में पहले से ही एक अमूर्त परत है जो डेटा सेटों के नाम मैप करता है।
एरिक

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

1
@three जब तक किसी को किसी हुनर ​​की महारत हासिल नहीं होती ( कौशल अधिग्रहण का Dreyfus मॉडल देखें ), उसे बस नियमों का पालन करना चाहिए जैसे "डायनामिक SQL में उपयोग की जाने वाली प्रक्रिया में तालिका के नाम नहीं पास करना"। यहाँ तक कि यह भी कि यह हमेशा बुरा नहीं होता है, यह सलाह देना ही गलत सलाह है । यह जानकर, शुरुआत करने वाले को इसका इस्तेमाल करने का लालच होगा! यह बुरी बात है। किसी विषय के केवल स्वामी ही नियमों को तोड़ रहे होंगे, क्योंकि वे किसी भी विशेष मामले में जानने वाले अनुभव के साथ ही होते हैं कि क्या वास्तव में नियम तोड़ने का कोई मतलब होता है।
एरिक

1
@ तीन-कप मैंने बहुत अधिक विस्तार के साथ अपडेट किया कि यह एक बुरा विचार क्यों है।
22

10

Plpgsql कोड के अंदर, EXECUTE कथन का उपयोग उन प्रश्नों के लिए किया जाना चाहिए जिनमें तालिका नाम या स्तंभ चर से आते हैं। गतिशील रूप से उत्पन्न IF EXISTS (<query>)होने पर भी निर्माण की अनुमति नहीं queryहै।

यहाँ दोनों समस्याओं के साथ आपका कार्य निर्धारित है:

CREATE OR REPLACE FUNCTION some_f(param character varying) RETURNS integer 
AS $$
DECLARE
 v int;
BEGIN
      EXECUTE 'select 1 FROM ' || quote_ident(param) || ' WHERE '
            || quote_ident(param) || '.id = 1' INTO v;
      IF v THEN return 1; ELSE return 0; END IF;
END;
$$ LANGUAGE plpgsql;

धन्यवाद, मैं आपके उत्तर को पढ़ने के कुछ मिनट पहले ही बना रहा था। एकमात्र अंतर मुझे हटाना पड़ा quote_ident()क्योंकि इसमें अतिरिक्त उद्धरण जोड़े गए, जिसने मुझे थोड़ा आश्चर्यचकित किया, ठीक है, 'क्योंकि इसका उपयोग अधिकांश उदाहरणों में किया जाता है।
जॉन डोए

उन अतिरिक्त उद्धरणों की आवश्यकता होगी, जब / जब तालिका नाम में [az] के बाहर के अक्षर हों, या यदि / जब यह किसी आरक्षित पहचानकर्ता के साथ टकराता है (उदाहरण: तालिका नाम के रूप में "समूह")
डैनियल वेरिटे

और, वैसे, क्या आप कृपया एक लिंक प्रदान कर सकते हैं जो यह साबित करेगा कि IF EXISTS <query>निर्माण मौजूद नहीं है? मुझे पूरा यकीन है कि मैंने एक काम कोड नमूने के रूप में ऐसा कुछ देखा।
जॉन डोए

1
@ जॉनडे: प्लस्पग्ल में IF EXISTS (<query>) THEN ...पूरी तरह से वैध निर्माण है। बस के लिए गतिशील SQL के साथ नहीं <query>। मैंने इसे बहुत इस्तेमाल किया है। साथ ही, इस फंक्शन में काफी सुधार किया जा सकता है। मैंने एक उत्तर पोस्ट किया।
इरविन ब्रान्डेसटेटर

1
क्षमा करें, आप इसके बारे में सही हैं if exists(<query>), यह सामान्य मामले में मान्य है। बस जाँच की और उसके अनुसार उत्तर संशोधित किया।
डैनियल वैटरे

4

पहला वास्तव में इस अर्थ में "काम" नहीं करता है कि आप इसका मतलब है, यह केवल उसी में काम करता है जहां तक ​​यह एक त्रुटि उत्पन्न नहीं करता है।

कोशिश करें SELECT * FROM quote_ident('table_that_does_not_exist');, और आप देखेंगे कि आपका फ़ंक्शन 1 क्यों लौटाता है: चयन quote_identएक पंक्ति ( नाम ) के साथ एक पंक्ति (चर) $1या इस विशेष मामले में तालिका लौटा रहा हैtable_that_does_not_exist ) के ।

आप जो करना चाहते हैं उसे गतिशील एसक्यूएल की आवश्यकता होगी, जो वास्तव में वह जगह है जिसका उपयोग quote_*कार्यों के लिए किया जाता है।


बहुत बहुत धन्यवाद, मैट, table_that_does_not_existएक ही परिणाम दिया, तुम सही हो।
जॉन डोए

2

यदि तालिका खाली है या नहीं (आईडी = 1) का परीक्षण करने के लिए सवाल था, तो यहां इरविन के संग्रहित संस्करण का एक सरलीकृत संस्करण है:

CREATE OR REPLACE FUNCTION isEmpty(tableName text, OUT zeroIfEmpty integer) AS
$func$
BEGIN
EXECUTE format('SELECT COALESCE ((SELECT 1 FROM %s LIMIT 1),0)', tableName)
INTO zeroIfEmpty;
END
$func$ LANGUAGE plpgsql;

1

मुझे पता है कि यह एक पुराना धागा है, लेकिन मैं हाल ही में इस समस्या को हल करने की कोशिश कर रहा था - मेरे मामले में, कुछ काफी जटिल लिपियों के लिए।

संपूर्ण स्क्रिप्ट को डायनेमिक SQL में बदलना आदर्श नहीं है। यह थकाऊ और त्रुटि-रहित काम है, और आप पैरामीटर करने की क्षमता खो देते हैं: मापदंडों को SQL में स्थिरांक में प्रदर्शन और सुरक्षा के लिए बुरे परिणामों के साथ प्रक्षेपित किया जाना चाहिए।

यहां एक सरल ट्रिक है जिससे आप SQL को बरकरार रख सकते हैं यदि आपको केवल अपनी तालिका से चयन करने की आवश्यकता है - एक अस्थायी दृश्य बनाने के लिए डायनेमिक SQL का उपयोग करें:

CREATE OR REPLACE FUNCTION some_f(_tbl varchar) returns integer
AS $$
BEGIN
    drop view if exists myview;
    execute format('create temporary view myview as select * from %s', _tbl);
    -- now you can reference myview in the SQL
    IF EXISTS (select * from myview where myview.id=1) THEN
     return 1;
    END IF;
    return 0;
END;
$$ language plpgsql;

0

यदि आप तालिका नाम, स्तंभ नाम और मान को गतिशील रूप से पैरामीटर के रूप में कार्य करने के लिए पास करना चाहते हैं

इस कोड का उपयोग करें

create or replace function total_rows(tbl_name text, column_name text, value int)
returns integer as $total$
declare
total integer;
begin
    EXECUTE format('select count(*) from %s WHERE %s = %s', tbl_name, column_name, value) INTO total;
    return total;
end;
$total$ language plpgsql;


postgres=# select total_rows('tbl_name','column_name',2); --2 is the value

-2

मेरे पास PostgreSQL का 9.4 संस्करण है और मैं हमेशा इस कोड का उपयोग करता हूं:

CREATE FUNCTION add_new_table(text) RETURNS void AS
$BODY$
begin
    execute
        'CREATE TABLE ' || $1 || '(
        item_1      type,
        item_2      type
        )';
end;
$BODY$
LANGUAGE plpgsql

और तब:

SELECT add_new_table('my_table_name');

यह मेरे लिए अच्छा काम करता है।

ध्यान! उपरोक्त उदाहरण उन लोगों में से एक है जो दिखाता है कि "डेटाबेस को क्वेरी करते समय हम कैसे सुरक्षा रखना चाहते हैं" नहीं: पी


1
newतालिका बनाना किसी मौजूदा तालिका के नाम के साथ काम करने से अलग है। किसी भी तरह से, आपको कोड के रूप में निष्पादित पाठ पैरामीटर से बचना चाहिए या आप SQL इंजेक्शन के लिए खुले हैं।
इरविन ब्रान्डेसटेटर

ओह, हाँ, मेरी गलती है। विषय ने मुझे गुमराह किया और इसके अलावा मैंने इसे अंत तक नहीं पढ़ा। आम तौर पर मेरे मामले में। : P क्यों एक पाठ पैरामीटर के साथ कोड इंजेक्शन के संपर्क में है?
dm3

ओह, यह वास्तव में खतरनाक है। जवाब के लिए धन्यवाद!
dm3
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.