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

时间:2015-11-28 01:20:43

标签: sql ruby-on-rails ruby postgresql

我在Professor上有3个范围涉及加入Professor::AvailabilityPeriod,这是一个代表教授可用性的模型,可以在一周的给定时间段内提供课程。这是通过将每个周期的开始和结束时间的表示存储为从周开始的秒开始实现的,我称之为starts_at_sfsowends_at_sfsow

Professor::AvailabilityPeriod有两个范围,overlapping(start_time, end_time)contained_within(start_time, end_time)

我在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

然后,我有一个第四个范围,总结所有其他,这是我将用于搜索控制器中的教授(工作正常):

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

问题:我需要available_at返回的记录首先出现,然后available_around,最后出现partially_available_at,依次出现,我还需要保持纯SQL,所以我可以稍后应用分页;绝对没有内存排序。

我一直在考虑使用子查询,因此我可以对每个范围执行某种优先级分配,例如:

 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")

...但这是不可能的,因为postgres不允许我为每个子查询选择多于1个字段,这不是事件有效的语法。

BTW:如果您对使用or方法感到困惑,where-or gem附带了Rails 5功能的后端。

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)
)

1 个答案:

答案 0 :(得分:0)

一直在寻求一些同事的帮助,似乎我一直需要的是UNION ALL。这允许我为每个子查询设置<number> AS priority,然后ORDER BY priority。这正是我需要的!

<强> 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

函数嵌套看起来有点尴尬,因为它是由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