2016-03-19 23 views
1

Ich brauche diese Abfrage in postgresql 9.3 schreiben:Wie kann diese Abfrage optimiert werden?

  1. Liste der beliebtesten Film in jedem Land. Der beliebteste Film/die beliebtesten Filme ist derjenige, der die höchste durchschnittliche Bewertung über alle Nutzer dieses Landes hat. Im Falle eines Gleichstandes, geben Sie alle Filme alphabetisch geordnet zurück. (2 Spalten)

Tabellen benötigt:

CREATE TABLE movie (
id integer, 
name varchar(200), 
year date 
); 

CREATE TABLE userProfile (
userid varchar(200), 
gender char(1), 
age integer, 
country varchar(200), 
registered date 
); 

CREATE TABLE ratings (
mid integer, 
userid varchar(200), 
rating integer 
); 

CREATE INDEX movie_id_idx ON movie (id); 
CREATE INDEX userProfile_userid_idx ON userProfile (userid); 
CREATE INDEX ratings_userid_idx ON ratings (userid); 
CREATE INDEX ratings_mid_idx ON ratings (mid); 
CREATE INDEX ratings_userid_mid_idx ON ratings (userid, mid); 

Hier ist mein query:

CREATE TEMP TABLE tops AS SELECT country, name 
FROM ratings AS r INNER JOIN userProfile AS u 
ON r.userid=u.userid 
INNER JOIN movie AS m ON m.id = r.mid LIMIT 0; 

~10 min 
CREATE TEMP TABLE avg_country AS 
SELECT country, r.mid, AVG(rating) AS rate 
FROM ratings AS r INNER JOIN userProfile AS u 
ON r.userid=u.userid 
GROUP BY country, r.mid; 

~8 min 
DO $$ 
DECLARE arrow record; 
BEGIN 
CREATE TABLE movie_names AS SELECT id, name FROM movie; 
FOR arrow IN SELECT DISTINCT country FROM userProfile ORDER BY country 
LOOP 
    CREATE TABLE movies AS SELECT mid FROM (SELECT MAX(rate) AS m_rate FROM avg_country 
    WHERE country=arrow.country) AS max_val CROSS JOIN LATERAL 
    (SELECT mid FROM avg_country 
    WHERE country=arrow.country AND rate=max_val.m_rate) AS a; 
    WITH names AS (DELETE FROM movie_names AS m 
    WHERE m.id IN (SELECT mid FROM movies) RETURNING name) 
    INSERT INTO tops 
    SELECT arrow.country, name FROM names ORDER BY name; 
    DROP TABLE movies; 
END LOOP; 
DROP TABLE movie_names; 
END$$; 

SELECT * FROM tops; 
DROP TABLE tops, avg_country; 

Vielen Dank im Voraus)

+1

Vor allem: Erstellen Sie keine Tabelle innerhalb einer Schleife. – Abelisto

Antwort

0

Dies ist auf kordirko s Antwort ähnlich, aber mit einer weniger Unterabfrage:

select country, movie_name, avg_rating 
from (select u.country, m.name as movie_name, avg(r.rating) as avg_rating 
      rank() over (partition by u.country order by avg(r.rating) desc) as seqnum 
     from userProfile u join 
      ratings r 
      on u.userid = r.userid join 
      movie m 
      on r.mid = m.id 
     group by u.country, m.id -- `name` is not needed here because id is unique 
    ) uc 
where seqnum = 1; 

Alternativ möchten, wenn Sie die Liste auf eine Zeile pro Land zu bekommen:

select country, string_agg(movie_name, '; ') as most_popular_movies 
from (select u.country, m.name as movie_name, avg(r.rating) as avg_rating 
      rank() over (partition by u.country order by avg(r.rating) desc) as seqnum 
     from userProfile u join 
      ratings r 
      on u.userid = r.userid join 
      movie m 
      on r.mid = m.id 
     group by u.country, m.id -- `name` is not needed here because id is unique 
    ) uc 
where seqnum = 1 
group by country; 
+0

Vielen Dank! Ich wusste nichts über Fensterfunktionen. Aber der Name sollte trotzdem in "group by" sein. –

0

Verwenden Sie eine Ebene, alt- altmodisches SQL - es ist alt, aber Gold.

WITH q AS (
    SELECT *, 
     dense_rank() over (partition by country order by avg_rating desc) rank 
    FROM (
     select u.country, m.name movie_name, avg(r.rating) avg_rating 
     from userProfile u 
     join ratings r on u.userid = r.userid 
     join movie m on r.mid = m.id 
     group by u.country, m.name 
) xx) 
SELECT country, movie_name 
FROM q WHERE rank <= 1 
Verwandte Themen