Rails ActiveRecord дружественный код из сложного соединения, суммы и запроса группы - PullRequest
2 голосов
/ 03 июня 2010

ПРОБЛЕМА

Привет,

Мне не везет, когда я пытаюсь разбить этот оператор SQL на дружественный код ActiveRecord / Rails, и я хотел бы узнать, как можно избежать оператора find_by_sql в этой ситуации.

Сценарий У меня есть пользователи, которые создают аудиты, когда они выполняют действие. Каждый аудит имеет определенный аудит_активности. Каждый аудит_активности оценивается в определенное количество баллов в зависимости от балла. Мне нужно найти общие баллы каждого пользователя, основываясь на их общей сумме набранных Audit_activity Score_weights. В конце концов мне нужно их ранжировать, что означает добавление сортировки к этому.

Мой код Вот мой sql и упрощенные версии рассматриваемых таблиц. Есть мысли?

SQL с полными именами столбцов (для ясности)

SELECT users.id, u.email, SUM(audit_activity.score_weight) 
FROM users 
JOIN audits ON users.id = audits.user_id 
JOIN audit_activities ON audit_activities.id = audits.audit_activity_id 
GROUP BY users.id;

Модели: Пользователь, Аудит, AuditActivity

Поля пользователя: id, email

class User < ActiveRecord::Base
 include Clearance::User
 has_many :audits
end

Поля аудита: id, user_id, audit_activity_id

class Audit < ActiveRecord::Base
  belongs_to :user
  belongs_to :audit_activity
end

Поля AuditActivity: id, оценка_weight

class AuditActivity < ActiveRecord::Base
  has_many :audits
end

Пример данных

Вот набор операторов SQL, чтобы вы могли поиграть с похожими данными, с которыми я работаю, и посмотреть, что произойдет при выполнении соответствующего запроса. Вы должны просто иметь возможность скопировать / вставить все это в браузер запросов к базе данных.

CREATE TABLE users(
 id INTEGER NOT NULL,
 email TEXT (25),
 PRIMARY KEY (id)
);

CREATE TABLE audits(
 id INTEGER NOT NULL,
 user_id INTEGER,
 audit_activity_id INTEGER,
 PRIMARY KEY (id)
); 

CREATE TABLE audit_activities(
 id INTEGER NOT NULL,
 score_weight INTEGER,
 PRIMARY KEY (id)
);

INSERT INTO users(id, email)
VALUES(1, "1user@a.com");
INSERT INTO users(id, email)
VALUES(2, "2user@b.com");
INSERT INTO users(id, email)
VALUES(3, "3user@c.com");

INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(1, 1, 1);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(2, 1, 2);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(3, 1, 1);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(4, 1, 3);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(5, 1, 1);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(6, 1, 4);

INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(7, 2, 4);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(8, 2, 4);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(9, 2, 4);

INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(10, 3, 3);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(11, 3, 2);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(12, 3, 2);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(13, 3, 2);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(14, 3, 3);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(15, 3, 1);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(16, 3, 1);

INSERT INTO audit_activities(id, score_weight)
VALUES(1, 1);
INSERT INTO audit_activities(id, score_weight)
VALUES(2, 2);
INSERT INTO audit_activities(id, score_weight)
VALUES(3, 7);
INSERT INTO audit_activities(id, score_weight)
VALUES(4, 11);

Запрос Опять же, вот запрос.

SELECT u.id, u.email, SUM(aa.score_weight) 
FROM users u 
JOIN audits a ON u.id = a.user_id 
JOIN audit_activities aa ON aa.id = a.audit_activity_id 
GROUP BY u.id;

Ответы [ 2 ]

5 голосов
/ 03 июня 2010
User.sum( :score_weight, :include => {:audits => :audit_activity}, :group => 'users.id' )
0 голосов
/ 03 июня 2010

Достаточно легко получить ваших пользователей и пройти через аудиты для каждого из них, суммируя значения по мере продвижения. Так было бы что-то вроде этого:

users = User.find(:all)
users.each do |user|
  puts "user: #{user.email}"
  score = 0
  user.audits.each do |audit|
       puts "  audit: #{audit.audit_activity.id}  score: #{audit.audit_activity.score_weight}"
       score += audit.audit_activity.score_weight
  end
  puts "total score for this user: #{score"
end

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

Если объемы данных будут большими, и, как вы говорите, вы захотите отсортировать по баллам пользователей, то я думаю, что ответом будет наличие поля с текущим баллом в записи пользователя, которое обновляется каждый раз, когда записывается отчет об аудиторской деятельности. Это может быть выполнено автоматически с помощью обратного вызова ассоциации (то есть метода after_add в ассоциации аудита в записи пользователя). Смотри http://guides.rubyonrails.org/association_basics.html#association-callbacks.

...