python-oracledb(또는 cx_Oracle)로 MERGE 를 실행한 뒤 cursor.rowcount 가 0 인데, 테이블을 조회해 보면 값은 제대로 바뀌어 있다.
MERGE 문을 익명 블록 안에 넣어 실행하면 드라이버가 보는 문장은 BEGIN ... END; 라는 PL/SQL 블록이지 DML 이 아니다. 블록 실행은 영향 행 수를 돌려주지 않으므로 rowcount 는 0 이 된다.
# rowcount 가 0 이 된다
cur.execute("""
BEGIN
MERGE INTO t_target a USING t_src b ON (a.id = b.id)
WHEN MATCHED THEN UPDATE SET a.val = b.val
WHEN NOT MATCHED THEN INSERT (id, val) VALUES (b.id, b.val);
END;
""")
MERGE 를 단독 SQL 로 실행하면 rowcount 에 갱신·삽입된 행의 합계가 들어온다.
cur.execute("""
MERGE INTO t_target a USING t_src b ON (a.id = b.id)
WHEN MATCHED THEN UPDATE SET a.val = b.val
WHEN NOT MATCHED THEN INSERT (id, val) VALUES (b.id, b.val)
""")
print(cur.rowcount)
conn.commit()
블록을 유지해야 한다면 SQL%ROWCOUNT 를 OUT 바인드로 받는다.
affected = cur.var(int)
cur.execute("""
BEGIN
MERGE INTO t_target a USING t_src b ON (a.id = b.id)
WHEN MATCHED THEN UPDATE SET a.val = b.val
WHEN NOT MATCHED THEN INSERT (id, val) VALUES (b.id, b.val);
:cnt := SQL%ROWCOUNT;
END;
""", cnt=affected)
print(affected.getvalue())
executemany 로 여러 건을 한 번에 넣은 경우, rowcount 는 배치 전체의 합계를 돌려준다. 배치마다 확인하려면 cursor.getbatcherrors() 와 함께 본다.
값이 이미 같아 실제 갱신이 없어도 Oracle 의 MERGE 는 WHEN MATCHED 조건에 걸린 행을 갱신한 것으로 센다. 즉 rowcount 가 0 이라면 매칭 자체가 없었다는 뜻이지, "값이 같아서 0" 인 상황은 아니다. 불필요한 갱신을 피하려면 WHEN MATCHED THEN UPDATE ... WHERE a.val <> b.val 처럼 조건을 붙인다.
커밋 여부는 rowcount 와 무관하다. rowcount 는 문장이 끝난 직후의 값이고, 커밋하지 않으면 다른 세션에서 변경이 보이지 않을 뿐이다. 다른 도구로 조회했을 때 값이 보인다면 이미 커밋된 것이다.
SQL%ROWCOUNT 는 MERGE 전체의 처리 건수만 준다. 삽입 몇 건, 갱신 몇 건인지는 나누어 주지 않는다. 구분이 필요하면 다음 중 하나를 쓴다.
MERGE 를 INSERT 와 UPDATE 로 나누어 실행한다.MERGE 문에는 RETURNING 절을 쓸 수 없다는 점도 함께 기억한다.