Schienen: Bestellung mit Nullen zuletzt

83

In meiner Rails-App bin ich ein paar Mal auf ein Problem gestoßen, das ich gerne wissen möchte, wie andere Leute es lösen:

Ich habe bestimmte Datensätze, bei denen ein Wert optional ist, daher haben einige Datensätze einen Wert und einige sind für diese Spalte null.

Wenn ich in einigen Datenbanken nach dieser Spalte ordne, werden die Nullen zuerst und in einigen Datenbanken zuletzt die Nullen sortiert.

Zum Beispiel habe ich Fotos, die zu einer Sammlung gehören können oder nicht, dh es gibt einige Fotos wo collection_id=nilund einige wo collection_id=1usw.

Wenn ich das Photo.order('collection_id desc)dann auf SQLite mache, bekomme ich die Nullen zuletzt, aber auf PostgreSQL bekomme ich zuerst die Nullen.

Gibt es eine nette Standard-Rails-Methode, um damit umzugehen und eine konsistente Leistung in jeder Datenbank zu erzielen?

Andrew
quelle

Antworten:

1

Durch Hinzufügen von Arrays bleibt die Reihenfolge erhalten:

@nonull = Photo.where("collection_id is not null").order("collection_id desc")
@yesnull = Photo.where("collection_id is null")
@wanted = @nonull+@yesnull

http://www.ruby-doc.org/core/classes/Array.html#M000271

Eric
quelle
2
Nun, ich liebe diese Idee nicht, aber ich denke, das würde funktionieren. Es tut mir leid, dass ich es so lange offen gelassen habe, ich hatte gehofft, dass einige andere Antworten erscheinen würden. Nachdem ich einige Zeit damit verbracht habe, darüber nachzudenken, denke ich, dass dies zu einer Methode für das Fotomodell gemacht werden könnte, und dann würde es sich nicht schlecht anfühlen.
Andrew
Es ist einfach, wenn Sie MySQL verwenden. Siehe meine Lösung.
Jacob
Richtig, ich hätte erwähnen sollen, dass mein Weg nur der agnostischste war, den ich finden konnte.
Eric
7
Dies ist eine schlechte Idee, da wherekein Array zurückgegeben wird, sondern eine ActiveRecord :: Relation zurückgegeben wird. Wenn Sie die Ergebnisse in ein Array zwingen, schlägt alles fehl, was eine standardmäßige ActiveRecord :: Relation erwartet (z. B. Paginierung).
Mike Bethany
1
Richtig, obwohl es den Anschein hat, dass die verfügbaren Methoden entweder aus AR ausbrechen oder nicht (vollständig) portabel sind.
Eric
267

Ich bin kein SQL-Experte, aber warum nicht einfach sortieren, wenn etwas zuerst null ist, und dann sortieren, wie Sie es sortieren wollten.

Photo.order('collection_id IS NULL, collection_id DESC')  # Null's last
Photo.order('collection_id IS NOT NULL, collection_id DESC') # Null's first

Wenn Sie nur PostgreSQL verwenden, können Sie dies auch tun

Photo.order('collection_id DESC NULLS LAST')  #Null's Last
Photo.order('collection_id DESC NULLS FIRST') #Null's First

Wenn Sie etwas Universelles möchten (z. B. wenn Sie dieselbe Abfrage in mehreren Datenbanken verwenden, können Sie diese verwenden (mit freundlicher Genehmigung von @philT).

Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
Absichten
quelle
21
+1, viel besser als die akzeptierte Antwort und kann über Arel
m_x
1
Wow, das ist ein alter Kommentar! Also denke ich, was ich sagen wollte, war:p = Photo.arel_table; Photo.order(p[:collection_id].eq(nil)).order(p[:collection_id].desc)
m_x
2
Beachten Sie, NULLS LASTdass Sie ab .last()Rails 4.2 nicht mehr mit Ihrer Abfrage verketten können. Ich habe unten eine Problemumgehung veröffentlicht.
Lanny Bose
1
Warum werden IS NULLdie NULL-Zeilen zuletzt bestellt? Sie würden denken, es würde sie an die erste Stelle setzen.
Jackocnr
2
@jackocr myguess: IS NULL ergibt 1, wenn wahr, 0, wenn falsch, sortiert, 0 kommt vor 1 ...
Absichten
36

Auch wenn es jetzt 2017 ist, gibt es noch keinen Konsens darüber, ob NULLs Vorrang haben sollte. Ohne dass Sie dies explizit angeben, variieren Ihre Ergebnisse je nach DBMS.

Der Standard legt nicht fest, wie NULL-Werte im Vergleich zu Nicht-NULL-Werten geordnet werden sollen, außer dass zwei beliebige NULL-Werte als gleich geordnet betrachtet werden sollen und dass NULL-Werte entweder über oder unter allen Nicht-NULL-Werten sortiert werden sollen.

Quelle, Vergleich der meisten DBMS

Um das Problem zu veranschaulichen, habe ich eine Liste der beliebtesten Fälle für die Rails-Entwicklung zusammengestellt:

PostgreSQL

NULLs haben den höchsten Wert.

Standardmäßig werden Nullwerte so sortiert, als wären sie größer als alle Nicht-Nullwerte.

Quelle: PostgreSQL-Dokumentation

MySQL

NULLs haben den niedrigsten Wert.

Wenn Sie ORDER BY ausführen, werden NULL-Werte zuerst angezeigt, wenn Sie ORDER BY ... ASC ausführen, und zuletzt, wenn Sie ORDER BY ... DESC ausführen.

Quelle: MySQL-Dokumentation

SQLite

NULLs haben den niedrigsten Wert.

Eine Zeile mit einem NULL-Wert ist höher als Zeilen mit regulären Werten in aufsteigender Reihenfolge und wird in absteigender Reihenfolge umgekehrt.

Quelle

Lösung

Leider bietet Rails selbst noch keine Lösung dafür.

PostgreSQL-spezifisch

Für PostgreSQL können Sie ganz intuitiv Folgendes verwenden:

Photo.order('collection_id DESC NULLS LAST') # NULLs come last

MySQL-spezifisch

Für MySQL könnten Sie das Minuszeichen in den Vordergrund stellen, diese Funktion scheint jedoch nicht dokumentiert zu sein. Scheint nicht nur mit numerischen Werten zu arbeiten, sondern auch mit Datumsangaben.

Photo.order('-collection_id DESC') # NULLs come last

PostgreSQL- und MySQL-spezifisch

Um beide abzudecken, scheint dies zu funktionieren:

Photo.order('collection_id IS NULL, collection_id DESC') # NULLs come last

Dieser funktioniert jedoch nicht in SQLite.

Universelle Lösung

Um Cross-Support für alle DBMS bereitzustellen, müssen Sie eine Abfrage schreiben CASE, die bereits von @PhilIT vorgeschlagen wurde:

Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')

Dies bedeutet, dass jeder Datensatz zuerst nach CASEErgebnissen sortiert wird (standardmäßig in aufsteigender Reihenfolge, dh die NULLWerte sind die letzten), dann nach calculation_id.

Adam Sibik
quelle
14
Photo.order('collection_id DESC NULLS LAST')

Ich weiß, dass dies ein alter ist, aber ich habe gerade diesen Ausschnitt gefunden und er funktioniert für mich.

Thomas Yancey
quelle
Ich weiß nicht, in welcher Version von Ruby / Rails dies funktioniert hat, aber für Ruby 2.5 und Rails 5 scheint dies nicht zu funktionieren.
Tasos Anesiadis
13

Setzen Sie ein Minuszeichen vor den Spaltennamen und kehren Sie die Reihenfolge um. Es funktioniert auf MySQL. Mehr Details

Product.order('something_date ASC') # NULLS came first
Product.order('-something_date DESC') # NULLS came last
raymondralibi
quelle
10

Etwas spät zur Show, aber es gibt eine generische SQL-Methode, um dies zu tun. Wie immer CASEzur Rettung.

Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
PhilT
quelle
6

Der einfachste Weg ist zu verwenden:

.order('name nulls first')

Jean-Etienne Durand
quelle
3
Dies ist am besten für Postgres
Sergii Mostovyi
1
Dies ist eine gute Lösung, aber Vorsicht, ActiveRecord lastscheint einen fehlerhaften DESC einzufügen.
Sirvine
6

Aus Gründen der Nachwelt wollte ich einen ActiveRecord-Fehler in Bezug auf hervorheben NULLS FIRST.

Wenn Sie versuchen anzurufen:

Model.scope_with_nulls_first.last

Rails versucht aufzurufen reverse_order.firstund reverse_orderist nicht kompatibel NULLS LAST, da es versucht, die ungültige SQL zu generieren:

PG::SyntaxError: ERROR:  syntax error at or near "DESC"
LINE 1: ...dents"  ORDER BY table_column DESC NULLS LAST DESC LIMIT...

Dies wurde vor einigen Jahren in einigen noch offenen Rails-Ausgaben ( eins , zwei , drei ) erwähnt. Ich konnte es umgehen, indem ich Folgendes tat:

  scope :nulls_first, -> { order("table_column IS NOT NULL") }
  scope :meaningfully_ordered, -> { nulls_first.order("table_column ASC") }

Es scheint, dass durch Verketten der beiden Aufträge gültiges SQL generiert wird:

Model Load (12.0ms)  SELECT  "models".* FROM "models"  ORDER BY table_column IS NULL DESC, table_column ASC LIMIT 1

Der einzige Nachteil ist, dass diese Verkettung für jeden Bereich durchgeführt werden muss.

Lanny Bose
quelle
2

In meinem Fall brauchte ich Sortierzeilen nach Start- und Enddatum nach ASC, aber in einigen Fällen war end_date null und diese Zeilen sollten oben stehen, habe ich verwendet

@invoice.invoice_lines.order('start_date ASC, end_date ASC NULLS FIRST')

Dmitriy Gusev
quelle
-1

Holen Sie sich zuerst Fotos, bei denen collection_idnicht null ist, und ordnen Sie sie collection_idabsteigend an.

Holen Sie sich dann Fotos, wo collection_idnull ist, und fügen Sie sie hinzu.

Photos.where.not(collection_id: [nil, ""])
  .order(collection_id: :desc) + where(collection_id: [nil, ""])
cseelus
quelle
-3

Es scheint, als müssten Sie dies in Ruby tun, wenn Sie konsistente Ergebnisse für alle Datenbanktypen wünschen, da die Datenbank selbst interpretiert, ob die NULL-Werte am Anfang oder Ende der Liste stehen oder nicht.

Photo.all.sort {|a, b| a.collection_id.to_i <=> b.collection_id.to_i}

Das ist aber nicht sehr effizient.

Jaredonline
quelle