Rails高级查询可能有多个联接

时间:2017-06-11 04:08:01

标签: sql ruby-on-rails postgresql join

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

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

  1. 搜索字词是裁剪的直接名称(CropCategory),查询应返回所有与搜索字词匹配的类别名称的捐赠。
  2. 搜索字词是一个类别(不是特定作物),查询应返回所有捐赠,其类别名称与搜索字词匹配,所有捐赠的类别名称与任何名称相匹配搜索词指定类别内的作物。
  3. 搜索字词与作物或类别不匹配它不返回任何内容。 ERB of the Database
  4. 目前,如果搜索字词是'玉米,我可以毫无问题地处理案例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?
    

1 个答案:

答案 0 :(得分:1)

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

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>


<nav class="navbar navbar-inverse bg-inverse navbar-toggleable-sm bg-faded navbar-fixed-top">
  <div class="container">
    <button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>
    <a id="header-initials" class="navbar-brand" href="home">JT</a>

    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav mr-auto">
        <li class="nav-item active">
          <a class="nav-link" href="#home">home</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#skills">skills</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#about">about</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#work">work</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#contact">contact</a>
        </li>
      </ul>
    </div>
  </div>
</nav>

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

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%'

上面的JOIN将生成一个预先过滤的集合,看起来像这样,为了更容易表示而缩小:

    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

正如您所看到的,每个捐赠ID都显示在捐赠类别下的所有作物旁边(请注意,此时,我不确定我的数据模型是对还是错。无论哪种方式,我所描述的原则都应该帮助你和其他人。因此,如果我们按绿叶过滤,我们会收回捐款1和2.如果我们按羽衣甘蓝过滤,我们会收回捐款1,2和3

希望一切都有意义,我们可以进入最后一步 - 将SQL转换为ActiveRecord。这很直接:

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

Donations .joins(category: :crops) .where('categories.name LIKE :term OR crops.name LIKE :term', term: "%#{search_term}%") 可能需要join或其他内容,具体取决于您的关系设置方式。我们的想法是遍历您定义的关系,以构建如上所述的连接。

<小时/> 编辑:根据OP在评论中的澄清,数据模型还包含一个隐含的类别层次结构,从作物推断出来 - &gt;类别关系,但用作物名称替换同名类别。隐含关系,需要额外的逻辑,但数据库设计不明显,可能很难查询 - 通常表明数据模型可能需要更新。但是,让我们从更新查询开始。

我们还需要包含“属于”搜索类别的类别,因此我们需要额外的加入。由于并非每个类别都有父类别,因此它应该是外部联接。然而,加入标准会很奇怪。我的第一个想法是创建一个临时表,将类别连接到其父类别名称,然后将其加入到该表中。所以查询变为:

category: { categories_crops: :crops })

转换为Rails:

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%'