J'ai ces modèles:
@products = Product.joins(:product_documents) .where('product_documents.doc_type = ?', 1) .order('product_documents.doc_url') .page(params[:page]).per(50)
Je veux lister TOUS les produits ET les trier en fonction de l'attribut product_documents.doc_type = 1
(note: certains produits ne Je n'ai téléchargé aucun document, certains en ont N).
J'ai essayé quelque chose comme ceci:
class Product < ApplicationRecord has_many :product_documents end class ProductDocument < ApplicationRecord belongs_to :product end
Mais cette requête ne me renvoie que les produits qui ont a téléchargé un document avec doc_type=1
.
Comment lister tous les produits (même ceux où il n'y a pas de document avec doc_type = 1
) et les trier par le fait s'ils ont uploadé un document avec doc_type=1
?
3 Réponses :
C'est l'idée de base, vous pouvez avoir un CASE
dans votre clause SQL order by (du moins si vous utilisez Postgres). Si doc_type = 1
puis les ordonne en premier, tout le reste va après cela, alors vous pouvez classer par d'autres colonnes.
Product.joins(:product_documents) .order(" CASE product_documents.doc_type WHEN 1 THEN 0 ELSE 1 END, product_documents.doc_url ")
Ce n'est pas testé, il peut donc y avoir des ajustements à faire ....
Vous utilisez where
mais vous voulez un sort
. Utilisez le code ci-dessous:
@products = Product.joins(:product_documents) .order('product_documents.doc_type asc') .order('product_documents.doc_url') .page(params[:page]).per(50)
où
est utilisé pour filtrer les données. order
est utilisé pour trier les données.
Le code ci-dessus triera les enregistrements en fonction de doc_type
et doc_url
.
Si vous ne souhaitez pas commander par doc_url , vous pouvez supprimer ce code.
Bien qu'il soit possible de tout faire en une seule requête, il deviendrait presque impossible d'obtenir de bonnes performances une fois que les tables deviendraient volumineuses (c'est pourquoi vous paginez, n'est-ce pas?) car il s'agit en fait de deux types différents de
Dans Rails, ces deux requêtes seraient:
{ product_id: 2, doc_url: 'B' } { product_id: 1, doc_url: 'C' }
Vous pourriez UNION
les résultats ensemble, mais la plupart du temps, vous ne paginez que sur l'une des deux requêtes avant de commencer à parcourir l'autre.
Si vous vraiment em > voulez le faire en une seule requête, ou si vos tables ne vont pas devenir vraiment grandes, alors faites:
{ product_id: 1, doc_url: 'A' } { product_id: 2, doc_url: 'B' }
Si vous devez exécuter ces requêtes fréquemment et rapidement, je vous suggère de modifier les données interrogées. Ajout d'une nouvelle colonne à Produit
qui ressemble à:
product_documents id | product_id | doc_url ------------------------- 1 | 1 | 'A' 2 | 1 | 'C' 3 | 2 | 'B'
ou
# with a Product#num_doc_type_one column Product.order_by(:num_doc_type_one)
Bien que vous ' Vous devrez vous assurer que vous gérez correctement ces colonnes avec les transactions et autres (évitez les rappels si vous le pouvez).
J'ai omis le .order ('product_documents .doc_url ')
car cela n'a pas vraiment de sens. Si un Produit
a de nombreux ProductDocument
, quel doc_url
doit être utilisé lors du tri? par exemple. donné:
class Product < ApplicationRecord has_one :primary_doc_one, class_name: ProductDocument.to_s end # making the query: Product.order_by('primary_doc_one_id NULLS FIRST') # or LAST
(en ignorant doc_type
) si la sortie de Product
est:
SELECT * FROM ( SELECT DISTINCT ON(p.id) p.id, doc_type FROM products p LEFT JOIN product_documents pd ON pd.product_id = p.id ORDER BY p.id ASC, doc_type DESC ) sub ORDER BY doc_type
ou
products_without_one = Product.where.not('EXISTS (?)', ProductDocument.where('products.id = product_documents.product_id').where(doc_type: 1).select(1) ) products_with_one = Product.joins(:product_documents).where(product_documents: { doc_type: 1 }).distinct
Merci pour votre effort. Après quelques jeux, j'ai décidé de déplacer cette colonne vers le modèle Product
.