Как построить SQL-запрос, где поле В подзапросе? - PullRequest
3 голосов
/ 14 мая 2019

Я работаю с набором данных 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)
);

Вот пример желаемого вывода: enter image description here

Для каждогоиз друзей пользователя я показываю следующее:

  • Имя друга
  • Название компании из последнего обзора
  • Город компании изих самый последний обзор
  • Текст их последнего обзора

В настоящее время это единственное "решение", с которым я добился успеха.

Шаг 1: Получите список всех идентификаторов для каждого из друзей пользователя.

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

Возвращает список всех идентификаторов пользователя для каждого из друзей пользователя.Так что у меня есть список друзей.

Шаг 2: С этой информацией я запускаю цикл foreach в своей программе над этим списком.Для каждой итерации списка идентификаторов друзей я выполняю следующий запрос и предоставляю временный идентификатор друга для текущей итерации цикла:

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, я получаю одинстрока вывода для того, что я хочу, например:

enter image description here

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

Цель: Я пытаюсь объединить эти 2 запроса или полностью изменить их, чтобы сгенерировать все строки одновременно в одном запросе.

Вопрос: Учитывая предоставленную информацию, как я могу исправить свои запросы, чтобы сгенерировать всю эту информацию из одного запроса?

Ответы [ 5 ]

1 голос
/ 16 мая 2019

Это похоже на проблему топ-н-на-группу.

Один из способов сделать это - использовать боковое соединение.

Убедитесь, что у вас есть индекс для таблицы reviews для (uid, date). Сводный индекс. Один указатель на два столбца в этом порядке.

Примерно так:

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}'
;
0 голосов
/ 16 мая 2019

Я также получил ответ примерно в то же время, что и Владимир Баранов , но я также опубликую свою версию.Я не обещаю, что это будет красиво:

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
0 голосов
/ 14 мая 2019

После изучения размещенной вами схемы я использовал 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. Я предполагаю, что упомянутое вами «неуспешное завершение» не имеет ничего общего с запросом как таковым, а скорее является временным сбоем используемого вами соединения с БД. Обратите внимание, что код обновлен с учетом предложения Михаила Шишкова об использовании параметров.

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

Буду признателен, если вы признаете ответ приемлемым.

0 голосов
/ 15 мая 2019

Следуя ответу Маноса, не уверен, что я понимаю, почему вам нужно ограничить каждый фид вообще

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

Если ваша проблема заключается в том, что ваш запрос приводит только к одной строке, вы должны удалить это где uid = , чтобы получить результаты для всех идентификаторов.

0 голосов
/ 14 мая 2019

Это должно работать нормально.

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

Вам не нужно делать равное сравнение, как в запросах на совпадение с одним истинным значением.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...