Frage

ich einmal gegeben wurde diese Aufgabe in einem RDBMS zu tun:

Bei Tabellen Kunde, Auftrag, Auftragszeilen und Produkt. Alles getan, was mit den üblichen Feldern und Beziehungen, mit einem Kommentar Memo-Feld auf den Bestellposten Tabelle.

Für einen Kunden eine Liste aller Produkte abrufen, die Kunden mit Produktnamen, Jahr der ersten Kauf, die Daten der drei letzten Käufe, Kommentar der letzten Bestellung, die Summe der Gesamteinnahmen für das entsprechende Produkt-Kunden Kombination jemals letzten 12 bestellt Monate.

Nach ein paar Tagen habe ich es zu tun als Abfrage und sich dafür entschieden, nur jeden Bestellposten für einen Kunden zu holen, und jedes Produkt und führen Sie durch die Daten prozedural die erforderliche Tabelle Clientside zu bauen.

Das halte ich für ein Symptom für eine oder mehrere der folgenden Optionen:

  • Ich bin ein fauler Idiot und habe gesehen, wie es in SQL zu tun
  • Stellen Operationen sind nicht so ausdrucksstark wie prozedurale Operationen
  • SQL nicht so ausdrucksstark ist, wie es sein sollte

Habe ich das Richtige zu tun? Habe ich noch andere Optionen?

War es hilfreich?

Lösung

Sie sollten definitiv in der Lage sein, diese Übung zu tun, ohne die Arbeit entspricht einen JOIN im Anwendungscode zu tun, das heißt, durch alle Zeilen aus beiden Auftragszeilen und Produkten zu holen und Iterieren durch sie hindurch. Sie müssen keine SQL-Assistent sein, dass man zu tun. JOIN ist SQL, was eine Schleife zu einer prozeduralen Sprache ist -., Dass beide sind grundlegende Sprachfunktionen, die Sie sollten wissen, wie Sie mit

Ein Fall Menschen fallen in denken, dass der gesamte Bericht in einer einzigen SQL-Abfrage erzeugt werden muss. Nicht wahr! Die meisten Berichte passen nicht in ein Rechteck, wie Tony Andrews weist darauf hin. Es gibt viele Rollups, Zusammenfassungen, Sonderfälle, etc., so ist es einfacher und effizienter Teile des Berichts in separaten Abfragen zu holen. Ebenso in einer prozeduralen Sprache würden Sie nicht versuchen, in einer einzigen Code-Zeile alle Ihre Berechnung tun, oder sogar in einer einzigen Funktion (hoffentlich).

Einige Reporting-Tools darauf bestehen, dass ein Bericht aus einer einzigen Abfrage generiert wird, und Sie haben keine Möglichkeit, in mehreren Abfragen zu verschmelzen. Wenn ja, dann müssen Sie mehrere Berichte (und wenn der Chef will es auf der einen Seite, dann müssen Sie etwas Paste-up von Hand tun) erzeugen.

Um eine Liste von allen Produkten zu erhalten bestellt (mit Produktnamen), die Daten der letzten drei Käufe und kommentieren aktuellen Auftrag ist einfach:

SELECT o.*, l.*, p.*
FROM Orders o
 JOIN OrderLines l USING (order_id)
 JOIN Products p USING (product_id)
WHERE o.customer_id = ?
ORDER BY o.order_date;

Es ist gut über das Ergebnis Zeile-für-Zeile zu durchlaufen die Daten und Kommentare zu den neuesten Aufträgen zu extrahieren, da man sowieso die Zeilen ist holen. Aber machen Sie es sich einfach, indem die Datenbank fragt die Ergebnisse nach Datum sortiert zurückzukehren.

Jahr Erstkauf aus der vorherige Abfrage zur Verfügung steht, wenn Sie sortieren nach dem order_date und das Ergebnis Zeile-für-Zeile holen, erhalten Sie Zugriff auf die ersten Ordnung. Andernfalls können Sie es auf diese Weise tun:

SELECT YEAR(MIN(o.order_date)) FROM Orders o WHERE o.customer_id = ?;

Die Summe der Produktkäufe für die letzten 12 Monate am besten durch eine separate Abfrage berechnet wird:

SELECT SUM(l.quantity * p.price)
FROM Orders o
 JOIN OrderLines l USING (order_id)
 JOIN Products p USING (product_id)
WHERE o.customer_id = ?
 AND o.order_date > CURDATE() - INTERVAL 1 YEAR;

Bearbeiten Sie sagten in einem anderen Kommentar, die Sie sehen möchten, wie die Daten der letzten drei Käufe in Standard-SQL zu bekommen:

SELECT o1.order_date
FROM Orders o1
  LEFT OUTER JOIN Orders o2 
  ON (o1.customer_id = o2.customer_id AND (o1.order_date < o2.order_date 
      OR (o1.order_date = o2.order_date AND o1.order_id < o2.order_id)))
WHERE o1.customer_id = ?
GROUP BY o1.order_id
HAVING COUNT(*) <= 3;

Wenn Sie ein kleines Stückchen von herstellerspezifischen SQL-Funktionen verwenden können, können Sie Microsoft / Sybase TOP verwenden n oder MySQL / PostgreSQL LIMIT:

SELECT TOP 3 order_date
FROM Orders
WHERE customer_id = ?
ORDER BY order_date DESC;

SELECT order_date
FROM Orders
WHERE customer_id = ?
ORDER BY order_date DESC
LIMIT 3;

Andere Tipps

  

Set-Vorgänge sind nicht so ausdrucksstark wie prozedurale Operationen

Vielleicht eher wie: „Set-Operationen sind nicht so vertraut wie Verfahrensoperationen an einen Entwickler zu prozeduralen Sprachen verwendet“; -)

Doing es iterativ, wie Sie jetzt getan haben, ist gut für kleine Datenmengen, aber einfach nicht die gleiche Art und Weise skaliert. Die Antwort, ob Sie das Richtige taten hängt davon ab, ob Sie mit der Leistung zufrieden sind jetzt und / oder nicht erwarten, um die Datenmenge viel zu erhöhen.

Wenn Sie einige Beispiel-Code zur Verfügung stellen könnte, könnten wir in der Lage sein, Ihnen eine Set-basierte Lösung für Sie zu finden, die schneller sein wird, mit zu beginnen und viel, viel besser zu skalieren. Wie Galactic erwähnt, Techniken wie temporäre Tabellen helfen die Aussagen weit besser lesbar, während weitgehend die Leistungsvorteile beibehalten machen kann.

In den meisten RDBMS haben Sie die Möglichkeit, temporäre Tabellen oder lokale Tabelle Variablen, die Sie verwenden können, um eine Aufgabe wie diese in überschaubare Einheiten zu brechen.

Ich sehe keine Möglichkeit, leicht, dies zu tun als Single query (ohne einig bös Subqueries), aber es sollte noch machbar sein, ohne zu Verfahrensabbruch Code, wenn Sie temporäre Tabellen verwenden.

Dieses Problem kann nicht auflösbar ist von ein query. Ich sehe mehrere verschiedene Teile ...

Für einen Kunden

  1. Hier finden Sie eine Liste aller bestellten Produkte (mit Produktnamen)
  2. Get Jahr des ersten Kaufs
  3. Erhalten Sie das Datum letzten drei Käufe
  4. Get kommentieren aktuelle Auftrag
  5. Get Summe von Produktkäufe für die letzten 12 Monate

Ihr Verfahren ist nur wenige Schritte von 1 bis 5 und SQL bekommt man die Daten.

Klingt wie ein Data-Warehouse-Projekt für mich. Wenn Sie Dinge wie „drei letzten Dinge“ und „Summe von etwas über die letzten 12 Monate“ müssen dann speichern sie das heißt denormalize.

EDIT: Dies ist ein völlig neu auf der Lösung nehmen, ohne Verwendung von temporären Tabellen oder merkwürdige Sub-Sub-Sub-Abfragen. Allerdings wird es nur auf SQL arbeitet 2005 oder höher, da sie den „Pivot“ Befehl verwendet, die in dieser Version neu ist.

Das grundlegende Problem ist der gewünschte Drehpunkt aus einem Satz von Reihen (in den Daten) in Spalten in der Ausgabe. Während in der Frage noodling, erinnerte ich mich, dass SQL Server jetzt einen „Pivot“ Betreiber damit zu tun hat.

Dies funktioniert auf SQL 2005 nur , die Beispieldaten.

-- This could be a parameter to a stored procedure
-- I picked this one because he has products that he ordered 4 or more times
declare @customerId nchar(5)
set @customerId = 'ERNSH'

select c.CustomerID, p.ProductName, products_ordered_by_cust.FirstOrderYear,
    latest_order_dates_pivot.LatestOrder1 as LatestOrderDate,
    latest_order_dates_pivot.LatestOrder2 as SecondLatestOrderDate,
    latest_order_dates_pivot.LatestOrder3 as ThirdLatestOrderDate,
    'If I had a comment field it would go here' as LatestOrderComment,
    isnull(last_year_revenue_sum.ItemGrandTotal, 0) as LastYearIncome
from
    -- Find all products ordered by customer, along with first year product was ordered
    (
        select c.CustomerID, od.ProductID,
            datepart(year, min(o.OrderDate)) as FirstOrderYear
        from Customers c
            join Orders o on o.CustomerID = c.CustomerID
            join [Order Details] od on od.OrderID = o.OrderID
        group by c.CustomerID, od.ProductID
    ) products_ordered_by_cust
    -- Find the grand total for product purchased within last year - note fudged date below (Northwind)
    join (
        select o.CustomerID, od.ProductID, 
            sum(cast(round((od.UnitPrice * od.Quantity) - ((od.UnitPrice * od.Quantity) * od.Discount), 2) as money)) as ItemGrandTotal
        from
            Orders o
            join [Order Details] od on od.OrderID = o.OrderID
        -- The Northwind database only contains orders from 1998 and earlier, otherwise I would just use getdate()
        where datediff(yy, o.OrderDate, dateadd(year, -10, getdate())) = 0
        group by o.CustomerID, od.ProductID
    ) last_year_revenue_sum on last_year_revenue_sum.CustomerID = products_ordered_by_cust.CustomerID
        and last_year_revenue_sum.ProductID = products_ordered_by_cust.ProductID
    -- THIS is where the magic happens.  I will walk through the individual pieces for you
    join (
        select CustomerID, ProductID,
            max([1]) as LatestOrder1,
            max([2]) as LatestOrder2,
            max([3]) as LatestOrder3
        from
        (
            -- For all orders matching the customer and product, assign them a row number based on the order date, descending
            -- So, the most recent is row # 1, next is row # 2, etc.
            select o.CustomerID, od.ProductID, o.OrderID, o.OrderDate,
                row_number() over (partition by o.CustomerID, od.ProductID order by o.OrderDate desc) as RowNumber
            from Orders o join [Order Details] od on o.OrderID = od.OrderID
        ) src
        -- Now, produce a pivot table that contains the first three row #s from our result table,
        -- pivoted into columns by customer and product
        pivot
        (
            max(OrderDate)
            for RowNumber in ([1], [2], [3])
        ) as pvt
        group by CustomerID, ProductID
    ) latest_order_dates_pivot on products_ordered_by_cust.CustomerID = latest_order_dates_pivot.CustomerID
        and products_ordered_by_cust.ProductID = latest_order_dates_pivot.ProductID
    -- Finally, join back to our other tables to get more details
    join Customers c on c.CustomerID = products_ordered_by_cust.CustomerID
    join Orders o on o.CustomerID = products_ordered_by_cust.CustomerID and o.OrderDate = latest_order_dates_pivot.LatestOrder1
    join [Order Details] od on od.OrderID = o.OrderID and od.ProductID = products_ordered_by_cust.ProductID
    join Products p on p.ProductID = products_ordered_by_cust.ProductID
where c.CustomerID = @customerId
order by CustomerID, p.ProductID

SQL-Abfragen zurückgehen in der Form einer einzigen „flach“ Tabelle mit Zeilen und Spalten. Reporting-Anforderungen sind oft komplexer als das, einen „gezackten“ Satz von Ergebnissen wie Ihr Beispiel anspruchsvoll. Es ist nichts falsch mit „going Verfahren“ solche Anforderungen zu lösen, oder ein Reporting-Tool, das auf der Oberseite der Datenbank sitzt. Allerdings sollten Sie SQL so weit wie möglich verwenden, um die beste Leistung aus der Datenbank zu erhalten.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top