2011-08-23 5 views
35

मेरे पास एक रिकॉर्ड है कि मैं डेटाबेस में मौजूद होना चाहता हूं यदि यह वहां नहीं है, और यदि यह पहले से मौजूद है (प्राथमिक कुंजी मौजूद है) मैं चाहता हूं कि फ़ील्ड वर्तमान स्थिति में अपडेट हो जाएं। इसे अक्सर upsert कहा जाता है।एसक्लएल्चेमी के साथ अपरर्ट कैसे करें?

निम्नलिखित अपूर्ण कोड स्निपेट दर्शाता है कि क्या काम करेगा, लेकिन यह अत्यधिक गुंजाइश लगता है (विशेष रूप से यदि बहुत अधिक कॉलम थे)। बेहतर/सर्वोत्तम तरीका क्या है?

Base = declarative_base() 
class Template(Base): 
    __tablename__ = 'templates' 
    id = Column(Integer, primary_key = True) 
    name = Column(String(80), unique = True, index = True) 
    template = Column(String(80), unique = True) 
    description = Column(String(200)) 
    def __init__(self, Name, Template, Desc): 
     self.name = Name 
     self.template = Template 
     self.description = Desc 

def UpsertDefaultTemplate(): 
    sess = Session() 
    desired_default = Template("default", "AABBCC", "This is the default template") 
    try: 
     q = sess.query(Template).filter_by(name = desiredDefault.name) 
     existing_default = q.one() 
    except sqlalchemy.orm.exc.NoResultFound: 
     #default does not exist yet, so add it... 
     sess.add(desired_default) 
    else: 
     #default already exists. Make sure the values are what we want... 
     assert isinstance(existing_default, Template) 
     existing_default.name = desired_default.name 
     existing_default.template = desired_default.template 
     existing_default.description = desired_default.description 
    sess.flush() 

क्या ऐसा करने का कोई बेहतर या कम वर्बोज़ तरीका है? कुछ इस तरह बहुत अच्छा होगा:

sess.upsert_this(desired_default, unique_key = "name") 

हालांकि unique_key kwarg स्पष्ट रूप से अनावश्यक है मैं इसे जोड़ा सिर्फ इसलिए कि SQLAlchemy जाता है के लिए एक ही प्राथमिक कुंजी के साथ काम (ORM आसानी से यह पता लगा लिए सक्षम होना चाहिए)। उदाहरण: मैं देख रहा हूं कि Session.merge लागू होगा, लेकिन यह केवल प्राथमिक कुंजी पर काम करता है, जो इस मामले में एक ऑटोइनक्रिकमेंटिंग आईडी है जो इस उद्देश्य के लिए बहुत उपयोगी नहीं है।

इसके लिए एक नमूना उपयोग केस बस एक सर्वर अनुप्रयोग शुरू करते समय है जो अपने डिफ़ॉल्ट अपेक्षित डेटा को अपग्रेड कर सकता है। यानी: इस अपरिपक्वता के लिए कोई सहमति नहीं है।

+1

क्यों आप 'name' क्षेत्र एक प्राथमिक कुंजी नहीं बना सकते अगर यह होता है अद्वितीय (और विलय इस मामले में काम करेगा)। आपको एक अलग प्राथमिक कुंजी की आवश्यकता क्यों है? – abbot

+4

@abbot: मैं एक आईडी फ़ील्ड बहस में शामिल नहीं होना चाहता, लेकिन ... संक्षिप्त जवाब "विदेशी कुंजी" है। लंबा यह है कि यद्यपि नाम वास्तव में केवल एकमात्र आवश्यक कुंजी है, फिर भी दो समस्याएं हैं। 1) जब एक टेम्पलेट रिकॉर्ड को किसी अन्य तालिका में 50 मिलियन रिकॉर्ड द्वारा संदर्भित किया जाता है जिसमें एक स्ट्रिंग फ़ील्ड के रूप में एफके पागल होता है। एक अनुक्रमित पूर्णांक बेहतर है, इसलिए प्रतीत होता है कि बिंदुहीन आईडी कॉलम। और 2) उस पर विस्तार, यदि स्ट्रिंग * एफके के रूप में उपयोग किया गया था, तो नाम बदलने के लिए अब दो स्थान हैं, जब यह बदलता है, जो परेशान है और मृत रिश्तों के मुद्दों के साथ छेड़छाड़ कर रहा है। आईडी * कभी नहीं * बदलती है। – Russ

+0

आप एक नया (बीटा) [पायथन के लिए अपर लाइब्रेरी] (https://github.com/seamusabshere/py-upsert) आज़मा सकते हैं ... यह psycopg2, sqlite3, MySQLdb –

उत्तर

31

SQLAlchemy के पास "सेव-अपडेट-अपडेट" व्यवहार है, जो हाल के संस्करणों में session.add में बनाया गया है, लेकिन पहले अलग session.saveorupdate कॉल था। यह "अपरिवर्तनीय" नहीं है लेकिन यह आपकी आवश्यकताओं के लिए पर्याप्त हो सकता है।

यह अच्छा है कि आप कई अद्वितीय कुंजी वाले वर्ग के बारे में पूछ रहे हैं; मेरा मानना ​​है कि यही कारण है कि ऐसा करने का कोई भी सही तरीका नहीं है। प्राथमिक कुंजी भी एक अनूठी कुंजी है। यदि कोई अनूठी बाधा नहीं थी, केवल प्राथमिक कुंजी, यह एक साधारण समस्या होगी: यदि दिए गए आईडी के साथ कुछ भी मौजूद नहीं है, या यदि आईडी कोई नहीं है, तो एक नया रिकॉर्ड बनाएं; अन्यथा उस प्राथमिक कुंजी के साथ मौजूदा रिकॉर्ड में सभी अन्य फ़ील्ड अपडेट करें।

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

यही कारण है कि "अप्सर्ट" ऑपरेशन में कोई निर्मित नहीं है। आवेदन को परिभाषित करना चाहिए कि प्रत्येक विशेष मामले में इसका क्या अर्थ है। दस्तावेज़ से

प्रतिलिपि बनाई जा रही:

6

SQLAlchemy दो तरीकों on_conflict_do_update() और on_conflict_do_nothing() साथ अब ON CONFLICT का समर्थन करता है

from sqlalchemy.dialects.postgresql import insert 

stmt = insert(my_table).values(user_email='[email protected]', data='inserted data') 
stmt = stmt.on_conflict_do_update(
    index_elements=[my_table.c.user_email], 
    index_where=my_table.c.user_email.like('%@gmail.com'), 
    set_=dict(data=stmt.excluded.data) 
    ) 
conn.execute(stmt) 

http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html?highlight=conflict#insert-on-conflict-upsert

1

मैं एक "इससे पहले कि आप छलांग देखो" दृष्टिकोण का उपयोग करें:

# first get the object from the database if it exists 
# we're guaranteed to only get one or zero results 
# because we're filtering by primary key 
switch_command = session.query(Switch_Command).\ 
    filter(Switch_Command.switch_id == switch.id).\ 
    filter(Switch_Command.command_id == command.id).first() 

# If we didn't get anything, make one 
if not switch_command: 
    switch_command = Switch_Command(switch_id=switch.id, command_id=command.id) 

# update the stuff we care about 
switch_command.output = 'Hooray!' 
switch_command.lastseen = datetime.datetime.utcnow() 

session.add(switch_command) 
# This will generate either an INSERT or UPDATE 
# depending on whether we have a new object or not 
session.commit() 

लाभ यह है कि यह डीबी-तटस्थ है और मुझे लगता है कि इसे पढ़ना स्पष्ट है।नुकसान यह है वहाँ की तरह एक परिदृश्य में एक संभावित रेस स्थिति है कि है निम्नलिखित:

  • हम एक switch_command के लिए डाटाबेस क्वेरी और एक
  • नहीं मिलता हम एक switch_command
  • किसी अन्य प्रक्रिया बना सकते हैं या धागा बनाता है के रूप में हमारा
  • हम प्रतिबद्ध करने के लिए कोशिश एक ही प्राथमिक कुंजी के साथ एक switch_command हमारे switch_command
+0

[यह प्रश्न] (https : //stackoverflow.com/questions/14520340/sqlalchemy-and-explicit-locking) एक कोशिश/पकड़ के साथ दौड़ की स्थिति को संभालती है – Ben

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