none
Suche einen Tipp zur Optimierung eine SQL abfrage RRS feed

  • Frage

  • Hallo,

    ich brauche mal eure Hilfe. Es geht um eine Abfrage, die sich Werte aus eine anderen Abfrage holt um die in einer CASE WHEN zu verwenden.

    Hier mal ein einfacher Testaufbau:

    --Test Tabellen erstellen
    create table dbo.Tabelle_A (ArtikelNr int primary Key)
    create table dbo.Tabelle_B (ArtikelNr int, Preis smallmoney, primary Key(ArtikelNr))
    
    --Abfrage
    select *,
    case when 
    	(select top 1 Preis from Tabelle_B b where b.ArtikelNr=a.ArtikelNr) > 0 
    then 
    	(select top 1 Preis from Tabelle_B b where b.ArtikelNr=a.ArtikelNr) 
    else 
    	999 
    end
    from Tabelle_A a

    Gehen wir mal davon aus, dass die Tabelle_A die Artikel enthält. Über eine deutlich komplexere Struktur müssen jetzt aus Tabelle_B die Preise ermittelt werden. Vorweg: Die Struktur ist sehr komplex. JOINs sind nicht möglich, da es 0 bis x Möglichkeiten gibt. 

    Dazu wird eine Unterabfrage gemacht. Diese ermittelt den Preis. Wenn der Preis gültig ist, wird der Preis neu ermittelt und in der Abfrage zurückgegeben - sonst der Listenpreis. So wie ich das in der CASE WHEN abfrage geschrieben habe, wird die Unterabfrage aber zweimal ausgeführt (s.Ablaufplan). Das ist bei so großen, sehr komplexen Abfragen ein Problem.

    Die Frage ist also, ob ich die Abfrage irgendwie so ändern kann, dass ich den Wert aus Tabelle_B nur einmal abfragen muss.

    In einer gespeicherten Prozedure würde ich das über eine Variable machen... Die Frage ist ob das in einer Abfrage auch geht?


    Donnerstag, 7. Januar 2016 17:23

Antworten

  • Hallo Joachim,
    verwende doch eine CTE vorab. Hier ein Beispiel dazu. Das Statement mit CTE macht nur halb so viel IO wie das erste. In der CTE kannst Du auch sehr übersichtlich Deine komplexeren Abfragen unterbringen.

    --Test Tabellen erstellen
    create table dbo.Tabelle_A (ArtikelNr int primary Key)
    create table dbo.Tabelle_B (ArtikelNr int, Preis smallmoney, primary
    Key(ArtikelNr))
    go
    
    Insert into Tabelle_A(ArtikelNr) values (1), (2), (3);
    Insert into Tabelle_B(ArtikelNr, Preis) values (1, 10), (2, 20), (3, 30);
    
    Set Statistics IO ON;
    
    --Abfrage
    select *,
    case when
    (select top 1 Preis from Tabelle_B b where b.ArtikelNr=a.ArtikelNr) > 0
    then
    (select top 1 Preis from Tabelle_B b where b.ArtikelNr=a.ArtikelNr)
    else
    999
    end
    from Tabelle_A a
    ;
    
    -- CTE
    With Vorberechnung
    as
    (select Preis, ArtikelNr
         from Tabelle_B
    )
    select *,
    case when b.Preis > 0 then b.Preis
    else 999
    end as MeinPreis
    from Tabelle_A a
    Inner Join Vorberechnung b
    on a.ArtikelNr = b.ArtikelNr
    ;
    go
    drop table dbo.Tabelle_A;
    drop table dbo.Tabelle_B;

     Einen schönen Tag noch,
    Christoph
    --
    Data Platform MVP - http://www.insidesql.org/blogs/cmu

    Freitag, 8. Januar 2016 08:20
    Beantworter

Alle Antworten

  • Hallo Joachim,

    ich sehe nicht ganz, warum die Struktur von Tabelle B zu komplex für einen Join sein soll. Du müsstest die Query für deine Case-When auch formulieren.
    Warum kannst Du das vereinfachte Resultset so wie in der Abfrage oben nicht anjoinen?

    Aber okay, gehen wir mal davon aus, dass Joinen wirklich nicht geht. Du hast oben eine "TOP" clause ohne order by verwendet, bei der Möglichkeit von X Preisen, wie stellst du sicher, dass es der richtige Preis ist, der ermittelt wird?

    Du kannst auch in Abfragen Variblen benutzen. Wenn du eine Lösung hast, um es in einer Procedure zu lösen, schreib die Prozedure und lass das "Create Procedure as" weg....

    Vielleicht vereinfache ich das Thema jetzt auch zu sehr. Wenn du dir eine Logik überlegen kannst, um eine Variable mit (genau) einem Wert zu füllen, denke ich, dass dieses Query, was den Wert ermittelt, auch als join funktionieren müsste:

    --Abfrage
    select *,
    case when 
    	b.Preis > 0 
    then 
    	b.Preis
    else 
    	999 
    end
    from Tabelle_A a
    join 
    (select top 1 Preis from Tabelle_B) b
    on b.ArtikelNr=a.ArtikelNr


    Donnerstag, 7. Januar 2016 19:25
  • Hallo Joachim,
    verwende doch eine CTE vorab. Hier ein Beispiel dazu. Das Statement mit CTE macht nur halb so viel IO wie das erste. In der CTE kannst Du auch sehr übersichtlich Deine komplexeren Abfragen unterbringen.

    --Test Tabellen erstellen
    create table dbo.Tabelle_A (ArtikelNr int primary Key)
    create table dbo.Tabelle_B (ArtikelNr int, Preis smallmoney, primary
    Key(ArtikelNr))
    go
    
    Insert into Tabelle_A(ArtikelNr) values (1), (2), (3);
    Insert into Tabelle_B(ArtikelNr, Preis) values (1, 10), (2, 20), (3, 30);
    
    Set Statistics IO ON;
    
    --Abfrage
    select *,
    case when
    (select top 1 Preis from Tabelle_B b where b.ArtikelNr=a.ArtikelNr) > 0
    then
    (select top 1 Preis from Tabelle_B b where b.ArtikelNr=a.ArtikelNr)
    else
    999
    end
    from Tabelle_A a
    ;
    
    -- CTE
    With Vorberechnung
    as
    (select Preis, ArtikelNr
         from Tabelle_B
    )
    select *,
    case when b.Preis > 0 then b.Preis
    else 999
    end as MeinPreis
    from Tabelle_A a
    Inner Join Vorberechnung b
    on a.ArtikelNr = b.ArtikelNr
    ;
    go
    drop table dbo.Tabelle_A;
    drop table dbo.Tabelle_B;

     Einen schönen Tag noch,
    Christoph
    --
    Data Platform MVP - http://www.insidesql.org/blogs/cmu

    Freitag, 8. Januar 2016 08:20
    Beantworter
  • Hallo Stefan,

    den Gedanken hatte ich auch schon aber eine Abfrage über den Join ist nur dann möglich, wenn die Werte die Du abfragst im ResultSet enthalten sind.

    Im Test von Deinem Beispiel bekomme ich:

    select *, case when b.Preis > 0 then b.Preis else 999 end from Tabelle_A a join (select top 1 Preis from Tabelle_B) b on b.ArtikelNr=a.ArtikelNr
    Den Fehler

    Ungültiger Spaltenname 'ArtikelNr'.

    das ist auch Logisch ... wie gesagt, ist die eigentliche Abfrage viel komplizierter. Es geht mir um die generelle Vorgehensweise ...

    Freitag, 8. Januar 2016 11:31
  • Hey Christoph,

    sehr gut ... das ist das was ich suche!

    Im direkten Vergleich der alten gegen die neue Abfrage braucht die mit CTE nur 40% zu 60% bei der alten ...

    PERFEKT !!!

    Freitag, 8. Januar 2016 11:37