Самый эффективный способ найти совпадение между двумя таблицами - PullRequest
1 голос
/ 29 марта 2019

Имеются две таблицы с не отсортированным или уникальным столбцом «заголовок»:

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 |

Я хотел бы создать двоичную матрицу пользователей и названий книг с состоянием «завершено».

[0, 0, 0, 1, 1, 0, 0]
[0, 0, 1, 0, 1, 0, 0]
[1, 1, 0, 1, 0, 1, 1]

Это дает желаемые результаты, но имеет очень высокую алгоритмическую сложность.Я надеюсь получить результаты с SQL.

Насколько проще было бы, если бы состояние было логическим, а заголовки были уникальными?

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

Ответы [ 4 ]

2 голосов
/ 30 марта 2019

SQL не очень хорош в матрицах.Но вы можете хранить значения в виде (x, y) пар.Вы хотите включить 0 значения, а также 1, поэтому идея состоит в том, чтобы сгенерировать строки, используя cross join, а затем ввести существующие данные:

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';
1 голос
/ 31 марта 2019

Если вы подумаете о создании нужного массива с использованием Ruby, а не SQL, сначала прочитайте данные из таблицы Book в массив book:

book = [
  [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_book:

user_book = [
  [1, 2, :in_progress], [1, 4, :completed], [1, 6, :completed],
  [2, 3, :completed],   [2, 6, :completed],
  [3, 1, :completed],   [3, 2, :completed], [3, 4, :completed], [3, 7, :in_progress],
  [3, 8, :completed],   [3, 9, :completed]
] 

Обратите внимание, что первый элемент каждого элемента book, целое число, является book_id, и первые два элемента каждого элемента user_book,целые числа, соответственно user_id и book_id.

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

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]] 
1 голос
/ 30 марта 2019

Вы можете сгруппировать UserBook по user_id и использовать агрегатные функции для выбора списка книг в каждой группе.Весь фрагмент кода выглядит следующим образом:

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

В MySQL вам необходимо заменить string_agg(title, ',') на GROUP_CONCAT(title)

0 голосов
/ 29 марта 2019

в прямом SQL

select * from books join user_books on (books.id = user_books.id) 
where user_books.state = 'completed';

в Ruby ActiveRecord

Book.joins(:user_books).where(:state => 'completed')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...