发布于 2026-01-06 6 阅读
0

Rails + PostgreSQL Array DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

Rails + PostgreSQL 数组

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

如果你继续阅读本文,我假设你了解 Ruby、Ruby 中的面向对象编程 (OOP)、Ruby Ruby 框架 (RoR) 和 Active Record。

是的,PostgreSQL 支持存储数组类型。根据他们的文档

PostgreSQL 允许将表的列定义为可变长度的多维数组。可以创建任何内置或用户定义的基本类型、枚举类型、复合类型、范围类型或域的数组。

让我们开始这段旅程吧!(我这里只使用 Rails API 作为示例,但本文内容同样适用于普通的 Rails 版本。)

目录:
1. 迁移
2. 创建
3. 显示
4. 更新
5. 查询
6. 结语

1. 迁移

很简单:

# db/migrate/*_create_books.rb
class CreateBooks < ActiveRecord::Migration[6.0]
  def change
    create_table :books do |t|
      t.string :title
      t.string :tags, array: true, default: []
      t.integer :ratings, array: true, default: []

      t.timestamps
    end
    add_index :books, :tags, using: 'gin'
    add_index :books, :ratings, using: 'gin'
  end
end
Enter fullscreen mode Exit fullscreen mode

如果您想添加新列:

# db/migrate/*_add_subjects_to_books.rb
class AddSubjectsToBooks < ActiveRecord::Migration
  def change
    add_column :books, :subjects, :string, array:true, default: []
  end
end
Enter fullscreen mode Exit fullscreen mode

注意:
t.string :tags, array: true并未定义该列t.array :tags。与 jsonb 不同,jsonb 定义了该列t.jsonb :payload。这是因为 PostgreSQL 中没有“数组”类型,只有“列类型数组”。PostgreSQL 数组并非像 Ruby 数组那样的通用容器,它们更像是 C、C++ 等语言中的数组。

2. 创建

创建记录也很简单:

irb(main):001:0> Book.create(title: "Hacking Growth", tags: ["business", "startup"], ratings: [4, 5])
   (0.1ms)  BEGIN
  Book Create (0.6ms)  INSERT INTO "books" ("title", "tags", "ratings", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["title", "Hacking Growth"], ["tags", "{business,startup}"], ["ratings", "{4,5}"], ["created_at", "2020-06-29 08:48:42.440895"], ["updated_at", "2020-06-29 08:48:42.440895"]]
   (0.4ms)  COMMIT
=> #<Book id: 1, title: "Hacking Growth", tags: ["business", "startup"], ratings: [4, 5], created_at: "2020-06-29 08:48:42", updated_at: "2020-06-29 08:48:42">
Enter fullscreen mode Exit fullscreen mode

3. 显示

两者tags现在ratings都是一个数组对象:

irb(main):002:0> book = Book.first
  Book Load (0.3ms)  SELECT "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT $1  [["LIMIT", 1]]
irb(main):003:0> book.tags
=> ["business", "startup"]
irb(main):004:0> book.tags[0]
=> "business"
Enter fullscreen mode Exit fullscreen mode

4. 更新

更新的最简单方法是:

irb(main):005:0> book.tags << 'management'
=> ["business", "startup", "management"]
irb(main):0006:0> book.save!
   (0.1ms)  BEGIN
  Book Update (1.2ms)  UPDATE "books" SET "tags" = $1, "updated_at" = $2 WHERE "books"."id" = $3  [["tags", "{business,startup,management}"], ["updated_at", "2020-06-29 08:54:36.731328"], ["id", 1]]
   (0.4ms)  COMMIT
=> true
irb(main):007:0> book.tags
=> ["business", "startup", "management"]
Enter fullscreen mode Exit fullscreen mode

还有其他任何向数组对象添加值的方法吗?

# This works
book.tags << 'management'

#This will work too
book.tags.push 'management'

# This is also will work
book.tags += ['management']
Enter fullscreen mode Exit fullscreen mode

但不要这样做:Book.first.tags << 'finance',它不会保存到数据库中。证明:

irb(main):008:0> Book.first.tags << "finance"
  Book Load (0.3ms)  SELECT "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT $1  [["LIMIT", 1]]
=> ["business", "startup", "management", "finance"]
irb(main):009:0> Book.first.save!
  Book Load (0.3ms)  SELECT "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT $1  [["LIMIT", 1]]
=> true
irb(main):010:0> Book.first.tags
  Book Load (0.3ms)  SELECT "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT $1  [["LIMIT", 1]]
=> ["business", "startup", "management"]
Enter fullscreen mode Exit fullscreen mode

如果要使用原始 SQL,可以查阅官方文档

5. 查询

假设我们要搜索所有带有“管理”标签的书籍:

# This is valid
irb(main):011:0> Book.where("'management' = ANY (tags)")

# This is more secure
irb(main):012:0> Book.where(":tags = ANY (tags)", tags: 'management')

# This is also valid
irb(main):013:0> Book.where("tags @> ?", "{management}")
Enter fullscreen mode Exit fullscreen mode

如果我们想搜索所有没有标签管理的书籍,该怎么办?

irb(main):013:0> Book.where.not("tags @> ?", "{management}")
Enter fullscreen mode Exit fullscreen mode

您可以在官方文档中查看运算符及其说明

那么,如果我们想搜索包含多个标签(例如管理和创业)的书籍该怎么办呢?

# This is valid
irb(main):014:0> Book.where("tags @> ARRAY[?]::varchar[]", ["management", "startup"])

# This is valid
irb(main):015:0> Book.where("tags &&  ?", "{management,startup}")

# If you use where.not, you basically search for all that do not contain the parameter given.
Enter fullscreen mode Exit fullscreen mode

如果我们想搜索所有评分高于 3 分的书籍,该怎么办呢?

irb(main):016:0> Book.where("array_length(ratings, 1) >= 3")
Enter fullscreen mode Exit fullscreen mode

我们不妨让搜索功能更强大一些,并支持模式匹配:

# %gem% is manaGEMent 
irb(main):017:0> Book.where("array_to_string(tags, '||') LIKE :tags", tags: "%gem%")
Enter fullscreen mode Exit fullscreen mode

您可以在官方文档中查看所有运算符和函数及其说明

6. 结语

以上就是全部内容。如果我发现什么有趣的东西,我会更新。

来源:本人以及摘自多篇文章

文章来源:https://dev.to/kputra/rails-postgresql-array-1jn0