1
votes

Rails 5 - Comment trier les données en fonction des données d'une autre table?

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?


0 commentaires

3 Réponses :


1
votes

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
")


1 commentaires

Ce n'est pas testé, il peut donc y avoir des ajustements à faire ....



1
votes

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)

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.


0 commentaires

1
votes

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

Requête en direct

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' }

Colonne de cache

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).

Divers

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


1 commentaires

Merci pour votre effort. Après quelques jeux, j'ai décidé de déplacer cette colonne vers le modèle Product .