backend

استعلامات Drizzle ORM العلاقاتية: دليل db.query لعام (2026)

٢٤ يونيو ٢٠٢٦

Drizzle ORM Relational Queries: db.query Guide (2026)

تسمح لك استعلامات العلاقات في Drizzle ORM بقراءة البيانات المتداخلة والمرتبطة في استدعاء واحد آمن النوع (type-safe) بدلاً من كتابة عمليات الربط (joins) اليدوية وإعادة تشكيل الصفوف. يمكنك تحديد العلاقات باستخدام relations()، وتمرير مخططك (schema) الكامل إلى drizzle()، ثم استدعاء db.query.<table>.findMany({ with: { ... } }). يقوم Drizzle بإصدار جملة SQL واحدة ويعيد كائنات متداخلة بشكل صحيح.1

ملخص

في الإصدار المستقر الحالي (drizzle-orm@0.45.2)، توجد استعلامات العلاقات على db.query. قم بالإعلان عن relations() لكل جدول، وقم بتهيئة العميل باستخدام drizzle(client, { schema })، واستخدم معامل with لسحب الصفوف المرتبطة — بعمق تداخل كما تحتاج. يمكنك تصفية وفرز وتقييد الصفوف التابعة (child)، ولكن في الإصدار v1 لا يمكنك تصفية الصفوف الأصلية (parent) بناءً على عمود في الصف التابع (هذه الميزة ستتوفر في v2). تم تشغيل كل نمط استعلام في هذا الدليل على drizzle-orm@0.45.2 مقابل مثيل Postgres حقيقي (PGlite) وتم التحقق من النوع باستخدام tsc --noEmit.2

ما ستتعلمه

  • كيفية تحديد العلاقات وربط db.query بشكل صحيح
  • كيفية الاستعلام عن العلاقات المتداخلة باستخدام معامل with (علاقة واحد إلى متعدد، التداخل العميق)
  • لماذا يكون db.query غير معرف (undefined) — والحل المكون من سطر واحد
  • كيفية اختيار أعمدة جزئية في الجداول الأصلية والتابعة
  • كيفية تصفية وترتيب وتقييد الصفوف التابعة (وقيود الإصدار v1 على تصفية الجداول الأصلية)
  • كيفية تشغيل استعلام "متعدد إلى متعدد" من خلال جدول ربط (junction table)
  • متى تستخدم استعلامات العلاقات مقابل استخدام select العادي مع الربط (join)
  • ما الذي يتغير في استعلامات العلاقات v2 (defineRelations)، ولماذا تظهر الوثائق المباشرة db._query

الإعداد: تحديد العلاقات وتمرير المخطط

استعلامات العلاقات هي امتداد لمنشئ الاستعلامات في Drizzle، لذا فهي تحتاج إلى شيئين: إعلان relations() لكل جدول، وتسليم المخطط بالكامل إلى drizzle() عند التهيئة.1 إليك مخطط بنمط المدونة — مستخدمون، منشورات، تعليقات، وجدول ربط posts_to_tags للوسوم:

// schema.ts
import { relations from 'drizzle-orm';
import { pgTable, serial, text, integer, primaryKey from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
});

export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: text('title').notNull(),
  published: integer('published').notNull().default(0),
  authorId: integer('author_id').notNull().references(() => users.id),
});

export const comments = pgTable('comments', {
  id: serial('id').primaryKey(),
  body: text('body').notNull(),
  postId: integer('post_id').notNull().references(() => posts.id),
});

export const tags = pgTable('tags', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
});

export const postsToTags = pgTable('posts_to_tags', {
  postId: integer('post_id').notNull().references(() => posts.id),
  tagId: integer('tag_id').notNull().references(() => tags.id),
}, (t) => [primaryKey({ columns: [t.postId, t.tagId] })]);

الآن قم بالإعلان عن العلاقات. يحتاج جانب الـ one إلى fields و references؛ أما جانب الـ many فيكتفي بتسمية الجدول المستهدف:

// schema.ts (continued)
export const usersRelations = relations(users, ({ many ) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one, many ) => ({
  author: one(users, { fields: [posts.authorId], references: [users.id] }),
  comments: many(comments),
  postsToTags: many(postsToTags),
}));

export const commentsRelations = relations(comments, ({ one ) => ({
  post: one(posts, { fields: [comments.postId], references: [posts.id] }),
}));

export const tagsRelations = relations(tags, ({ many ) => ({
  postsToTags: many(postsToTags),
}));

export const postsToTagsRelations = relations(postsToTags, ({ one ) => ({
  post: one(posts, { fields: [postsToTags.postId], references: [posts.id] }),
  tag: one(tags, { fields: [postsToTags.tagId], references: [tags.id] }),
}));

أخيرًا، قم بتهيئة قاعدة البيانات مع استيراد المخطط كمساحة اسم (namespace). يعتمد استيراد drizzle ووسائطه بالضبط على المحرك (driver) الذي تستخدمه، ولكن الهيكل هو نفسه — يجب عليك تمرير schema:3

// db.ts
import { drizzle from 'drizzle-orm/node-postgres'; // or /pglite, /postgres-js, etc.
import * as schema from './schema';

export const db = drizzle(client, { schema );

كيف يمكنني الاستعلام عن العلاقات المتداخلة في Drizzle ORM؟

للاستعلام عن العلاقات المتداخلة، استدعِ db.query.<table>.findMany() (أو findFirst()) ومرر كائن with يحدد كل علاقة تريد تضمينها. للتعمق أكثر، قم بتداخل with آخر بداخله. يعيد هذا المستخدمين، كل منهم مع منشوراته، وكل منشور مع تعليقاته:

const usersWithPostsAndComments = await db.query.users.findMany({
  with: {
    posts: {
      with: {
        comments: true,
      },
    },
  },
});
// usersWithPostsAndComments[0].posts[0].comments -> Comment[]

يقوم Drizzle بإنشاء جملة SQL واحدة فقط لهذا الغرض (باستخدام lateral joins للاستعلامات الفرعية تحت الغطاء) ويعيد كائنات متداخلة ومحددة النوع بالكامل — دون الحاجة لتعيين الصفوف يدويًا.1 في التشغيل الذي تم التحقق منه، أعاد المنشور الأول للمستخدم الأول مصفوفة من تعليقين، ويستمر نوع النتيجة عبر كل مستويات التداخل.

findFirst() هو نفس الـ API مع تطبيق LIMIT 1، مما يعيد كائنًا واحدًا بدلاً من مصفوفة:

const onePost = await db.query.posts.findFirst({
  with: { comments: true ,
});

لماذا يكون db.query غير معرف في Drizzle ORM؟

يكون db.query غير معرف (undefined) (أو فارغًا) عندما لا تمرر مخططك (schema) — بما في ذلك صادرات relations() الخاصة به — إلى drizzle(). يتم بناء استعلامات العلاقات بالكامل من كائن المخطط هذا، لذا بدونه لا يوجد شيء لربطه بـ db.query. الحل هو سطر واحد:

// ❌ db.query is empty — no schema
const db = drizzle(client);

// ✅ db.query.<table> now exists
import * as schema from './schema';
const db = drizzle(client, { schema );

تأكد من أن استيراد مساحة الاسم الخاص بك (import * as schema) يلتقط كلاً من الجداول وإعلانات relations() من نفس الملف (أو الملفات). إذا قمت بتقسيم المخطط عبر ملفات متعددة، فقم بنشرها جميعًا: drizzle(client, { schema: { ...schema1, ...schema2 } }).1

كيف يمكنني اختيار أعمدة معينة فقط؟

استخدم خيار columns لتضمين أو استبعاد الحقول، في الجدول الأصلي وفي أي علاقة متداخلة. يقوم Drizzle بدفع هذا إلى SQL، لذا لا يتم نقل الأعمدة غير المختارة أبدًا:1

const posts = await db.query.posts.findMany({
  columns: { id: true, title: true ,
  with: {
    author: { columns: { name: true ,
  },
});
// each row: { id, title, author: { name } }

عندما تخلط بين مفاتيح true و false، يتم تجاهل مدخلات false — فإدراج أي عمود بقيمة true يعني استبعاد كل شيء آخر تلقائيًا. لإسقاط حقل واحد فقط بدلاً من ذلك، استخدم false فقط: columns: { content: false }.

كيف يمكنني تصفية وترتيب وتقييد الصفوف التابعة؟

يمكنك تطبيق where و orderBy و limit و offset على الاستعلام عالي المستوى، و where و orderBy و limit على العلاقات المتداخلة. يجلب هذا مستخدمًا واحدًا، ثم منشوراته المنشورة فقط، الأحدث أولاً، بحد أقصى واحد:

import { eq, desc from 'drizzle-orm';

const user = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: {
    posts: {
      where: eq(posts.published, 1),
      orderBy: [desc(posts.id)],
      limit: 1,
    },
  },
});

هناك قيدان يستحقان المعرفة في v1. أولاً، offset يعمل فقط على الاستعلام عالي المستوى، وليس على العلاقات المتداخلة.1 ثانيًا — وهذا ما يربك الناس — فإن where المتداخل يصفي الصفوف التابعة التي تعود، وليس الصفوف الأصلية. لا توجد طريقة في v1 لقول "أعد فقط المستخدمين الذين لديهم منشور يطابق X" من خلال الـ API الخاص بالعلاقات؛ تصفية الصفوف عالية المستوى بواسطة عمود جدول مرتبط هي ميزة في v2.4 إذا كنت بحاجة إلى ذلك اليوم في الإصدار المستقر، فانتقل إلى select() مع ربط صريح، أو قم بالتصفية في استعلام فرعي.

تفصيل واحد مطمئن: عندما لا يتطابق شيء، يعيد Drizzle مصفوفة فارغة، وليس null. الأصل الذي ليس له تابع يعود مع children: []، والاستعلام الذي لا يحتوي على صفوف مطابقة يعيد []. تم تأكيد كلا الأمرين في التشغيل الذي تم التحقق منه.

كيف يمكنني تشغيل استعلام "متعدد إلى متعدد" في Drizzle ORM؟

في الإصدار v1 لا يوجد مساعد مخصص لـ "متعدد إلى متعدد"، لذا تقوم بالاستعلام من خلال جدول الربط: قم بتضمين علاقة الربط، وامسح أعمدتها الخاصة باستخدام columns: {}، وقم بتداخل علاقة الجانب البعيد بداخلها. يعيد هذا منشورًا بمسار مسطح تقريبًا إلى وسومه:

const post = await db.query.posts.findFirst({
  where: eq(posts.id, 1),
  with: {
    postsToTags: {
      columns: {}, // omit junction columns from the result
      with: {
        tag: true,
      },
    },
  },
});

// tag names:
const tagNames = post?.postsToTags.map((pt) => pt.tag.name); // ['orm', 'sql']

يحافظ columns: {} على صفوف الربط خارج حمولة البيانات الخاصة بك مع السماح لك بالوصول إلى tag. أعاد التشغيل الذي تم التحقق منه ['orm', 'sql'] لمنشور مرتبط بوسمين. (يستبدل Relational Queries v2 هذا النمط بالكامل بإعلان through واحد — انظر أدناه.)4

الاستعلامات العلائقية مقابل select + join: أيهما يجب أن أستخدم؟

استخدم الاستعلامات العلائقية (db.query) عندما تريد كائنات متداخلة مشكلة حسب نطاق عملك — مستخدم مع منشورات مع تعليقات — وأنت تقوم بقراءة البيانات. استخدم منشئ الاستعلامات الشبيه بـ SQL (db.select() مع leftJoin) عندما تحتاج إلى صفوف مسطحة، أو تجميعات (count، sum)، أو عمليات المجموعات، أو تصفية/ترتيب العناصر الأب بواسطة عمود منضم لا يمكن لـ API العلائقي في الإصدار v1 التعبير عنه. كلاهما يتشاركان نفس المخطط (schema) ويمكنهما العمل جنباً إلى جنب؛ الاستعلامات العلائقية هي طبقة اختيارية فوق المنشئ الأساسي، وليست بديلاً له.1 لمزيد من أشكال الوصول المعقدة للبيانات، يوضح دليلنا حول المعاملات الذرية مع Drizzle ORM و pg-boss المنشئ الأساسي في سير عمل كثيف الكتابة.

ما الفرق بين استعلامات Drizzle العلائقية v1 و v2؟

الاستعلامات العلائقية v2 هي إعادة تصميم يتم شحنها في drizzle-orm@beta (الإصدار v1.0.0-beta.1 وما فوق)؛ أما خط الإصدار المستقر 0.45.x الذي تقوم بتثبيته افتراضياً فهو v1.4 التغييرات الرئيسية هي:

  • مكان واحد للعلاقات. يستبدل v2 استدعاءات relations() لكل جدول باستدعاء واحد لـ defineRelations(schema, (r) => ({ ... }))، وتقوم بتمرير { relations } إلى drizzle() بدلاً من { schema }.
  • إعادة تسمية المفاتيح. fieldsfrom، و referencesto (كل منهما يقبل عموداً واحداً أو مصفوفة)، و relationNamealias.
  • تصفية العناصر الأب بواسطة العلاقات. يضيف v2 نمط الكائنات لـ where/orderBy ويسمح لك بتصفية صفوف المستوى الأعلى بواسطة أعمدة جدول مرتبط — وهو بالضبط الشيء الذي لا يستطيع v1 فعله.
  • علاقة "متعدد إلى متعدد" أصلية. مساعد through يزيل تكرار الكود الخاص بجدول الربط (junction-table) تماماً.
  • تبديل الوصول. هذه هي المفاجأة الكبرى: في v2 يتم إعادة تعيين db.query النظيف إلى الصيغة الجديدة، وينتقل API القديم الخاص بـ v1 إلى db._query (مع استيراد relations من drizzle-orm/_relations). لهذا السبب تظهر الوثائق الحية db._query — تلك الصفحات توثق الإصدار v1.0 المرشح للإصدار. في الإصدار المستقر 0.45.x، لا تزال الاستعلامات العلائقية v1 هي db.query.4

إذا كنت تستخدم الإصدار المستقر اليوم، فاستخدم db.query كما هو موضح في هذا الدليل. عندما تقوم بالترقية إلى v1.0، يمكنك الهجرة استعلاماً تلو الآخر: يستمر الكود القديم في العمل عبر db._query بينما يستخدم الكود الجديد db.query المعاد تصميمه.

الخلاصة

تمنحك الاستعلامات العلائقية في Drizzle ORM قراءات متداخلة ومحددة النوع باستخدام db.query وعامل التشغيل with، مع الحفاظ على توفر منشئ الاستعلامات الشبيه بـ SQL لعمليات الربط والتجميع. في الإصدار المستقر 0.45.x، قم بتعريف relations()، ومرر schema الكامل إلى drizzle()، وتذكر أن v1 يصفي الأبناء ولكن ليس الآباء. عندما تنتقل إلى v1.0، انتبه لتبديل db.querydb._query.

الخطوات التالية: ادمج هذه القراءات مع الترقيم المعتمد على المؤشر (cursor pagination) في Postgres و Node.js لمجموعات النتائج الكبيرة، وأضف عمليات إعادة محاولة آمنة لكتاباتك باستخدام مفاتيح عدم التكرار (idempotency keys) في Node.js و Postgres.

Footnotes

  1. Drizzle ORM — وثائق الاستعلام (الاستعلامات العلائقية). https://orm.drizzle.team/docs/rqb 2 3 4 5 6 7 8 9

  2. drizzle-orm 0.45.2 (npm الأحدث، يونيو 2026)؛ تم التحقق منه مقابل Postgres عبر @electric-sql/pglite 0.5.3 وفحص الأنواع باستخدام tsc --noEmit (strict، nodenext). https://www.npmjs.com/package/drizzle-orm

  3. Drizzle ORM — نظرة عامة على اتصال قاعدة البيانات (مسارات استيراد drizzle() الخاصة بالمحرك). https://orm.drizzle.team/docs/connect-overview

  4. Drizzle ORM — دليل الهجرة للاستعلامات العلاقاتية من الإصدار الأول إلى الثاني (ينطبق على drizzle-orm@beta، v1.0.0-beta.1+). https://orm.drizzle.team/docs/relations-v1-v2 2 3 4 5 6 7 8

الأسئلة الشائعة

قم باستدعاء db.query.&lt;table&gt;.findMany({ with: { relationName: true } }) ، وقم بتداخل with أخرى بالداخل للتعمق أكثر. يعيد Drizzle مجموعة نتائج واحدة متداخلة ومحددة النوع من جملة SQL واحدة. 1