Rails: Đặt hàng có nulls cuối cùng


83

Trong ứng dụng Rails của mình, tôi đã gặp sự cố một vài lần mà tôi muốn biết cách người khác giải quyết:

Tôi có một số bản ghi nhất định trong đó giá trị là tùy chọn, vì vậy một số bản ghi có giá trị và một số bản ghi trống cho cột đó.

Nếu tôi sắp xếp theo cột đó trên một số cơ sở dữ liệu thì null sắp xếp đầu tiên và trên một số cơ sở dữ liệu, null sắp xếp sau cùng.

Ví dụ: tôi có Ảnh có thể thuộc Bộ sưu tập hoặc không, tức là có một số Ảnh ở đâu collection_id=nilvà một số ở đâu, collection_id=1v.v.

Nếu tôi làm vậy Photo.order('collection_id desc)thì trên SQLite, tôi nhận được null sau cùng nhưng trên PostgreSQL, tôi nhận được null trước.

Có cách nào hay và chuẩn Rails để xử lý điều này và có được hiệu suất nhất quán trên bất kỳ cơ sở dữ liệu nào không?

Câu trả lời:


0

Việc thêm các mảng với nhau sẽ bảo toàn thứ tự:

@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


2
Chà, tôi không thích ý tưởng này, nhưng tôi nghĩ nó sẽ thành công. Xin lỗi vì đã bỏ ngỏ quá lâu, tôi hy vọng rằng một số câu trả lời khác sẽ xuất hiện. Tuy nhiên, đã dành thêm thời gian để suy nghĩ về nó, tôi nghĩ điều này có thể được thực hiện thành một phương pháp trên Photo model và sau đó nó sẽ không quá tệ.
Andrew

Thật dễ dàng nếu bạn đang sử dụng mysql. Xem giải pháp của tôi.
Jacob

Đúng vậy, tôi nên nói rằng cách của tôi chỉ là cách bất khả tri nhất mà tôi có thể tìm thấy.
Eric

8
Đây là một ý tưởng tồi vì wherekhông trả về một mảng, nó trả về một ActiveRecord :: Relation và buộc các kết quả vào một mảng sẽ khiến mọi thứ mong đợi một ActiveRecord :: Relation tiêu chuẩn bị lỗi (như phân trang).
Mike Bethany

1
Đúng, mặc dù có vẻ như các phương thức khả dụng thoát khỏi AR hoặc không (hoàn toàn) di động.
Eric

269

Tôi không phải là chuyên gia về SQL, nhưng tại sao không chỉ sắp xếp theo nếu cái gì đó rỗng trước rồi sắp xếp theo cách bạn muốn sắp xếp nó.

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

Nếu bạn chỉ sử dụng PostgreSQL, bạn cũng có thể làm điều này

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

Nếu bạn muốn một cái gì đó phổ quát (giống như bạn đang sử dụng cùng một truy vấn trên nhiều cơ sở dữ liệu, bạn có thể sử dụng (nhờ @philT)

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

21
+1, tốt hơn nhiều so với câu trả lời được chấp nhận và có thể được thể hiện qua Arel
m_x

1
wow, đây là một nhận xét cũ! Vì vậy, tôi đoán những gì tôi đang cố gắng nói là:p = Photo.arel_table; Photo.order(p[:collection_id].eq(nil)).order(p[:collection_id].desc)
m_x

2
Lưu ý rằng điều NULLS LASTđó không cho phép bạn xâu chuỗi .last()vào truy vấn của mình kể từ Rails 4.2. Tôi đã đăng một công việc xung quanh bên dưới.
Lanny Bose

1
Tại sao đặt hàng bằng cách IS NULLđặt các hàng NULL cuối cùng? Bạn sẽ nghĩ rằng nó sẽ đặt họ đầu tiên.
jackocnr 14/07/17

2
myguess @jackocr: LÀ đánh giá lại NULL tới 1 nếu đúng, 0 nếu sai sự thật, sắp xếp, 0 đến trước 1 ...
Intentss

36

Mặc dù bây giờ là năm 2017, vẫn chưa có sự thống nhất về việc liệu NULLs có nên được ưu tiên hay không. Nếu bạn không rõ ràng về nó, kết quả của bạn sẽ thay đổi tùy thuộc vào DBMS.

Tiêu chuẩn không chỉ định cách NULL nên được sắp xếp so với các giá trị không phải NULL, ngoại trừ hai NULL bất kỳ được coi là có thứ tự như nhau và NULL phải sắp xếp trên hoặc dưới tất cả các giá trị không phải NULL.

nguồn, so sánh hầu hết các DBMS

Để minh họa vấn đề, tôi đã biên soạn danh sách một vài trường hợp phổ biến nhất khi nói đến phát triển Rails:

PostgreSQL

NULLs có giá trị cao nhất.

Theo mặc định, các giá trị null sắp xếp như thể lớn hơn bất kỳ giá trị nào không phải null.

nguồn: Tài liệu PostgreSQL

MySQL

NULLs có giá trị thấp nhất.

Khi thực hiện ORDER BY, giá trị NULL được hiển thị đầu tiên nếu bạn thực hiện ORDER BY ... ASC và cuối cùng nếu bạn thực hiện ORDER BY ... DESC.

nguồn: Tài liệu MySQL

SQLite

NULLs có giá trị thấp nhất.

Hàng có giá trị NULL cao hơn hàng có giá trị thông thường theo thứ tự tăng dần và nó được đảo ngược theo thứ tự giảm dần.

nguồn

Giải pháp

Thật không may, bản thân Rails vẫn chưa cung cấp giải pháp cho nó.

PostgreSQL cụ thể

Đối với PostgreSQL, bạn có thể sử dụng khá trực quan:

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

MySQL cụ thể

Đối với MySQL, bạn có thể đặt trước dấu trừ, nhưng tính năng này dường như không có giấy tờ. Có vẻ như không chỉ hoạt động với các giá trị số mà còn với cả ngày tháng.

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

PostgreSQL và MySQL cụ thể

Để bao gồm cả hai, điều này dường như hoạt động:

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

Tuy nhiên, cái này không hoạt động trong SQLite.

Giải pháp phổ quát

Để cung cấp hỗ trợ chéo cho tất cả DBMS, bạn phải viết truy vấn bằng cách sử dụng CASE@PhilIT đã đề xuất:

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

nghĩa là sắp xếp từng bản ghi trước tiên theo CASEkết quả (theo thứ tự tăng dần mặc định, có nghĩa là NULLcác giá trị sẽ là những thứ cuối cùng), thứ hai là calculation_id.


14
Photo.order('collection_id DESC NULLS LAST')

Tôi biết đây là một đoạn cũ nhưng tôi vừa tìm thấy đoạn mã này và nó phù hợp với tôi.


Tôi không biết phiên bản này của Ruby / Rails hoạt động như thế nào, nhưng đối với ruby ​​2.5 và rails 5 dường như không hoạt động.
Tasos Anesiadis

13

Đặt dấu trừ ở phía trước tên_cột và đảo ngược hướng thứ tự. Nó hoạt động trên mysql. Thêm chi tiết

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

10

Tuy nhiên, có một cách thức SQL chung để làm điều đó. Như thường lệ, CASEđể giải cứu.

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

6

Cách dễ nhất là sử dụng:

.order('name nulls first')


3
Đây là tốt nhất cho Postgres
Sergii Mostovyi

1
Đây là giải pháp tốt, nhưng hãy cẩn thận rằng ActiveRecord lastdường như chèn một DESC có lỗi.
sirvine

6

Vì lợi ích của hậu thế, tôi muốn làm nổi bật một lỗi ActiveRecord liên quan đến NULLS FIRST.

Nếu bạn cố gắng gọi:

Model.scope_with_nulls_first.last

Rails sẽ cố gắng gọi reverse_order.firstreverse_orderkhông tương thích với NULLS LAST, vì nó cố tạo ra SQL không hợp lệ:

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

Điều này đã được tham chiếu cách đây vài năm trong một số vấn đề Rails vẫn còn mở ( một , hai , ba ). Tôi đã có thể giải quyết vấn đề này bằng cách làm như sau:

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

Có vẻ như bằng cách xâu chuỗi hai lệnh với nhau, SQL hợp lệ sẽ được tạo:

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

Nhược điểm duy nhất là chuỗi này phải được thực hiện cho từng phạm vi.


2

Trong trường hợp của tôi, tôi cần các dòng sắp xếp theo ngày bắt đầu và ngày kết thúc bởi ASC, nhưng trong một số trường hợp end_date là rỗng và các dòng đó phải ở trên, tôi đã sử dụng

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


-3

Có vẻ như bạn phải làm điều đó trong Ruby nếu bạn muốn có kết quả nhất quán giữa các loại cơ sở dữ liệu, vì cơ sở dữ liệu tự giải thích xem NULLS có nằm ở đầu hay cuối danh sách hay không.

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

Nhưng điều đó không hiệu quả lắm.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.