JETZT in NoSQL-Datenbanken

In diesem Beitrag wird erläutert, wie Sammlungen in NoSQL-Datenbanken mongodb, arangodb, orientdb und rethinkdb verbunden werden (sie sind nicht nur NoSQL-Datenbanken, sondern werden auch durch eine kostenlose Version mit einer ziemlich loyalen Lizenz vereint). In relationalen Datenbanken wird eine ähnliche Funktionalität mit SQL JOIN implementiert. Trotz der Tatsache, dass CRUD-Operationen in NoSQL-Datenbanken sehr ähnlich sind und sich nur in Details unterscheiden, z. B. in einer Datenbank, wird die Funktion create ({...}) verwendet, um ein Objekt zu erstellen, in die andere einzufügen ({...}) und in Das dritte - Speichern ({...}) - Die Implementierung einer Auswahl von zwei oder mehr Sammlungen in jeder der Datenbanken wird auf völlig unterschiedliche Weise implementiert. Daher ist es interessant, für alle Datenbanken dieselbe Auswahl durchzuführen. Für alle Datenbanken wird die Stichprobe (eine Viele-zu-Viele-Beziehung) für zwei Tabellen berücksichtigt.

Beispielsweise werden Tabellen verwendet.

Der Autor

  • Vorname

Das Buch

  • Titel

BooksAuthor

  • Der Autor
  • Das Buch

Für Mongodb wird die Auswahl aus den Tabellen folgendermaßen implementiert:

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

Im Gegensatz zur SQL JOIN-Auswahl ist die resultierende Auswahl keine flache Tabelle, in der der Autor so viele Bücher wiederholt, wie er geschrieben hat, sondern ein hierarchisches Objekt, in dem jeder Autor durch ein Objekt dargestellt wird, dessen Eigenschaft books ein Array von Buchobjekten enthält . Aus meiner Sicht ist dies ein sehr großes Plus in NoSQL-Datenbanken. Möglicherweise benötigen Sie jedoch auch eine "flache" Version, ähnlich wie SQL JOIN. Um es in der Anfrage zu erhalten, müssen Sie die "Erweiterung" von Arrays hinzufügen: { $unwind: '$books' } .

Das im Beispiel dargestellte Beispiel ist ein Analogon von SQL LEFT JOIN, dh alle Autoren werden in das Beispiel aufgenommen, auch wenn sie keine Bücher haben. Um ein Analogon zu SQL [INNER] JOIN zu erstellen, müssen Sie die Bedingung { $match: { books: { $ne: [ ] } } } hinzufügen, oder wenn $ unwind verwendet wird:

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

Fahren wir also mit Arangodb fort, einer hybriden Datenbank. Neben der Arbeit mit Dokumenten wird auch die Arbeit mit Diagrammen implementiert. Lassen Sie uns zunächst sehen, wie Sie in Arangodb eine Auswahl nur mit Dokumenten (nicht mit Grafiken) treffen können:

  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 verwendet das Schlüsselwort FOR, um Sammlungen zu verbinden, und FILTER, um die Verknüpfungsbedingung anzugeben. Das in diesem Fall erhaltene Beispiel ähnelt SQL [INNER] JOIN (dh es ist ein "flaches" Objekt und enthält keine leeren Werte).

In Arangodb ist es jedoch viel bequemer, Diagrammfunktionen für die Auswahl aus mehreren Objekten zu verwenden:

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

Wir verwenden jetzt für die Kommunikation kein Dokument, sondern eine Sammlung von Kanten des Diagramms (Rand) des Buchautors. Die IN OUTBOUND a bookauthor wählt für einen bestimmten Autor a Sammlung verwandter Dokumente aus, die in der Antwort unter dem Namen book_vertex . Die COLLECT a1 = a INTO b1 Anweisung COLLECT a1 = a INTO b1 ist ein Analogon von SQL GROUP - sie akkumuliert einen Wert in einem Array, der in der Antwort unter dem Namen b1 für jeden Wert von Autor verfügbar ist, der in der Antwort unter dem Namen a1 verfügbar ist. Mit der Konstruktion b1[*].book_vertex können b1[*].book_vertex unnötige Verschachtelungsebenen aus dem Objekt entfernen, damit das Ergebnis für weitere Arbeiten geeignet ist.

Das Implementieren von SQL-Abfragen vom Typ LEFT JOIN in Arangodb ist schwieriger, weil Die FOR - FILTER - Anweisung legt Einschränkungen fest, die denen von SQL [INNER] JOIN ähneln. Um die "linken Verbindungen" zu implementieren, werden der LET-Oprerator und die Unterabfrage verwendet:

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

In diesem Fall ist seitdem keine Datengruppierung erforderlich Für jeden Autor wird eine Unterabfrage durchgeführt, und die Antwort enthält bereits ein gotisches Array von Buchobjekten.

Gehen Sie zur orientdb-Datenbank. Es ist auch eine hybride Datenbank, mit der Sie sowohl mit Dokumenten als auch mit Grafiken arbeiten können. Die Ideologie der Arbeit mit Graphen ähnelt dem vorherigen Beispiel in Arangodb. Das heißt, um Sammlungen zu verknüpfen, wird eine Sammlung von Kanten des Buchautorendiagramms verwendet.

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

Vielleicht hat orientdb die erfolgreichste Implementierung, als Es kommt der SQL-Syntax am nächsten und ist in Bezug auf die Arbeit mit Diagrammen präzise. Der Ausdruck out('BookAuthor').title as books from Author bedeutet, dass für die Autorensammlung alle ausgehenden Kanten aus der BookAuthor-Sammlung ausgewählt werden, die die Autorensammlung mit der Buchsammlung verknüpfen. In diesem Fall ist das resultierende Objekt hierarchisch (ein Objekt für jeden Autor mit einem Array von Buchobjekten). Wenn Sie das Array zu einem flachen Objekt "erweitern" müssen, wird der Operator UNWIND verwendet.

Und zum Schluss noch einmal überlegen. Vor nicht allzu langer Zeit hat das Team, das diese Datenbank entwickelt, aufgehört zu existieren und die Entwicklung an die offene Community übertragen. Ich sage das sofort, weil Jemand könnte diese Nachricht übersprungen haben. Vor einer detaillierteren Bekanntschaft schien mir die Implementierung von JOIN in rethinkdb am bequemsten zu sein. Vielleicht, weil eine solche Gelegenheit sofort in die Datenbank-API aufgenommen und sogar join () genannt wurde. Aber dann stellte sich heraus, dass nicht alles so rosig ist und nicht alle Funktionen, die JOIN implementieren, gleich effizient arbeiten und ausreichend flexibel sind, um die richtigen Abfragen zu erstellen. Das gleiche End-to-End-Beispiel ist jetzt auf rethinkdb implementiert:

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

Sie sollten auf solche Punkte achten. In diesem Beispiel wurde die Sekundärindexkommunikation mithilfe der Funktion eqJoin () implementiert, die beim Verbinden von Objekten Paare verwenden kann: einen Primärschlüssel mit einem Primärschlüssel oder einen Primärschlüssel mit einem Sekundärschlüssel (jedoch keinen Sekundärschlüssel mit einem Sekundärschlüssel). Für komplexere Bedingungen wird die Funktion map () verwendet, die um eine Größenordnung schwieriger zu verstehen ist. Andere Funktionen, die JOIN implementieren, sind nicht optimiert (vermutlich wird eine vollständige Aufzählung der Werte implementiert).

Der Text der Beispiele befindet sich im Repository .

apapacy@gmail.com
4. Juni 2018

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


All Articles