最有效的查询来执行匹配多个字段的查询

时间:2018-01-31 05:34:56

标签: sql postgresql

我正在研究FreeCodeCamp Book Trading Club项目。我的PostgreSQL数据库中有以下关系:

用户

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  google_id NUMERIC NOT NULL
);

用户的样本数据

==================
| id | google_id |
==================
| 6  | Tyrion    |
------------------
| 8  | Jon       |
==================

图书

CREATE TABLE books (
  id VARCHAR PRIMARY KEY,
  title TEXT NOT NULL
);

图书的样本数据

=============================
| id          | title       |
=============================
| Kh5NawYsmBc | Banana Wars |
-----------------------------
| H0UULR931e4 | I, Robot    |
-----------------------------
| VIaOhHb/L98 | Sapiens     |
=============================

用户图书索引表

CREATE TABLE user_books (
  user_id INTEGER REFERENCES users(id),
  book_id VARCHAR REFERENCES books(id),
  status VARCHAR
);

user_books的示例数据

==================================
| user_id | book_id     | status |
==================================
| 8       | Kh5NawYsmBc | has    |
----------------------------------
| 6       | H0UULR931e4 | has    |
----------------------------------
| 6       | Kh5NawYsmBc | wants  |
----------------------------------
| 8       | H0UULR931e4 | wants  |
----------------------------------
| 6       | VIaOhHb/L98 | has    |
==================================

还有更多字段,但它们与问题无关,为简单起见,我只展示了这些字段。这就是我想要做的事情:

  1. 当Tyrion用户想要一本书时,该书将被添加到 books表以及user_books表(book_id,user_id) 并且status表中的user_books字段将设置为 的'希望'

  2. 接下来,我需要检查user_books表中是否还有其他用户 通过在user_books表格中搜索book_id来查找该书 提利昂想要的。只有status'的行才有' 应该被选中。

  3. 然后,对于每个拥有Tyrion想要的书的用户,我都需要 检查提利昂has是否有他们想要的书。

  4. 可能有多个用户拥有Tyrion想要的书籍,也想要一本Tyrion的书籍。提利昂可能有许多其他用户想要的书。但只有一场比赛就足够了。

    因此,如果用户Jon wants出现了Tyrion has的图书,我们就会找到匹配项,这就是我想要返回的结果。

    这些是我的担忧:

    1. 是否可以在一个查询中完成所有操作?
    2. 我的数据库结构是否适合这种类型的大规模查询?
    3. 最有效的方法是什么?
    4. 我正在使用Node,Express服务器作为此应用程序的后端。

      如果我使用的条款不具有表现力或答案已经存在,我道歉。我搜索过但无法找到正确的答案,或者我使用的条款不正确。我是SQL数据库的初学者。

      更新

      我已更新用户的表格创建,以删除UNIQUE上的PRIMARY KEY约束,因为正如许多人正确指出的那样,它没用。还纠正了数据类型。

      这是我提出的解决方案,适用于2位拥有2本书的用户。但我怀疑对于更多用户来说可能会很糟糕:

      SELECT 
        A.book_id AS book_id, 
        A.user_id AS user_one_id, 
        A.status AS user_one_status, 
        B.user_id AS user_two_id, 
        B.status AS user_two_status
      FROM (
      
      
        --- BOOKS THAT USERS WITH REQUESTED BOOK WANT
        SELECT A1.book_id, A1.user_id, A1.status
        FROM user_books AS A1
        INNER JOIN (
      
          SELECT *
          FROM user_books 
          WHERE book_id = '${reqBookId}' AND status = 'has'
      
        ) AS A2
        ON A1.user_id = A2.user_id
        WHERE A1.status = 'wants'
      
      
      ) AS A
      INNER JOIN (
      
      
        --- BOOKS THAT THE REQUESTING USER HAS
        SELECT *
        FROM user_books
        WHERE user_id = ${reqUserId} AND status = 'has'
      
      
      ) AS B
      ON A.book_id = B.book_id
      

4 个答案:

答案 0 :(得分:1)

第1步是它自己的事情并且不太正确(稍后会详细介绍),但其余部分可以通过一个(非常棒的)三重自我联接在一个查询中完成:

select ub2.user_id, ub2.book_id, u.google_id, b.title 
    from user_books ub1
    inner join user_books ub2 on ub2.user_id = ub1.user_id   
    inner join user_books ub3 on ub3.book_id = ub2.book_id 
    inner join books b on b.book_id = ub2.book_id
    inner join users u on u.user_id = ub2.user_id
    where 
        ub1.book_id = {the book Tyrion wants} and ub1.status = 'has' 
        and ub2.status = 'wants'
        and ub3.user_id = {Tyrion's id} and ub3.status = 'has'

在ub1中,我们获得了Tyrion想要的所有用户的列表。 在ub2中,我们可以获得这些用户想要的所有书籍。 在ub3中,我们发现了提利昂必须交易的书籍,如果它存在的话,它们的交集是可行交易的清单。

通过添加更多自联接,此方法还可以扩展为更大的多步骤多人交易。自连接是查询的核心;添加到用户和书籍只需要在最后完成一次以获得最终的名称和标题 - 我们不需要那些中间步骤。

因此,问题的第1部分存在一个小问题,即无论何时发出请求或任何给定的图书在每次有人请求时都会创建一个新的book_id并且不匹配将永远制作。因此,您必须查看已经在数据库中的内容(但如果您按照标题查找,则查找内容必须非常简洁才能解决变化和拼写错误 - 如果你可以指望像UPC或ISBN这样的通用书籍,那就太棒了。如果未找到,则将该行添加到book表中。如果找到了本书,请不要将其添加到书籍表中,然后......

对于用户来说完全相同:查找;如果他不在用户表中添加他。

现在您已经验证或添加了book_id和user_id,现在可以将请求添加到user_book表中。如果这本书是新书还是用户是新书,请停下来,因为要么他正在寻找一本没有书的书,要么他还没有书还没有交易,你可以做的最多就是要求书的目录,你已经完成了。如果图书或用户都不是新的,请运行查询。

我希望这会有所帮助。

答案 1 :(得分:0)

要查找可能与'The interesting book'想要的'Tyrion'交换的所有用户和图书,您可以运行以下内容:

SELECT u2.google_id, b1.title
FROM users u1
   JOIN user_books ub1 ON u1.id = ub1.user_id
   JOIN books b1 ON ub1.book_id = b1.id
   JOIN user_books ub2 ON b1.id = ub2.book_id
   JOIN users u2 ON u2.id = ub2.book_id
   JOIN user_books ub3 ON ub3.user_id = u2.id
   JOIN books b2 ON b2.id = ub3.book_id
WHERE u1.google_id = 'Tyrion'
  AND ub1.status = 'has'
  AND ub2.status = 'wants'
  AND ub3.status = 'has'
  AND b2.title = 'The interesting book';

如果对嵌套循环连接中涉及的所有列以及除WHERE之外的user_books.status子句中的所有列都有适当的索引,则查询应该尽可能高效。

我认为你的表结构是有意义的,除了冗余的UNIQUE约束以及并非所有人工主键都是数字的事实。 user_books上的(user_id, book_id)应该有一个主键。

答案 2 :(得分:0)

以下查询可以为您提供什么' Jon'他希望并优先考虑他人想要的东西。我提供了用于测试的示例插入语句。

INSERT INTO users (google_id) VALUES('Tyrion')
INSERT INTO users (google_id) VALUES('Jon')
INSERT INTO users (google_id) VALUES('Robert')
INSERT INTO users (google_id) VALUES('Victor')

插入图书表。

INSERT INTO Books values('Kh5NawYsmBc', 'Banana Wars')
INSERT INTO Books values('H0UULR931e4', 'I, Robot ')
INSERT INTO Books values('VIaOhHb/L98', 'Sapiens     ')
INSERT INTO Books values('RanDomNum1', 'Let us C')
INSERT INTO Books values('RanDomNum2', 'Teach yourself Java')

插入user_Books表。

INSERT INTO user_books values(2,'Kh5NawYsmBc' , 'has')
INSERT INTO user_books values(1, 'H0UULR931e4' , 'has')
INSERT INTO user_books values(1, 'Kh5NawYsmBc' , 'wants')
INSERT INTO user_books values(1, 'H0UULR931e4' , 'wants')
INSERT INTO user_books values(2, 'VIaOhHb/L98' , 'has')
INSERT INTO user_books values(3, 'RanDomNum1' , 'has')
INSERT INTO user_books values(4, 'RanDomNum2' , 'has')
INSERT INTO user_books values(4, 'VIaOhHb/L98' , 'has')
INSERT INTO user_books values(2, 'H0UULR931e4' , 'wants')
INSERT INTO user_books values(4, 'H0UULR931e4' , 'has')

查询:

select sq2.google_id, sq5.title from 
    (select u1.*, ub1.*, b1.id [Bkid], b1.title from users u1 join user_books ub1 on u1.id = ub1.user_id
    join books b1 on ub1.book_id = b1.id where u1.google_id = 'Jon' and ub1.status = 'wants'
    ) sq1 
inner join 
    (select u1.*, ub1.*, b1.id [Bkid], b1.title from users u1 join user_books ub1 on u1.id = ub1.user_id
    join books b1 on ub1.book_id = b1.id where ub1.status = 'has' and u1.google_id <> 'Jon'
    ) sq2 on  sq1.Bkid = sq2.Bkid 
left join
    (select sq3.google_id [hasID], sq4.google_id [wantsID], sq3.title from 
        (select u1.*, ub1.*, b1.id [Bkid], b1.title from users u1 join user_books ub1 on u1.id = ub1.user_id
        join books b1 on ub1.book_id = b1.id where ub1.status = 'has' and u1.google_id = 'Jon'
        ) sq3 
        inner join 
        (select u1.*, ub1.*, b1.id [Bkid], b1.title from users u1 join user_books ub1 on u1.id = ub1.user_id
        join books b1 on ub1.book_id = b1.id where ub1.status = 'wants' and u1.google_id <> 'Jon'
        ) sq4
        on sq3.book_id = sq4.book_id
    ) as sq5
on sq2.google_id = sq5.wantsID
order by 2 desc

结果如下:

google_id                                          title
---------------------------------------------- -----------------------------
Tyrion                                             Banana Wars
Victor                                             NULL

答案 3 :(得分:0)

这是我从Laurenz AlbejWolf发布的答案中获取提示后使用的查询:

SELECT
  u1.city_id,
  ub3.user_id AS user_two_id,
  ub3.book_id
FROM user_books ub1
  INNER JOIN users AS u1 ON ub1.user_id = u1.id
  INNER JOIN user_books AS ub2 ON ub1.user_id = ub2.user_id
  INNER JOIN user_books AS ub3 ON ub2.book_id = ub3.book_id
  INNER JOIN users AS u2 ON ub2.user_id = u2.id
WHERE
  ub1.book_id = '{requestedBookId}' AND
  ub1.user_id = {requestingUsersId} AND
  ub2.status = 'has' AND
  ub3.status = 'wants' AND
  u1.city_id = u2.city_id AND
  ub3.book_id NOT IN (SELECT user1_book_requested FROM trades) AND
  ub3.book_id NOT IN (SELECT user2_book_requested FROM trades)

WHERE中的最后两个条款仅确保为交易匹配选择的图书不是交易的一部分。

谢谢你们!