
يوم جيد
اليوم أريد أن أتحدث عن ORM SQLAlchemy. دعنا نتحدث عن ماهية إمكانياتها ومرونتها ، وننظر أيضًا في الحالات التي لم يتم وصفها دائمًا بشكل واضح.
يحتوي ORM هذا على عتبة إدخال أعلى من المتوسط ، لذلك سأحاول شرح كل شيء بلغة بسيطة وأمثلة. هذه المقالة ستكون مفيدة لأولئك الذين يعملون بالفعل مع sqlalchemy ويريدون ترقية مهاراتهم أو مجرد التعرف على هذه المكتبة.
لغة البرمجة المستخدمة هي بيثون 3.6.
DB - بوستجرس.
رابط جيثب
إذن ما هو ORM؟
تُعد ORM (تعيين الكائنات ذات الصلة) تقنية تتيح لك تعيين نماذج لا تتوافق أنواعها. على سبيل المثال: جدول قاعدة بيانات وكائن لغة برمجة.
بمعنى آخر ، يمكنك الوصول إلى كائنات الفصل لإدارة البيانات في جداول قاعدة البيانات. يمكنك أيضًا إنشاء كائنات فئة وتعيينها وتعيينها وحذفها وتصفيتها والأهم من ذلك أن ترث جداول جداول البيانات ، مما يقلل بشكل كبير من محتوى قاعدة التعليمات البرمجية.
لاستخدام SQLAlchemy ، تحتاج إلى فهم كيفية عمله.
سيتعين على المطورين الذين يستخدمون Django-ORM إعادة بناء تفكيرهم قليلاً لإنشاء استعلامات ORM. في رأيي ، SQLAlchemy هو وحش وظيفي يمكنك قدراته ويجب عليك استخدامه ، ولكن عليك أن تفهم أن ORMs ليست مثالية دائمًا. لذلك ، سنناقش اللحظات التي يكون فيها استخدام هذه التكنولوجيا مناسبًا.
لدى SQLAlchemy مفهوم تعريفات النموذج التعريفي وغير التعريفي.
تتضمن التعريفات غير التعريفي استخدام معين () ، الذي يصف تعيين كل عمود قاعدة بيانات وفئة طراز.
تستخدم هذه المقالة التعريف التعريفي للنماذج.
المزيد هنا
هيكل الديسيبل
من أجل تناسق البيانات بالكامل ، فلنقم بإنشاء الجداول التالية.
يتم استخدام النموذج الأساسي لتحديد الأعمدة الأساسية في قاعدة البيانات.
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)
الموظف - جدول يصف الموظف الذي يعمل في المكتب
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 ليس جدول. الطبقة الموروثة من الموظف. فرصة عظيمة لفصل المنطق واستخدام الفصل كما لو كان جدول منفصل.
class EmployeeWithSkills(Employee): skills = relation(Skill, secondary=EmployeesSkills.__tablename__, lazy='joined')
القسم - القسم الذي يعمل فيه هذا الموظف. يمكن أن يتكون الشخص من عدة أقسام.
class Department(BaseModel): __tablename__ = 'departments' name = Column(VARCHAR(255), nullable=False) description = Column(VARCHAR(255), nullable=False)
جدول مراسلات الموظف والوحدات التي هو عضو فيها.
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)
جدول المراسلات للموظفين ومهاراتهم.
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)
نقوم بإنشاء عمليات ترحيل باستخدام الحزمة alembic ، والتي تتيح لك إنشاءها تلقائيًا. في هذا الدرس ، يعد الإنشاء التلقائي للهجرات مقبولًا تمامًا.
تحتوي آخر عملية ترحيل على بيانات الاختبار التي تملأ قاعدة البيانات.
كيفية تكوين alembic يمكن قراءتها هنا
نحن ننفذ عزيز رئيس ترقية علني لتنفيذ الهجرة.
الطلبات والعلاقات
دعنا نتقدم بالطلب الأول ونحصل على معلومات حول الموظف بواسطة هويته.
سيبدو الطلب كالتالي:
lesson1:
employee = session.query(Employee).filter(Employee.id == eid).one() output: ID: 2, Tony Stark
.one()
في النهاية يعني أننا نعتزم الحصول على إدخال واحد فقط. إذا كان هناك العديد من الإدخالات ، فسيتم رفع استثناء مناسب.
إذا كنا نريد الحصول على جميع الأقسام المتاحة ، فيمكننا استخدام الاستعلام التالي باستخدام .all()
lesson2:
emmployee = session.query(Department).all() output: ID: 2, name: Guards ID: 4, name: Legions
النظر في العمل مع وظائف التجميع.
يمكننا الحصول على عدد الأقسام المتاحة باستخدام الوظيفة المدمجة.
.count()
أو استخدم func.count()
. باستخدام الطريقة الثانية ، يمكنك الوصول إلى أي وظائف SQL باستخدام إما select
أو لحساب النتائج الوسيطة.
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
يستخدم العديد من المطورين الدالة count()
للتحقق من البيانات في الطلب. هذه ليست ممارسة جيدة ، مما تسبب في استخدام موارد قاعدة البيانات الإضافية وزيادة وقت تنفيذ الاستعلام. الحل الجيد هو استخدام الدالة exists()
التي تُرجع قيمة عددية:
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
الانتقال ، نحن تعقيد المهمة والتعرف على relation
الكيان أو relationship
. الحقيقة هي أنه في SQLAlchemy
بالإضافة إلى استخدام foreign_key
على مستوى قاعدة البيانات ، يتم استخدام العلاقات بين الكائنات أيضًا.
وبالتالي ، يمكننا الحصول على صف قاعدة البيانات يعتمد على المفتاح الخارجي في الكائن.
هذه الكائنات هي إسقاط على جداول قاعدة البيانات ، مترابطة.
Relations
في SQLAlchemy
على تكوين مرن ، مما يتيح لك الحصول على البيانات من قاعدة البيانات بطرق مختلفة في أوقات مختلفة باستخدام الوسيطة المسماة lazy
.
الدرجات الرئيسية من "الكسل":
select
هو الافتراضي. يقدم ORM طلبًا فقط عند الوصول إلى البيانات. يتم تنفيذه في طلب منفصل.dynamic
- يتيح لك الحصول على كائن طلب ، والذي يمكن تعديله حسب الرغبة. يتلقى البيانات من قاعدة البيانات فقط بعد استدعاء الكل () أو واحد () أو أي طرق أخرى متاحة.joined
- يضاف إلى الطلب الرئيسي باستخدام LEFT JOIN. ويتم تنفيذها على الفور.subquery
- يشبه التحديد ، ولكن يتم تنفيذه باعتباره استعلام فرعي.
الافتراضي هو select
.
يمكن أن تكون التصفية في الاستعلامات ثابتة وديناميكية. يسمح التصفية الديناميكية بتعبئة الطلب مع المرشحات ، والتي قد تختلف حسب تقدم الوظيفة.
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
تحدد فئة عامل التصفية DFilter عوامل التصفية استنادًا إلى أي إدخال. إذا تم تعريف فئة عامل التصفية ، ولكن يتم تطبيق شروط الطلب.
تقبل الدالة .filter () قبول الشروط الثنائية لـ SQLAlchemy ، بحيث يمكن تمثيلها باستخدام *
يقتصر استخدام عوامل التصفية الديناميكية على الخيال فقط. تظهر نتيجة الاستعلام الأبطال غير العاملين حاليًا.
output: Inactive_heros: Name: Tony Stark Name: Scott Lang Name: Peter Parker
أقترح العمل مع العلاقة كثير إلى كثير.
لدينا جدول الموظف حيث توجد علاقة بجدول مراسلات EmployeesSkills. أنه يحتوي على Foreign_key على جدول الموظف و foreign_key
إلى طاولة المهارة.
الدرس 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
باستخدام فئة EmployeeWithSkills في الاستعلام أعلاه ، فإننا نشير إليها كجدول قاعدة بيانات ، ولكن في الواقع مثل هذا الجدول غير موجود. هذه الفئة مختلفة عن علاقة الموظف التي لها علاقة بالكثير. حتى نتمكن من التمييز بين منطق الطبقات ، وملئه بمجموعة مختلفة من العلاقات. نتيجة للطلب ، سنرى مهارات أحد الموظفين.
نظرًا لأن الموظف يمكن أن يكون في عدة أقسام ، فقم بإنشاء علاقة تسمح لك بالحصول على هذه المعلومات.
إنشاء فئة EmployeeWithDepartments الموروثة من الموظف وإضافة ما يلي:
class EmployeeWithDepartments(Employee): departments = relation( Department, # primaryjoin=EmployeeDepartments.employee_id == Employee.id, secondary=EmployeeDepartments.__tablename__, # secondaryjoin=EmployeeDepartments.department_id == Department.id, )
الفئة التي تم إنشاؤها ليست جدول قاعدة بيانات جديد. لا يزال هذا هو جدول الموظف نفسه ، تم توسيعه فقط باستخدام relation
. بهذه الطريقة يمكنك الوصول إلى جدول EmployeeWithDepartments
أو EmployeeWithDepartments
في الاستعلامات. الفرق سيكون فقط في غياب / وجود relation
.
تشير الوسيطة الأولى إلى أي جدول سننشئ relation
.
primaryjoin
هو الشرط الذي سيتم من خلاله توصيل الجدول الثاني قبل إرفاقه بالكائن.
secondary
هو اسم الجدول الذي يحتوي على foreign_keys للمطابقة. المستخدمة في حالة كثير إلى كثير.
secondaryjoin
- شروط لمطابقة الجدول الوسيط مع الأخير.
primaryjoin
و secondaryjoin
للإشارة بوضوح إلى المراسلات في المواقف المعقدة.
في بعض الأحيان تنشأ المواقف عندما يكون من الضروري إنشاء عوامل تصفية يتم الإعلان عن حقولها في العلاقات ، وتكون العلاقات ، في المقابل ، هي علاقات الطبقة الأصلية.
EmployeeWithCadreMovements -> relation(CadreMovement) -> field
إذا كانت العلاقة تعرض قائمة من القيم ، فيجب استخدام .any () ، إذا تم توفير قيمة واحدة فقط ، فيجب استخدام .has ()
لفهم أفضل ، سيتم تفسير هذه البنية في لغة SQL إلى بنية موجودة ().
نحن نسمي الدالة get مع المعلمة سبب المعلمة ، على سبيل المثال ، simple
.
lesson6
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
النظر في إمكانية الحصول على علاقة باستخدام وظيفة التجميع. على سبيل المثال ، نحصل على آخر حركة موظفين لمستخدم معين.
primaryjoin هو شرط للانضمام إلى الجداول (إذا تم استخدام lazy = 'join'). أذكر أن اختيار يستخدم افتراضيا.
في هذه الحالة ، يتم إنشاء طلب منفصل عند الوصول إلى سمة الفئة. لهذا الطلب يمكننا تحديد شروط التصفية.
كما تعلم ، لا يمكنك استخدام وظائف التجميع في شكل "خالص" في أي مكان ، حتى نتمكن من تنفيذ هذه الميزة عن طريق تحديد العلاقة
مع المعلمات التالية:
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) ) )
عند التنفيذ ، يجمع الطلب مثل هذا:
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 )
رابط جيثب
يؤدي
SQLAlchemy هي أداة قوية لبناء الاستعلام تقلل من وقت التطوير من خلال دعم الميراث.
ولكن يجب أن تبقي على خط رفيع بين استخدام ORM وكتابة استعلامات معقدة. في بعض الحالات ، يمكن أن تخلط ORM بين المطور أو تجعل الشفرة مرهقة وغير قابلة للقراءة.
حظا سعيدا