DROP TABLE IF EXISTS ratings;
DROP TABLE IF EXISTS dummy;

CREATE TABLE ratings
(
 id      INTEGER NOT NULL,
 name    CHAR(1) NOT NULL,
 PRIMARY KEY (id)
);

CREATE TABLE dummy
(
 id      INTEGER NOT NULL,
 PRIMARY KEY (id)
);

INSERT INTO ratings VALUES (1,  'A');
INSERT INTO ratings VALUES (5,  'B');
INSERT INTO ratings VALUES (3,  'B');
INSERT INTO ratings VALUES (4,  'D');
INSERT INTO ratings VALUES (2,  'E');

INSERT INTO dummy VALUES (1);


/****************************************************************/
/* Aufgabe: Nummerieren Sie die Tabelle Ratings gemäß den       */
/*          Platzierungen durch:                                */
/*           Rating A = 1. Platz                                */
/*           Rating B = 2. Platz                                */
/*           Rating D = 4. Platz (da es 2 zweite Plätze gibt)   */ 
/*           Rating E = 5. Platz                                */
/****************************************************************/

/****************************************************************/
/* Erste fehlerhafte Loesung:                                   */
/*   Zweimal Platz 3 anstelle von Platz 2                       */
/****************************************************************/

SELECT   r2.id, r2.name, COUNT(r1.id) AS pos
FROM     ratings r1, ratings r2
WHERE    r1.name <= r2.name
GROUP BY r2.id, r2.name
ORDER BY pos, id
;

-- So sieht die zugrundeliegende Tabelle ohne Grouping aus:
SELECT   r2.id, r2.name, r1.id as r1id
FROM     ratings r1, ratings r2
WHERE    r1.name <= r2.name
ORDER BY name, r1id
;

/****************************************************************/
/* Zweite ebenfalls fehlerhafte Loesung:                        */
/*   Platz 1 fehlt (da die zugehoerige Gruppe leer ist)         */
/****************************************************************/

SELECT   r2.id, r2.name, COUNT(r1.id) + 1 AS pos
FROM     ratings r1, ratings r2
WHERE    r1.name < r2.name
GROUP BY r2.id, r2.name
ORDER BY pos, id
;

-- So sieht die zugrundeliegende Tabelle ohne Grouping aus:
SELECT   r2.id, r2.name, r1.id as r1id
FROM     ratings r1, ratings r2
WHERE    r1.name < r2.name
ORDER BY name, r1id
;

/****************************************************************/
/* Erste korrekte Loesung:                                      */
/*   Platz 1 wird separat berechnet.                            */
/****************************************************************/


(SELECT   r2.id, r2.name, COUNT(r1.id) + 1 AS pos
 FROM     ratings r1, ratings r2
 WHERE    r1.name < r2.name
 GROUP BY r2.id, r2.name
)
UNION
(SELECT   r2.id, r2.name, 1 AS pos
 FROM     ratings r2
 WHERE    r2.name = (SELECT min(r1.name) FROM ratings r1)
)
ORDER BY pos, id
;

-- Berechnung des ersten Platzes:
SELECT   r2.id, r2.name, 1 AS pos
FROM     ratings r2
WHERE    r2.name = (SELECT min(r1.name) FROM ratings r1)
;

/****************************************************************/
/* Zweite korrekte Loesung:                                     */
/*   Es wird ein kuenstlicher erster Platz eingefuegt, der      */
/*   dann vom zweiten fehlerhaften Algorithmus ignoriert wird.  */
/****************************************************************/

SELECT   r2.id, r2.name, COUNT(r1.id) AS pos
FROM     ((SELECT id, name FROM ratings)
          union
          (SELECT 0, '@'   FROM dummy) 
         ) r1,
         ratings r2
WHERE    r1.name < r2.name
GROUP BY r2.id, r2.name
ORDER BY pos, id
;

-- So sieht die zugrundeliegende Tabelle ohne Grouping aus:
SELECT   r2.id, r2.name, r1.id as r1id 
FROM     ((SELECT id, name FROM ratings) 
          union 
          (SELECT 0, '@'   FROM dummy) 
         ) r1,
         ratings r2
WHERE    r1.name < r2.name
ORDER BY name, r1id
;

/****************************************************************/
/* Dritte korrekte Loesung:                                     */
/*   Es werden mit dem ersten fehlerhaften Algorithmus die      */
/*   die schlechtesten Werte ermittel. Diese werden in um-      */
/*   gekehrter Reihenfolge durchnummeriert.                     */
/****************************************************************/

SELECT   r2.id, r2.name, 
         (SELECT COUNT(*)+1 FROM ratings) - COUNT(r1.id) AS pos
FROM     ratings r1, ratings r2
WHERE    r1.name >= r2.name
GROUP BY r2.id, r2.name
ORDER BY pos, id
;

-- So sieht die zugrundeliegende Tabelle ohne Grouping aus:
SELECT   r2.id, r2.name, r1.id as r1id
FROM     ratings r1, ratings r2
WHERE    r1.name >= r2.name
ORDER BY name, r1id
;

/****************************************************************/
/* Vierte korrekte Loesung:                                     */
/*   Es wird fuer jedes Element direkt die Anzahl der besseren  */
/*   Elemente ermittelt.                                        */
/****************************************************************/

SELECT  r2.id, r2.name,
        (SELECT COUNT(*)+1
         FROM   ratings r1
         WHERE  r1.name < r2.name
        ) AS pos
FROM     ratings r2
ORDER BY pos, id
;

/****************************************************************/
/* Fünfte Loesung:                                              */
/*   Verwendung von Windowing Functions (seit SQL:2003).        */
/*   Wesentlich effizienter als alle vorangegangenen Versionen: */
/*   O(n) an Stelle von O(n^2).                                 */
/****************************************************************/

-- fehlerhafte Platzierung
SELECT id, name,  COUNT (*) OVER (ORDER BY name) AS pos
FROM   ratings
ORDER BY pos, id
;

-- korrekt Platzierung
SELECT id, name,  (SELECT COUNT(*)+1 FROM ratings) 
                - COUNT (*) OVER (ORDER BY name DESC) AS pos
FROM   ratings
ORDER BY pos, id
;

SELECT id, name,  
       RANK() OVER (ORDER BY name) AS pos
FROM   ratings
ORDER BY pos, id
;

-- weitere Durchnummerierungsoperatoren
SELECT id, name,  
       RANK() OVER (ORDER BY name) AS pos,
       DENSE_RANK() OVER (ORDER BY name) AS dense_rank,
       ROW_NUMBER() OVER (ORDER BY name) AS nr
FROM   ratings
ORDER BY pos, id
;

/****************************************************************/
/* Siebte Lösung: Mit Rekursion                                 */
/****************************************************************/

-- fehlerhafte Platzierung
WITH RECURSIVE standings (id, name, pos)
AS
(
  SELECT id, name, 1
  FROM   ratings
  WHERE  name = (SELECT MIN(name) FROM ratings)
  UNION
  SELECT r.id, r.name, s.pos+1
  FROM   ratings r, standings s
  WHERE  r.name = (SELECT MIN(r2.name) 
                   FROM   ratings r2
                   WHERE  r2.name > s.name
                  )
)
SELECT id, name, pos
FROM standings
ORDER BY pos, id
LIMIT 1000
;

-- korrekte Platzierung
WITH RECURSIVE standings (id, name, pos)
AS
(
  SELECT id, name, CAST (1 AS BIGINT)
  FROM   ratings
  WHERE  name = (SELECT MIN(name) FROM ratings)
  UNION
  SELECT r.id, r.name,
         s.pos + (SELECT COUNT(*) FROM ratings r2 WHERE r2.name = s.name)
  FROM   ratings r, standings s
  WHERE  r.name = (SELECT MIN(r3.name)
                   FROM   ratings r3
                   WHERE  r3.name > s.name
                  )
)
SELECT id, name, pos
FROM standings
ORDER BY pos, id
LIMIT 1000
;

/****************************************************************/
/* Achte, unsaubere, aber in PostgreSQL funktionierende        */
/* Loesung. Diese Loesung verwendet nicht-funktionale und       */
/* nicht-standard-konforme Features:                            */
/* - einen Sequence-Generator                                   */
/* - ORDER BY ist in Subqueries nicht erlaubt. Dies ist         */
/*   hier aber notwendig, um die Elemente in korrekter          */
/*   Reihenfolge durchzumummerieren.                            */
/*                                                              */
/* Diese Loesung hat gegenueber den Loesungen 1 bis 4           */
/* allerdings den Vorteil, dass sie wesentlich effizienter      */
/* ist:                                                         */
/*   O(n) an Stelle von O(n^2).                                 */
/*                                                              */
/* VERALTET: Windowing ist genauso effizient, aber standard-    */
/*           konform.                                           */
/****************************************************************/

-- fehlerhafte Platzierung
CREATE TEMPORARY SEQUENCE seq_counter_1;

SELECT   r.id, r.name, nextval('seq_counter_1') AS pos
FROM     ratings r
ORDER BY pos, id
;

-- korrekte Platzierung
CREATE TEMPORARY SEQUENCE seq_counter_2;

SELECT r.id, r.name, tmp2.pos2 AS pos
FROM   ratings r,
       (SELECT   MIN(pos1), name1
        FROM     (SELECT   nextval('seq_counter_2') as pos , r.name
                  FROM     ratings r
                  ORDER BY name
                 ) tmp1(pos1, name1)
        GROUP BY name1
       ) tmp2(pos2, name2)
WHERE  r.name = tmp2.name2
ORDER BY pos, id
;
