এসকিউএলএলচেমির কি জ্যাঙ্গোর get_or_create এর সমতুল্য রয়েছে?


160

আমি ইতিমধ্যে ডাটাবেস থেকে একটি অবজেক্ট পেতে চাই (সরবরাহিত প্যারামিটারের উপর ভিত্তি করে) বা এটি না থাকলে এটি তৈরি করতে চাই।

জ্যাঙ্গোর get_or_create(বা উত্স ) এটি করে। এসকিউএএলএলচেমিতে কি সমান শর্টকাট আছে?

আমি বর্তমানে এটি স্পষ্টভাবে এই মত লিখছি:

def get_or_create_instrument(session, serial_number):
    instrument = session.query(Instrument).filter_by(serial_number=serial_number).first()
    if instrument:
        return instrument
    else:
        instrument = Instrument(serial_number)
        session.add(instrument)
        return instrument

4
যারা এখনও অবজেক্টটি যুক্ত করতে চান এটি এখনও উপস্থিত না থাকলে দেখুন session.merge: stackoverflow.com/questions/12297156/…
আন্তন তারাসেনকো

উত্তর:


96

এটি মূলত এটি করার উপায়, এএফআইএইকে সহজেই কোনও শর্টকাট নেই।

আপনি অবশ্যই এটি সাধারণকরণ করতে পারেন:

def get_or_create(session, model, defaults=None, **kwargs):
    instance = session.query(model).filter_by(**kwargs).first()
    if instance:
        return instance, False
    else:
        params = dict((k, v) for k, v in kwargs.iteritems() if not isinstance(v, ClauseElement))
        params.update(defaults or {})
        instance = model(**params)
        session.add(instance)
        return instance, True

2
আমি মনে করি আপনি যেখানে "সেশন.কিওয়ারি (মডেল.ফিল্টার_বি (** কোয়ার্গস)। প্রথম ()" পড়েছেন সেখানে আপনার "সেশন.কুইয়ারি (মডেল.ফিল্টার_বি (** কোয়ার্গস)) পড়তে হবে first প্রথম ()"।
পকোচ

3
এটির চারপাশে কোনও লক থাকা উচিত যাতে এই থ্রেডটি করার সুযোগ পাওয়ার আগে অন্য থ্রেড কোনও উদাহরণ তৈরি না করে?
ইওহানম

2
@ ইওহানএম: সাধারণত আপনার অধিবেশন থ্রেডলোকাল হবে তাই এটি বিবেচ্য নয়। এসকিউএএএলএলচেমি অধিবেশনটি থ্রেড-নিরাপদ নয়।
ওল্ফ

5
@ ওল্ফএইচ একই সাথে একই রেকর্ড তৈরি করার চেষ্টা করা অন্য প্রক্রিয়া হতে পারে। জ্যাঞ্জোর get_or_create এর বাস্তবায়ন দেখুন। এটি সততা ত্রুটি পরীক্ষা করে এবং অনন্য সীমাবদ্ধতার যথাযথ ব্যবহারের উপর নির্ভর করে।
ইভান ভাইরাবায়ান

1
@ ইভানভিরাবায়ান: আমি ধরে নিয়েছি @ ইওহানম এই অধিবেশনটির কথা বলছে। try...except IntegrityError: instance = session.Query(...)সেক্ষেত্রে session.addব্লকের চারপাশে একটি জায়গা থাকা উচিত ।
ওল্ফ

109

@ ওওএলপিএইচ এর সমাধান অনুসরণ করে, এই কোডটি আমার জন্য কাজ করেছে (সাধারণ সংস্করণ):

def get_or_create(session, model, **kwargs):
    instance = session.query(model).filter_by(**kwargs).first()
    if instance:
        return instance
    else:
        instance = model(**kwargs)
        session.add(instance)
        session.commit()
        return instance

এটির সাহায্যে আমি আমার মডেলের যে কোনও অবজেক্টকে পেতে_র_র তৈরি করতে সক্ষম।

ধরুন আমার মডেল অবজেক্টটি হ'ল:

class Country(Base):
    __tablename__ = 'countries'
    id = Column(Integer, primary_key=True)
    name = Column(String, unique=True)

আমার অবজেক্টটি পেতে বা তৈরি করতে আমি লিখি:

myCountry = get_or_create(session, Country, name=countryName)

3
আমার মতো আপনারা যারা অনুসন্ধান করছেন তাদের জন্য, এটি ইতিমধ্যে বিদ্যমান না থাকলে কোনও সারি তৈরি করার উপযুক্ত সমাধান।
স্পেন্সার রথবুন

3
আপনার সেশনে নতুন উদাহরণ যুক্ত করার দরকার নেই? অন্যথায় আপনি যদি কলিং কোডে একটি সেশন ডটমিট () জারি করেন তবে সেশনে নতুন উদাহরণ যুক্ত না হওয়ায় কিছুই হবে না।
ক্যাডেন্ট অরেঞ্জ

1
এই জন্য আপনাকে ধন্যবাদ. আমি এটি এত দরকারী হিসাবে খুঁজে পেয়েছি যে ভবিষ্যতের ব্যবহারের জন্য আমি এর একটি সংক্ষেপ তৈরি করেছি। gist.github.com/jangeador/e7221fc3b5ebeeac9a08
জাঙ্গিয়াডোর

আমার কোডটি কোথায় রাখা দরকার?
ভিক্টর আলভারাডো

7
আপনি এই মতামত হিসাবে অধিবেশনটি পাস করার কারণে, এড়ানো ভাল হতে পারে commit(বা কমপক্ষে কেবল flushপরিবর্তে কেবল ব্যবহার করুন) use এটি এই পদ্ধতির কলারে সেশন নিয়ন্ত্রণ ছেড়ে দেয় এবং অকাল প্রতিশ্রুতি জারি করার ঝুঁকি রাখে না। এছাড়াও, one_or_none()পরিবর্তে ব্যবহার first()করা কিছুটা নিরাপদ হতে পারে।
exhuma

52

আমি এই সমস্যাটি নিয়ে খেলছি এবং মোটামুটি শক্ত সমাধান দিয়ে শেষ করেছি:

def get_one_or_create(session,
                      model,
                      create_method='',
                      create_method_kwargs=None,
                      **kwargs):
    try:
        return session.query(model).filter_by(**kwargs).one(), False
    except NoResultFound:
        kwargs.update(create_method_kwargs or {})
        created = getattr(model, create_method, model)(**kwargs)
        try:
            session.add(created)
            session.flush()
            return created, True
        except IntegrityError:
            session.rollback()
            return session.query(model).filter_by(**kwargs).one(), False

আমি সমস্ত বিবরণে একটি মোটামুটি বিস্তৃত ব্লগ পোস্ট লিখেছি , তবে কেন আমি এটি ব্যবহার করেছি সে সম্পর্কে কয়েকটি বেশ ধারণা।

  1. এটি এমন একটি টিউপলে আনপ্যাক করে যা আপনাকে জানায় যে বস্তুর অস্তিত্ব আছে কি না। এটি প্রায়শই আপনার কর্মপ্রবাহে কার্যকর হতে পারে।

  2. ফাংশনটি @classmethodসজ্জিত স্রষ্টা ফাংশনগুলির সাথে কাজ করার ক্ষমতা দেয় (এবং তাদের নির্দিষ্ট বৈশিষ্ট্যগুলি)।

  3. আপনার যখন ডায়াস্টোরের সাথে একাধিক প্রক্রিয়া সংযুক্ত থাকে তখন সমাধানটি রেস শর্তগুলির বিরুদ্ধে সুরক্ষা দেয়।

সম্পাদনা করুন: আমি পরিবর্তন করেছি session.commit()করতে session.flush()যেমন ব্যাখ্যা এই ব্লগ পোস্টে । নোট করুন যে এই সিদ্ধান্তগুলি ব্যবহৃত ডেটাস্টোরের সাথে সুনির্দিষ্ট (এই ক্ষেত্রে পোস্টগ্রিস)।

সম্পাদনা 2: আমি ফাংশনটিতে একটি ডিফল্ট মান হিসাবে একটি using using ব্যবহার করে আপডেট করেছি কারণ এটি সাধারণত পাইথন গেটচা। মন্তব্যের জন্য ধন্যবাদ , নাইজেল! যদি এই গোটচা সম্পর্কে আপনার কৌতূহল থাকে তবে এই স্ট্যাক ওভারফ্লো প্রশ্ন এবং এই ব্লগ পোস্টটি দেখুন


1
স্পেন্সার যা বলছেন তার তুলনায় , সমাধানটি ভাল কারণ এটি রেসের শর্তগুলি প্রতিরোধ করে (সেশনটি প্রতিশ্রুতিবদ্ধ / চালিত করে, সাবধান হন) এবং জাঙ্গো যা করেন তা পুরোপুরি অনুকরণ করে।
কিডৌক

@ কিদ্দৌক না, এটি "নিখুঁতভাবে" নকল করে না। জ্যাঙ্গো এর get_or_createহয় না সুতা-নিরাপদ। এটি পারমাণবিক নয়। এছাড়াও, get_or_createউদাহরণটি তৈরি করা হলে বা অন্যথায় কোনও মিথ্যা পতাকা প্রদান করলে জ্যাঙ্গোর সত্যিকারের পতাকা ফেরত আসে।
কর

@ কেট যদি আপনি জ্যাঙ্গোর দিকে তাকান তবে get_or_createএটি প্রায় একই জিনিসটি করে। এই সমাধানটি True/Falseবস্তুটি তৈরি বা আনয়ন করা হলেও সংকেত দিতে পতাকাটি ফেরত দেয় এবং এটিও পারমাণবিক নয়। তবে, থ্রেড-সুরক্ষা এবং পারমাণবিক আপডেটগুলি ডেটাবেসের জন্য উদ্বেগ, জ্যাঙ্গো, ফ্লাস্ক বা এসকিউএলএলচেমির জন্য নয় এবং এই সমাধান এবং জ্যাঙ্গোর উভয় ক্ষেত্রেই ডাটাবেসের লেনদেনের মাধ্যমে সমাধান করা হয়।
এরিক

1
মনে করুন যে কোনও নাল নথির ক্ষেত্রটিকে একটি নতুন রেকর্ডের জন্য নাল মান প্রদান করা হয়েছিল, এটি ইন্টিগ্রিরিটিয়েরর উত্থাপন করবে। পুরো জিনিসটি গণ্ডগোল হয়ে যায়, এখন আমরা জানি না আসলে কী ঘটেছিল এবং আমরা আবার একটি ত্রুটি পেয়েছি, কোনও রেকর্ড পাওয়া যায় নি।
রাজাত

2
এই ক্লায়েন্টটি বস্তুটি তৈরি না করায় IntegrityErrorমামলাটি কী ফিরে আসা উচিত নয় False?
কেভিমেচ

11

এরিকের দুর্দান্ত উত্তরের একটি পরিবর্তিত সংস্করণ

def get_one_or_create(session,
                      model,
                      create_method='',
                      create_method_kwargs=None,
                      **kwargs):
    try:
        return session.query(model).filter_by(**kwargs).one(), True
    except NoResultFound:
        kwargs.update(create_method_kwargs or {})
        try:
            with session.begin_nested():
                created = getattr(model, create_method, model)(**kwargs)
                session.add(created)
            return created, False
        except IntegrityError:
            return session.query(model).filter_by(**kwargs).one(), True
  • একটি ব্যবহার করুন নেস্টেড লেনদেন শুধুমাত্র রোল ফিরে পরিবর্তে ফিরে সবকিছু গুড়গুড় নতুন আইটেম সংযোজন (এই দেখুন উত্তর SQLite সঙ্গে নেস্টেড লেনদেনের ব্যবহার করতে)
  • সরান create_method। যদি তৈরি বস্তুর সম্পর্ক থাকে এবং সেগুলিকে সম্পর্কের মাধ্যমে সদস্যদের অর্পণ করা হয় তবে এটি স্বয়ংক্রিয়ভাবে অধিবেশনটিতে যুক্ত হবে। যেমন একটি তৈরি book, যা হয়েছে user_idএবং userসংশ্লিষ্ট সম্পর্ক, তারপর করছেন হিসাবে book.user=<user object>ভেতরে create_methodযোগ হবে bookসেশনের। এর অর্থ একটি ইভেন্টের রোলব্যাক থেকে উপকার পেতে create_methodঅবশ্যই এটির ভিতরে থাকতে হবে with। নোট করুন যে begin_nestedস্বয়ংক্রিয়ভাবে ফ্লাশ ট্রিগার করে।

মনে রাখবেন যে মাইএসকিউএল ব্যবহার করে, লেনদেনের বিচ্ছিন্নতা স্তরটি কাজ করার READ COMMITTEDপরিবর্তে সেট করতে হবে REPEATABLE READ। জ্যাঙ্গোর get_or_create (এবং এখানে ) একই স্তর ব্যবহার করে, জাজানো ডকুমেন্টেশনও দেখুন


আমি পছন্দ করি যে এটি সম্পর্কিত নয় এমন পরিবর্তনগুলি ফিরিয়ে এড়াতে এড়িয়ে যায়, তবে সেশনটি একই লেনদেনের আগে সেশনটি যদি মডেলটি জিজ্ঞাসা করে থাকে তবে মাইএসকিউএল ডিফল্ট বিচ্ছিন্নতা স্তরের IntegrityErrorসাথে পুনরায় জিজ্ঞাসাটি ব্যর্থ হতে পারে । আমি যে উত্সাহটি নিয়ে আসতে পারি তার সেরা সমাধানটি এই ক্যোয়ারির আগে কল করা , যা ব্যবহারকারী এটি আশাও করতে পারে না বলে এটিও আদর্শ নয়। সেশন.রোলব্যাক () একটি নতুন লেনদেন শুরুর ক্ষেত্রে একই প্রভাব রয়েছে বলে রেফারেন্স করা উত্তরে এই সমস্যা নেই। NoResultFoundREPEATABLE READsession.commit()
কেভিমেচ

হু, তিল কোয়েস্টটি কোনও নেস্টেড লেনদেনের কাজে লাগিয়ে দেবে? আপনি ঠিক বলেছেন যে commitএই ফাংশনটির অভ্যন্তরীণটি একটি করার চেয়ে তাত্ক্ষণিকভাবে খারাপ rollback, যদিও নির্দিষ্ট ব্যবহারের ক্ষেত্রে এটি গ্রহণযোগ্য হতে পারে।
অ্যাডভারসাস

হ্যাঁ, প্রাথমিক জিজ্ঞাসাটি কোনও নেস্টেড লেনদেনে রাখার ফলে দ্বিতীয় ক্যোয়ারির পক্ষে কাজ করা কমপক্ষে সম্ভব হয়ে যায়। এটি এখনও ব্যর্থ হবে যদি ব্যবহারকারী স্পষ্টভাবে একই লেনদেনের আগে মডেলটি জিজ্ঞাসা করে। আমি সিদ্ধান্ত নিয়েছি যে এটি গ্রহণযোগ্য এবং ব্যবহারকারীকে কেবল এটি না করা বা অন্যথায় ব্যতিক্রমটি ধরতে এবং commit()নিজেরাই সিদ্ধান্ত নেওয়ার বিষয়ে সতর্ক করতে হবে। কোডটি সম্পর্কে আমার উপলব্ধি যদি সঠিক হয় তবে জাঙ্গো এটি করে।
কেভমিচ

জ্যাঙ্গো ডকুমেন্টেশনে তারা বলেছে যে `পড়ুন কমিটিকেটেড , so it does not look like they try to handle this. Looking at the [source](https://github.com/django/django/blob/master/django/db/models/query.py#L491) confirms this. I'm not sure I understand your reply, you mean the user should put his/her query in a nested transaction? It's not clear to me how a সেভপয়েন্ট` প্রভাবগুলি পড়ুন REPEATABLE READ। যদি কোনও প্রভাব না থাকে তবে পরিস্থিতিটি অবিস্মরণীয় বলে মনে হয়, যদি কার্যকর হয় তবে খুব শেষ প্রশ্নটি বাসা বাঁধতে পারে?
অ্যাডভারস

এটি সম্পর্কে আকর্ষণীয় READ COMMITED, সম্ভবত আমার নিজের ডেটাবেস ডিফল্টগুলি স্পর্শ না করার বিষয়ে পুনর্বিবেচনা করা উচিত। আমি পরীক্ষা করে দেখেছি যে SAVEPOINTকোনও ক্যোয়ারী তৈরির আগে থেকেই পুনরুদ্ধার করা এটিকে এমন করে তোলে যেন সেই ক্যোয়ারীটি কখনই সুখী হয় না REPEATABLE READ। অতএব, আমি নেস্টেড লেনদেনে ট্রাই ক্লজে ক্যোয়ারীটি আবদ্ধ করা প্রয়োজনীয় বলে মনে করেছি যাতে IntegrityErrorক্লজ ব্যতীত কোয়েরিটি সব কাজ করতে পারে।
কেভমিচ

6

এই SQLALchemy রেসিপি কাজটি সুন্দর এবং মার্জিত করে।

প্রথম কাজটি হ'ল কোনও ফাংশনটি সংজ্ঞায়িত করা হয় যা সেশনটির সাথে কাজ করার জন্য দেওয়া হয় এবং সেশন () এর সাথে একটি অভিধান যুক্ত করে যা বর্তমানের অনন্য কীগুলির উপর নজর রাখে ।

def _unique(session, cls, hashfunc, queryfunc, constructor, arg, kw):
    cache = getattr(session, '_unique_cache', None)
    if cache is None:
        session._unique_cache = cache = {}

    key = (cls, hashfunc(*arg, **kw))
    if key in cache:
        return cache[key]
    else:
        with session.no_autoflush:
            q = session.query(cls)
            q = queryfunc(q, *arg, **kw)
            obj = q.first()
            if not obj:
                obj = constructor(*arg, **kw)
                session.add(obj)
        cache[key] = obj
        return obj

এই ফাংশনটি ব্যবহারের একটি উদাহরণ একটি মিশ্রণে থাকবে:

class UniqueMixin(object):
    @classmethod
    def unique_hash(cls, *arg, **kw):
        raise NotImplementedError()

    @classmethod
    def unique_filter(cls, query, *arg, **kw):
        raise NotImplementedError()

    @classmethod
    def as_unique(cls, session, *arg, **kw):
        return _unique(
                    session,
                    cls,
                    cls.unique_hash,
                    cls.unique_filter,
                    cls,
                    arg, kw
            )

এবং অবশেষে অনন্য get_or_create মডেল তৈরি করছে:

from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

engine = create_engine('sqlite://', echo=True)

Session = sessionmaker(bind=engine)

class Widget(UniqueMixin, Base):
    __tablename__ = 'widget'

    id = Column(Integer, primary_key=True)
    name = Column(String, unique=True, nullable=False)

    @classmethod
    def unique_hash(cls, name):
        return name

    @classmethod
    def unique_filter(cls, query, name):
        return query.filter(Widget.name == name)

Base.metadata.create_all(engine)

session = Session()

w1, w2, w3 = Widget.as_unique(session, name='w1'), \
                Widget.as_unique(session, name='w2'), \
                Widget.as_unique(session, name='w3')
w1b = Widget.as_unique(session, name='w1')

assert w1 is w1b
assert w2 is not w3
assert w2 is not w1

session.commit()

রেসিপিটি ধারণাটির আরও গভীরতর হয় এবং বিভিন্ন পদ্ধতির সরবরাহ করে তবে আমি এটিকে দুর্দান্ত সাফল্যের সাথে ব্যবহার করেছি।


1
আমি এই রেসিপিটি পছন্দ করি যদি কেবলমাত্র একটিমাত্র এসকিউএলএলচেমি সেশন অবজেক্ট ডাটাবেসটি পরিবর্তন করতে পারে। আমি ভুল হতে পারি, তবে অন্য সেশনগুলি (এসকিউএলএলচেমি বা না) একই সাথে ডাটাবেসটি সংশোধন করে আমি দেখতে পাচ্ছি না যে লেনদেন চলমান থাকাকালীন এটি অন্যান্য সেশনগুলির দ্বারা তৈরি হওয়া বস্তুর বিরুদ্ধে কীভাবে সুরক্ষা দেয়। সেক্ষেত্রে আমি মনে করি যে সেশন.এডিডি () এর পরে ফ্লাশিং এবং স্ট্যাকওভারফ্লো . com/a/21146492/3690333 এর মত ব্যতিক্রম হ্যান্ডলিংয়ের উপর নির্ভর করে এমন সমাধানগুলি আরও নির্ভরযোগ্য।
ট্রিলিস্যাক

3

শব্দার্থগতভাবে নিকটতম সম্ভবত:

def get_or_create(model, **kwargs):
    """SqlAlchemy implementation of Django's get_or_create.
    """
    session = Session()
    instance = session.query(model).filter_by(**kwargs).first()
    if instance:
        return instance, False
    else:
        instance = model(**kwargs)
        session.add(instance)
        session.commit()
        return instance, True

Sessionস্কেলচেমিতে বিশ্বব্যাপী সংজ্ঞায়িত করা কতটা কোশার তা নিশ্চিত নয় , তবে জ্যাঙ্গো সংস্করণ কোনও সংযোগ নেয় না ...

ফিরে আসা টিপলটিতে উদাহরণ এবং একটি বুলিয়ান রয়েছে যা উদাহরণটি তৈরি করা হয়েছে কিনা তা বোঝায় (উদাহরণস্বরূপ আমরা এটি ডিবি থেকে উদাহরণটি পড়লে এটি মিথ্যা)।

জ্যাঙ্গো get_or_createপ্রায়শই বিশ্বব্যাপী ডেটা উপলব্ধ কিনা তা নিশ্চিত করতে ব্যবহৃত হয়, তাই আমি যতটা সম্ভব প্রাথমিক পর্যায়ে প্রতিশ্রুতিবদ্ধ।


এটি ততক্ষণ কাজ করা উচিত যতক্ষণ সেশন তৈরি এবং এটি দ্বারা ট্র্যাক হয় scoped_session, যার থ্রেড-নিরাপদ অধিবেশন পরিচালন প্রয়োগ করা উচিত (এটি কি 2014 সালে বিদ্যমান ছিল?)
কাওবার্ট

2

আমি কেভিনকে কিছুটা সরল করে দিলাম। সম্পূর্ণ বিবরণটি একটি if/ elseবিবৃতিতে মোড়ানো এড়াতে সমাধান । এই উপায়টিতে একটি মাত্র আছে return, যা আমি ক্লিনারটি দেখতে পাই:

def get_or_create(session, model, **kwargs):
    instance = session.query(model).filter_by(**kwargs).first()

    if not instance:
        instance = model(**kwargs)
        session.add(instance)

    return instance

1

আপনি গৃহীত বিচ্ছিন্নতা স্তরের উপর নির্ভর করে উপরের সমাধানগুলির কোনওটিই কাজ করবে না। আমি যে সর্বোত্তম সমাধানটি পেয়েছি তা হ'ল নিম্নলিখিত ফর্মের একটি র এসকিউএল:

INSERT INTO table(f1, f2, unique_f3) 
SELECT 'v1', 'v2', 'v3' 
WHERE NOT EXISTS (SELECT 1 FROM table WHERE f3 = 'v3')

বিচ্ছিন্নতা স্তর এবং সমান্তরালতার ডিগ্রি যাই হোক না কেন এটি লেনদেনগতভাবে নিরাপদ।

সাবধানতা: এটিকে দক্ষ করে তোলার জন্য, অনন্য কলামটির জন্য একটি আইএনডেক্স করা বুদ্ধিমানের কাজ হবে।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.