Als je Something.find(array_of_ids)
doet in Rails, hangt de volgorde van de resulterende array niet af van de volgorde van array_of_ids
.
Is er een manier om de zoekactie uit te voeren en de bestelling te behouden?
ATM Ik sorteer de records handmatig op volgorde van ID’s, maar dat is nogal flauw.
UPD: als het mogelijk is om de volgorde te specificeren met behulp van de :order
param en een soort SQL-clausule, hoe dan?
Antwoord 1, autoriteit 100%
Vreemd genoeg heeft niemand iets als dit voorgesteld:
index = Something.find(array_of_ids).group_by(&:id)
array_of_ids.map { |i| index[i].first }
Zo efficiënt als het wordt, behalve dat de SQL-backend het doet.
Bewerken:om mijn eigen antwoord te verbeteren, kun je het ook als volgt doen:
Something.find(array_of_ids).index_by(&:id).slice(*array_of_ids).values
#index_by
en #slice
zijn behoorlijk handige toevoegingen in ActiveSupportvoor respectievelijk arrays en hashes.
Antwoord 2, autoriteit 88%
Het antwoord is alleen voor mysql
Er is een functie in mysql genaamd FIELD( )
Hier is hoe je het zou kunnen gebruiken in .find():
>> ids = [100, 1, 6]
=> [100, 1, 6]
>> WordDocument.find(ids).collect(&:id)
=> [1, 6, 100]
>> WordDocument.find(ids, :order => "field(id, #{ids.join(',')})")
=> [100, 1, 6]
For new Version
>> WordDocument.where(id: ids).order("field(id, #{ids.join ','})")
Bijwerken:
Dit wordt verwijderd in Rails 6.1 Rails-broncode
Antwoord 3, autoriteit 58%
Zoals Mike Woodhousevermeld in zijn antwoord, dit gebeurt omdat Rails onder de motorkap een SQL-query gebruikt met een WHERE id IN... clause
om alle records in één query op te halen . Dit is sneller dan elke id afzonderlijk op te halen, maar zoals je hebt opgemerkt, blijft de volgorde van de records die je ophaalt niet behouden.
Om dit op te lossen, kunt u de records op applicatieniveau sorteren volgens de originele lijst met ID’s die u gebruikte bij het opzoeken van de record.
Op basis van de vele uitstekende antwoorden op Sorteer een array volgens de elementen van een andere array, raad ik de volgende oplossing aan:
Something.find(array_of_ids).sort_by{|thing| array_of_ids.index thing.id}
Of als je iets sneller nodig hebt (maar waarschijnlijk iets minder leesbaar), kun je dit doen:
Something.find(array_of_ids).index_by(&:id).values_at(*array_of_ids)
Antwoord 4, autoriteit 32%
Dit lijkt te werken voor postgresql(bron) – en retourneert een ActiveRecord-relatie
class Something < ActiveRecrd::Base
scope :for_ids_with_order, ->(ids) {
order = sanitize_sql_array(
["position((',' || id::text || ',') in ?)", ids.join(',') + ',']
)
where(:id => ids).order(Arel.sql(order))
}
end
# usage:
Something.for_ids_with_order([1, 3, 2])
kan ook worden uitgebreid voor andere kolommen, b.v. Voor de name
KOLOM, gebruikt u position(name::text in ?)
…
Antwoord 5, Autoriteit 30%
Zoals ik geantwoord hier , heb ik net een edelsteen uitgebracht (Order_as_Specified ) waarmee u inheemse SQL-bestellingen vindt:
Something.find(array_of_ids).order_as_specified(id: array_of_ids)
Voor zover ik heb kunnen testen, werkt het in native in alle RDBMS’s en retourneert een activerecord-relatie die kan worden geketend.
Antwoord 6, Autoriteit 6%
Niet mogelijk in SQL, die helaas in alle gevallen zou werken, zou u ofwel single vondsten moeten schrijven voor elk record of bestelling in Ruby, hoewel er waarschijnlijk een manier is om het te laten werken met behulp van gepatenteerde technieken:
eerste voorbeeld:
sorted = arr.inject([]){|res, val| res << Model.find(val)}
Zeer inefficiënt
Tweede voorbeeld:
unsorted = Model.find(arr)
sorted = arr.inject([]){|res, val| res << unsorted.detect {|u| u.id == val}}
Antwoord 7, Autoriteit 5%
Er is een edelsteen find_with_order waarmee u het efficiënt kunt doen met behulp van native SQL-query.
En het ondersteunt zowel Mysql
en PostgreSQL
.
Bijvoorbeeld:
Something.find_with_order(array_of_ids)
Als u relatie wilt:
Something.where_with_order(:id, array_of_ids)
Antwoord 8, Autoriteit 2%
Onder de kap, find
met een reeks ID’s zal een SELECT
met een WHERE id IN...
clausule, dat zou moeten efficiënter zijn dan door de ID’s.
Dus het verzoek is tevreden in één reis naar de database, maar SELECT
S zonder ORDER BY
Clausules zijn ongesorteerd. ActiveRecord begrijpt dit, dus we breiden onze find
als volgt uit:
Something.find(array_of_ids, :order => 'id')
Als de volgorde van ID’s in uw array willekeurig en significant is (dwz u wilt de volgorde van rijen teruggekeerd om overeen te komen met uw array, ongeacht de reeks van ID’s die daarin zijn opgenomen), dan denk ik dat u de beste server door nabewerking zou zijn De resultaten in de code – u kunt een :order
clausule bouwen, maar het zou duivelachtig ingewikkeld zijn en helemaal niet intentions-onthullend.
Antwoord 9, Autoriteit 2%
@Gunchars Antwoord is geweldig, maar het werkt niet uit de doos in rails 2.3 omdat de HASH-klasse niet wordt besteld. Een eenvoudige oplossing is om de slaapbare klasse ‘index_by
uit te breiden om de volgorde van de bestelling te gebruiken:
module Enumerable
def index_by_with_ordered_hash
inject(ActiveSupport::OrderedHash.new) do |accum, elem|
accum[yield(elem)] = elem
accum
end
end
alias_method_chain :index_by, :ordered_hash
end
Nu @Gunchars-aanpak werkt
Something.find(array_of_ids).index_by(&:id).slice(*array_of_ids).values
bonus
module ActiveRecord
class Base
def self.find_with_relevance(array_of_ids)
array_of_ids = Array(array_of_ids) unless array_of_ids.is_a?(Array)
self.find(array_of_ids).index_by(&:id).slice(*array_of_ids).values
end
end
end
Dan
Something.find_with_relevance(array_of_ids)
Antwoord 10, Autoriteit 2%
Ervan uitgaande dat Model.pluck(:id)
[1,2,3,4]
retourneert en je de volgorde van [2,4,1,3]
Het concept is om de ORDER BY CASE WHEN
SQL-clausule te gebruiken. Bijvoorbeeld:
SELECT * FROM colors
ORDER BY
CASE
WHEN code='blue' THEN 1
WHEN code='yellow' THEN 2
WHEN code='green' THEN 3
WHEN code='red' THEN 4
ELSE 5
END, name;
In Rails kunt u dit bereiken door een openbare methode in uw model te gebruiken om een vergelijkbare structuur te construeren:
def self.order_by_ids(ids)
if ids.present?
order_by = ["CASE"]
ids.each_with_index do |id, index|
order_by << "WHEN id='#{id}' THEN #{index}"
end
order_by << "END"
order(order_by.join(" "))
end
else
all # If no ids, just return all
end
Doe dan:
ordered_by_ids = [2,4,1,3]
results = Model.where(id: ordered_by_ids).order_by_ids(ordered_by_ids)
results.class # Model::ActiveRecord_Relation < ActiveRecord::Relation
Het goede hieraan. Resultaten worden geretourneerd als ActiveRecord-relaties (waardoor u methoden kunt gebruiken zoals last
, count
, where
, pluck
, enz. )
Antwoord 11
Hoewel ik het nergens in een CHANGELOG zie, lijkt het erop dat deze functionaliteit was gewijzigdmet de release van versie 5.2.0
.
Hier commithet bijwerken van de documenten getagd met 5.2.0
Het lijkt echter ook te zijn geweest gebackporteerdnaar versie 5.0
.
Antwoord 12
Volgens het officiële document zou het in dezelfde volgorde moeten staan.
https://api.rubyonrails.org/classes /ActiveRecord/FinderMethods.html#method-i-find
Something.find(array_of_ids)
de volgorde van de resulterende array moet dezelfde volgorde zijn als array_of_ids. Ik heb dit getest in Rails 6.
Antwoord 13
Met verwijzing naar het antwoord hier
Object.where(id: ids).order("position(id::text in '#{ids.join(',')}')")
werkt voor Postgresql.
Antwoord 14
Ik vond de op order
gebaseerde opties erg leuk.
Mijn 2c is dat het logisch is om het in een scope toe te voegen, zodat je het kunt gebruiken in een keten met andere AR-methoden
scope :find_in_order, ->(ids) {
where(id: ids).order([Arel.sql('FIELD(id, ?)'), ids])
}
Antwoord 15
Er is een orderclausule in find (:order=>’…’) die dit doet bij het ophalen van records.
Je kunt hier ook hulp krijgen.