简体   繁体   English

SQL:使用OR链接排序范围(Ruby on Rails)

[英]SQL: sort scopes chained with OR (Ruby on Rails)

I have 3 scopes on Professor that involves joining Professor::AvailabilityPeriod s, a model that represents professor availability to give classes at a given period of the week. 关于Professor ,我有3个范围,涉及加入Professor::AvailabilityPeriod ,该模型表示教授在一周的给定时段内上课的可用性。 This is achieved by storing the representation of the start and end time of each period as seconds from start of week, which I called starts_at_sfsow , and ends_at_sfsow . 这是通过将每个时间段的开始和结束时间的表示形式存储为从星期开始的秒数来实现的,我将其称为starts_at_sfsowends_at_sfsow

Professor::AvailabilityPeriod has 2 scopes, overlapping(start_time, end_time) and contained_within(start_time, end_time) . Professor::AvailabilityPeriod具有2个范围,即overlapping(start_time, end_time)overlapping(start_time, end_time) contained_within(start_time, end_time)

I have 3 scopes on Professor : 我对Professor有3个范围:

class Professor < ActiveRecord::Base
  scope :available_at, -> (opts) do
    start_time = opts.fetch(:start_time)
    end_time = opts.fetch(:end_time)

    includes(:availability_periods).
    joins(:availability_periods).merge(
      self::AvailabilityPeriod.overlaping(
        start_time: start_time,
        end_time: end_time,
      )
    )
  end

  scope :available_around, ->(opts) do
    start_time = opts.fetch(:start_time)
    end_time = opts.fetch(:end_time)

    available_at(
      start_time: start_time - 1.hour,
      end_time: start_time
    ).or available_at(
      start_time: end_time,
      end_time: end_time + 1.hour
    )
  end

  scope :partially_available_at, ->(opts) do
    start_time = opts.fetch(:start_time)
    end_time = opts.fetch(:end_time)

    includes(:availability_periods).
    joins(:availability_periods).merge(
      self::AvailabilityPeriod.contained_within(
        start_time: start_time,
        end_time: end_time,
      )
    )
  end
end

Then, I have a 4th scope that sums all the others, which is what I will use to search for professors in the controller (works fine): 然后,我有了第四个范围,将所有其他范围加起来,这就是我将用于在控制器中搜索教授的功能(工作正常):

  scope :available_at_or_around_or_partially, -> (opts) do
    distinct.
      available_at(opts).
      or(available_around(opts)).
      or(partially_available_at(opts))
  end

The problem: I need records returned by available_at to appear first, then available_around , and finally partially_available_at , in that order, and I also need to keep it pure SQL, so I can apply pagination later ; 问题:我需要用available_at返回的记录按顺序出现,然后是available_around ,最后是partially_available_at ,并且还需要保持纯SQL,以便以后可以应用分页 absolutely no in memory sorting. 绝对没有内存排序。

I been thinking on using subqueries so I can do some kind of priority assignment to each scope like: 我一直在考虑使用子查询,因此我可以对每个范围进行某种优先级分配,例如:

 distinct.
 where(id: available_at(opts).select("id, 1 AS priority")).   
 or(
   where(id: available_around(opts).select("id, 2 AS priority"))
 ). 
 or(
   where id: partially_available_at(opts).select("id, 3 AS priority")
 )
 order("priority, id")

... but this is not possible as postgres doesn't allow me to select more than 1 field per sub query, and that isn't event valid syntax. ...但是这是不可能的,因为postgres不允许我为每个子查询选择多个字段,而且这不是事件有效的语法。

BTW: if you are getting confused by the use of or method, that comes with where-or gem , a backport of Rails 5 functionality. 顺便说一句:如果您对or方法的使用感到困惑,那么它附带了where-or gem ,这是Rails 5功能的反向端口。

SQL : SQL

Professor.available_at_or_around_or_partially start_time: Time.now, end_time: Time.now.+(2.hours)

SELECT     "professors".*
INNER JOIN "professor_availability_periods"
ON         "professor_availability_periods"."professor_id" = "professors"."id"
WHERE
(
  (
    /* available_at */
    ("professor_availability_periods"."starts_at_sfsow" <= 446340) /* start_time as seconds from start of week */
    AND ("professor_availability_periods"."ends_at_sfsow" >= 453540) /* end time as seconds from start of week*/

    /* available_around */
    OR (
      ("professor_availability_periods"."starts_at_sfsow" <= 442740)
      AND ("professor_availability_periods"."ends_at_sfsow" >= 446340)

      OR

      ("professor_availability_periods"."starts_at_sfsow" <= 453540)
      AND ("professor_availability_periods"."ends_at_sfsow" >= 457140)
    )
  )

  /* partially_available_at */
  OR ("professor_availability_periods"."starts_at_sfsow" >= 446340)
  AND ("professor_availability_periods"."ends_at_sfsow" <= 453540)
)

Been seeking the help of some colleagues and it seems that what I needed all this time was UNION ALL . 在寻求一些同事的帮助时,似乎我一直需要的是UNION ALL This allows me to set <number> AS priority for each subquery and then ORDER BY priority . 这使我可以为每个子查询设置<number> AS priority ,然后设置ORDER BY priority That's exactly what I need! 那正是我所需要的!

SQL : SQL

SELECT DISTINCT "professors".* FROM (
  (SELECT "professors".* FROM
    (
      (SELECT *, 1 AS priority FROM "professors"
        INNER JOIN "professor_availability_periods"
        ON "professor_availability_periods"."professor_id" = "professors"."id"
        WHERE
          ("professor_availability_periods"."starts_at_sfsow" <= 455580)
          AND ("professor_availability_periods"."ends_at_sfsow" >= 462780)
      ) UNION ALL (
        SELECT *, 2 AS priority FROM "professors"
        INNER JOIN "professor_availability_periods"
        ON "professor_availability_periods"."professor_id" = "professors"."id"
        WHERE (
          ("professor_availability_periods"."starts_at_sfsow" <= 451980)
          AND ("professor_availability_periods"."ends_at_sfsow" >= 455580)
          OR
          ("professor_availability_periods"."starts_at_sfsow" <= 462780)
          AND ("professor_availability_periods"."ends_at_sfsow" >= 466380)
        )
      )
    ) professors
  ) UNION ALL (
    SELECT *, 3 AS priority FROM "professors"
    INNER JOIN "professor_availability_periods"
    ON "professor_availability_periods"."professor_id" = "professors"."id"
    WHERE
      ("professor_availability_periods"."starts_at_sfsow" >= 455580)
      AND ("professor_availability_periods"."ends_at_sfsow" <= 462780)
  )
) professors
ORDER BY priority

The function nesting looks a little awkward because it's generated by active_record_union , which in turn let's me perform this query through ActiveRecord as: 函数嵌套看起来有点尴尬,因为它是由active_record_union生成的,这又让我通过ActiveRecord执行以下查询:

scope :search_by_availability, -> (opts) do
  available_at(opts).select("*", "1 AS priority").
  union_all(available_around(opts).select("*", "2 AS priority")).
  union_all(partially_available_at(opts).select("*", "3 AS priority")).
  order("priority").distinct(:id)
end

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM