BERGABUNG di Basis Data NoSQL

Posting ini akan membahas cara menghubungkan koleksi di basis data NoSQL mongodb, arangodb, orientdb dan rethinkdb (selain menjadi basis data NoSQL, mereka juga memiliki versi gratis dengan lisensi yang cukup loyal). Dalam database relasional, fungsionalitas serupa diimplementasikan menggunakan SQL JOIN. Terlepas dari kenyataan bahwa operasi CRUD dalam database NoSQL sangat mirip dan hanya berbeda dalam detail, misalnya, dalam satu database, fungsi create ({...}) digunakan untuk membuat objek, masukkan ({...}) di yang lain, dan di yang ketiga - save ({...}), - implementasi pemilihan dua atau lebih koleksi di setiap basis data diimplementasikan dengan cara yang sangat berbeda. Oleh karena itu, akan menarik untuk melakukan seleksi yang sama di semua database. Untuk semua database, pengambilan sampel (hubungan banyak-ke-banyak) untuk dua tabel akan dipertimbangkan.

Misalnya, tabel akan digunakan.

Penulis

  • nama depan

Buku itu

  • Judul

BooksAuthor

  • Penulis
  • Buku itu

Untuk mongodb, pemilihan dari tabel akan dilaksanakan seperti ini:

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' }}],{ }) }); 

Tidak seperti pemilihan SQL JOIN, pemilihan yang dihasilkan tidak akan menjadi tabel datar di mana Penulis akan diulang sebanyak buku seperti yang telah ditulisnya, tetapi objek hierarkis di mana setiap Penulis akan diwakili oleh satu objek yang akan memiliki properti buku yang berisi array objek Buku . Dari sudut pandang saya, ini merupakan nilai tambah yang sangat besar dalam database NoSQL. Tetapi Anda mungkin juga membutuhkan versi "flat", mirip dengan SQL JOIN. Untuk menerimanya dalam permintaan, Anda perlu menambahkan "perluasan" array: { $unwind: '$books' } .

Sampel yang disajikan dalam contoh adalah analog dari SQL LEFT JOIN, yaitu, semua penulis akan dimasukkan dalam sampel, bahkan jika mereka tidak memiliki buku. Untuk membuat analog dari SQL [INNER] GABUNG, Anda harus menambahkan kondisi { $match: { books: { $ne: [ ] } } } , atau jika $ relax digunakan:

 { $unwind: { path: "$role", preserveNullAndEmptyArrays: false } } 

Jadi, mari kita beralih ke arangodb, yang merupakan basis data hybrid. Selain bekerja dengan dokumen, ini mengimplementasikan kerja dengan grafik. Pertama, mari kita lihat bagaimana di arangodb Anda dapat memilih menggunakan hanya dokumen (bukan grafik):

  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 menggunakan kata kunci UNTUK untuk bergabung dengan koleksi, dan FILTER untuk menentukan kondisi bergabung. Sampel yang diperoleh dalam kasus ini akan mirip dengan SQL [INNER] GABUNG (yaitu, itu akan menjadi objek "datar" dan tidak akan berisi nilai kosong)

Tetapi jauh lebih nyaman di arangodb untuk menggunakan fitur grafik untuk pemilihan dari beberapa objek:

 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} `); }(); 

Kami sekarang menggunakan untuk komunikasi bukan dokumen, tetapi kumpulan tepi grafik (tepi) penulis buku. IN OUTBOUND a bookauthor memilih untuk Penulis yang diberikan kumpulan dokumen terkait, yang ditempatkan dalam respons di bawah nama book_vertex . Pernyataan COLLECT a1 = a INTO b1 adalah analog dari SQL GROUP - ia mengakumulasi nilai dalam array yang akan tersedia dalam respons di bawah nama b1 untuk setiap nilai Author , yang dalam responsnya akan tersedia dengan nama a1 . Konstruksi b1[*].book_vertex memungkinkan b1[*].book_vertex untuk menghapus level sarang yang tidak perlu dari objek sehingga hasilnya nyaman untuk pekerjaan lebih lanjut.

Mengimplementasikan query SQL LEFT JOIN type di arangodb lebih sulit karena pernyataan FOR - FILTER menetapkan batasan yang mirip dengan SQL [INNER] GABUNG. Untuk mengimplementasikan "koneksi kiri", LET oprerator dan subquery digunakan:

  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} `); 

Dalam hal ini, pengelompokan data tidak diperlukan sejak saat itu subquery dilakukan untuk setiap Pengarang dan responsnya sudah berisi larik gothic dari objek Buku.

Pergi ke database orientdb. Ini juga merupakan basis data hybrid yang memungkinkan Anda bekerja dengan dokumen dan grafik. Ideologi bekerja dengan grafik mirip dengan contoh sebelumnya di arangodb. Yaitu, untuk menautkan koleksi, kumpulan tepi grafik penulis buku digunakan.

 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() } () 

Mungkin orientdb memiliki implementasi yang paling sukses, seperti ini paling dekat dengan sintaks SQL dan ringkas dalam hal bekerja dengan grafik. Ekspresi out('BookAuthor').title as books from Author berarti memilih untuk koleksi Penulis semua tepi keluar dari koleksi BookAuthor yang menghubungkan koleksi Penulis ke koleksi Buku. Dalam hal ini, objek yang dihasilkan akan menjadi hierarkis (satu objek untuk setiap Penulis dengan array objek Buku). Jika Anda perlu "memperluas" array menjadi objek datar, operator UNWIND digunakan.

Dan akhirnya, pertimbangkan rethinkdb. Belum lama ini, tim yang mengembangkan basis data ini tidak ada lagi dan mengalihkan pengembangannya ke komunitas terbuka. Saya mengatakan ini segera, karena seseorang mungkin telah melewatkan berita ini. Sebelum berkenalan lebih rinci, menurut saya implementasi JOIN di rethinkdb paling nyaman. Mungkin karena peluang seperti itu segera dimasukkan ke dalam basis data API, dan bahkan disebut join (). Tapi kemudian ternyata semuanya tidak begitu cerah, dan tidak semua fungsi yang mengimplementasikan JOIN bekerja sama secara efisien dan memiliki fleksibilitas yang cukup untuk membangun kueri yang tepat. Contoh end-to-end kami yang sama sekarang diterapkan pada 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); }(); 

Anda harus memperhatikan poin-poin tersebut. Dalam contoh ini, komunikasi indeks sekunder diimplementasikan menggunakan fungsi eqJoin (), yang dapat menggunakan pasangan saat menghubungkan objek: kunci primer dengan kunci primer atau kunci primer dengan kunci sekunder (tetapi bukan kunci sekunder dengan kunci sekunder). Untuk kondisi yang lebih kompleks, fungsi map () digunakan, yang merupakan urutan besarnya lebih sulit untuk dipahami. Fungsi-fungsi lain yang mengimplementasikan GABUNG tidak dioptimalkan (mungkin, enumerasi nilai lengkap diimplementasikan).

Teks dari contoh tersebut terletak di repositori .

apapacy@gmail.com
4 Juni 2018

Source: https://habr.com/ru/post/id413123/


All Articles