【问题标题】:Most efficient way to find overlap between two tables查找两个表之间重叠的最有效方法
【发布时间】:2019-03-29 19:57:35
【问题描述】:

给定两个表的“标题”列未排序或唯一:

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 得到结果。

如果 state 是 boolean 且 title 是唯一的,它会简单到什么程度?

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

【问题讨论】:

    标签: sql ruby-on-rails ruby postgresql


    【解决方案1】:

    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';
    

    【讨论】:

      【解决方案2】:

      您可以将UserBookuser_id 分组,并使用聚合函数来选择每个组中的书籍列表。整个代码sn-ps如下:

      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)

      【讨论】:

        【解决方案3】:

        您是否考虑使用 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_iduser_book的每个元素的前两个元素,整数,分别是user_idbook_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]] 
        

        【讨论】:

        • 用 Ruby 完美替代 SQL!有时我认为 Ruby 也可以用作查询语言。
        【解决方案4】:

        在直接 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')
        

        【讨论】:

          猜你喜欢
          • 2012-05-12
          • 2019-12-23
          • 1970-01-01
          • 2014-11-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多