ActiveRecord: taille vs nombre

201

Dans Rails, vous pouvez trouver le nombre d'enregistrements en utilisant à la fois Model.sizeet Model.count. Si vous traitez des requêtes plus complexes, y a-t-il un avantage à utiliser une méthode par rapport à l'autre? Comment sont-ils différents?

Par exemple, j'ai des utilisateurs avec des photos. Si je veux montrer un tableau des utilisateurs et combien de photos ils ont, l'exécution de nombreuses instances user.photos.sizesera-t-elle plus rapide ou plus lente que user.photos.count?

Merci!

Andrew
la source

Réponses:

344

Vous devriez lire cela , c'est toujours valable.

Vous adapterez la fonction que vous utilisez en fonction de vos besoins.

Fondamentalement:

  • si vous chargez déjà toutes les entrées, par exemple User.all, vous devez utiliser lengthpour éviter une autre requête db

  • si vous n'avez rien chargé, utilisez countpour faire une requête de comptage sur votre base de données

  • si vous ne voulez pas vous soucier de ces considérations, utilisez sizece qui s'adaptera

apnée
la source
35
Si sizede toute façon, la situation s’adapte à la situation, alors à quoi bon lengthet countà quoi bon ?
sscirrus
27
@sscirus - Cela sizepeut donc les appeler lorsque vous effectuez l'appel size(après avoir déterminé lequel appeler).
Batkins
35
Cependant, soyez prudent avec la taille par défaut. Par exemple, si vous créez un nouvel enregistrement sans passer par la relation, c'est Comment.create(post_id: post.id)-à- dire , votre post.comments.sizevolonté ne sera pas à jour, alors que le post.comments.countsera. Soyez donc prudent.
mrbrdo
14
De plus, si vous créez plusieurs objets via une relation:, company.devices.build(:name => "device1"); company.devices.build(:name => "device2")alors company.devices.sizeet .lengthinclura le nombre d'objets que vous avez construits mais que vous n'avez pas enregistrés, .countne rapportera que le nombre de la base de données.
Shawn J.Goff
6
@sscirrus, la taille est une commande dangereuse car elle est automatisée, parfois vous voulez à nouveau interroger la base de données.
Alex C
79

Comme les autres réponses le disent:

  • countexécutera une COUNTrequête SQL
  • length va calculer la longueur du tableau résultant
  • size va essayer de choisir le plus approprié des deux pour éviter des requêtes excessives

Mais il y a encore une chose. Nous avons remarqué un cas où sizeagit différemment de count/ lengthtout à fait, et j'ai pensé le partager car il est assez rare pour être ignoré.

  • Si vous utilisez un :counter_cachesur une has_manyassociation, sizeutilisera directement le nombre mis en cache et ne fera aucune requête supplémentaire.

    class Image < ActiveRecord::Base
      belongs_to :product, counter_cache: true
    end
    
    class Product < ActiveRecord::Base
      has_many :images
    end
    
    > product = Product.first  # query, load product into memory
    > product.images.size      # no query, reads the :images_count column
    > product.images.count     # query, SQL COUNT
    > product.images.length    # query, loads images into memory
    

Ce comportement est documenté dans les guides Rails , mais soit je l'ai raté la première fois, soit je l'ai oublié.

citron vert
la source
En fait, avant les rails 5.0.0.beta1, ce comportement serait déclenché même s'il y a une _countcolonne (sans la counter_cache: truedirective sur l'association). Ce problème
cbliard
8

Parfois, size"choisit le mauvais" et renvoie un hachage (ce qui countferait l'affaire)

Dans ce cas, utilisez lengthpour obtenir un entier au lieu du hachage .

jvalanen
la source
J'ai utilisé '.size' sur une collection à partir d'une instance de has_many et même s'il y avait un enregistrement dans la collection, size renvoyait un '0'. L'utilisation de .count a renvoyé la valeur correcte de «1».
admazzola
4

tl; dr

  • Si vous savez que vous n'aurez pas besoin de l'utilisation des données count.
  • Si vous savez que vous utiliserez ou avez utilisé l'utilisation des données length.
  • Si vous ne savez pas ce que vous faites, utilisez size...

compter

Décide d'envoyer une Select count(*)...requête à la base de données. La voie à suivre si vous n'avez pas besoin des données, mais simplement du nombre.

Exemple: nombre de nouveaux messages, nombre total d'éléments lorsque seule une page va être affichée, etc.

longueur

Charge les données requises, c'est-à-dire la requête comme requis, puis les compte simplement. La voie à suivre si vous utilisez les données.

Exemple: résumé d'un tableau entièrement chargé, titres des données affichées, etc.

Taille

Il vérifie si les données ont été chargées (c'est-à-dire déjà dans des rails) si c'est le cas, puis il suffit de les compter, sinon il appelle count. (plus les pièges, déjà mentionnés dans d'autres entrées).

def size
  loaded? ? @records.length : count(:all)
end

Quel est le problème?

Que vous frappiez deux fois la base de données si vous ne le faites pas dans le bon ordre (par exemple, si vous restituez le nombre d'éléments dans une table au-dessus de la table rendue, il y aura effectivement 2 appels envoyés à la base de données).

estani
la source
3

Les stratégies suivantes appellent toutes la base de données pour effectuer une COUNT(*)requête.

Model.count

Model.all.size

records = Model.all
records.count

Ce qui suit n'est pas aussi efficace qu'il charge tous les enregistrements de la base de données dans Ruby, qui compte ensuite la taille de la collection.

records = Model.all
records.size

Si vos modèles ont des associations et que vous souhaitez trouver le nombre d'objets appartenant (par exemple @customer.orders.size), vous pouvez éviter les requêtes de base de données (lectures sur disque). Utilisez un cache de compteur et Rails gardera la valeur du cache à jour et renverra cette valeur en réponse à la sizeméthode.

Dennis
la source
2
Les deux Model.all.sizeet Model.all.countgénèrent une countrequête dans Rails 4 et supérieur. Le véritable avantage de sizene génère pas la requête de comptage si l'association est déjà chargée. Dans Rails 3 et ci-dessous, je pense que ce Model.alln'est pas une relation, donc tous les enregistrements sont déjà chargés. Cette réponse est peut-être obsolète et je suggère de la supprimer.
Damon Aw
1

J'ai recommandé d'utiliser la fonction taille.

class Customer < ActiveRecord::Base
  has_many :customer_activities
end

class CustomerActivity < ActiveRecord::Base
  belongs_to :customer, counter_cache: true
end

Considérez ces deux modèles. Le client a de nombreuses activités client.

Si vous utilisez un: counter_cache sur une association has_many, la taille utilisera directement le nombre mis en cache et ne fera aucune requête supplémentaire.

Prenons un exemple: dans ma base de données, un client a 20 000 activités client et j'essaie de compter le nombre d'enregistrements des activités client de ce client avec chacune des méthodes de comptage, de longueur et de taille. ci-dessous le rapport de référence de toutes ces méthodes.

            user     system      total        real
Count:     0.000000   0.000000   0.000000 (  0.006105)
Size:      0.010000   0.000000   0.010000 (  0.003797)
Length:    0.030000   0.000000   0.030000 (  0.026481)

j'ai donc trouvé que l'utilisation de: counter_cache Size est la meilleure option pour calculer le nombre d'enregistrements.

manthan andharia
la source