Étant donné deux tables avec la colonne "titre" qui n'est pas triée ou unique:
matrix = []
User.all.each do |user|
books = Book.distinct.sort(title: :asc).pluck(:title).uniq
user_books = UserBook.where(user: user, state: "completed").order(title: :asc).pluck(:title)
matrix << books.map{|v| user_books.include?(v) ? 1 : 0}
end
Je voudrais créer une matrice binaire d'utilisateurs et de titres de livres avec l'état "terminé".
[0, 0, 0, 1, 1, 0, 0] [0, 0, 1, 0, 1, 0, 0] [1, 1, 0, 1, 0, 1, 1]
Cela donne les résultats que j'aimerais, mais a une complexité algorithmique très élevée. J'espère obtenir les résultats avec SQL.
À quel point cela pourrait-il être plus simple si l'état était booléen et les titres uniques?
Book |id|title | |1 |book_1| |2 |book_2| |3 |book_3| |4 |book_4| |5 |book_5| |6 |book_5| |7 |book_5| |8 |book_6| |9 |book_7| UserBook |user_id|book_id|state |title | |1 |2 |"in progress"|book_2 | |1 |4 |"completed" |book_4 | |1 |6 |"completed" |book_5 | |2 |3 |"completed" |book_3 | |2 |6 |"completed" |book_5 | |3 |1 |"completed" |book_1 | |3 |2 |"completed" |book_2 | |3 |4 |"completed" |book_4 | |3 |7 |"in progress"|book_5 | |3 |8 |"completed" |book_6 | |3 |9 |"completed" |book_7 |
4 Réponses :
en SQL simple
Book.joins(:user_books).where(:state => 'completed')
Dans Ruby ActiveRecord
select * from books join user_books on (books.id = user_books.id) where user_books.state = 'completed';
SQL n'est pas très bon pour les matrices. Mais vous pouvez stocker les valeurs sous forme de paires (x, y). Vous souhaitez inclure les valeurs 0 ainsi que 1 , l'idée est donc de générer les lignes en utilisant une jointure croisée , puis d'intégrer l'existant données:
select b.book_id, u.user_id,
(case when ub.id is not null then 1 else 0 end) as is_completed
from books b cross join
users u left join
user_books ub
on ub.user_id = u.id and
ub.book_id = b.id and
ub.state = 'completed';
Vous pouvez regrouper UserBook par user_id et utiliser des fonctions d'agrégation pour sélectionner la liste des livres de chaque groupe. Les extraits de code complets sont les suivants:
books = Book.order(title: :asc).pluck(:title).uniq
matrix = []
UserBook.where(state: "completed")
.select("string_agg(title, ',') as grouped_name")
.group(:user_id)
.each do |group|
user_books = group.grouped_name.split(',')
matrix << books.map { |title| user_books.include?(title) ? 1 : 0 }
end
Dans MySQL, vous devez remplacer string_agg (title, ',') par GROUP_CONCAT (title) code>
Si vous envisagez de produire le tableau souhaité en utilisant Ruby, plutôt que SQL, commencez par lire les données de la table Book dans un tableau book :
h = book.map { |book_id,title| [book_id, title[/\d+\z/].to_i-1] }.to_h
#=> {1=>0, 2=>1, 3=>2, 4=>3, 5=>4, 6=>4, 7=>4, 8=>5, 9=>6}
cols = h.values.max + 1
#=> 6
arr = Array.new(3) { Array.new(cols, 0) }
#=> [[0, 0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0, 0]]
user_book.each do |user_id, book_id, status|
arr[user_id-1][h[book_id]] = 1 if status == :completed
end
arr
#=> [[0, 0, 0, 1, 1, 0, 0],
# [0, 0, 1, 0, 1, 0, 0],
# [1, 1, 0, 1, 0, 1, 1]]
Excellent remplacement de SQL par Ruby! Parfois, je pense que Ruby peut également être utilisé comme un langage de requête.