{"id":970,"date":"2012-07-14T15:00:35","date_gmt":"2012-07-14T14:00:35","guid":{"rendered":"http:\/\/meier-online.com\/?p=970"},"modified":"2015-02-20T01:07:34","modified_gmt":"2015-02-20T00:07:34","slug":"rails_database_optimisation2","status":"publish","type":"post","link":"https:\/\/meier-online.com\/en\/2012\/07\/rails_database_optimisation2\/","title":{"rendered":"(Deutsch) Ruby On Rails Datenbankoptimierung Teil 2"},"content":{"rendered":"<p>[Zu Teil 1]<\/p>\n<h3>Verkn\u00fcpfungen mit dem SQL-join<\/h3>\n<p>Relationale Datenbanken erlauben es, Tabellen miteinander zu verkn\u00fcpfen. In SQL gibt es f\u00fcr diese Verkn\u00fcpfung das Schl\u00fcsselwort &#8220;<code>JOIN<\/code>&#8220;. Eine solche Verkn\u00fcpfung kann theoretisch sehr frei spezifiziert werden, in der Praxis wird man fast immer aufgrund der Gleichheit bestimmter ID-Spalten verkn\u00fcpfen.<\/p>\n<p>In unserem Beispiel eines Schiffsinformationssystems haben wir eine Tabelle &#8220;vessels&#8221; mit den Schiffsdaten und eine Tabelle &#8220;countries&#8221; mit den Daten zu einem Flaggenstaat. Typischerweise wollen wir die Schiffsdaten zusammen mit der Daten des dazugeh\u00f6rigen Flaggenstaates ausgeben. Dazu verkn\u00fcpfen wir die Tabellen an Hand des Fremdschl\u00fcssels &#8220;legal_country_id&#8221;.<\/p>\n<p>In unserem Beispiel liefert der SQL-Join eine Tabelle, in der Schiffe gleich mit den dazugeh\u00f6rigen Flaggenstaaten eingetragen sind. Die Daten dieser Tabelle kann man zum Beispiel auf einer \u00dcbersichtseite darstellen. Es ist nur eine Datenbankabfrage n\u00f6tig, obwohl man Daten aus zwei Tabellen anzeigt.<\/p>\n<p>Es stellt sich dann allerdings die Frage, was eigentlich mit Schiffen ohne Flaggenstaat passiert?<\/p>\n<p>Um das steuern zu k\u00f6nnen gibt es verschiedene &#8220;Geschmacksrichtungen&#8221; des Joins:<\/p>\n<p>INNER JOIN: Beide Datens\u00e4tze m\u00fcssen vorhanden sein. Wenn einem Schiff (noch) kein Flaggenstaat zugewiesen wurde, wird es auch nicht geliefert.<\/p>\n<p>OUTER JOIN, LEFT JOIN: Es wird auch dann ein Ergebnis geliefert, wenn es keinen verkn\u00fcpften Datensatz gibt. Es werden also auch Schiffe ohne definierten Flaggenstaat geliefert. Dabei werden Daten aus der ersten (in Leserichtung linken) Tabelle geliefert.<\/p>\n<p>RIGHT JOIN Es wird auch dann ein Ergebnis geliefert, wenn es nur in der zweiten Tabelle einen Datensatz gibt. Ein RIGHT JOIN w\u00fcrde also Staaten liefern, in denen kein Schiff registriert sind. In der Praxis ist das \u00f6fter verwirrend als n\u00fctzlich, so dass man besser die Reihenfolge umdreht und dann einen LEFT JOIN nimmt.<\/p>\n<p>Wie nutzt man join von Rails<\/p>\n<p>Zun\u00e4chst gibt es in Ruby schon eine Methode, die &#8220;<code>join<\/code>&#8221; hei\u00dft, die aber semantisch etwas ganz anderes macht. Sie verbindet die Elemente eines Array zu einem String:<\/p>\n<p>[1, 2, 3, 4].join(&#8216;-&#8216;)<br \/>\n&#8216;1-2-3-4&#8217;<\/p>\n<p>Zus\u00e4tzlich bietet ActiveRecord bzw. ActiveRelation eine Methode &#8220;joins&#8221;. Wichtig ist das &#8220;s&#8221; am Ende. Die Methode sieht zun\u00e4chst sehr vielversprechend aus.<\/p>\n<p>company = Company.find_by_name(&#8216;Hapag-Lloyd&#8217;)<\/p>\n<p>company.container_vessels.joins(:legal_country)<\/p>\n<p>SQL:<\/p>\n<p>SELECT &#8220;container_vessels&#8221;.*<\/p>\n<p>FROM &#8220;container_vessels&#8221;<\/p>\n<p>INNER JOIN &#8220;countries&#8221; ON &#8220;countries&#8221;.&#8221;id&#8221; = &#8220;container_vessels&#8221;.&#8221;legal_country_id&#8221;<\/p>\n<p>WHERE &#8220;container_vessels&#8221;.&#8221;company_id&#8221; = 3<\/p>\n<p>Leider liefert die Magie von Ruby-on-Rails diesmal nicht das gew\u00fcnschte. Es wird zwar ein SQL-Join erzeugt, die Spalten der anderen Tabelle werden jedoch weggeschmissen. Active-Record ist ein sogennanter Object-Relation-Mapper (ORM), und als solcher darauf optimiert, immer Objekte einer Klasse zu liefern, in unserem Fall also ContainerVessel. Wenn man sp\u00e4ter auf die verkn\u00fcpften Objekte zugreift, werden diese doch wieder einzeln geholt.<\/p>\n<blockquote><p><span style=\"color: #008000;\">ActiveRecord-Abfragen liefern Objekte der jeweiligen Klasse zur\u00fcck!<\/span><\/p><\/blockquote>\n<p>Einziger Effekt des generierten INNER JOIN ist, das nur Schiffe mit existierenden Flaggenstaat geliefert werden.<\/p>\n<p>Das zeigt auch auf, wof\u00fcr die joins() Methode trotzdem gut: Man kann mit ihr Objekte aufgrund von Eigenschaften von verkn\u00fcpften Objekten ausw\u00e4hlen.<\/p>\n<p>Beispielsweise kann man so alle Schiffe einer Reederei ausw\u00e4hlen, die einen EU-Flaggenstaat haben.<\/p>\n<pre lang=\"ruby\" escaped=\"true\">\r\nhapag.container_vessels.joins(:legal_country).where('countries.in_eu' =&gt; 'true')\r\nhapag.container_vessels.joins(:legal_country).where(:countries =&gt; {:in_eu =&gt; true})\r\n<\/pre>\n<p>SELECT &#8220;container_vessels&#8221;.*<\/p>\n<p>FROM &#8220;container_vessels&#8221;<\/p>\n<p>INNER JOIN &#8220;countries&#8221; ON &#8220;countries&#8221;.&#8221;id&#8221; = &#8220;container_vessels&#8221;.&#8221;legal_country_id&#8221;<\/p>\n<p>WHERE &#8220;container_vessels&#8221;.&#8221;company_id&#8221; = 3 AND &#8220;countries&#8221;.&#8221;in_eu&#8221; = &#8216;t&#8217;<\/p>\n<p>Der SQL-Join f\u00fchrt dazu, das die passenden Datens\u00e4tze schon in der Datenbank ausgew\u00e4hlt werden. Der Rails-Prozess sieht die Country-Datens\u00e4tze noch nicht mal. Und was nicht da ist, kostet keinen Hauptspeicher und auch keine Performance.<\/p>\n<p>In der Syntax muss man aufpassen: In der Spezifikation der Bedingungen bewegt man sich auf SQL-Ebene (&#8216;countries&#8217;), w\u00e4hrend man in der joins-Methode die Rails-Association angibt(&#8216;legal_country&#8217;).<\/p>\n<p>(Demn\u00e4chst geht es weiter mit echten Joins in Rails&#8230;)<!--more--><\/p>","protected":false},"excerpt":{"rendered":"<p>Sorry, this entry is only available in Deutsch.<\/p>\n","protected":false},"author":4,"featured_media":978,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[104],"tags":[212,114,178,128],"class_list":["post-970","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-develop","tag-performance","tag-ruby","tag-scaling","tag-sql","entry"],"_links":{"self":[{"href":"https:\/\/meier-online.com\/en\/wp-json\/wp\/v2\/posts\/970"}],"collection":[{"href":"https:\/\/meier-online.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/meier-online.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/meier-online.com\/en\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/meier-online.com\/en\/wp-json\/wp\/v2\/comments?post=970"}],"version-history":[{"count":21,"href":"https:\/\/meier-online.com\/en\/wp-json\/wp\/v2\/posts\/970\/revisions"}],"predecessor-version":[{"id":2038,"href":"https:\/\/meier-online.com\/en\/wp-json\/wp\/v2\/posts\/970\/revisions\/2038"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/meier-online.com\/en\/wp-json\/wp\/v2\/media\/978"}],"wp:attachment":[{"href":"https:\/\/meier-online.com\/en\/wp-json\/wp\/v2\/media?parent=970"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/meier-online.com\/en\/wp-json\/wp\/v2\/categories?post=970"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/meier-online.com\/en\/wp-json\/wp\/v2\/tags?post=970"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}