Je travaille avec l'ensemble de données Yelp disponible en ligne. J'essaye d'optimiser ma requête depuis des jours. Pour le schéma que je vais énumérer ci-dessous, je dois construire une requête pour fournir ce qui suit:
Voici le schéma:
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}')
Voici un exemple de la sortie souhaitée:
Pour chacun des amis de l'utilisateur, j'affiche ce qui suit:
Actuellement, c'est la seule "solution" avec laquelle j'ai eu du succès.
Étape 1: Obtenez une liste de tous les ID pour chacun des amis de l'utilisateur.
SELECT fid from friends where uid = '{userId}'
Ceci renvoie une liste de tous les ID utilisateur pour chacun des amis de l'utilisateur. Donc, j'ai essentiellement une liste d'identifiants d'amis.
Étape 2: Avec ces informations, j'exécute une boucle foreach dans mon programme sur cette liste. Pour chaque itération de la liste des identifiants d'amis, j'exécute la requête ci-dessous et je fournis l'identifiant d'ami temporaire pour l'itération actuelle de la boucle:
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)
);
Pour CHAQUE fois, je l'exécute pendant boucle, j'obtiens une seule ligne de sortie pour ce que je désire, comme ceci:
C'est super ... sauf que je dois exécuter cette requête pour chacun des amis de l'utilisateur. Ceci est extrêmement coûteux.
Objectif: J'essaie de combiner ces 2 requêtes, ou de les réorganiser complètement, pour générer toutes les lignes à la fois dans une seule requête.
Question: Compte tenu des informations fournies, comment puis-je corriger mes requêtes pour générer toutes ces informations à partir d'une seule requête?
5 Réponses :
Cela devrait fonctionner correctement.
SELECT name FROM employees as E WHERE E.uid IN (SELECT uid FROM employees WHERE name = 'John')
Vous n'avez pas besoin de faire une comparaison égale comme dans les requêtes de correspondance de valeur de vérité unique.
Merci, mais je pense que mon exemple était trop simple. Pouvez-vous s'il vous plaît regarder ma question modifiée? J'ai ajouté mon code réel et mon problème.
Après avoir examiné le schéma que vous avez publié, j'ai utilisé MySQL pour créer la base de données et remplir les tables avec les exemples de données suivants:
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
Cela m'a permis de vérifier que la solution d'origine que j'ai proposée était corriger en exécutant la requête dans MySQL Workbench. Je suppose que «l'échec de la fin» que vous mentionnez n'a rien à voir avec la requête en soi, mais est plutôt une défaillance temporaire de l'API de connexion DB que vous utilisez. Notez que le code est mis à jour pour incorporer la proposition de Mihail Shishkov pour l'utilisation des paramètres.
-- 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)));
Sur la base des exemples de données et en utilisant « utilisateur1 » comme valeur pour le paramètre @UID, les résultats de la requête sont:
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
De plus, je suppose que l'amitié est une relation bidirectionnelle dans le contexte de votre schéma (comme dans le monde réel), ce qui signifie que l'amitié entre 'user1' et 'user2' ne doit être définie que par un seul enregistrement dans la table 'friends' avec les valeurs ('user1', 'user2') et le reverse em > ('user2', 'user1') n'est pas nécessaire. Donc, par souci d'exhaustivité, vous pouvez utiliser la requête suivante:
-- 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)));
Maintenant, en utilisant « user2 » comme valeur pour @UID et la version étendue de la requête, nous obtenons les résultats suivants:
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');
J'apprécierais que vous reconnaissiez la réponse comme acceptable.
Salut Manos. Votre requête n'est jamais terminée (je ne sais pas pourquoi). J'ai soigneusement édité ma réponse. J'ai inclus le schéma et remplacé le code par des informations que je pense être beaucoup plus pertinentes. Pouvez-vous jeter un autre coup d'œil?
Ne concaténez jamais de chaînes pour générer des requêtes SQL. Utilisez plutôt des paramètres. stackoverflow.com/questions/3216233/...
@MihailShishkov Merci, je vais jeter un coup d'œil mais cela ne me résout pas le problème.
Je suis désolé que la solution modifiée ne fonctionne toujours pas. Ma base de données contient quelques millions d'enregistrements parmi toutes les tables. Je ne sais pas si c'est une requête super coûteuse ou quoi, mais cela ne se termine pas. (Même ma requête qui renvoie une seule ligne, quand je l'exécute 30 fois, elle se terminera en 5 secondes environ ...)
De plus, j'utilise PostgresQL, comme indiqué dans la balise. Je ne pouvais pas utiliser la fonction SET comme spectacle, mais j'ai essayé d'exécuter votre requête avec un UID codé en dur remplacé par votre variable.
Pour commencer, je n'ai pas pris en compte le fait que vous utilisez PostgreSQL. Dans tous les cas, l'affectation de variable (SET) n'a été utilisée que dans le cadre de MySQL Workbench afin de simuler le comportement de votre application.
De plus, la taille de votre base de données affecte clairement les performances. Vous avez certainement besoin d'index pour les clés primaires et étrangères si vous ne les avez pas déjà. Vous devez également envisager d'utiliser INT au lieu de VARCHAR pour le type de données de vos clés.
Suite à la réponse de Manos, je ne suis pas sûr de comprendre pourquoi vous devez limiter chaque fid du tout
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));
Si votre problème est que votre requête ne donne qu'une seule ligne, vous devez supprimer où uid = pour obtenir des résultats pour tous les uid.
Cela ressemble à un problème de top n par groupe.
Une façon de le faire est d'utiliser une jointure latérale.
Assurez-vous d'avoir un index sur la table reviews sur (uid, date) . Un indice composite. Un index sur deux colonnes dans cet ordre.
Quelque chose comme ceci:
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}'
;
Requête
CREATE INDEX IX_uid_date ON reviews (uid, date);
Absolument tragiquement, après avoir essayé de comprendre cela pendant d'innombrables heures, je l'ai compris ... littéralement environ 5 minutes avant votre publication. J'ai vérifié vos résultats et ils sont valides, alors je vous attribuerai la prime.
@Birdman, félicitations! Il n'y a rien de tragique lorsque vous apprenez quelque chose par vous-même. Cela prend toujours du temps lorsque vous rencontrez un problème pour la première fois, mais vous avez tendance à mieux comprendre et à vous souvenir de la solution lorsque vous la résolvez vous-même. Je vous recommande de laisser la prime ouverte pendant la durée maximale, car vous pourriez obtenir d'autres réponses meilleures.
Je suis également arrivé à une réponse à peu près en même temps que Vladimir Baranov , mais je publierai ma version ainsi que. Je ne promets pas que ce soit joli:
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
Il existe souvent plusieurs façons d'écrire une requête SQL qui produit le même résultat. Je vous recommande d'essayer toutes les requêtes proposées, de vérifier lesquelles d'entre elles produisent un résultat correct et de comparer leurs performances sur vos données et votre matériel réels. Différentes requêtes peuvent avoir des performances radicalement différentes, même lorsqu'elles produisent le même résultat.
Vous n'avez pas besoin ou ne voulez pas d'une expression
E.uid = ???????dans votre clauseWHEREpour votre deuxième requête.Vous ne savez pas ce que vous demandez. Quelle langue utilisez-vous? Pouvez-vous ajouter des exemples de données?
@TimBiegeleisen J'ai ajouté le code actuel, pouvez-vous jeter un œil?
@Nick Salut Nick, j'ai ajouté mon code réel car je pense que mon exemple était trop simple. Pouvez-vous jeter un oeil?
Veuillez lire comment utiliser les déclarations préparées. Votre code actuel présente de nombreux problèmes.
@TimBiegeleisen Comment ça? C'est un code minimal que je peux fournir sans coller plus de 100 lignes de code. Même pour ceux qui ne connaissent pas la syntaxe de Npgsql, je pense que ce qui se passe devrait être assez clair.
@Birdman Pouvez-vous joindre un schéma de base de données et des données dans db-fiddle ?