我正在Ruby on Rails应用程序上工作(尽管这实际上更多是一个数据结构问题),其中我有Posts
,Books
和Chapters
作为模型。假设希望能够引用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也有native range types范围(双关语意:):>
而且这些只是内置类型。
ActiveRecord盒外并没有真正支持本地范围,但是您可以使用Rails 5中引入的属性API来处理类型转换。
class Chapter < ApplicationRecord attribute :page_range, range: true end
这里的巨大优势之一是查询,因为PG知道此列实际上是一个范围,并且与以前的解决方案相比,可以创建非常有效的查询计划。
在这里使用JSON或数组类型非常可疑,因为您失去了关系模型的所有好处,而没有range列的好处。如果一个模型有多个范围,我将创建一个单独的联接表。
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)
如果您担心章节的范围,则可以通过在帖子中存储范围的开始和结束来进一步优化。