Bài này dùng postgre trong Rails và sẽ được update liên tục.

Duyệt array và query từng phần tử trong array

Bài toán đặt ra là cho một array các ids:

ids = [1, 2, 3, 100000, 4, 5]

Bạn muốn duyệt từng phần tử trong ids, với mỗi phần tử sẽ query vào bảng User trong DB và trả về giá trị tương ứng, nếu ko có record tương ứng với id thì trả về nil. Ví dụ id = 100000 sẽ không có record tương ứng trong DB, còn các id còn lại có record tương ứng trong DB. Mảng bạn muốn nhận được sẽ như sau:

names = ['name1', 'name2', 'name3', nil, 'name4', 'name5']

 

Cách làm trâu bò sẽ như sau:

names = ids.map do |id|
  user = User.find_by(id: id) # Bạn ko thể dùng User.find(id) vì id không có sẽ bị lỗi.
  user&.name  # dấu &. để check nếu user là nil sẽ trả về nil luôn
end

Làm như cách trên thì với ids là một mảng vài chục tới vài trăm phần tử thì sẽ phải đợi rất lâu.

 

Bạn không thể query được như sau vì với id không tìm thấy sẽ bị bỏ qua (bạn muốn trả về nil).

names = User.where(id: ids).pluck(:name)
# => names = ['name1', 'name2', 'name3', 'name4', 'name5']

 

Có một cách mà chỉ cần 1 câu query như sau:

id_names = User.where(id: ids).pluck(:id, :name).to_h
# => id_names = { 1 => 'name1', 2 => 'name2', 3 => 'name3', 4 => 'name4', 5 => 'name5' }

names = ids.map{ |id| id_names[id] }
# => names = ['name1', 'name2', 'name3', nil, 'name4', 'name5']

Nhưng nếu làm như trên vẫn phải duyệt lại mảng ids 

 

Có một cách chỉ cần một câu query mà vẫn trả về mảng như ý muốn của ta như sau:

query = "WITH table_tmp AS (SELECT unnest(array[#{ids.join(', ')}]) AS id)
SELECT name
FROM table_tmp LEFT OUTER JOIN users ON table_tmp.id = users.id;"
# => query = "WITH table_tmp AS (SELECT unnest(array[1, 2, 3, 100000, 4, 5]) AS id)
#             SELECT name
#             FROM table_tmp LEFT OUTER JOIN users ON table_tmp.id = users.id;"


names = User.find_by_sql(query).pluck(:name)
# => names = ['name1', 'name2', 'name3', nil, 'name4', 'name5']

Cẩn thận khi dùng where.not

Mặc định trong SQL khi bạn query where.not(column: value) thì sẽ bỏ luôn cả những dữ liệu có column = null. Nên hãy cẩn thận khi dùng nó, đặc biệt là khi đặt nó trong scope hay default_scope trong Rails.

Nếu bạn muốn query như trên nhưng vẫn trả về cả những cột bằng null nữa thì làm như sau:

scope :scope_name, -> { Table.where("column != value OR column IS NULL") }