【问题标题】:Rails Advanced Query Possibly With Multiple JoinsRails 高级查询可能具有多个连接
【发布时间】:2017-06-11 04:08:01
【问题描述】:

我正在尝试执行允许按类别搜索的高级查询(请参阅下面的 ERB)。

搜索词将是一个字符串。基本上有三种情况:

  1. 搜索词是作物的直接名称(CropCategory),查询应返回类别名称与搜索词匹配的所有捐赠。
  2. 搜索词是一个类别(不是特定作物),查询应返回类别名称与搜索词匹配的所有捐赠,以及类别名称与搜索范围内任何作物名称匹配的所有捐赠由搜索词指定的类别。
  3. 搜索词与作物或类别不匹配它不返回任何内容。

目前,如果搜索词是“玉米”,例如返回“玉米”类别的所有条目,我可以毫无问题地处理案例 1。

案例 2 仅部分处理。如果搜索词是“绿叶蔬菜”,则返回“绿叶蔬菜”类别的所有条目,但是,尽管属于生菜的 CategoryCrop 也属于“绿叶蔬菜”类别,但不会出现“生菜”或“羽衣甘蓝”类别的条目

查询代码如下:

donations = Donation.all.order("date DESC")
donations = donations.joins(:category).includes(:category)
donations = donations.where("name like ?", "%#{search_term}%").references(:categories) if search_term.present?

【问题讨论】:

    标签: sql ruby-on-rails postgresql join


    【解决方案1】:

    您想要做的是将所有捐赠加入他们的类别和与该类别相关的作物,然后过滤与给定字符串匹配的类别名称或作物名称。让我们从 SQL 中开始:

    SELECT d.*
    FROM donations d
    JOIN categories ca       ON ca.id = d.category_id
    JOIN categories_crops cc ON cc.category_id = ca.id
    JOIN crops cr            ON cr.id = cc.crop_id
    WHERE
      ca.name LIKE 'foo%'
    OR
      cr.name LIKE 'foo%'
    

    所以假设您的数据集如下所示:

        donations           categories           crops        categories_crops 
    ----------------   ------------------   ------------   ---------------------
    id | category_id   id | name            id | name      crop_id | category_id
    ================   ==================   ============   =====================
    1  | 1             1  | leafy greens    1 | lettuce    1       | 1
    2  | 1             2  | dark greens     2 | broccoli   2       | 2
    3  | 2                                  3 | kale       3       | 1
                                                           3       | 2
    

    上面的 JOIN 将生成一个看起来像这样的预过滤集,为了更容易表示而进行了精简:

    donations X categories X categories_crops X crops
    -------------------------------------------------
    donation_id | category.name | crops.name
    =================================================
    1           | leafy greens  | lettuce
    1           | leafy greens  | kale
    2           | leafy greens  | lettuce
    2           | leafy greens  | kale
    3           | dark greens   | broccoli
    3           | dark greens   | kale
    

    如您所见,每个捐赠 ID 显示在其捐赠类别下的所有作物旁边(请注意,此时,我不确定您的数据模型是否正确。无论哪种方式,我所描述的原则都应该对您和其他人有所帮助)。因此,如果我们按 leafy 过滤,我们将取回捐赠 1 和 2。如果我们按 kale 过滤,我们将取回捐赠 1、2 和 3 .

    希望一切都说得通,我们可以进入最后一步——将 SQL 转换为 ActiveRecord。很直接:

    Donations
      .joins(category: :crops)
      .where('categories.name LIKE :term OR crops.name LIKE :term', term: "%#{search_term}%")
    

    join 可能需要为category: { categories_crops: :crops }) 或其他名称,具体取决于您的关系是如何设置的。这个想法是遍历您定义的关系以构建如上所述的连接。


    编辑: 根据 OP 在 cmets 中的说明,数据模型还包含隐含的类别层次结构,从作物 -> 类别关系推断,但用作物名称替换同名类别。隐式关系,需要额外的逻辑,但在数据库设计中并不明显,可能很难查询——这通常表明数据模型可能需要更新。但让我们从更新查询开始。

    我们还需要包含“属于”正在搜索的类别的类别,因此我们需要一个额外的连接。由于不是每个类别都有父类别,因此应该是外连接。然而,加入标准会很奇怪。我的第一个想法是创建一个临时表,将类别与其父类别名称连接起来,然后将连接留在上面。于是查询变为:

    SELECT d.*
    FROM donations d
    JOIN categories ca       ON ca.id = d.category_id
    JOIN categories_crops cc ON cc.category_id = ca.id
    JOIN crops cr            ON cr.id = cc.crop_id
    LEFT JOIN (
      SELECT ca2.name as parent_category_name, ca3.id as child_category_id
      FROM categories_crops cc2
      JOIN categories ca2 ON ca2.id = cc2.category_id
      JOIN crops cr2 ON cr2.id = cc2.crop_id
      JOIN categories ca3 ON ca3.name = cr2.name
    ) cp
    ON cp.child_category_id = ca.id
    
    WHERE
      ca.name LIKE 'foo%'
    OR
      cr.name LIKE 'foo%'
    OR
      cp.parent_category_name LIKE 'foo%'
    

    翻译成 Rails:

    Donations
      .joins(category: :crops)
      .joins(<<-SQL)
        LEFT JOIN (
          SELECT ca2.name as parent_category_name, ca3.category_id as child_category_id
          FROM categories_crops cc2
          JOIN categories ca2 ON ca2.id = cc2.category_id
          JOIN crops cr2 ON cr2.id = cc2.crop_id
          JOIN categories ca3 ON ca3.name = cr2.name
        ) cp ON cp.child_category_id = categories.id
      SQL
      .where('categories.name LIKE :term OR crops.name LIKE :term OR cp.parent_category_name LIKE :term', term: "%#{search_term}%")
    

    【讨论】:

    • 感谢您的回复!如果搜索词是 kale 我会同时获得 kaleleafy greens 的条目,但如果该词是 leafy greens,这似乎会有所帮助 我仍然只得到 绿叶 条目。
    • @Bevilacqua 您能否详细说明您希望在这种情况下得到什么?我不确定为什么搜索 leafy greens 应该返回超过 lettucekale
    • 这实际上正是我希望它返回的,但目前它只返回用绿叶蔬菜明确标记的条目。前任。如果三个条目在捐赠类别中:[生菜]、[羽衣甘蓝]、[绿叶蔬菜]。我只会得到绿叶的条目。
    • 羽衣甘蓝和莴苣不都是属于类别的作物,而不是属于类别本身吗?我想我遗漏了一些关于你的数据模型的东西。我的答案中的示例数据是否假设不正确?
    • 将该引用更改为 ca3.id;我不小心在主键前面加上了表名,这不是 Rails 的默认行为(但许多 DBA 更喜欢)。
    猜你喜欢
    • 2012-07-10
    • 1970-01-01
    • 1970-01-01
    • 2013-08-17
    • 2022-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多