2016-07-06 2 views
0

아직 채워지지 않은 테이블에 여러 SQL 레코드를 업로드한다고 가정합니다. 기본 키 ("ID")가있는 레코드가 테이블이나 테이블에 커밋 될 레코드에 이미 존재하는 경우 기존 레코드를 새 레코드로 바꾸고 싶습니다. 나는 2008 년SQLAlchemy에 조건부 추가 문

내 첫번째 추측은

try: 
    session.add(record) 
    session.commit 
except: 
    session.query().\ 
     filter(Class.ID == record.ID).\ 
     update(some expression) 
    session.commit()  

는 표현이 무엇을해야 될 것 MSSQL, SQL 서버를 사용하고있어? 그리고 이것을하는 더 깨끗한 (그리고 더 안전한!) 방법이 있습니까?

답변

2

일반적으로 원 자성을 보장하는 문장을 사용하지 않는 한, 삽입 또는 업데이트 (삭제를 잊지 말 것)를 시도하는 여러 행위자로부터 발생할 수있는 경쟁 조건을 항상 고려해야합니다. 심지어 MERGE statement, 비록 하나의 진술을 할 수 있습니다 have race conditions 올바르게 사용되지 않는 경우.

전통적으로 이러한 종류의 "upsert"는 MERGE 문과 같은 저장 프로 시저 또는 다른 SQL 또는 구현 특정 기능을 사용하여 수행됩니다.

SQLAlchemy 솔루션은 무결성 오류가 발생하면 삽입을 시도하고 업데이트를 수행하거나 udpate를 수행하고 영향을받은 행이없는 경우 삽입을 시도해야합니다. 기본 키 ("ID"와 기록이있는 경우

from sqlalchemy.exc import IntegrityError 

while True: # Infinite loop, use a retry counter if necessary 
    try: 
     # begin a save point, prevents the whole transaction failing 
     # in case of an integrity error 
     with session.begin_nested(): 
      session.add(record) 
      # Flush instead of commit, we need the transaction intact 
      session.flush() 
      # If the flush is successful, break out of the loop as the insert 
      # was performed 
      break 

    except IntegrityError: 
     # Attempt the update. If the session has to reflect the changes 
     # performed by the update, change the `synchronize_session` argument. 
     if session.query(Class).\ 
       filter_by(ID=record.ID).\ 
       update({...}, 
         syncronize_session=False): 
      # 1 or more rows were affected (hopefully 1) 
      break 

     # Nothing was updated, perhaps a DELETE in between 

    # Both operations have failed, retry 

session.commit() 

에 대해서 : 모두 작업 (행 사이에 삭제 또는 삽입 얻을 수 있습니다) 실패 할 경우 재 시도 할 준비가되어 있어야한다)이 테이블이나 테이블에 커밋 될 레코드에 이미 존재하는 경우 기존 레코드를 새 레코드로 바꾸고 싶습니다.

문제의 테이블에는 동시 업데이트, 당신은 작업의 이런 종류의 Session.merge를 사용할 수있는 일이 없다는 것을 확신 할 수있는 경우 :

# Records have primary key set, on which merge can either load existing 
# state and merge, or create a new record in session if none was found. 
for record in records: 
    merged_record = session.merge(record) 
    # Note that merged_record is not record 

session.commit() 

SQLAlchemy의 병합 먼저 확인합니다 경우 인스턴스와 주어진 기본 키가 신원 맵에 존재합니다. 그렇지 않은 경우 loadTrue으로 전달되면 데이터베이스에서 기본 키를 확인합니다. 주어진 인스턴스에 기본 키가 없거나 인스턴스를 찾을 수없는 경우 새 인스턴스가 만들어집니다.

그러면 병합은 주어진 인스턴스의 상태를 찾거나 만든 인스턴스에 복사합니다. 새 인스턴스가 반환됩니다.

+0

왜 업데이트가 아닌 삽입을 먼저 시도합니까? 먼저 업데이트하면 오류가 발생하지 않으며 다른 이유로 안전하지 않은 무결성 오류가 첫 번째 try 블록에서 발생할 수 있습니다. –

+0

그건 당신의 유스 케이스에 달려있다. 대부분 새 데이터를 삽입하거나 기존 데이터를 업데이트 할 것인가? 대답에 따라 작업을 바꾸면됩니다. 당신은 예제를 거의 사용하지 않았기 때문에,'Class.ID'가 있다고 가정하는 중복 된 프라이 머리 키로부터 무결성 에러를 얻을 수 있다고 가정 할 수 있습니다. 블록은 저장 지점을 사용하므로 안전합니다. 삽입 지점이 실패하면 롤백됩니다. 오류가 잡히고 제대로 처리되므로 안전합니다. 다른 오류가 있으면 롤백이 발생합니다. –

+0

다른 말로하면 행이 존재하는지 여부를 검사 할 수 없다는 것입니다 (단 하나의 INSERT ... SELECT ... FROM ... WHERE NOT EXISTS() 쿼리를 의미하지는 않습니다). 그것은 본질적으로 2 개의 작업이며 경쟁 조건을 갖습니다. 삽입을 수행하고 실패했는지 여부를 확인해야합니다. –

0

아니요. 이렇게하는 것이 훨씬 더 좋은 패턴입니다. 레코드가 이미 존재하는지 쿼리를 먼저 확인한 다음 그에 따라 진행하십시오. 당신의 구문을 사용

,이 같은 일 것 다음

result = session.query().filter(Class.ID == record.ID).first() 

# If record does not exist in Db, then add record 
if result is None: 
    try: 
     session.add(record) 
     session.commit() 
    except: 
     db.rollback() 
     log.error('Rolling back transaction in query-none block') 

# If record does exist, then update value of record in Db 
else: 
    try: 
     session.query().\ 
      filter(Class.ID == record.ID).\ 
      update(some expression) 
     session.commit() 
    except: 
     db.rollback() 
     log.error('Rolling back transaction') 

당신이 가진 궤도에있어 그래서, try/except 블록에서 데이터베이스 작업을 마무리하는 것이 좋은 생각 당신이 쓴 것을 시험해보십시오. 수행중인 작업에 따라 예외 블록은 일반적으로 오류 메시지를 표시하거나 db 롤백을 수행해야합니다.

+1

두 명 이상의 동시 행위자가 레코드에 대해 작업 할 때 주어진 조건자가있는 레코드가없고 삽입을 시도 할 수 있기 때문에 실패합니다. –

+0

예, 좋은 지적입니다.그래서 try/except 블록을 사용하여 작업을 제안했습니다. 내 코드를 업데이트 할게. 고마워. – Malcriado415