
Hari yang baik
Hari ini saya ingin berbicara tentang ORM SQLAlchemy. Mari kita bicara tentang apa itu tentang kemampuan dan fleksibilitasnya, dan juga mempertimbangkan kasus-kasus yang tidak selalu dijelaskan dengan jelas.
ORM ini memiliki ambang masuk di atas rata-rata, jadi saya akan mencoba menjelaskan semuanya dalam bahasa yang sederhana dan dengan contoh. Artikel ini akan berguna bagi mereka yang sudah bekerja dengan sqlalchemy dan ingin meningkatkan keterampilan mereka atau hanya berkenalan dengan perpustakaan ini.
Bahasa pemrograman yang digunakan adalah python 3.6.
DB - PostgreSQL.
Tautan Github
Jadi apa itu ORM?
ORM (Object-Relational Mapping) adalah teknologi yang memungkinkan Anda memetakan model yang tipenya tidak kompatibel. Sebagai contoh: tabel database dan objek bahasa pemrograman.
Dengan kata lain, Anda bisa mengakses objek kelas untuk mengelola data dalam tabel database. Anda juga dapat membuat, memodifikasi, menghapus, memfilter dan, yang paling penting, mewarisi objek kelas yang dipetakan ke tabel database, yang secara signifikan mengurangi konten basis kode.
Untuk menggunakan SQLAlchemy, Anda perlu memahami cara kerjanya.
Pengembang yang menggunakan Django-ORM harus sedikit membangun kembali pola pikir mereka untuk membuat permintaan ORM. Menurut pendapat saya, SQLAlchemy adalah monster fungsional yang kemampuannya dapat dan harus digunakan, tetapi Anda perlu memahami bahwa ORM tidak selalu sempurna. Karena itu, kita akan membahas saat-saat ketika penggunaan teknologi ini disarankan.
SQLAlchemy memiliki konsep definisi model deklaratif dan non-deklaratif.
Definisi non-deklaratif menyiratkan penggunaan mapper (), yang menggambarkan pemetaan setiap kolom basis data dan kelas model.
Artikel ini menggunakan definisi model deklaratif.
Lebih lanjut di sini
Struktur DB
Untuk konsistensi data yang lengkap, mari kita buat tabel berikut.
Model dasar digunakan untuk menentukan kolom dasar dalam database.
class BaseModel(Base): __abstract__ = True id = Column(Integer, nullable=False, unique=True, primary_key=True, autoincrement=True) created_at = Column(TIMESTAMP, nullable=False) updated_at = Column(TIMESTAMP, nullable=False) def __repr__(self): return "<{0.__class__.__name__}(id={0.id!r})>".format(self)
Karyawan - tabel yang menggambarkan karyawan yang bekerja di kantor
class Employee(BaseModel): __tablename__ = 'employees' first_name = Column(VARCHAR(255), nullable=False) last_name = Column(VARCHAR(255), nullable=False) phone = Column(VARCHAR(255), unique=True, nullable=True) description = Column(VARCHAR(255), nullable=True)
EmployeeWithSkills bukan sebuah tabel. Kelas diwarisi dari Karyawan. Kesempatan besar untuk memisahkan logika dan menggunakan kelas seolah-olah itu adalah tabel yang terpisah.
class EmployeeWithSkills(Employee): skills = relation(Skill, secondary=EmployeesSkills.__tablename__, lazy='joined')
Departemen - departemen tempat karyawan ini bekerja. Seseorang dapat terdiri dari beberapa departemen.
class Department(BaseModel): __tablename__ = 'departments' name = Column(VARCHAR(255), nullable=False) description = Column(VARCHAR(255), nullable=False)
Tabel korespondensi karyawan dan unit di mana ia menjadi anggota.
class EmployeeDepartments(BaseModel): __tablename__ = 'employee_departments' employee_id = Column(Integer, ForeignKey('employees.id', ondelete='CASCADE'), nullable=False, index=True) department_id = Column(Integer, ForeignKey('departments.id', ondelete='CASCADE'), nullable=False, index=True)
Tabel korespondensi karyawan dan keterampilan mereka.
class EmployeesSkills(BaseModel): __tablename__ = 'employees_skills' employee_id = Column(ForeignKey('employee.id', ondelete='CASCADE'), nullable=False, index=True) skill_id = Column(ForeignKey('skills.id', ondelete='CASCADE'), nullable=False, index=True)
Kami membuat migrasi menggunakan paket alembic, yang memungkinkan Anda untuk membuatnya secara otomatis. Dalam pelajaran ini, pembuatan migrasi secara otomatis dapat diterima.
Migrasi terbaru berisi data uji yang akan mengisi basis data.
Cara mengkonfigurasi alembic dapat dibaca di sini
Kami melakukan peningkatan kepala alembic yang berharga untuk melakukan migrasi.
Permintaan dan hubungan
Mari kita membuat permintaan pertama dan mendapatkan informasi tentang karyawan dengan id-nya.
Permintaan akan terlihat seperti ini:
lesson1:
employee = session.query(Employee).filter(Employee.id == eid).one() output: ID: 2, Tony Stark
.one()
pada akhirnya berarti kami hanya ingin mendapatkan satu entri. Jika ada beberapa entri, pengecualian yang sesuai akan dimunculkan.
Jika kami ingin mendapatkan semua departemen yang tersedia, maka kami dapat menggunakan kueri berikut menggunakan .all()
lesson2:
emmployee = session.query(Department).all() output: ID: 2, name: Guards ID: 4, name: Legions
Pertimbangkan bekerja dengan fungsi agregasi.
Kami bisa mendapatkan jumlah departemen yang tersedia menggunakan fungsi bawaan.
.count()
atau gunakan func.count()
. Menggunakan metode kedua, Anda dapat mengakses fungsi SQL apa pun dengan select
atau menghitung hasil antara.
lesson3:
def get_departments_count(session: DBSession) -> int: count = session.query(Department).count() return count def get_departments_func_count(session: DBSession) -> int: count = session.query(func.count(Department.id)).scalar() return count
Banyak pengembang menggunakan fungsi count()
untuk memeriksa data dalam permintaan. Ini bukan praktik yang baik, menyebabkan penggunaan sumber daya basis data tambahan dan meningkatkan waktu eksekusi kueri. Solusi yang baik adalah dengan menggunakan fungsi exists()
yang mengembalikan nilai skalar:
lesson3:
def check_department_exists(session: DBSession, department_name: str) -> bool: is_exists = session.query(exists().where(Department.name == department_name)).scalar() return is_exists
Selanjutnya, kami menyulitkan tugas dan berkenalan dengan relation
entitas atau relationship
. Faktanya adalah bahwa dalam SQLAlchemy
selain menggunakan foreign_key
di tingkat basis data, hubungan antar objek juga digunakan.
Dengan demikian, kita bisa mendapatkan baris basis data bergantung pada kunci asing di objek.
Objek-objek ini adalah proyeksi pada tabel database, saling berhubungan.
Relations
dalam SQLAlchemy
memiliki konfigurasi yang fleksibel, memungkinkan Anda untuk mendapatkan data dari database dengan cara yang berbeda pada waktu yang berbeda menggunakan argumen bernama lazy
.
Tingkat utama "kemalasan":
select
adalah default. ORM membuat permintaan hanya ketika mengakses data. Itu dilakukan dalam permintaan terpisah.dynamic
- memungkinkan Anda mendapatkan objek permintaan, yang dapat dimodifikasi sesuai keinginan. Ini menerima data dari database hanya setelah memanggil semua () atau satu () atau metode lain yang tersedia.joined
- ditambahkan ke permintaan utama menggunakan LEFT JOIN. Itu dilakukan segera.subquery
- mirip dengan memilih, tetapi dieksekusi sebagai subquery.
Defaultnya adalah select
.
Pemfilteran dalam kueri bisa statis dan dinamis. Penyaringan dinamis memungkinkan mengisi permintaan dengan filter, yang dapat bervariasi tergantung pada progres fungsi.
lesson4:
def dynamic_filter(session: DBSession, filter: DFilter = None): query = session.query(Employee) if filter is not None: query = query.filter(*filter.conds) employees = query.all() return employees
Kelas filter DFilter mendefinisikan filter berdasarkan input apa pun. Jika kelas filter ditentukan, tetapi lebih lanjut dalam kondisi permintaan berlaku.
Fungsi .filter () menerima menerima kondisi biner SQLAlchemy, sehingga dapat direpresentasikan menggunakan *
Penggunaan filter dinamis hanya dibatasi oleh imajinasi. Hasil kueri menunjukkan pahlawan mana yang saat ini tidak aktif.
output: Inactive_heros: Name: Tony Stark Name: Scott Lang Name: Peter Parker
Saya sarankan bekerja dengan hubungan banyak ke banyak.
Kami memiliki tabel Karyawan di mana ada hubungannya dengan tabel korespondensi EmployeesSkills. Ini berisi foreign_key di tabel karyawan dan foreign_key
ke meja keterampilan.
pelajaran 5:
def get_employee_with_skills(session: DBSession, eid: int): employee = session.query(EmployeeWithSkills).filter(EmployeeWithSkills.id == eid).one() return employee output: Employee Tony Stark has skills: Skill: Fly, Desc: I belive I can Fly. I belive I can touch the sky Skill: Light Shield, Desc: Light protect. Perfect for everything
Menggunakan kelas EmployeeWithSkills dalam kueri di atas, kami menyebutnya sebagai tabel database, tetapi pada kenyataannya tabel seperti itu tidak ada. Kelas ini berbeda dari Karyawan yang memiliki hubungan, yang memiliki hubungan banyak ke banyak. Jadi kita bisa membedakan logika kelas, mengisinya dengan serangkaian hubungan yang berbeda. Sebagai hasil dari permintaan, kami akan melihat keterampilan salah satu karyawan.
Karena karyawan dapat berada di beberapa departemen, buat hubungan yang memungkinkan Anda mendapatkan informasi ini.
Buat kelas EmployeeWithDepartments yang diwarisi dari Karyawan dan tambahkan berikut ini:
class EmployeeWithDepartments(Employee): departments = relation( Department, # primaryjoin=EmployeeDepartments.employee_id == Employee.id, secondary=EmployeeDepartments.__tablename__, # secondaryjoin=EmployeeDepartments.department_id == Department.id, )
Kelas yang dibuat bukan tabel database baru. Ini masih tabel Karyawan yang sama, hanya diperluas menggunakan relation
. Dengan cara ini Anda bisa mengakses tabel EmployeeWithDepartments
atau EmployeeWithDepartments
dalam kueri. Perbedaannya hanya akan ada / tidak adanya relation
.
Argumen pertama menunjukkan tabel mana yang akan kita buat relation
.
primaryjoin
adalah kondisi di mana tabel kedua akan terhubung sebelum terpasang ke objek.
secondary
adalah nama tabel yang berisi foreign_keys untuk dicocokkan. Digunakan untuk banyak-banyak.
secondaryjoin
- kondisi untuk mencocokkan tabel perantara dengan yang terakhir.
primaryjoin
dan primaryjoin
berfungsi untuk secara eksplisit menunjukkan korespondensi dalam situasi yang kompleks.
Kadang-kadang situasi muncul ketika perlu untuk membuat filter yang bidangnya dinyatakan dalam relasi, dan relasi, pada gilirannya, adalah relasi dari kelas asli.
EmployeeWithCadreMovements -> relation(CadreMovement) -> field
Jika relasi menampilkan daftar nilai, maka .any () harus digunakan, jika hanya satu nilai yang diberikan, maka .has () harus digunakan
Untuk pemahaman yang lebih baik, konstruk ini akan ditafsirkan dalam bahasa SQL ke dalam konstruk exist ().
Kami memanggil fungsi get dengan parameter parameter alasan, misalnya, simple
.
pelajaran6
def has_in_relations(session: DBSession, reason: str): employees = session.query(EmployeeWithCadreMovements).filter(EmployeeWithCadreMovements.cadre_movements.any(CadreMovement.reason == reason)).all() return employees output: [Steve Rogers, Tony Stark]
lession7
Pertimbangkan kemungkinan untuk memperoleh hubungan menggunakan fungsi agregasi. Sebagai contoh, kami mendapatkan perpindahan personel terakhir dari pengguna tertentu.
primaryjoin adalah kondisi untuk bergabung dengan tabel (jika lazy = 'bergabung' digunakan). Ingat bahwa pilih digunakan secara default.
Dalam hal ini, permintaan terpisah dihasilkan ketika mengakses atribut kelas. Untuk permintaan ini kita dapat menentukan kondisi penyaringan.
Seperti yang Anda ketahui, Anda tidak dapat menggunakan fungsi agregasi dalam bentuk "murni" dalam kondisi WHERE, jadi kami dapat mengimplementasikan fitur ini dengan menentukan relasi
dengan parameter berikut:
last_cadre_movement = relation( CadreMovement, primaryjoin=and_( CadreMovement.employee == Employee.id, uselist=False, CadreMovement.id == select([func.max(CadreMovement.id)]).where(CadreMovement.employee == Employee.id) ) )
Ketika dieksekusi, permintaan dikompilasi seperti ini:
SELECT cadre_movements.id AS cadre_movements_id, cadre_movements.created_at AS cadre_movements_created_at, cadre_movements.updated_at AS cadre_movements_updated_at, cadre_movements.employee AS cadre_movements_employee, cadre_movements.old_department AS cadre_movements_old_department, cadre_movements.new_department AS cadre_movements_new_department, cadre_movements.reason AS cadre_movements_reason FROM cadre_movements WHERE cadre_movements.employee = %(param_1)s AND cadre_movements.id = ( SELECT max(cadre_movements.id) AS max_1 FROM cadre_movements WHERE cadre_movements.employee = %(param_1)s )
Tautan Github
Ringkasan
SQLAlchemy adalah alat pembuat kueri yang kuat yang mengurangi waktu pengembangan dengan mendukung warisan.
Tetapi Anda harus menjaga garis tegas antara menggunakan ORM dan menulis pertanyaan kompleks. Dalam beberapa kasus, ORM dapat membingungkan pengembang atau membuat kode rumit dan tidak dapat dibaca.
Semoga beruntung