SQLAlchemy – Select Data

시작하기에 앞서 데이타베이스 연결을 우선적으로 합니다.

from sqlalchemy import create_engine
from sqlalchemy import text

uri = 'mysql://DB_USER:DB_PASS@DB_HOST/DB_NAME'

engine = create_engine(uri, echo=False)

코드를 실행하다보면 connection이 끊어질때가 있는데 그때마다 engine을 다시 연결해주시면 됩니다.

그리고 이전시간에 사용했던 테이블을 계속해서 사용할 건데요. 테이블이 데이타베이스에 없으신 분들은 아래 코드를 실행하여 생성해주시기 바랍니다.

from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy import ForeignKey
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "user_account"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(30), nullable=False)
    fullname = mapped_column(String(30))
    addresses = relationship("Address", back_populates="user")

    def __repr__(self) -> str:
        return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"

class Address(Base):
    __tablename__ = "address"

    id = mapped_column(Integer, primary_key=True)
    user_id = mapped_column(Integer, ForeignKey("user_account.id"))
    email_address = mapped_column(String(30), nullable=True)
    user = relationship("User", back_populates="addresses")

    def __repr__(self) -> str:
        return f"Address(id={self.id!r}, user_id={self.user_id!r}, email_address={self.email_address!r})"

# 테이블 생성
Base.metadata.create_all(engine)

# 생성한 테이블 삭제
Base.metadata.drop_all(engine)

select() SQL 표현식 구조

select문도 insert와 마찬가지로 statement을 먼저 만든후에 실행을 하게 되는데요. select()함수의 statement를 보시면 아래와 같습니다. 아래는 이름이 “spongebob”인 사용자의 레코드를 가져오는 코드입니다.

from sqlalchemy import select
stmt = select(User).where(User.name == "spongebob")

>>> print(stmt)
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = :name_1

위의 User는 ORM으로 정의 되었지만 ORM없이 mapper()를 통하여 mapping을 하는 경우 Table()로 정의된 변수로 대체하여 실행해도 같은 결과가 나옵니다. 다만 Table객체는 칼럼이 .c안에 들어가 있으므로 Table.c.칼럼명 이렇게 해 주셔야 에러없이 실행이 됩니다. 결과는 ORM통해서 실행했을때와 완전 동일하게 나옵니다.

from sqlalchemy import select
stmt = select(user_table).where(user_table.c.name == "spongebob")

>>> print(stmt)
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = :name_1

위와 같이 statement을 만들고나서 engine에 connect해서 해당 statement을 execute()함수로 실행을 시켜야 결과를 반환합니다.

with engine.connect() as conn:
    for row in conn.execute(stmt):
        print(row)

위와 같은 코드를 실행하면 아래와 같이 spongebob의 레코드를 볼수 있습니다. Model은 mapped_column이나 relationship또는 DeclarativeBase등을 사용하여 베이스를 만들었지만 사실상 ORM의 Session으로 실행하지 않으면 온전히 ORM을 사용한다고 볼수가 없습니다. 그런 의미에서 아래 나오는 결과는 쿼리의 결과이지 ORM이 넘겨주는 객체의 결과물은 아니에요.

(1, 'spongebob', 'Spongebob Squarepants')

ORM을 사용할때는 engine에서 connect를 하지 않고, Session을 통해서 쿼리를 하는데요.

from sqlalchemy.orm import Session

stmt = select(User).where(User.name == "spongebob")
with Session(engine) as session:
    for obj_user in session.execute(stmt):
        print(obj_user)

위와 같이 Session을 사용하는 경우 반환되는 레코드가 User모델객체이기 때문에 print를 했을때 위에서 선언한 User.__repr__()함수에서 정의한 대로 결과를 출력하니까 여기서 보여주지 않아도 되는 필드는 빼거나 보여주고 싶은 항목은 추가하시면 됩니다. 위의 ORM 코드를 실행하면 다음과 같습니다.

(User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)

검색필드 지정 및 가져올 테이블 설정

select()함수는 해당 모델의 데이타를 불러오는 기능을 합니다. 기본쿼리를 보면 다음과 같습니다.

>>> print(select(User))
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account

select에 인자로 필드명을 나열하면 명시한 필드만 불러오도록 쿼리를 생성합니다.

>>> print(select(User.name, User.fullname))
SELECT user_account.name, user_account.fullname
FROM user_account

실제로 쿼리를 실행해보겠습니다

from sqlalchemy.orm import Session

with Session(engine) as session:
    row = session.execute(select(User)).first()
    print(row)

위의 코드를 실행하면 다음과 같습니다.

(User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)

검색결과가 배열로 반환되기 때문에 User객체 하나만 보고 싶다면 배열의 첫번째 항목을 읽어오면 됩니다.

>>> row[0]
User(id=1, name='spongebob', fullname='Spongebob Squarepants')

To be continued…사장님이 일시켜서 가봐야대요….

Resources