Поиск уникальных записей, упорядоченных по полю в ассоциации с PostgreSQL и Rails 3? - PullRequest
3 голосов
/ 09 декабря 2011

ОБНОВЛЕНИЕ : Итак, благодаря @Erwin Brandstetter, у меня теперь есть это:

def self.unique_users_by_company(company)
  users = User.arel_table
  cards = Card.arel_table

  users_columns = User.column_names.map { |col| users[col.to_sym] }

  cards_condition = cards[:company_id].eq(company.id).
    and(cards[:user_id].eq(users[:id]))

  User.joins(:cards).where(cards_condition).group(users_columns).
    order('min(cards.created_at)')
end

... который, кажется, делает именно то, что я хочу. Однако есть два недостатка, которые я бы хотел устранить:

  1. Предложение order() использует прямой SQL вместо Arel (не смог понять).
  2. Вызов .count по приведенному выше запросу дает мне эту ошибку:

    NoMethodError: неопределенный метод to_sym для # из /Users/neezer/.rvm/gems/ruby-1.9.3-p0/gems/activerecord-3.1.1/lib/active_record/relation/calculations.rb:227:in 'Execute_grouped_calculation'

    ... что, вероятно, связано с тем, как я отображаю users_columns, поэтому мне не нужно вручную вводить все из них в предложении group.

Как я могу исправить эти две проблемы?


ОРИГИНАЛЬНЫЙ ВОПРОС :

Вот что у меня есть, что решает первую часть моего вопроса:

def self.unique_users_by_company(company)
  users = User.arel_table
  cards = Card.arel_table

  cards_condition = cards[:company_id].eq(company.id)
    .and(cards[:user_id].eq(users[:id]))

  User.where(Card.where(cards_condition).exists)
end

Это дает мне 84 уникальных записи, и это правильно.

Проблема в том, что мне нужны эти User записи, упорядоченные по cards[:created_at] (в зависимости от того, что раньше для этого конкретного пользователя). Добавление .order(cards[:created_at]) к объему в конце описанного выше метода абсолютно ничего не делает.

Я попытался добавить .joins(:cards), но это дает 587 записей, что неверно (дубликаты User с). group_by, насколько я понимаю, здесь также практически бесполезно из-за того, как PostgreSQL справляется с этим .

Мне нужно, чтобы мой результат был ActiveRecord::Relation (чтобы он был цепным), который возвращает список уникальных пользователей, у которых есть карты, принадлежащие данной компании, упорядоченные по дате создания их первой карты. ... с запросом, написанным на Ruby и не зависящим от базы данных. Как я могу это сделать?


class Company
  has_many :cards
end

class Card
  belongs_to :user
  belongs_to :company
end

class User
  has_many :cards
end

Пожалуйста, дайте мне знать, если вам нужна какая-либо другая информация, или если я не совсем понял свой вопрос.

Ответы [ 2 ]

4 голосов
/ 09 декабря 2011

Запрос, который вы ищете, должен выглядеть следующим образом:

SELECT user_id, min(created_at) AS min_created_at
FROM   cards
WHERE  company_id = 1
GROUP  BY user_id
ORDER  BY min(created_at)

Вы можете присоединиться к таблице user, если вам нужны столбцы этой таблицы в результате, в противном случае вы даже не захотитеэто нужно для запроса.
Если вам не нужно min_created_at в списке SELECT, вы можете просто оставить его в стороне.

Должно быть легко перевести на Ruby (что я нехорошо)


Чтобы получить всю запись пользователя (как я понял из вашего комментария):

SELECT u.*,
FROM   user u
JOIN  (
    SELECT user_id, min(created_at) AS min_created_at
    FROM   cards
    WHERE  company_id = 1
    GROUP  BY user_id
    ) c ON u.id = c.user_id
ORDER  BY min_created_at

Или:

SELECT u.*
FROM   user u
JOIN   cards c ON u.id = c.user_id
WHERE  c.company_id = 1
GROUP  BY u.id, u.col1, u.col2, ..   -- You have to spell out all columns!
ORDER  BY min(c.created_at)

В PostgreSQL 9.1+ вы можете просто написать:

GROUP  BY u.id

(как в MySQL) .. при условии, что id является первичным ключом.

Я цитирую заметки о выпуске :

Разрешить столбцы не-GROUP BY в списке целей запроса, когда первичный ключ указан в предложении GROUP BY (Питер Эйзентраут)

Стандарт SQLпозволяет такое поведение, и из-за первичного ключа, результат однозначен.

1 голос
/ 09 декабря 2011

Тот факт, что вам нужно, чтобы он был цепным, усложняет ситуацию, в противном случае вы можете либо самостоятельно перейти в SQL, либо выбрать нужные столбцы через select("users.id"), чтобы обойти проблему Postgres. Потому что в основе вашего запроса что-то вроде

SELECT users.id
FROM users 
INNER JOIN cards ON users.id = cards.user_id
WHERE cards.company_id = 1
GROUP BY users.id, DATE(cards.created_at)
ORDER BY DATE(cards.created_at) DESC

Какой в ​​синтаксисе Arel более или менее:

User.select("id").joins(:cards).where(:"cards.company_id" => company.id).group_by("users.id, DATE(cards.created_at)").order("DATE(cards.created_at) DESC")
...