ruby-on-rails - 将记录加入 Rails 范围的最佳方法是什么?
问题描述
我正在开发一个 Ruby on Rails 应用程序(尽管这实际上更像是一个数据结构问题),我拥有Posts
、Books
和Chapters
作为模型。假设 a 希望能够引用 a 中的多个章节,Post
并且以后能够通过他们引用的章节和书籍来过滤帖子。以以后易于查询的方式将这些记录连接在一起的最佳方法是什么?
我的第一个想法是典型的has_many :through
联想。
class Post < ApplicationRecord
has_many :post_chapters
has_many :chapters, through: :post_chapters
end
class PostChapter < ApplicationRecord
belongs_to :post
belongs_to :chapter
end
class Chapter < ApplicationRecord
belongs_to :book
has_many :post_chapters
has_many :posts, through: :post_chapters
end
class Book < ApplicationRecord
has_many :chapters
end
如果我只需要存储对几章的引用,这将非常有效。我最终会PostChapter
为每章引用一个额外的记录。但是如果有人引用第 1 章到第 1000 章会发生什么?然后应用程序需要创建 1000 条记录才能判断第 X 章是否包含在参考中。
有没有办法将它存储为某种范围连接,它只存储第一章和最后一章,但以后仍然很容易查询?
如果有帮助的话,我正在使用 PostgreSQL。
解决方案
正如@beartech 所指出的,您对数据库大小的担忧可能完全没有根据,这很可能只是过早优化的情况。
但要回答实际问题,有几种方法可以在 Postgres 中存储范围。第一种“经典”多语言方式是使用两列,然后使用:
Post.where("? BETWEEN posts.starting_chaper AND posts.ending_chapter", 99)
因为这只是普通的 SQL,它可以在任何关系数据库上工作。
Postgres 还有一系列原生范围类型(双关语):
- int4range - 整数范围
- int8range - bigint 的范围
- numrange - 数值范围
- tsrange - 没有时区的时间戳范围
- tstzrange - 带有时区的时间戳范围
- daterange - 日期范围
这些只是内置类型。
在 ActiveRecord 中并没有真正支持原生范围,但您可以使用Rails 5中引入的属性 API来处理类型转换。
class Chapter < ApplicationRecord
attribute :page_range, range: true
end
这里的一个巨大优势是当涉及到查询时,因为 PG 知道该列实际上是一个范围,并且与以前的解决方案相比可以创建一个非常有效的查询计划。
在这里使用 JSON 或数组类型是非常有问题的,因为您失去了关系模型的所有好处并且没有范围列的好处。如果一个模型有多个范围,我会创建一个单独的连接表。
class Post < ApplicationRecord
has_many :post_chapters
has_many :chapter_ranges
has_many :chapters, through: :post_chapters
end
class ChapterRange
belongs_to :post
attribute :chapters, range: true
end
# Check if one chapter is contained in range:
Post.joins(:chapter_ranges)
.where("? @> chapter_ranges.chapters" 10)
# range is contained by
Post.joins(:chapter_ranges)
.where("int4range(?, ?) @> chapter_ranges.chapters" 2, 4)
# overlap
Post.joins(:chapter_ranges)
.where("int4range(?, ?) && chapter_ranges.chapters" 2, 4)
推荐阅读
- css - 角度材料垂直步进器分离标签和内容部分
- node.js - SyntaxError: Unexpected identifier > let articleGuids = {};
- c++ - IF 语句不通过
- ubuntu - Ubuntu 18.04 - 防火墙
- php - 如何在woocommerce中的子类别存档中显示父类别的产品?
- javascript - 当 Provider 重新渲染时,Consumer 重新渲染
- sql-server-2017 - Sql server 数据库存储过程查询
- django - Django 邮件无法将邮件发送到 icloud 邮件 ID
- c# - 成功插入数据库后抛出消息,如果不成功则抛出错误消息
- php - 打开和关闭单个博客文章页面时执行功能(Wordpress)