programing

SQL Chemy는 Django의 get_or_create와 동등한 기능을 가지고 있습니까?

sourcejob 2022. 9. 25. 00:19
반응형

SQL Chemy는 Django의 get_or_create와 동등한 기능을 가지고 있습니까?

이미 있는 경우 데이터베이스에서 개체를 가져오거나(제공된 매개 변수를 기준으로), 존재하지 않는 경우 개체를 만듭니다.

장고(또는 소스)가 이 작업을 수행합니다.SQL Chemy에 동등한 숏컷이 있나요?

현재 다음과 같이 명확하게 쓰고 있습니다.

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

@WoLpH의 솔루션에 이어 다음과 같은 코드를 사용할 수 있습니다(심플 버전).

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)

기본적으로는 그렇게 하는 것입니다만, AFAIK에서는 바로 이용할 수 있는 지름길은 없습니다.

물론 다음과 같이 일반화할 수 있습니다.

def get_or_create(session, model, defaults=None, **kwargs):
    instance = session.query(model).filter_by(**kwargs).one_or_none()
    if instance:
        return instance, False
    else:
        params = {k: v for k, v in kwargs.items() if not isinstance(v, ClauseElement)}
        params.update(defaults or {})
        instance = model(**params)
        try:
            session.add(instance)
            session.commit()
        except Exception:  # The actual exception depends on the specific database so we catch all exceptions. This is similar to the official documentation: https://docs.sqlalchemy.org/en/latest/orm/session_transaction.html
            session.rollback()
            instance = session.query(model).filter_by(**kwargs).one()
            return instance, False
        else:
            return instance, True

2020 업데이트(Python 3.9+만 해당)

다음은 Python 3.9의 새로운 dict union 연산자(|=)를 사용한 보다 깔끔한 버전입니다.

def get_or_create(session, model, defaults=None, **kwargs):
    instance = session.query(model).filter_by(**kwargs).one_or_none()
    if instance:
        return instance, False
    else:
        kwargs |= defaults or {}
        instance = model(**kwargs)
        try:
            session.add(instance)
            session.commit()
        except Exception:  # The actual exception depends on the specific database so we catch all exceptions. This is similar to the official documentation: https://docs.sqlalchemy.org/en/latest/orm/session_transaction.html
            session.rollback()
            instance = session.query(model).filter_by(**kwargs).one()
            return instance, False
        else:
            return instance, True

주의:

Django 버전과 마찬가지로 중복된 키 제약 조건 및 유사한 오류를 탐지합니다.get 또는 create가 단일 결과를 반환하지 않을 경우 레이스 조건이 될 수 있습니다.

이 문제의 일부를 완화하려면 다른 문제를 추가해야 합니다.one_or_none()바로 뒤에 유행하는 스타일session.commit()레이스 조건에 대한 100% 보증은 아직 없습니다.with_for_update()또는 직렬화 가능한 트랜잭션 모드입니다.

저는 이 문제를 가지고 놀다가 상당히 강력한 해결책을 찾았습니다.

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: 일반적인 Python gotcha이므로 함수에서 {}을(를) 기본값으로 업데이트했습니다.코멘트 고마워요, 나이젤!이 gotcha에 대해 궁금하신 경우 StackOverflow 질문 및 이 블로그 게시물을 확인하십시오.

에릭의 훌륭한 대답의 수정판

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>의 of의 create_methodbook세션에 접속합니다., ,,create_method에 한다with을 사용하다:begin_nested플러시를 자동으로 트리거합니다.

만약 MySQL을 사용하여, 트랜잭션 격리 수준 MySQL을에 사용하는 경우 트랜잭션 분리 수준을 다음과 같이 설정해야 합니다 설정해야 합니다.READ COMMITTED오히려 보다는보다REPEATABLE READ이게 먹혀들게.Django의 get_or_create( 여기서)는 동일한 스트래타젬을 사용합니다.Django 매뉴얼도 참조해 주세요.

SQL ALchemy 레시피는 훌륭하고 우아하게 작동합니다.

가장 먼저 해야 할 일은 현재 고유 키를 추적하는 세션()과 사전을 관련짓는 함수를 정의하는 것입니다.

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()

레시피는 아이디어에 더 깊이 들어가 다양한 접근 방식을 제공하지만, 저는 이 방법을 매우 성공적으로 사용했습니다.

의미론적으로 가장 가까운 것은 다음과 같습니다.

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

작별을 고하는 이래를 세계적으로 정의된 글로벌하게 정의되어 있는 것에 의존하는 것이 얼마나 안전한지 모르겠다에 의존하는 것이 분명하다.Sessionsqlalchemy지만 장고 버전은 연결을 끊지 않...sqalchemy에 있지만,장고 버전은 연관성이없기 때문에...

반환되는 태플에는 인스턴스와 인스턴스가 생성되었는지 여부를 나타내는 부울이 포함됩니다(즉, DB에서 인스턴스를 읽는 경우 False).

장고의 장고의get_or_create도록 자주 나는 초기 지점 가능한에서를 저지른 것은 세계적 데이터들, 이용할 수 있도록는 데 사용됩니다.글로벌 데이터를 이용할 수 있도록 하기 위해 사용되는 경우가 많기 때문에 가능한 한 빨리 실시합니다.

나는 약간 케빈. 해결책 @ 간이 @ Kevin.sollation을 약간 사용하여 기능 전체를simplified@Kevin.so로 감싸지 않도록 합니다에 있는 모든 기능 포장을 피하기 위해를 더욱 간소화하였다.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

는 @ erik의 해결책을 가지고 있는 파이썬 패키지뿐만 아니라 파이선 버전을 패키지는 @ erik의 솔루션뿐만 아니라 버전도 포함되어 있습니다 있다.update_or_create(). https://github.com/enricobarzetti/sqlalchemy_get_or_create

채택한 격리 수준에 따라 위의 솔루션 중 어느 것도 작동하지 않습니다.제가 찾은 최고의 솔루션은 다음과 같은 형식의 RAW SQL입니다.

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

이것은 격리 수준과 병렬화의 정도에 관계없이 거래적으로 안전합니다.

주의: 효율적으로 만들려면 고유한 열에 인덱스를 지정하는 것이 좋습니다.

중 때(를 들어, 필드의 길이가 최대일 때)입니다.STRING(40) )을get or create문자열이 길면 위의 솔루션이 실패합니다.

위의 솔루션을 기반으로 하여 다음과 같은 접근방식을 제공합니다.

from sqlalchemy import Column, String

def get_or_create(self, add=True, flush=True, commit=False, **kwargs):
    """

    Get the an entity based on the kwargs or create an entity with those kwargs.

    Params:
        add: (default True) should the instance be added to the session?
        flush: (default True) flush the instance to the session?
        commit: (default False) commit the session?
        kwargs: key, value pairs of parameters to lookup/create.

    Ex: SocialPlatform.get_or_create(**{'name':'facebook'})
        returns --> existing record or, will create a new record

    ---------

    NOTE: I like to add this as a classmethod in the base class of my tables, so that
    all data models inherit the base class --> functionality is transmitted across
    all orm defined models.

    """


    # Truncate values if necessary
    for key, value in kwargs.items():

        # Only use strings
        if not isinstance(value, str):
            continue

        # Only use if it's a column
        my_col = getattr(self.__table__.columns, key)

        if not isinstance(my_col, Column):
            continue

        # Skip non strings again here
        if not isinstance(my_col.type, String):
            continue

        # Get the max length
        max_len = my_col.type.length

        if value and max_len and len(value) > max_len:

            # Update the value
            value = value[:max_len]
            kwargs[key] = value

    # -------------------------------------------------

    # Make the query...
    instance = session.query(self).filter_by(**kwargs).first()

    if instance:
        return instance

    else:
        # Max length isn't accounted for here.
        # The assumption is that auto-truncation will happen on the child-model
        # Or directtly in the db
        instance = self(**kwargs)

    # You'll usually want to add to the session
    if add:
        session.add(instance)

    # Navigate these with caution
    if add and commit:
        try:
            session.commit()
        except IntegrityError:
            session.rollback()

    elif add and flush:
        session.flush()


    return instance

언급URL : https://stackoverflow.com/questions/2546207/does-sqlalchemy-have-an-equivalent-of-djangos-get-or-create

반응형