2010-06-20 14 views
6

Dies ist eine Frage der SQL-Effizienz.SQL-Subabfragen - gibt es einen besseren Weg

Vor einiger Zeit musste ich eine Sammlung von Abfragen schreiben, um Daten aus einem ERP-System zu ziehen. Die meisten von diesen waren einfach genug, aber einer von ihnen führte zu einer ziemlich ineffizienten Abfrage und sie hat mich seitdem nervt, da es einen besseren Weg geben muss.

Das Problem ist nicht komplex. Sie haben Zeilen mit Verkaufsdaten. In jeder Zeile haben Sie unter anderem die Menge, den Verkaufspreis und den Verkäufercode.

Provision wird basierend auf einer abgestuften Skala bezahlt. Je mehr sie verkaufen, desto besser ist die Provision. Die Schritte können 1000, 10000, 10000 $ usw. sein. Das Problem der realen Welt ist komplexer, aber das ist es im Wesentlichen.

Die einzige Art, wie ich dies zu tun fand, war, so etwas zu tun (natürlich nicht die wirkliche Abfrage)

select qty, price, salesman, 
    (select top 1 percentage from comissions 
    where comisiones.salesman = saleslines.salesman 
    and saleslines.qty > comisiones.qty 
    order by comissiones.qty desc 
) percentage 
from saleslines 

dies in der richtigen Kommission ergibt, ist aber horrend schwer.

Gibt es einen besseren Weg, dies zu tun? Ich bin nicht auf der Suche nach jemanden, um meine SQL neu zu schreiben, mehr "guck mal wie foobar Abfragen" und ich kann es von dort nehmen.

Die reale Lebensprovisionsstruktur kann für verschiedene Verkäufer, Artikel und Kunden und sogar Verkaufsdaten spezifiziert werden. Es ändert sich auch von Zeit zu Zeit, also muss alles von den Daten in den Tabellen gesteuert werden ... d. H. Ich kann keine festen Bereiche in den SQL-Code setzen. Die aktuelle Abfrage gibt 3-4000000 Zeilen zurück und dauert etwa 20-30 Sekunden. Zum Glück wird es nur monatlich benutzt, aber die Langsamkeit nervt mich irgendwie.

Dies ist auf mssql.

Ian

edit:

ich von Anfang an ein komplexeres Beispiel gegeben haben sollte. Mir ist jetzt klar, dass in meinem ersten Beispiel einige wesentliche Elemente der Komplexität fehlen und ich mich für alle entschuldige.

erfassen Dies kann es besser

select client-code, product, product-family, qty, price, discount, salesman, 
    (select top 1 percentage from comissions 
     where comisiones.salesman = saleslines.salesman 
     and saleslines.qty > comisiones.qty 
     and [ 
      a collection of conditions which may or may not apply: 
      Exclude rows if the salesman has offered discounts above max discounts 
       which appear in each row in the commissions table 
      There may be a special scale for the product family 
      There may be a special scale for the product 
      There may be a special scale for the client 

      A few more cases 
      ] 
     order by [ 
      The user can control the order though a table 
      which can prioritize by client, family or product 
      It normally goes from most to least specific. 
      ] 
    ) percentage 
    from saleslines 

unnötig die wirkliche Abfrage folgen nicht leicht zu sagen ist. Um das Leben interessanter zu machen, ist seine Benennung mehrsprachig.

Somit kann die Provision für jede Zeile der Salesline unterschiedlich sein.

Es mag übermäßig komplex klingen, aber wenn Sie daran denken, wie Sie Provision zahlen würden, macht es Sinn. Sie möchten nicht jemanden für den Verkauf von Material mit hohen Rabatten bezahlen, sondern möchten auch einem bestimmten Kunden einen Rabatt auf ein bestimmtes Produkt anbieten, wenn sie X-Einheiten kaufen. Der Verkäufer sollte mehr verdienen, wenn sie mehr verkaufen.

In allen oben genannten bin ich ausgeschlossen datumsbegrenzten Sonderangebote.

Ich denke, Partitionen können die Lösung sein, aber ich muss diese tiefer zu erforschen, da ich nichts über Partitionen weiß. Es gab mir ein paar Ideen.

+0

wenn es nur monatlich laufen, würde ich darüber keine Sorgen machen. Es sieht so aus, als müsste es viele Berechnungen an mehreren großen Tischen durchführen. Wenn es häufiger ausgeführt werden muss, möchten Sie möglicherweise die Ergebnismenge schrittweise mit einer anderen Strategie erstellen. –

+0

Manchmal ist es besser, die Arbeit tatsächlich zu messen und eine bessere Infrastruktur wie Indizes oder schnellere Festplatten usw. einzuführen. Sie sollten die Abfrage profilieren und sehen, dass die Bits schwer sind. –

+0

Welche Version von SQL Server verwenden Sie? –

Antwort

3

Wenn Sie eine Version von SQL Server verwenden, die Common-Tabellenausdrücke wie SQL Server 2005 unterstützt und später eine effizientere Lösung könnte sein:

With RankedCommissions As 
    (
    Select SL.qty, SL.price, SL.salesman, C.percentage 
     , Row_Number() Over (Partition By SL.salesman Order By C.Qty Desc) As CommissionRank 
    From SalesLines As SL 
     Join Commissions As C 
      On SL.salesman = C.salesman 
       And SL.qty > C.qty 
    ) 
Select qtr, price, salesman, percentage 
From RankedCommissions 
Where CommissionRank = 1 

Wenn Sie die Möglichkeit zu berücksichtigen, erforderlich, dass gibt es keine Kommissionen Werte für einen bestimmten Verkäufer, wo die SalesLine.Qty> Commission.Qty, dann so etwas wie Sie tun können:

With RankedCommissions As 
    (
    Select SL.qty, SL.price, SL.salesman, C.percentage 
     , Row_Number() Over (Partition By SL.salesman Order By C.Qty Desc) As CommissionRank 
    From SalesLines As SL 
     Join Commissions As C 
      On SL.salesman = C.salesman 
       And SL.qty > C.qty 
    ) 
Select SL.qtr, SL.price, SL.salesman, RC.percentage 
From SalesLines As SL 
    Left Join RankedCommissions As RC 
     On RC.salesman = SL.salesman 
      And RC.CommissionRank = 1 
+0

Sie müssen keine Unterstützung für Common-Table-Ausdrücke * haben, um diesen Ansatz zu verwenden. Sie können das CTE genauso einfach als Inline-Ansicht schreiben. Die Funktion "ROW_NUMBER" erfordert jedoch SQL Server 2005 oder höher. –

+0

Meine ursprüngliche Abfrage wurde für sql2000 geschrieben, aber die Datenbank migrierte kürzlich nach sql2008, was mein Interesse an der Nutzung neuer Funktionen weckte. Thomas, das sieht nach einer Lösung aus. Aber es ist ein ganz anderer Ansatz, also muss ich mich eine Weile zurücklehnen und sehen, wie ich es nutzen kann. Wie die Lösung. Danke -Ian – Ian

+0

@Ian - Verstehen. CTEs und Ranking-Funktionen wie Row_Number sind in den Jahren 2005/2008 ein unglaublich leistungsstarkes neues Feature. Sie helfen dabei, eine Reihe von problematischen Abfragen wie der von Ihnen dargestellten zu vereinfachen. Der wahre Trick in meiner Lösung ist die Verwendung der Partition By-Klausel, die die Werte von Verkäufer (Verkäufer?) Bestellt. – Thomas

0
select 
    qty, price, salesman, 
    max(percentage) 
from saleslines 
    inner join comissions on commisions.salesman = saleslines.salesman and 
      saleslines.qty > comissions.qty 
group by 
    qty, price, salesman 
+0

-1, Ihr Code gibt den höchsten Prozentsatz an; Die ursprüngliche Abfrage gibt den Prozentsatz für die höchste Menge an. – Gabe

+0

Der INNER JOIN hat eine Bedingung (saleslines.qty> comissions.qty), die alle Commission-Linien mit einer Menge filtert, die größer ist als die Menge der Salesline. Der Höchstwert für die verbleibenden Zeilen gibt den Prozentsatz für die höchste Menge an (nicht den höchsten Prozentsatz). – potatopeelings

+0

oder so denke ich :-) – potatopeelings

Verwandte Themen