【问题标题】:Difference between queries generated by joins and includes由连接和包含生成的查询之间的区别
【发布时间】:2020-07-07 20:14:38
【问题描述】:

我有以下查询,它根据document 订购日期加载items

 items = ::Item.joins(:document).where(:documents => {:order_created_at => @start_date..@end_date, type: "Order"}).order(name: :asc)

这是生成的 SQL:

SELECT  `items`.*
    FROM  `items`
    INNER JOIN  `documents`  ON `documents`.`id` = `items`.`document_id`
    WHERE  `documents`.`order_created_at`
           BETWEEN '2020-06-01 05:00:00'
               AND '2020-07-08 04:59:59'
      AND  `documents`.`type` = 'Order'
    ORDER BY  `items`.`name` ASC

这是一个基本的内部连接,所以很明显我只得到与所选文档匹配的项目。我想优化这个查询,因为我引用了Document 模型中的值,例如item.document.created_at。为了做到这一点,我在上面的查询中将joins 替换为includes,这是新的查询:

items = ::Item.includes(:document).where(:documents => {:order_created_at => @start_date..@end_date, type: "Order"}).order(name: :asc)

这运行正常,但我不知道查询在逻辑上是否相同。如果您注意到,现在在文档表上与项目表有一个左外连接。我不完全明白如何解释。我正在尝试查询与连接时完全相同的项目,但出于优化原因使用包含方法。以下是查询结果:

SELECT "items"."id" AS t0_r0, "items"."name" AS t0_r1, "items"."description" AS t0_r2, 
"items"."shipping" AS t0_r3, "items"."sku" AS t0_r4, "items"."eta" AS t0_
r5, "items"."warranty" AS t0_r6, "items"."part_number" AS t0_r7, "items"."status" AS t0_r8, 
"items"."url_key" AS t0_r9, "items"."allow_rma" AS t0_r10, "items"."price" AS t0_r11,
"items"."meta_title" AS t0_r12, "items"."meta_keywords" AS t0_r13, 
"items"."meta_description" AS t0_r14, "items"."image" AS t0_r15, "items"."thumbnail" AS 
t0_r16, "items"."popularity" AS t0_r17, "items"."purchased_count" AS t0_r18, 
"items"."quantity" AS t0_r19, "items"."weight" AS t0_r20, "items"."created_at" AS t0_r21, 
"items"."updated_at" AS t0_r22,
"items"."document_id" AS t0_r23, "items"."product_id" AS t0_r24, "items"."total" AS t0_r25, 
"items"."warehouse_name" AS t0_r26, "items"."quantity_available" AS t0_r27, "items".
"quantity_to_return" AS t0_r28, "items"."reason_to_return" AS t0_r29, 
"items"."item_condition" AS t0_r30, "items"."resolution" AS t0_r31, "items"."brand" AS 
t0_r32, "items"."category" AS t0_r33, "items"."mod_name" AS t0_r34, "items"."product_type" 
AS t0_r35, "items"."search_variations" AS t0_r36, "items"."model_number" AS t0_r37, 
"items"."quantity_returned" AS t0_r38, "items"."line_number" AS t0_r39, 
"items"."external_id" AS t0_r40, "documents"."id" AS t1_r0, "documents"."status" AS t1_r1, 
"documents"."subtotal" AS t1_r2, "documents"."shipping" AS t1_r3, "documents"."handling" AS 
t1_r4, "documents"."grand_total" AS t1_r5, "documents"."total_paid" AS t1_r6, 
"documents"."total_refunded" AS t1_r7, "documents"."total_due" AS t1_r8, 
"documents"."order_prefix" AS t1_r9, "documents"."order_postfix" AS t1_r10, 
"documents"."shipping_method" AS t1_r11, "documents"."status_code" AS t1_r12, 
"documents"."created_at" AS t1_r13, "documents"."updated_at" AS t1_r14, 
"documents"."user_id" AS t1_r15, "documents"."downloaded_by_shipworks" AS t1_r16, 
"documents"."transaction_id" AS t1_r17, "documents"."settled" AS t1_r18, 
"documents"."voided" AS t1_r19, "documents"."voided_date" AS t1_r20, 
"documents"."voided_transaction_id" AS t1_r21, "documents"."tax" AS t1_r22, 
"documents"."payment_gateway" AS t1_r23, "documents"."admin_id" AS t1_r24, 
"documents"."purchase_order_number" AS t1_r25, "documents"."loaded_success_page" AS t1_r26, 
"documents"."discount_amount" AS t1_r27, "documents"."type" AS t1_r28, "documents"."token" 
AS t1_r29, "documents"."shipping_method_id" AS t1_r30, "documents"."document_id" AS t1_r31, 
"documents"."pay_without_credit_card" AS t1_r32, "documents"."order_created_at" AS t1_r33, 
"documents"."discount_id" AS t1_r34, "documents"."submitted_from_admin" AS t1_r35, 
"documents"."order_number" AS t1_r36, "documents"."marketplace" AS t1_r37, 
"documents"."amazon_prime" AS t1_r38, "documents"."discount_code" AS t1_r39, 
"documents"."discount_applied" AS t1_r40, "documents"."fulfillment_channel" AS t1_r41, 
"documents"."submitted_from_cron" AS t1_r42, "documents"."shipping_discount" AS t1_r43, 
"documents"."part_discount" AS t1_r44, "documents"."last_downloaded_by_fulfillment" AS 
t1_r45 FROM "items" LEFT OUTER JOIN "documents" ON "documents"."id" = "items"."document_id" 
WHERE "documents"."order_created_at" BETWEEN '2020-06-01 05:00:00' AND '2020-07-08 04:59:59' 
AND "documents"."type" = 'Order' ORDER BY "items"."name" ASC

【问题讨论】:

  • 由于您要过滤documents 中的列,因此LEFT 是不合适的。

标签: mysql sql ruby-on-rails ruby ruby-on-rails-6


【解决方案1】:

请务必注意,includesjoins 有两个不同的用例。它们有时可能会生成类似的查询,但在其他情况下,它们会生成完全不同的查询。

joins 一方面生成INNER JOIN 数据库查询。可用于查询连接表上带有条件的记录。 joins 不会急切加载相关记录。

includes 用例另一方面,是急切加载关联记录并避免 N+1 查询。 Rails 可能会使用数据库连接来一次性加载记录及其相关记录。 或者它可能会触发两个查询来获取所有需要的记录。不保证会一直做数据库JOIN

也就是说:当您关心预先加载相关记录时,请使用includes。当您的查询依赖于数据库 INNER JOIN 时,请使用 joins。尽管在某些情况下看起来像,但它们不可互换。

【讨论】:

  • 所以,我依赖INNER JOIN 并且我确实关心急切加载。由于includes 不一致,有没有办法使用INNER JOIN 进行预加载?
  • 只需同时使用joinsincludes,并允许Rails 在内部发挥它的魔力——比如Item.joins(:document).includes(:document).where(...)
【解决方案2】:

因为您还过滤了文档的字段,所以LEFT OUTER JOIN 的效果是多余的。

之所以存在,是因为includes 的目的是不会导致item 被排除在外,即使它没有文档。

如果item 没有document,则where 将导致它被排除。

  • 因为order_created_at 将是NULL
  • 并且NULL 不在开始日期和结束日期之间

因此,您实际上又拥有了一个inner join

【讨论】:

    【解决方案3】:
    documents:  INDEX(type, order_created_at)  -- in that order
    items:  INDEX(document_id)
    

    虚假的“LEFT”可能是生成查询的包的草率。它使读者感到困惑,但对执行无害。 MySQL 的优化器将看到documents 不是可选的(由于WHERE 子句)并忽略LEFT

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-06-15
      • 2014-03-29
      • 1970-01-01
      • 1970-01-01
      • 2016-06-14
      • 2016-09-03
      • 1970-01-01
      相关资源
      最近更新 更多