ستناقش هذه المشاركة كيفية توصيل المجموعات في قواعد بيانات NoSQL mongodb و arangodb و orientdb و rethinkdb (بالإضافة إلى كونها قواعد بيانات NoSQL ، يتم توحيدها أيضًا بواسطة إصدار مجاني بترخيص مخلص إلى حد ما). في قواعد البيانات العلائقية ، يتم تنفيذ وظائف مماثلة باستخدام SQL JOIN. على الرغم من حقيقة أن عمليات CRUD في قواعد بيانات NoSQL متشابهة للغاية وتختلف فقط في التفاصيل ، على سبيل المثال ، في قاعدة بيانات واحدة ، يتم استخدام وظيفة الإنشاء ({...}) لإنشاء كائن ، وإدراج ({...}) في الآخر ، وفي الثالث - حفظ ({...}) ، - يتم تنفيذ مجموعة مختارة من مجموعتين أو أكثر في كل من قواعد البيانات بطرق مختلفة تمامًا. لذلك ، سيكون من المثير للاهتمام إجراء نفس التحديد على جميع قواعد البيانات. بالنسبة لجميع قواعد البيانات ، سيتم أخذ العينات (علاقة أطراف بأطراف) لجدولين.
على سبيل المثال ، سيتم استخدام الجداول.
المؤلف
الكتاب
كتب
بالنسبة إلى mongodb ، سيتم تنفيذ التحديد من الجداول على النحو التالي:
const mongo = require('mongodb-bluebird'); mongo.connect("mongodb://localhost:27017/test").then(async function(db) { const author = db.collection('author'); const book = db.collection('book'); const bookauthor = db.collection('bookauthor'); ['Joe', 'John', 'Jack', 'Jeremy'].map(async (name) => await author.insert({name}) ); ['Art', 'Paint'].map(async (title) => await book.insert({title}) ); let Author = await author.findOne({ name: 'Joe' }); let Book = await book.findOne({ title: 'Paint' }); await bookauthor.insert({author: Author._id, book: Book._id}) Author = await author.findOne({ name: 'John' }); await bookauthor.insert({author: Author._id, book: Book._id}) Book = await book.findOne({ title: 'Art' }); await bookauthor.insert({author: Author._id, book: Book._id}) const result = await author.aggregate([{ $lookup:{ from: 'bookauthor', localField: '_id', foreignField: 'author', as: 'ba' }}, { $lookup: { from: 'book', localField: 'ba.book', foreignField: '_id', as: 'books' }}],{ }) });
على عكس اختيار SQL JOIN ، لن يكون التحديد الناتج عبارة عن جدول مسطح يتكرر فيه المؤلف مثل العديد من الكتب التي ألفها ، ولكن كائنًا هرميًا يمثل فيه كل مؤلف بعنصر واحد يحتوي على خاصية الكتب التي تحتوي على مجموعة من كائنات الكتاب . من وجهة نظري ، هذه إضافة كبيرة جدًا في قواعد بيانات NoSQL. ولكن قد تحتاج أيضًا إلى إصدار "مسطح" ، مشابه لـ SQL JOIN. لاستلامها في الطلب ، تحتاج إلى إضافة "توسيع" المصفوفات:
{ $unwind: '$books' }
.
العينة المقدمة في المثال هي تمثيلية لـ SQL LEFT JOIN ، أي أنه سيتم تضمين جميع المؤلفين في العينة ، حتى إذا لم يكن لديهم كتب. لإنشاء تناظري لـ SQL [INNER] JOIN ، يجب إضافة الشرط
{ $match: { books: { $ne: [ ] } } }
، أو إذا تم استخدام $ unlock:
{ $unwind: { path: "$role", preserveNullAndEmptyArrays: false } }
لذا ، دعنا ننتقل إلى arangodb ، وهي قاعدة بيانات هجينة. بالإضافة إلى العمل مع المستندات ، فإنه ينفذ العمل مع الرسوم البيانية. أولاً ، دعنا نرى كيف يمكنك في arangodb إجراء تحديد باستخدام المستندات فقط (وليس الرسوم البيانية):
FOR a IN author FOR ba IN bookauthor FILTER a._id == ba.author FOR b IN book FILTER b._id == ba.book SORT a.name, b.title RETURN { author: a, book: b }
تستخدم Arangodb الكلمة الأساسية FOR للانضمام إلى المجموعات ، و FILTER لتحديد شرط الصلة. سيكون النموذج الذي تم الحصول عليه في هذه الحالة مشابهًا لـ SQL [INNER] JOIN (أي أنه سيكون كائنًا "مسطحًا" ولن يحتوي على قيم فارغة)
ولكن من الملائم أكثر في arangodb استخدام ميزات الرسم البياني للتحديدات من عدة كائنات:
const { Database, aql } = require('arangojs'); const db = new Database({ url: "http://localhost:8529" }); db.useDatabase("test"); db.useBasicAuth("test", "test"); const author = db.collection('author') const book = db.collection('book') const bookauthor = db.edgeCollection('bookauthor') void async function() { ['Joe', 'John', 'Jack', 'Jeremy'].map(async (name) => await author.save({name}) ); ['Art', 'Paint'].map(async (title) => await book.save({title}) ); let Author = await author.firstExample({ name: 'Joe' }); let Book = await book.firstExample({ title: 'Paint' }); await bookauthor.save({date: 'Some data'}, Author._id, Book._id) Author = await author.firstExample({ name: 'John' }); await bookauthor.save({date: 'Some data'}, Author._id, Book._id) Book = await book.firstExample({ title: 'Art' }); await bookauthor.save({date: 'Some data'}, Author._id, Book._id) const cursor = await db.query(aql` FOR a IN author FOR book_vertex, book_edge IN OUTBOUND a bookauthor COLLECT a1 = a INTO b1 RETURN {author: a1, books: b1[*].book_vertex} `); }();
نحن نستخدم الآن للتواصل ليس مستندًا ، بل مجموعة من حواف الرسم البياني (حافة) للمؤلف. يحدد
IN OUTBOUND a bookauthor
مؤلف
a
لمجموعة معينة من الوثائق ذات الصلة ، والتي يضعها في الرد تحت اسم
book_vertex
. عبارة
COLLECT a1 = a INTO b1
هي نظير لـ SQL GROUP - فهي تجمع قيمة في مصفوفة ستكون متاحة في الاستجابة تحت اسم
b1
لكل قيمة للمؤلف
، والتي ستكون متاحة في الاستجابة تحت اسم
a1
. يسمح
b1[*].book_vertex
البناء
b1[*].book_vertex
بإزالة مستويات التعشيش غير الضرورية من الكائن بحيث تكون النتيجة ملائمة لمزيد من العمل.
تنفيذ استعلامات نوع SQL LEFT JOIN في arangodb أكثر صعوبة لأنه تعين عبارة FOR - FILTER قيودًا مماثلة لـ SQL [INNER] JOIN. لتنفيذ "الاتصالات اليسرى" ، يتم استخدام مشغل LET والاستعلام الفرعي:
const cursor = await db.query(aql` FOR a IN author LET books = ( FOR book_vertex, book_edge IN OUTBOUND a bookauthor RETURN book_vertex ) RETURN {author: a, books} `);
في هذه الحالة ، تجميع البيانات غير مطلوب منذ ذلك الحين يتم تنفيذ استعلام فرعي لكل مؤلف وتحتوي الاستجابة بالفعل على مجموعة قوطية من كائنات الكتاب.
انتقل إلى قاعدة بيانات orientdb. وهي أيضًا قاعدة بيانات مختلطة تسمح لك بالعمل مع كل من المستندات والرسوم البيانية. إيديولوجية العمل مع الرسوم البيانية تشبه المثال السابق في arangodb. أي لربط المجموعات ، يتم استخدام مجموعة من حواف الرسم البياني للمؤلف.
const OrientDB = require('orientjs'); const server = OrientDB({ host: 'localhost', port: 2424, }); void async function() { const db = server.use({ name:'test', username: 'test', password: 'test' }); await db.open(); try { await db.class.drop('Author UNSAFE'); } catch(ex) { console.log(ex) } try { await db.class.drop('Book UNSAFE'); } catch(ex) { console.log(ex) } try { await db.class.drop('BookAuthor UNSAFE'); } catch(ex) { console.log(ex) } const author = await db.class.create('Author', 'V'); const book = await db.class.create('Book', 'V'); const bookauthor = await db.class.create('BookAuthor', 'E'); ['Joe', 'John', 'Jack', 'Jeremy'].map(async (name) => await author.create({name}) ); ['Art', 'Paint'].map(async (title) => await book.create({title}) ); await author.list(); await book.list(); let Author = await db.select().from('Author').where({name: 'Joe'}).one(); let Book = await db.select().from('book').where({ title: 'Paint' }).one(); await db.create('EDGE', 'BookAuthor').from(Author['@rid']).to(Book['@rid']).set({date: 'Some data'}).one(); Author = await db.select().from('Author').where({name: 'John'}).one(); await db.create('EDGE', 'BookAuthor').from(Author['@rid']).to(Book['@rid']).set({date: 'Some data'}).one(); Book = await db.select().from('book').where({ title: 'Art' }).one(); await db.create('EDGE', 'BookAuthor').from(Author['@rid']).to(Book['@rid']).set({date: 'Some data'}).one(); const cursor = await db.query(`select name, out('BookAuthor').title as books from Author`).all() } ()
ربما كان Orientdb أنجح تنفيذ ، كما هو الأقرب إلى بناء جملة SQL وموجز من حيث العمل مع الرسوم البيانية. التعبير
out('BookAuthor').title as books from Author
يعني تحديد مجموعة الحواف الصادرة من مجموعة BookAuthor لمجموعة المؤلف التي تربط مجموعة المؤلف بمجموعة الكتب. في هذه الحالة ، سيكون الكائن الناتج هرميًا (كائن واحد لكل مؤلف يحتوي على صفيف من كائنات الكتاب). إذا كنت بحاجة إلى "توسيع" الصفيف إلى كائن مسطح ، يتم استخدام عامل التشغيل UNWIND.
وأخيرًا ، ضع في اعتبارك إعادة التفكير. منذ وقت ليس ببعيد ، توقف الفريق الذي طور قاعدة البيانات هذه عن الوجود ونقل التطوير إلى المجتمع المفتوح. أقول هذا على الفور ، لأنه ربما تخطي شخص ما هذه الأخبار. قبل معرفة أكثر تفصيلاً ، بدا لي أن تنفيذ JOIN في rethinkdb هو الأكثر ملاءمة. ربما لأنه تم دمج هذه الفرصة على الفور في API لقاعدة البيانات ، وحتى تسمى join (). ولكن بعد ذلك اتضح أن كل شيء ليس وردًا جدًا ، ولا تعمل جميع الوظائف التي تنفذ JOIN على قدم المساواة بكفاءة ولديها مرونة كافية لبناء الاستعلامات الصحيحة. يتم تنفيذ المثال نفسه من طرف إلى طرف الآن على rethinkdb:
r = require('rethinkdb') void async function() { const conn = await r.connect({ host: 'localhost', port: 28015 }); try { await r.db('test').tableDrop('author').run(conn); await r.db('test').tableDrop('book').run(conn); await r.db('test').tableDrop('bookauthor').run(conn); } catch (ex) { console.log(ex) } await r.db('test').tableCreate('author').run(conn); await r.db('test').tableCreate('book').run(conn); await r.db('test').tableCreate('bookauthor').run(conn); await r.db('test').table('bookauthor').indexCreate('author').run(conn); await r.db('test').table('bookauthor').indexCreate('book').run(conn); await r.db('test').table('bookauthor').indexWait('author', 'book').run(conn); ['Joe', 'John', 'Jack', 'Jeremy'].map(async (name) => await r.db('test').table('author').insert({ name }).run(conn) ); ['Art', 'Paint'].map(async (title) => await r.db('test').table('book').insert({ title }).run(conn) ); let Author = await r.db('test').table('author').filter({ name: 'Joe' }).run(conn).then(authors => authors.next()); let Book = await r.db('test').table('book').filter({ title: 'Paint' }).run(conn).then(books => books.next()); await r.db('test').table('bookauthor').insert({author: Author.id, book: Book.id}).run(conn); Author = await r.db('test').table('author').filter({ name: 'John' }).run(conn).then(authors => authors.next()); await r.db('test').table('bookauthor').insert({author: Author.id, book: Book.id}).run(conn); Book = await r.db('test').table('book').filter({ title: 'Art' }).run(conn).then(books => books.next()); await r.db('test').table('bookauthor').insert({author: Author.id, book: Book.id}).run(conn); const cursor = await r.db('test').table('author') .eqJoin('id', r.db('test').table('bookauthor'), {index: 'author'}).zip() .eqJoin('book', r.db('test').table('book')).zip().run(conn); }();
يجب الانتباه إلى هذه النقاط. في هذا المثال ، تم تنفيذ اتصال الفهرس الثانوي باستخدام وظيفة eqJoin () ، والتي يمكن أن تستخدم أزواج عند توصيل الكائنات: مفتاح أساسي بمفتاح أساسي أو مفتاح أساسي بمفتاح ثانوي (ولكن ليس مفتاحًا ثانويًا بمفتاح ثانوي). بالنسبة للظروف الأكثر تعقيدًا ، يتم استخدام الدالة map () ، وهو ترتيب من الحجم يصعب فهمه. لا يتم تحسين الوظائف الأخرى التي تنفذ JOIN (من المفترض ، يتم تنفيذ تعداد كامل للقيم).
نص الأمثلة موجود في
المستودع .
apapacy@gmail.com
4 يونيو 2018