Comment puis-je trouver un tableau associé plus ancien registre de filtrage par l'un de ses attributs?

0

La question

J'ai le modèle Subscription qui has_many Versions.

Un Version a un status, plan_id et authorized_at date.

Toutes les modifications apportées à un Subscription vient d'un Version modifications de la mise à jour de son parent Subscription.

L'objectif est de trouver de chaque Abonnement Version avec la plus ancienne authorized_at date WHERE l' versions.plan_id est le même que le subscriptions.plan_id (en d'autres mots j'ai besoin de la date d'autorisation de la Version où l' plan_id changé au courant Subscription's plan_id).

C'est la requête que j'ai trouvé. J'obtiens une erreur dans la fonction aggregate syntaxe:

syntax error at or near "MIN" LINE 3: MIN (authorized_at) from versions ^

requête:

select subscriptions.id,
MIN (authorized_at) from versions
where versions.plan_id = subscriptions.plan_id
) as current_version
from subscriptions
join versions on subscriptions.id = versions.subscription_id
where versions.status = 'processed'

Moi aussi, je suis pas sûr si je devrais être en groupant les versions par plan_id et puis la cueillette de chaque groupe. Je suis un peu perdu.

database postgresql ruby ruby-on-rails
2021-11-23 14:26:06
3

La meilleure réponse

1

Vous pouvez utiliser un latéral sous-requête qui peut être mieux décrit comme une boucle foreach dans SQL. Ils sont extrêmement performants de manière à sélectionner les colonnes à partir d'une seule corrélation enregistrement ou agrégats à partir d'un groupe d'enregistrements liés.

Pour chaque ligne dans les abonnements de la DB va sélectionner une seule ligne à partir de versions commandé par authorized_at:

SELECT "subscriptions".*,
       "latest_version"."authorized_at" AS current_version,
       "latest_version"."id" AS current_version_id -- could be very useful
FROM   "subscriptions" 
LATERAL
  (
     SELECT   "versions"."authorized_at", "versions"."id"
     FROM     "versions"
     WHERE    "versions"."subscription_id" = "subscriptions"."id" -- lateral reference
     AND      "versions"."plan_id" = "subscriptions"."plan_id"
     AND      "versions"."status" = 'processed'
     ORDER BY "versions"."authorized_at" ASC
     LIMIT 1
  ) latest_version ON TRUE

La création d'latérale rejoint dans ActiveRecord peut être fait soit avec des chaînes SQL ou Arel:

class Subscription < ApplicationRecord
  # Performs a lateral join and selects the 
  # authorized_at of the latest version 
  def self.with_current_version
    lateral = Version.arel_table.then do |v|
      v.project(
        v[:authorized_at],
        v[:id] # optional
      ).where(
        v[:subscription_id].eq(arel_table[:id])
          .and(v[:plan_id].eq(arel_table[:plan_id]) )
          .and(v[:status].eq('processed'))
      )
      .order(v[:authorized_at].asc)
      .take(1) # limit 1
      .lateral('latest_version ON TRUE')
    end
    lv = Arel::Table.new(:latest_version) # just a table alias
    select(
      *where(nil).arel.projections, # selects everything previously selected
      lv[:authorized_at].as("current_version"),
      lv[:id].as("current_version_id") # optional
    ).joins(lateral.to_sql)
  end
end

Si vous voulez juste de sélectionner l' id et current_version colonne vous devriez envisager d'utiliser arracher au lieu de sélectionner les modèles de base de données qui ne sont pas correctement hydraté.

2021-11-23 16:49:48
1

Vous pouvez utiliser DISTINCT ON pour filtrer les lignes, et de garder une seule par abonnement -- le premier de chaque groupe selon le ORDER BY la clause.

Par exemple:

select distinct on (s.id) s.id, v.authorized_at
from subscription s
join versions v on v.subscription_id = s.id and v.plan_id = s.plan_id
where v.status = 'processed'
order by s.id, v.authorized_at
2021-11-23 16:40:51

Je vous remercie. La requête semble fonctionner dans la console, mais quand j'essaie de l'utiliser dans mon point de vue comme un modèle de portée-je obtenir ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near "WHERE" LINE 6: ...s.id, v.authorized_at WHERE "sub...
pinkfloyd90

@pinkfloyd90 je ne suis pas expert sur ActiveRecord, mais peut-être que ça ne fonctionne pas parce que la vue est probablement pas mis à jour.
The Impaler
0

Ci-dessous le code qui va vous donner la versions où Version est plan_id est égale à l'Abonnement plan_id.

@versions = Version.joins("LEFT JOIN subscriptions ON subscriptions.plan_id = versions.plan_id")

Pour filtrer les enregistrements en Version status

@versions = Version.joins("LEFT JOIN subscriptions ON subscriptions.plan_id = versions.plan_id").where(status: "processed")

Pour filtrer les enregistrements en Version status et commande par authorized_at dans l'ordre croissant.

@versions = Version.joins("LEFT JOIN subscriptions ON subscriptions.plan_id = versions.plan_id").where(status: "processed").order(:authorized_at)

Pour filtrer les enregistrements en Version status et commande par authorized_at dans la descente de l'ordre.

@versions = Version.joins("LEFT JOIN subscriptions ON subscriptions.plan_id = versions.plan_id").where(status: "processed").order(authorized_at: :desc)

Espérons que cela fonctionne pour vous!

2021-11-23 14:33:48

Je pense que vous avez besoin de joindre sur subscriptions.id = versions.subscription_id ou vous obtiendrez des versions de ce résultat qui n'appartiennent pas à cet abonnement. Vous aussi vous n'avez même pas touché le cœur de la question qui est la sélection d'un agrégat.
max

Dans d'autres langues

Cette page est dans d'autres langues

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................