使用 activerecord 为特定用户查找不是多对多关系的记录 (ruby-on-rails)

Use activerecord to find records that are not in many-to-many relation for specific user (ruby-on-rails)

好的,我的数据库包含三个table:用户、单词、状态(这是一个关系table,没有ID)。每个用户可以有很多词,每个词可以为不同的用户重复(有些用户和词可以不关联)。每对用户词都有状态(真或假)记录在状态 table.

数据库:

  users            statuses              words
| id   |      | user_id         |      | id   |   
| name |      | word_id         |      | word |
|      |      | status: boolean |      |      |

我想创建一个网页,其中包含当前用户没有状态的所有词。

型号:

class User < ActiveRecord::Base
  has_many :statuses
  has_many :words, through: :statuses
end

class Status < ActiveRecord::Base
  belongs_to :user
  belongs_to :word
  validates :word, uniqueness: {scope: :user}
end

class Word < ApplicationRecord
  has_and_belongs_to_many :users
end

我写了一个请求,它工作正常,但它很丑,我觉得不对

def words_without_status
    sql = "SELECT words.* FROM words
           LEFT  JOIN (SELECT status.* FROM status
           LEFT  JOIN users ON users.id = status.user_id
           WHERE (status.user_id = #{current_user.id})) AS tmp
           ON words.id = tmp.word_id
           WHERE tmp.word_id IS NULL
           ORDER BY id"
    @words = ActiveRecord::Base.connection.execute(sql)
end

这是 returns 所有具有 current_user 状态的单词的代码,但我需要相反的代码。

@words = current_user.words

谢谢。

请检查以下查询: 查找 current_user 的词,使其状态为 false-

@words = current_user.statuses.includes(:words).where("status : false")

免责声明:您实际上并没有使用您的 status.status 布尔列。假设这只是一个不必要的信息(因此“没有状态的单词”意味着statustable中实际上没有记录), 你的初始 sql 看起来不错,但也许我们可以设计出更优雅的东西。

假设您首先尝试获取对特定用户具有任何状态的词的 ID:

SELECT status.word_id FROM status WHERE (status.user_id = #{current_user.id})

获取所有没有状态的单词是一个相反的操作,所以你可以这样做:

SELECT words.* WHERE words.id NOT IN (
  SELECT status.word_id FROM status WHERE (status.user_id = #{current_user.id})
)

如果您更喜欢 activerecordish 方法,它可以写成:

@words = Word.where("id NOT IN (SELECT status.word_id FROM status WHERE (status.user_id = :user_id))", user_id: current_user.id)

使用 LEFT JOINS 而不是 NOT IN 可能是另一种选择,但我省略了这一点让您发现。应针对每种特定情况分别测量实际性能差异。

有两种方法:使用连接或子查询。

有连接的看起来像

Word.includes(:statuses => :word).where(statuses: { user: current_user, status: true })

如果您想使用子查询,那么:

Word.where(id: Status.select(:word_id).where(status: true, user: current_user))

我建议您在数据集上对这两种方法进行基准测试,然后选择速度更快的一种。