【问题标题】:How to construct SQL query where field IN sub query?如何构造 SQL 查询 where 字段 IN 子查询?
【发布时间】:2019-05-14 05:16:06
【问题描述】:

我正在使用在线提供的 Yelp 数据集。几天来,我一直在尝试优化我的查询。对于我将在下面列出的架构,我需要构建一个查询以提供以下内容:

  • 给定用户的 UID,显示用户每个朋友的最新评论信息。

这是架构:

CREATE TABLE business(
    bid varchar(40) PRIMARY KEY,
    name varchar(100),
    city varchar(40),
    state char(2),
    zip varchar(10),
    latitude real,
    longitude real,
    address varchar(100),
    numreviews INTEGER DEFAULT 0,
    numcheckins INTEGER DEFAULT 0,
    avgreview float DEFAULT 0,
    isopen bool,
    stars float
);

CREATE TABLE users(
    uid varchar(40) PRIMARY KEY,
    name varchar(40),
    avgstars float,
    fans INTEGER,
    coolvotes INTEGER,
    reviewcount INTEGER,
    funnyvotes INTEGER,
    signup varchar(20),
    usefulvotes INTEGER,
    latitude real,
    longitude real
);

CREATE TABLE reviews(
    rid varchar(40) PRIMARY KEY,
    bid varchar(40),
    uid varchar(40),
    stars float,
    date varchar(20),
    funny INTEGER,
    useful INTEGER, 
    cool INTEGER,
    text varchar(1024),
    FOREIGN KEY (uid) REFERENCES users(uid),
    FOREIGN KEY (bid) REFERENCES business(bid)
);

CREATE TABLE friends(
    uid varchar(40) REFERENCES users(uid),
    fid varchar(40) REFERENCES users(uid)
);

以下是所需输出的示例:

对于用户的每个朋友,我显示以下内容:

  • 朋友的名字
  • 最近评论中的商家名称
  • 商家最近评价的城市
  • 他们最近的评论中的文字

目前这是我唯一成功的“解决方案”。

第 1 步:获取用户每个朋友的所有 ID 列表。

SELECT fid from friends where uid = '{userId}'

这将返回用户每个朋友的所有用户 ID 的列表。所以我基本上有一个朋友ID列表。

第 2 步: 有了这些信息,我在我的程序中对该列表运行 foreach 循环。对于好友 ID 列表的每次迭代,我执行以下查询并为当前循环迭代提供临时好友 ID:

SELECT U.name, B.name, B.city, R.text, R.date FROM reviews as R, users as U, business as B
WHERE U.uid = '{currentFriendId}'
AND R.uid = '{currentFriendId}'
AND B.bid = R.bid
AND date = (SELECT MAX(date) FROM reviews WHERE uid = '{currentFriendId}')

每次我运行这个 for 循环时,我都会得到一行我想要的输出,例如:

这很棒...除了我必须为用户的每个朋友运行此查询。这是非常昂贵的。

目标:我正在尝试将这 2 个查询结合起来,或者完全修改它们,以便在一个查询中一次生成所有行。

问题:鉴于提供的信息,我如何修复我的查询以从单个查询生成所有这些信息?

【问题讨论】:

  • 第二个查询不需要或不希望在 WHERE 子句中使用 E.uid = ??????? 表达式。
  • 不清楚你在问什么。您使用什么语言?可以添加一些示例数据吗?
  • @TimBiegeleisen 我添加了实际代码,你能看一下吗?
  • @Nick 嗨尼克,我添加了我的实际代码,因为我认为我的示例太简单了。可以看看吗?
  • 请阅读有关使用准备好的语句的信息。你当前的代码有很多问题。

标签: c# sql postgresql


【解决方案1】:

这看起来像是一个 top-n-per-group 问题。

一种方法是使用横向连接。

确保您在(uid, date) 上的reviews 表上有一个索引。综合指数。按此顺序在两列上建立一个索引。

类似这样的:

CREATE INDEX IX_uid_date ON reviews (uid, date);

查询

SELECT
     t.UserName
    ,t.BusinessName
    ,t.city
    ,t.text
    ,t.date
FROM
    friends
    INNER JOIN LATERAL
    (
        SELECT
            users.name AS UserName
            ,business.name AS BusinessName
            ,business.city
            ,reviews.text
            ,reviews.date
        FROM
            reviews
            INNER JOIN users ON users.uid = reviews.uid
            INNER JOIN business ON business.bid = reviews.bid
        WHERE
            reviews.uid = friends.fid
        ORDER BY reviews.date DESC
        LIMIT 1
    ) AS t ON true
WHERE
    friends.uid = '{userId}'
;

【讨论】:

  • 绝对可悲的是,在尝试了无数个小时之后,我终于弄明白了……实际上是在您发帖前大约 5 分钟。我检查了你的结果,但它们是有效的,所以我会奖励你。
  • @Birdman,恭喜!当您自己学习某些东西时,没有什么可悲的。第一次遇到问题总是需要一些时间,但是当你自己解决问题时,你往往会更好地理解和记住解决方案。我建议您将赏金开放最长持续时间,因为您可能会得到其他更好的答案。
【解决方案2】:

这应该可以正常工作。

SELECT name FROM employees as E
WHERE E.uid IN (SELECT uid FROM employees WHERE name = 'John')

您不需要像在单真值匹配查询中那样进行相等比较。

【讨论】:

  • 谢谢,但我觉得我的例子太简单了。你能看看我编辑的问题吗?我添加了我的实际代码和问题。
【解决方案3】:

跟进 Manos 的回答,我不确定我是否理解您需要限制每个 fid 的原因

SELECT U.name, B.name, B.city, R.text, R.date 
    FROM business AS B 
    INNER JOIN reviews AS R ON B.bid = R.bid 
    INNER JOIN users AS U ON R.uid = U.uid 
    WHERE (R.date = (SELECT MAX(X.date) FROM reviews AS X WHERE X.uid = R.uid)) 
    AND (R.uid IN (SELECT fid FROM friends));

如果您的问题是您的查询仅产生一行,则应删除 where uid = 以获取所有 uid 的结果。

【讨论】:

    【解决方案4】:

    我也与Vladimir Baranov 大致同时得到了答案,但我也会发布我的版本。我不保证它很漂亮:

    SELECT R.name as user_name, B.name as business_name, B.City, R.text
    FROM (SELECT bid, name, text 
         FROM (SELECT R.rid, R.bid, R.uid, R.text, max_date
            FROM reviews as R INNER JOIN 
               (SELECT uid, MAX(date) as max_date FROM reviews WHERE uid IN (SELECT fid from friends where uid = 'BfcNxKpnF9z5wJLXY7elRg') GROUP BY uid) sub
                ON R.uid = sub.uid AND R.date = sub.max_date) as review_info
         INNER JOIN users
         on review_info.uid = users.uid) as R
    INNER JOIN business as B
    ON R.bid = B.bid
    

    【讨论】:

    • 通常有几种方法可以编写产生相同结果的 SQL 查询。我建议您尝试所有建议的查询,检查其中哪些产生正确的结果,并比较它们在您的真实数据和硬件上的性能。不同的查询可能具有截然不同的性能,即使它们产生相同的结果。
    【解决方案5】:

    检查您发布的架构后,我使用 MySQL 创建数据库并使用以下示例数据填充表:

    INSERT INTO users (uid, name) VALUES
    ('user1', 'user1 name'),
    ('user2', 'user2 name'),
    ('user3', 'user3 name'),
    ('user4', 'user4 name'),
    ('user5', 'user5 name');
    
    INSERT INTO friends (uid, fid) VALUES
    ('user1', 'user2'), ('user1', 'user3'),
    ('user2', 'user4'), ('user2', 'user5');
    
    INSERT INTO business (bid, name, city) VALUES
    ('b1', 'business 1', 'city 1'),
    ('b2', 'business 2', 'city 2'),
    ('b3', 'business 3', 'city 3'),
    ('b4', 'business 4', 'city 4');
    
    INSERT INTO reviews (rid, bid, uid, stars, date, text) VALUES
    ('r1', 'b1', 'user1', 5, '2019-05-01', 'blah'),
    ('r2', 'b2', 'user1', 5, '2019-05-02', 'blah'),
    ('r3', 'b3', 'user1', 5, '2019-05-03', 'blah'),
    ('r4', 'b1', 'user2', 4, '2019-05-11', 'blah'),
    ('r5', 'b2', 'user3', 3, '2019-05-12', 'blah'),
    ('r6', 'b1', 'user4', 5, '2019-05-13', 'blah');
    

    这使我能够通过在 MySQL Workbench 中执行查询来验证我提出的原始解决方案是否正确。 我假设您提到的“未能完成”与查询本身无关,而是您使用的数据库连接 api 的临时故障。 请注意,代码已更新以包含 Mihail Shishkov 的使用参数的提议。

    -- Display review information originating from friends of user1
    -- DECLARE @UID varchar(40);    -- Uncomment for MS-SQL (variables need to be declared)
    SET @UID = 'user1';
    
    SELECT U.name, B.name, B.city, R.text, R.date 
    FROM business AS B
    INNER JOIN reviews AS R ON B.bid = R.bid
    INNER JOIN users AS U ON R.uid = U.uid
    WHERE (R.date = (SELECT MAX(X.date) FROM reviews AS X WHERE (X.uid = R.uid)))
      AND (R.uid IN (SELECT F.fid FROM friends AS F WHERE (F.uid = @UID)));
    

    根据样本数据,使用'user1'作为@UID参数的值,查询结果为:

    name        name        city    text  date
    ------------------------------------------------
    user2 name  business 1  city 1  blah  2019-05-11
    user3 name  business 2  city 2  blah  2019-05-12
    

    此外,我假设友谊在您的架构上下文中是双向关系(就像在现实世界中一样),这意味着“user1”和“user2”之间的友谊只需要由表 'friends' 的值 ('user1', 'user2') 和 reverse ('user2', 'user1') 是不必要的。 因此,为了完整起见,您可以使用以下查询:

    -- Display review information originating from friends of user2
    SET @UID = 'user2';
    
    SELECT U.name, B.name, B.city, R.text, R.date 
    FROM business AS B
    INNER JOIN reviews AS R ON B.bid = R.bid
    INNER JOIN users AS U ON R.uid = U.uid
    WHERE (R.date = (SELECT MAX(X.date) FROM reviews AS X WHERE (X.uid = R.uid)))
      AND (R.uid IN (SELECT F.fid FROM friends AS F WHERE (F.uid = @UID) UNION
                     SELECT F.uid FROM friends AS F WHERE (F.fid = @UID)));
    

    现在,使用 'user2' 作为@UID 参数的值和查询的扩展版本,我们得到以下结果:

    name        name        city    text  date
    ------------------------------------------------
    user1 name  business 3  city 3  blah  2019-05-03
    user4 name  business 1  city 1  blah  2019-05-13
    

    如果您承认答案是可以接受的,我将不胜感激。

    【讨论】:

    • 嗨,马诺斯。您的查询从未完成(不知道为什么)。我彻底编辑了我的答案。我包含了架构,并用我认为更相关的信息替换了代码。你能再看看吗?
    • 永远不要连接字符串来生成 SQL 查询。改用参数。 stackoverflow.com/questions/3216233/…
    • @MihailShishkov 谢谢,我会看看,但这并不能解决我的问题。
    • 很抱歉,编辑后的解决方案仍然不起作用。我的数据库在所有表中有几百万条记录。我不确定这是一个超级昂贵的查询还是什么,但它没有完成。 (即使我的查询返回单行,当我运行 30 次时,它也会在 5 秒左右完成......)
    • 另外,我使用的是 PostgresSQL,如标签所示。我无法使用显示的 SET 功能,但我尝试使用硬编码的 UID 来运行您的查询,并将其换成您的变量。
    猜你喜欢
    • 1970-01-01
    • 2016-07-16
    • 2015-01-01
    • 2015-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-13
    相关资源
    最近更新 更多