【问题标题】:Rails - order on column's values (priority column)Rails - 列值的顺序(优先列)
【发布时间】:2015-04-21 14:43:35
【问题描述】:

我有一个数组priority = ['HIGH', 'MEDIUM', 'LOW'],用于设置“紧急”数据库列。我想检索按优先级排序的数据,尽管应用 Task.order(:urgency) 按字母顺序返回结果(即 HIGH、LOW、MEDIUM)。

我正在使用 PostgreSQL 作为数据库。

我(显然)希望这些从高优先级返回到低优先级。有没有一种简单的方法来实现这一点,也许使用数组中值的位置?

【问题讨论】:

  • 您使用的是什么数据库系统? MySQL ? PostgreSQL ?还有什么?
  • PostgreSQL - 对不起,应该提到。马上更新!
  • 这并不能直接回答您的订购问题,但对于“紧急”或“状态”,如果您使用的是 Rails 4.1+,请考虑使用 Active Record 枚举(Release NotesDocumentation) .优点是它免费为您提供范围,您可以按照您需要的顺序放置“紧急度”(实际的 db 列是一个整数) - 您可以根据它进行排序,它的执行速度应该比 SQL 快得多案例声明。如果需要,我可以提供一个完整的例子。

标签: sql ruby-on-rails postgresql


【解决方案1】:

一个简单的CASE WHEN 就可以解决问题(这里使用的postgreSQL 语法):

scope :urgency_ordered {
  order(<<-SQL)
    CASE tasks.urgency 
    WHEN 'HIGH' THEN 'a' 
    WHEN 'MEDIUM' THEN 'b' 
    WHEN 'LOW' THEN 'c' 
    ELSE 'z' 
    END ASC, 
    id ASC
  SQL
}

这样称呼:

Task.urgency_ordered

【讨论】:

  • 很有魅力 - 感谢 Yoshiji 先生!我不得不将我的作用域包装在一个 lambda 中,例如 scope :urgency_ordered, -&gt;{order("CASE tasks.urgency WHEN 'HIGH' THEN 'a' WHEN 'MEDIUM' THEN 'b' WHEN 'LOW' THEN 'c' ELSE 'z' END ASC, id ASC")}(以防其他人需要以相同的方式使用它),但它完美地完成了这项工作!
  • 您不需要使用 lambda :仅当您的范围内需要动态值时,才需要使用 lambda,例如 Date.todayuser 参数(因为范围取决于变量)。在这里,在您的情况下, order 子句不依赖于变量,它始终与 Task 的排序方式相同。
  • 啊,很有道理。我进行更改的原因是没有它我会得到一个The scope body needs to be callable. 错误,并且有点谷歌搜索告诉我 lambda 会修复它。在这种情况下确实如此-尽管您的理由对我来说很有意义。为了我自己的好奇心,你知道为什么会这样吗? (不用担心,毕竟我的问题解决了!)
  • 您使用的是 Rails 4 吗?可能已添加范围需要可调用,我不太确定...
  • 是的,Rails 4 - 可能就是这样。感谢所有的帮助,非常感谢。
【解决方案2】:

我迟到了,但我很惊讶还没有人提出这个答案:如果您使用的是 MySQL(未经测试,但也应该适用于 Postgres),有一种更短、更安全、更通用的方法之前的任何答案。它适用于任何字段并防止 sql 注入,因此您可以从用户传递任何值列表。

将以下范围添加到您的模型或 ApplicationRecord:

class Task < ActiveRecord::Base
  scope :order_by_field, ->(field, values) {
    order(sanitize_sql_for_order(["field(#{field}, ?)", values]))
  }
end

您现在可以直接在您的关系上调用范围:

tasks.ordered_by_field(:priority, ["high", "medium", "low"])

【讨论】:

    【解决方案3】:

    如果使用 Rails 4.1+,请考虑使用 Active Record 枚举(Release NotesDocumentation)作为 DB 无关解决方案:

    class Task < ActiveRecord::Base
      enum priority: [ :high, :medium, :low ] 
      # Or enum priority: { high: 0, medium: 1, low: 2 }
    
      scope :priority_order, ->{ order(:priority) }
      scope :reverse_priority_order, ->{ order(priority: :desc) }
    end
    

    这将需要一个新的整数列作为优先级,并将您拥有的文本数据复制/移动到新的整数列,这可以在迁移中完成。然后调用Task.priority_order 应该可以解决问题。

    【讨论】:

    • 这看起来是一个很好的解决方案。如果我可以标记两个答案...感谢您的意见,我以后一定会记住这一点!
    • Np,Yoshiji 先生的回答很好——它应该适用于任何版本的 Rails,并且不需要更改模型代码和/或视图的其他部分。如果您有幸使用 4.1+,我喜欢使用枚举功能。如果您将来确实决定采用这条路线,我可以发布迁移 - 我只需要有关您现有代码的更多详细信息(以免破坏现有功能)。
    【解决方案4】:

    已经很晚了,但这里有我的 2cents。 (导轨 4.2.1)

    如果你像这样直接使用解决方案:

    class Task < ActiveRecord::Base
        enum priority: { high: 0, medium: 1, low: 2 }
        scope :priority_order, order(:priority)
    end
    

    你会得到这个错误:The scope body needs to be callable.

    您需要像这样调用范围。

    scope :priority_order, -> {
        order(:priority)
    }
    

    将其定义为 ProcLambda

    为什么,你可能会问:)

    它确保每次触发范围时都使用块中的内容。

    【讨论】:

    • 很好,如果我建议 Rails 4.1+ 功能,我想我应该提供兼容 Rails 4.0+ 的范围!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-01
    • 2019-09-18
    相关资源
    最近更新 更多