[RUBY] Dessinez SQL dans votre tête lorsque vous utilisez ActiveRecord

Avez-vous à l'esprit SQL lorsque vous utilisez ActiveRecord?

ActiveRecord est très pratique et vous pouvez accéder à la base de données pour obtenir et mettre à jour des données sans connaître SQL. Par conséquent, il peut être écrit sans connaître SQL au moment de la mise en œuvre.

Au moment de l'implémentation, vous n'avez pas besoin d'être conscient du SQL brut en utilisant ActiveRecord, mais lorsque vous l'exécutez enfin, SQL est exécuté. Par conséquent, lorsque vous examinez le SQL qui est réellement émis, vous pouvez être surpris qu'un tel SQL soit émis.

Voici quelques exemples d'implémentation qui peuvent être évités si vous écrivez en pensant à SQL.

REJOINDRE une table inutile

Supposons que vous ayez le modèle suivant.

def User < ApplicationRecord
  has_many :user_organizations
end

def UserOrganization < ApplicationRecord
  belongs_to :user
  belongs_to :organization
end

def Organization < ApplicationRecord
  has_many :user_organizations
end

"Obtenez le modèle utilisateur réduit par organization_id. Comment le mettez-vous en œuvre quand on vous le dit?

target_organization_id = 1
users = User.join(user_organizations: :organization)
  .where(user_organizations: { organization: { id: target_organization_id }})

Cette implémentation fonctionne correctement comme spécifié. Le SQL émis est le suivant

SELECT `users`.*
FROM `users`
  INNER JOIN `user_organizations` ON `user_organizations`.`user_id` = `users`.`id`
  INNER JOIN `organizations` ON `organizations`.`id` = `user_organizations`.`organization_id`
WHERE `organization`.`id` = 1

Que pensez-vous de ce SQL? Si vous y réfléchissez, vous remarquerez que vous n'êtes pas obligé de REJOINDRE la table des organisations. Le SQL amélioré est le suivant.

SELECT `users`.*
FROM `users`
  INNER JOIN `user_organizations` ON `user_organizations`.`user_id` = `users`.`id`
WHERE `user_organizations`.`organization_id` = 1

L'enregistrement actif qui réalise cela est le suivant.

target_organization_id = 1
users = User.join(:user_organizations)
  .where(user_organizations: { organization_id: target_organization_id })

Compte tenu de l'implémentation centrée sur le modèle ActiveRecord, il est facile de JOINDRE même le modèle avec l'ID spécifié comme la première implémentation. Même si vous effectuez une révision du code, vous verrez souvent une telle implémentation. Plus le JOIN est petit, meilleures sont les performances de SQL, alors sachez que vous devez implémenter ActiveRecord avec le moins de JOIN possible.

eager_load JOINTURE EXTÉRIEURE GAUCHE

Comme précédemment, "Obtenez le modèle utilisateur réduit par organization_id. ] De plus, si vous souhaitez utiliser ʻuser.exam_organization` plus tard et que vous voulez le mettre en cache, comment l'implémentez-vous?

Avec l'implémentation précédente, exam_organizations n'est pas mis en cache, donc chaque fois que vous obtenez exam_organization, SQL sera émis, ce qui donne N + 1. Par conséquent, il sera mis en cache en changeant les jointures en eager_load (ou includes) comme indiqué ci-dessous.

target_organization_id = 1
users = User.eager_load(:user_organizations)
  .where(user_organizations: { organization_id: target_organization_id })

Maintenant, il est mis en cache en toute sécurité, mais quand je regarde le SQL émis, je remarque que INNER JOIN a changé en LEFT OUTER JOIN.

SELECT `users`.id AS t0_r0, ...(Toutes les colonnes sont répertoriées. Omis parce que c'est long)
FROM `users`
  LEFT OUTER JOIN `user_organizations` ON `user_organizations`.`user_id` = `users`.`id`
WHERE `user_organizations`.`organization_id` = 1

Si vous écrivez Rails sans penser à SQL, je pense que vous ne vous souciez souvent pas de savoir si INNER JOIN est LEFT OUTER JOIN comme dans cet exemple. Cependant, quand SQL me vient à l'esprit, je ne peux pas choisir LEFT OUTER JOIN même s'il s'agit d'une jointure avec des données, donc cela semble étrange. Dans un tel cas, vous pouvez mettre en cache les données lors de la jointure avec INNER JOIN en ajoutant des jointures comme indiqué ci-dessous.

target_organization_id = 1
users = User.eager_load(:user_organizations).joins(:user_organizations)
  .where(user_organizations: { organization_id: target_organization_id })

finalement

J'ai donné quelques exemples, mais les deux sont faciles à éviter si vous écrivez avec SQL à l'esprit. Même si vous faites quelque chose d'un peu inefficace, cela fonctionne généralement bien, donc vous ne le remarquez souvent pas, mais comme il y a une différence de performances due à l'empilement, je n'ai pas été au courant de SQL lors de l'utilisation d'ActiveRecord jusqu'à présent. Je pense qu'il est bon que les gens connaissent le SQL émis.

Recommended Posts

Dessinez SQL dans votre tête lorsque vous utilisez ActiveRecord
Points à garder à l'esprit lors de l'utilisation de l'instruction if
Différences de code lors de l'utilisation du système de longueur en Java