2009-03-02 7 views
8

Ich habe zwei Tabellen mit einer 1: n-Beziehung: "content" und "versioned-content-data" (z. B. eine Artikelentität und alle Versionen dieses Artikels) . Ich möchte eine Ansicht erstellen, die die Top-Version jedes "Inhalts" anzeigt.So konvertieren Sie eine SQL-Unterabfrage in einen Join

Derzeit verwende ich diese Abfrage (mit einer einfachen Unterabfrage):

 
SELECT 
    t1.id, 
    t1.title, 
    t1.contenttext, 
    t1.fk_idothertable 
    t1.version 
FROM mytable as t1 
WHERE (version = (SELECT MAX(version) AS topversion 
        FROM mytable 
        WHERE (fk_idothertable = t1.fk_idothertable)))

Die Unterabfrage ist eigentlich eine Abfrage an die gleiche Tabelle, die die höchste Version eines bestimmten Elements extrahiert. Beachten Sie, dass die versionierten Elemente dasselbe fk_idothertable haben.

In SQL Server habe ich versucht, eine indizierte Sicht diese Abfrage zu erstellen, aber es scheint, dass ich nicht in der Lage bin, da Unterabfragen sind in indizierten Sichten nicht erlaubt. Also ... hier ist meine Frage ... Können Sie sich eine Möglichkeit vorstellen, diese Abfrage in eine Art Abfrage mit JOINs zu konvertieren?

Es scheint, wie indizierte Sichten nicht enthalten:

  • Subqueries
  • allgemeine Tabellenausdrücke
  • abgeleitete Tabellen
  • HAVING-Klauseln

Ich bin verzweifelt. Alle anderen Ideen sind willkommen :-)

Vielen Dank!

+0

ist Ihre Unterabfrage korrekt? Ich sehe nur eine Tabelle referenziert –

+0

Ja, ist eine Unterabfrage zu der gleichen Tabelle, die die maximale Version eines Elements extrahiert, die die gleiche fk_idothertable – sachaa

+0

btw ... fk_idothertable ist der Fremdschlüssel der Master-Tabelle – sachaa

Antwort

13

Dies wird wahrscheinlich nicht Hilfe, wenn die Tabelle bereits in Produktion ist, aber die richtige Art, dies zu modellieren, besteht darin, Version = 0 die permanente Version zu machen und immer die Version des älteren Materials zu inkrementieren.Also, wenn Sie eine neue Version einsetzen würden Sie sagen:

UPDATE thetable SET version = version + 1 WHERE id = :id 
INSERT INTO thetable (id, version, title, ...) VALUES (:id, 0, :title, ...) 

Dann ist diese Abfrage nur

wäre
SELECT id, title, ... FROM thetable WHERE version = 0 

keine Subqueries, keine MAX-Aggregation. Sie wissen immer, was die aktuelle Version ist. Sie müssen nie Max (Version) auswählen, um den neuen Datensatz einzufügen.

+1

Sehr schlaue Idee. Danke Joe! – sachaa

+0

Sehr ordentlich! Gut gemacht. –

-1

So ... Ich nehme an, dass die 'mytable' in der Unterabfrage eine andere tatsächliche Tabelle war ... also nannte ich es mytable2. Wenn es die gleiche Tabelle ist, dann wird das immer noch funktionieren, aber dann stelle ich mir vor, dass fk_idothertable nur 'id' ist.


SELECT 
    t1.id, 
    t1.title, 
    t1.contenttext, 
    t1.fk_idothertable 
    t1.version 
FROM mytable as t1 
    INNER JOIN (SELECT MAX(Version) AS topversion,fk_idothertable FROM mytable2 GROUP BY fk_idothertable) t2 
     ON t1.id = t2.fk_idothertable AND t1.version = t2.topversion 

hoffe, das hilft

+0

Die Unterabfrage ist eigentlich eine Abfrage an die gleiche Tabelle, die die höchste Version eines bestimmten Elements extrahiert. Beachten Sie, dass dieselben versionierten Elemente dasselbe fk_idothertable haben. – sachaa

0

Ich weiß nicht, wie effizient dies wäre, aber:

 
SELECT t1.*, t2.version 
FROM mytable AS t1 
    JOIN (
     SElECT mytable.fk_idothertable, MAX(mytable.version) AS version 
     FROM mytable 
    ) t2 ON t1.fk_idothertable = t2.fk_idothertable 
+0

Sie brauchen eine Gruppe von. –

+0

Aber immer noch ... sogar mit einer GROUP BY diese Abfrage gibt im Grunde alle Listenelemente von Mytable + die maximale Version jedes Elements zurück. Es gibt nicht nur die Elemente mit der höchsten Version zurück. – sachaa

0

Sie in der Lage sein könnte, die MAX eine Tabelle alias die Gruppe tut zu machen.

Es könnte wie folgt aussehen:

SELECT 
    t1.id, 
    t1.title, 
    t1.contenttext, 
    t1.fk_idothertable 
    t1.version 
FROM mytable as t1 JOIN 
    (SELECT fk_idothertable, MAX(version) AS topversion 
    FROM mytable 
    GROUP BY fk_idothertable) as t2 
ON t1.version = t2.topversion 
3

Vielleicht so etwas wie?

SELECT 
    t2.id, 
    t2.title, 
    t2.contenttext, 
    t2.fk_idothertable, 
    t2.version 
FROM mytable t1, mytable t2 
WHERE t1.fk_idothertable == t2.fk_idothertable 
GROUP BY t2.fk_idothertable, t2.version 
HAVING t2.version=MAX(t1.version) 

Nur eine wilde Vermutung ...

+0

Danke für Ihre Antwort. Ich mochte die Eleganz Ihrer Lösung, aber wenn ich sie ausführe, bekomme ich: Die Spalte 'mytable.id' ist in der Auswahlliste ungültig, weil sie weder in einer Aggregatfunktion noch in der GROUP BY-Klausel enthalten ist. Irgendwelche anderen Ideen? – sachaa

+0

Fügen Sie einfach alle Felder in SELECT zu GROUP BY - AFAIK hinzu, die der Standard benötigt, aber es gibt DBMS, die es nicht benötigen. – jpalecek

+0

Wenn ich das tue, wenn der Titel zwischen den Versionen wechselt, wird dieser Gegenstand (mit demselben fk_idothertable) zweimal in den Ergebnissen erscheinen (anderer Titel + andere Version) – sachaa

0

Ich denke FerranB der Nähe war, aber nicht ganz haben die Gruppierung rechts:

with 
latest_versions as (
    select 
     max(version) as latest_version, 
     fk_idothertable 
    from 
     mytable 
    group by 
     fk_idothertable 
) 
select 
    t1.id, 
    t1.title, 
    t1.contenttext, 
    t1.fk_idothertable, 
    t1.version 
from 
    mytable as t1 
    join latest_versions on (t1.version = latest_versions.latest_version 
     and t1.fk_idothertable = latest_versions.fk_idothertable); 

M

+0

Danksagung Mark. Diese Abfrage gibt zwar die korrekten Ergebnisse zurück, da sie aber einen "gemeinsamen Tabellenausdruck" verwendet nicht gültig für mich, um eine indizierte Sicht in SQL Server zu erstellen. – sachaa

0
If SQL Server accepts LIMIT clause, I think the following should work: 
SELECT 
    t1.id, 
    t1.title, 
    t1.contenttext, 
    t1.fk_idothertable 
    t1.version 
FROM mytable as t1 ordery by t1.version DESC LIMIT 1; 
(DESC - For descending sort; LIMIT 1 chooses only the first row and 
DBMS usually does good optimization on seeing LIMIT).