【问题标题】:Grouping Query with Has Many Relations in Rails在 Rails 中对具有许多关系的查询进行分组
【发布时间】:2017-10-09 16:35:07
【问题描述】:

我使用分类模型在 Projets 和类别之间建立了has_many, through: 关系。一个项目belongs_to一个客户。

class Client < ApplicationRecord
  has_many :projets
end

class Category < ApplicationRecord
  has_many :categorizations, dependent: :destroy
  has_many :projets, through: :categorizations
end

class Categorization < ApplicationRecord
  belongs_to :category
  belongs_to :projet
end

class Projet < ApplicationRecord
  belongs_to :client
  has_many :categorizations, dependent: :destroy
  has_many :categories, through: :categorizations
end

对于特定类别,我想列出所有项目,按客户分组。例如

(对于 category_id = 3)

客户 A 项目 1 项目 2

客户 B 项目 3

客户 C 项目 4

到目前为止,我可以做到这一点,但只能通过使用两个查询(其中一个非常低效(n+1 问题)。

这是代码

def listing
  @projets_clients = Projet
    .select("client_id")
    .includes(:client)
    .joins(:categorizations)
    .where(categorizations: { category: @category })
    .group("client_id")

  @clients = []
  @projets_clients.each do |p|
    @clients << Client.includes(:projets).find(p.client_id)
  end
end

如果有人可以提出更好的方法,我很想学习如何优化它,因为我自己还没有找到更好的方法。

谢谢。

【问题讨论】:

    标签: sql ruby-on-rails activerecord rails-activerecord


    【解决方案1】:

    有几种不同的方法可以做到这一点。对于复杂的查询,我有时会发现编写和执行直接 SQL 更容易。但是,对于您的情况,根据数据大小,您可以直接加载数据并将其转换为哈希。

    注意:当我测试这段代码时,我使用了projects 而不是projets

    @category = Category.includes(projects: [:client]).find(2)
    @projects_by_client = @category.projects.group_by(&:client_id)
    
    # In your view
    <%- @projects_by_client.each do |client_id, projects| %>
      <%= projects.first.client.name %>
      <%- projects.each do |project| %>
        <%= project.name %>
      <% end %>
    <% end %>
    

    更充实的解决方案可能会使用带有查询对象和演示者对象的完整 sql。我使用以下代码启动了一个快速项目,输出就是您要查找的内容。

    # app/controllers/clients_controller.rb
    class ClientsController < ApplicationController
      def show
        result = ClientQuery.call(params[:id])
        @presenter = ClientPresenter.new(result)
      end
    end
    
    # app/services/client_query.rb
    class ClientQuery
      class << self
        def call(client_id)
          sql_query(client_id)
        end
    
        protected
    
        def sql_query(client_id)
          ActiveRecord::Base.
            connection.
            execute(
              sanitized_sql_statement(client_id)
            )
        end
    
        def sanitized_sql_statement(client_id)
          ActiveRecord::Base.send(
            :sanitize_sql_array,
            [
              sql_statement,
              client_id
            ]
          )
        end
    
        def sql_statement
          <<-SQL
            SELECT
              c.id AS client_id,
              c.name AS client_name,
              p.name AS project_name
            FROM
              clients c
            INNER JOIN
              projects p ON p.client_id = c.id
            INNER JOIN
              categorizations cz ON cz.project_id = p.id
            INNER JOIN
              categories ct ON ct.id = cz.category_id
            WHERE
              ct.id = ?;
          SQL
        end
      end
    end
    
    # app/presenters/client_presenter.rb
    class ClientPresenter
      attr_reader :clients
    
      def initialize(data)
        @clients = {}
        process_sql_result(data)
      end
    
      private
    
      def process_sql_result(data)
        data.each do |row|
          client_id = row['client_id']
    
          @clients[client_id] ||= { client_name: row['client_name'] }
          @clients[client_id][:projects] ||= []
          @clients[client_id][:projects] << row['project_name']
        end
      end
    end
    
    
    # app/views/show.html.erb
    <%- @presenter.clients.each do |client_id, client_presenter| %>
      <h1><%= client_presenter[:client_name] %></h1>
      <ul>
      <%- client_presenter[:projects].each do |project_name| %>
        <li><%= project_name %></li>
      <% end %>
      </ul>
    <% end %>
    

    这当然只是您可以在单个查询中获取数据并呈现数据的众多方法之一。

    【讨论】:

    • 哇,非常感谢您的详细回答! SQL 方法非常有趣。很抱歉与“项目”(法语)混淆。您的第一个示例中的代码虽然有一个小错误,但感谢您的慷慨帮助(如何嵌套包含!)我想我知道它是什么:@projects_by_client = @category.projects.group_by(&:client_id)(需要也添加 .projects - 我认为 '@clients =' 行也是无关紧要的)也许对其他人进行更新会很有用,然后我可以将其标记为正确答案?现在完美运行,非常感谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-21
    • 1970-01-01
    相关资源
    最近更新 更多