Frage

Ich betreibe häufig Probleme dieser Form und haben keine gute Lösung noch nicht gefunden:

Angenommen, wir haben zwei Datenbanktabellen repräsentieren ein E-Commerce-System.

userData (userId, name, ...)
orderData (orderId, userId, orderType, createDate, ...)

Für alle Benutzer im System, wählen Sie ihre Benutzerinformationen, die neuesten Informationen, um mit type = ‚1‘, und ihre jüngste Bestellung Informationen mit type = ‚2‘. Ich möchte dies in einer Abfrage tun. Hier ist ein Beispiel Ergebnis:

(userId, name, ..., orderId1, orderType1, createDate1, ..., orderId2, orderType2, createDate2, ...)
(101, 'Bob', ..., 472, '1', '4/25/2008', ..., 382, '2', '3/2/2008', ...)
War es hilfreich?

Lösung

Das sollte funktionieren, müssen Sie die Tabelle / Spaltennamen einstellen müssen, um:

select ud.name,
       order1.order_id,
       order1.order_type,
       order1.create_date,
       order2.order_id,
       order2.order_type,
       order2.create_date
  from user_data ud,
       order_data order1,
       order_data order2
 where ud.user_id = order1.user_id
   and ud.user_id = order2.user_id
   and order1.order_id = (select max(order_id)
                            from order_data od1
                           where od1.user_id = ud.user_id
                             and od1.order_type = 'Type1')
   and order2.order_id = (select max(order_id)
                             from order_data od2
                            where od2.user_id = ud.user_id
                              and od2.order_type = 'Type2')

Ihre Daten Denormalisierung könnte auch eine gute Idee sein. Diese Art der Sache wird ziemlich teuer sein zu tun. So könnten Sie eine last_order_date zu Ihrem Userdata hinzuzufügen.

Andere Tipps

Ich habe zur Lösung dieses Problems drei verschiedene Ansätze zur Verfügung gestellt:

  1. Verwenden von Pivots
  2. Verwenden von Case-Anweisungen
  3. Verwenden von Inline-Abfragen in der where-Klausel

Alle Lösungen übernehmen wir die „jüngsten“, um auf der orderId Säule basieren bestimmen. Mit Hilfe der createDate Spalte Komplexität fügt hinzu, aufgrund Zeitstempel Kollisionen und ernsthaft die Leistung beeinträchtigen, da createDate wahrscheinlich nicht Teil der indizierten Schlüssel. Ich habe nur diese Abfragen mit MS SQL Server 2005 getestet, so habe ich keine Ahnung, ob sie auf dem Server arbeiten.

Lösungen (1) und (2) erfüllt fast identisch. In der Tat, sie beide Ergebnis in der gleichen Anzahl von liest aus der Datenbank.

Lösung (3) nicht die bevorzugte Lösung, wenn mit großen Datenmengen arbeiten. Es macht konsequent Hunderte von logischen lesen mehr als (1) und (2). Wenn für einen bestimmten Benutzer Filterung, Ansatz (3) mit den anderen Verfahren vergleichbar. Im Single-User-Fall hilft ein Tropfen auf der CPU-Zeit, die deutlich höhere Anzahl von liest entgegenzuwirken; jedoch, wie das Plattenlaufwerk auftritt Misses belebte und Cache wird dieser leichte Vorteil verschwinden.

Fazit

Für das präsentierte Szenario verwenden, um den Pivot-Ansatz, wenn sie von Ihrem DBMS unterstützt wird. Es erfordert weniger Code als die Case-Anweisung und vereinfacht die Auftragsarten in der Zukunft hinzuzufügen.

Bitte beachten Sie, dass in einigen Fällen ist PIVOT nicht flexibel genug und Kennwertfunktionen Fall-Anweisungen sind der Weg zu gehen.

Code

Ansatz (1) mit PIVOT:

select 
    ud.userId, ud.fullname, 
    od1.orderId as orderId1, od1.createDate as createDate1, od1.orderType as orderType1,
    od2.orderId as orderId2, od2.createDate as createDate2, od2.orderType as orderType2

from userData ud
    inner join (
            select userId, [1] as typeOne, [2] as typeTwo
            from (select
                userId, orderType, orderId
            from orderData) as orders
            PIVOT
            (
                max(orderId)
                FOR orderType in ([1], [2])
            ) as LatestOrders) as LatestOrders on
        LatestOrders.userId = ud.userId 
    inner join orderData od1 on
        od1.orderId = LatestOrders.typeOne
    inner join orderData od2 on
        od2.orderId = LatestOrders.typeTwo

Ansatz (2) mit Case-Anweisungen:

select 
    ud.userId, ud.fullname, 
    od1.orderId as orderId1, od1.createDate as createDate1, od1.orderType as orderType1,
    od2.orderId as orderId2, od2.createDate as createDate2, od2.orderType as orderType2

from userData ud 
    -- assuming not all users will have orders use outer join
    inner join (
            select 
                od.userId,
                -- can be null if no orders for type
                max (case when orderType = 1 
                        then ORDERID
                        else null
                        end) as maxTypeOneOrderId,

                -- can be null if no orders for type
                max (case when orderType = 2
                        then ORDERID 
                        else null
                        end) as maxTypeTwoOrderId
            from orderData od
            group by userId) as maxOrderKeys on
        maxOrderKeys.userId = ud.userId
    inner join orderData od1 on
        od1.ORDERID = maxTypeTwoOrderId
    inner join orderData od2 on
        OD2.ORDERID = maxTypeTwoOrderId

Ansatz (3) mit Inline-Abfragen in der Where-Klausel (basierend auf Steve K. Antwort):

select  ud.userId,ud.fullname, 
        order1.orderId, order1.orderType, order1.createDate, 
        order2.orderId, order2.orderType, order2.createDate
  from userData ud,
       orderData order1,
       orderData order2
 where ud.userId = order1.userId
   and ud.userId = order2.userId
   and order1.orderId = (select max(orderId)
                            from orderData od1
                           where od1.userId = ud.userId
                             and od1.orderType = 1)
   and order2.orderId = (select max(orderId)
                             from orderData od2
                            where od2.userId = ud.userId
                              and od2.orderType = 2)

Script zu generieren Tabellen und 1000 Benutzer mit 100 Bestellungen pro:

CREATE TABLE [dbo].[orderData](
    [orderId] [int] IDENTITY(1,1) NOT NULL,
    [createDate] [datetime] NOT NULL,
    [orderType] [tinyint] NOT NULL, 
    [userId] [int] NOT NULL
) 

CREATE TABLE [dbo].[userData](
    [userId] [int] IDENTITY(1,1) NOT NULL,
    [fullname] [nvarchar](50) NOT NULL
) 

-- Create 1000 users with 100 order each
declare @userId int
declare @usersAdded int
set @usersAdded = 0

while @usersAdded < 1000
begin
    insert into userData (fullname) values ('Mario' + ltrim(str(@usersAdded)))
    set @userId = @@identity

    declare @orderSetsAdded int
    set @orderSetsAdded = 0
    while @orderSetsAdded < 10
    begin
        insert into orderData (userId, createDate, orderType) 
            values ( @userId, '01-06-08', 1)
        insert into orderData (userId, createDate, orderType) 
            values ( @userId, '01-02-08', 1)
        insert into orderData (userId, createDate, orderType) 
            values ( @userId, '01-08-08', 1)
        insert into orderData (userId, createDate, orderType) 
            values ( @userId, '01-09-08', 1)
        insert into orderData (userId, createDate, orderType) 
            values ( @userId, '01-01-08', 1)
        insert into orderData (userId, createDate, orderType) 
            values ( @userId, '01-06-06', 2)
        insert into orderData (userId, createDate, orderType) 
            values ( @userId, '01-02-02', 2)
        insert into orderData (userId, createDate, orderType) 
            values ( @userId, '01-08-09', 2)
        insert into orderData (userId, createDate, orderType) 
            values ( @userId, '01-09-01', 2)
        insert into orderData (userId, createDate, orderType) 
            values ( @userId, '01-01-04', 2)

        set @orderSetsAdded = @orderSetsAdded + 1
    end
    set @usersAdded = @usersAdded + 1
end

Kleiner Schnipsel zum Testen der Abfrageleistung auf MS SQL Server zusätzlich zu SQL Profiler:

-- Uncomment these to clear some caches
--DBCC DROPCLEANBUFFERS
--DBCC FREEPROCCACHE

set statistics io on
set statistics time on

-- INSERT TEST QUERY HERE

set statistics time off
set statistics io off

Leider habe ich nicht Orakel vor mir, aber das ist die Grundstruktur von dem, was ich in Oracle tun würde:

SELECT b.user_id, b.orderid, b.orderType, b.createDate, <etc>,
       a.name
FROM orderData b, userData a
WHERE a.userid = b.userid
AND (b.userid, b.orderType, b.createDate) IN (
  SELECT userid, orderType, max(createDate) 
  FROM orderData 
  WHERE orderType IN (1,2)
  GROUP BY userid, orderType) 

T-SQL Probenlösung (MS SQL):

SELECT
    u.*
    , o1.*
    , o2.* 
FROM
(
    SELECT
        , userData.*
        , (SELECT TOP 1 orderId.url FROM orderData WHERE orderData.userId=userData.userId AND orderType=1 ORDER BY createDate DESC)
            AS order1Id
        , (SELECT TOP 1 orderId.url FROM orderData WHERE orderData.userId=userData.userId AND orderType=2 ORDER BY createDate DESC)
            AS order2Id
    FROM userData
) AS u
LEFT JOIN orderData o1 ON (u.order1Id=o1.orderId)
LEFT JOIN orderData o2 ON (u.order2Id=o2.orderId)

In SQL 2005 Sie könnten auch RANK () OVER-Funktion verwenden. (Aber AFAIK seine vollständig MSSQL spezifische Funktion)

Sie könnten in der Lage sein, eine Union-Abfrage für dies zu tun. Die genaue Syntax braucht etwas Arbeit, vor allem die Gruppe durch den Abschnitt, aber die Union sollte es tun können.

Zum Beispiel:

SELECT orderId, orderType, createDate
FROM orderData
WHERE type=1 AND MAX(createDate)
GROUP BY orderId, orderType, createDate

UNION

SELECT orderId, orderType, createDate
FROM orderData
WHERE type=2 AND MAX(createDate)
GROUP BY orderId, orderType, createDate

Ihre neueste meinen Sie alle neu in den aktuellen Tag? Sie können jederzeit mit Ihrem ErstellDat überprüfen und alle Benutzer- und Auftragsdaten, wenn die ErstellDat> = aktuellen Tag.

SELECT * FROM
"orderData", "userData"
WHERE
"userData"."userId"  ="orderData"."userId"
AND "orderData".createDate >= current_date;

AKTUALISIERT

Hier ist, was Sie nach Ihrem Kommentar mögen hier:

SELECT * FROM
"orderData", "userData"
WHERE
"userData"."userId"  ="orderData"."userId"
AND "orderData".type = '1'
AND "orderData"."orderId" = (
SELECT "orderId" FROM "orderData"
WHERE 
"orderType" = '1'
ORDER "orderId" DESC
LIMIT 1

)

Ich benutze Dinge wie diese in MySQL:

SELECT
   u.*,
   SUBSTRING_INDEX( MAX( CONCAT( o1.createDate, '##', o1.otherfield)), '##', -1) as o2_orderfield,
   SUBSTRING_INDEX( MAX( CONCAT( o2.createDate, '##', o2.otherfield)), '##', -1) as o2_orderfield
FROM
   userData as u
   LEFT JOIN orderData AS o1 ON (o1.userId=u.userId AND o1.orderType=1)
   LEFT JOIN orderData AS o2 ON (o1.userId=u.userId AND o2.orderType=2)
GROUP BY u.userId

Kurz gesagt, verwenden Sie MAX (), um die neueste zu erhalten, durch Voranstellung des Kriterienfeld (ErstellDat) auf den interessanten Bereich (e) (otherfield). SUBSTRING_INDEX () abstreift dann das Datum an.

OTOH, wenn Sie eine beliebige Anzahl von Aufträgen müssen (wenn usertype eine beliebige Zahl sein kann, und nicht eine begrenzte ENUM); es besser ist, mit einer separaten Abfrage, so etwas wie das zu handhaben:

select * from orderData where userId=XXX order by orderType, date desc group by orderType

für jeden Benutzer.

orderId Unter der Annahme, monoton mit zunehmender Zeit:

SELECT *
FROM userData u
INNER JOIN orderData o
  ON o.userId = u.userId
INNER JOIN ( -- This subquery gives the last order of each type for each customer
  SELECT MAX(o2.orderId)
    --, o2.userId -- optional - include if joining for a particular customer
    --, o2.orderType -- optional - include if joining for a particular type
  FROM orderData o2
  GROUP BY o2.userId
    ,o2.orderType
) AS LastOrders
  ON LastOrders.orderId = o.orderId -- expand join to include customer or type if desired

Dann schwenkt den Client an oder bei Verwendung von SQL Server gibt es eine PIVOT Funktionalität

Hier ist ein Weg, den Typen 1 und 2 Daten auf die gleiche Zeile zu bewegen:
(Durch den Typ 1 und Typ-2-Informationen in ihre eigenen wählt platzieren, die dann in der FROM-Klausel gewöhnen.)

SELECT
  a.name, ud1.*, ud2.*
FROM
    userData a,
    (SELECT user_id, orderid, orderType, reateDate, <etc>,
    FROM orderData b
    WHERE (userid, orderType, createDate) IN (
      SELECT userid, orderType, max(createDate) 
      FROM orderData 
      WHERE orderType = 1
      GROUP BY userid, orderType) ud1,
    (SELECT user_id, orderid, orderType, createDate, <etc>,
    FROM orderData 
    WHERE (userid, orderType, createDate) IN (
      SELECT userid, orderType, max(createDate) 
      FROM orderData 
      WHERE orderType = 2
      GROUP BY userid, orderType) ud2

Hier ist, wie ich es tun. Dies ist Standard-SQL und arbeitet in jeder Marke von Datenbank.

SELECT u.userId, u.name, o1.orderId, o1.orderType, o1.createDate,
  o2.orderId, o2.orderType, o2.createDate
FROM userData AS u
  LEFT OUTER JOIN (
    SELECT o1a.orderId, o1a.userId, o1a.orderType, o1a.createDate
    FROM orderData AS o1a 
      LEFT OUTER JOIN orderData AS o1b ON (o1a.userId = o1b.userId 
        AND o1a.orderType = o1b.orderType AND o1a.createDate < o1b.createDate)
    WHERE o1a.orderType = 1 AND o1b.orderId IS NULL) AS o1 ON (u.userId = o1.userId)
  LEFT OUTER JOIN (
    SELECT o2a.orderId, o2a.userId, o2a.orderType, o2a.createDate
    FROM orderData AS o2a 
      LEFT OUTER JOIN orderData AS o2b ON (o2a.userId = o2b.userId 
        AND o2a.orderType = o2b.orderType AND o2a.createDate < o2b.createDate)
    WHERE o2a.orderType = 2 AND o2b.orderId IS NULL) o2 ON (u.userId = o2.userId);

Beachten Sie, dass, wenn Sie mehrere Aufträge beiden Typen, deren Termine sind gleich den letzten Termin haben, werden Sie mehrere Zeilen in der Ergebnismenge erhalten. Wenn Sie mehrere Aufträge beiden Typen haben, werden Sie M N x Zeilen in der Ergebnismenge erhalten. Deshalb würde ich empfehlen, dass Sie die Zeilen jeder Art in separaten Abfragen zu holen.

Steve K ist absolut richtig, danke! Ich habe seine Antwort ein wenig umschreiben für die Tatsache zu berücksichtigen, dass es vielleicht nicht, um für einen bestimmten Typ sein (was ich nicht erwähnt zu, so kann ich nicht bemängeln Steve K.)

Hier ist, was ich in Liquidation mit:

select ud.name,
       order1.orderId,
       order1.orderType,
       order1.createDate,
       order2.orderId,
       order2.orderType,
       order2.createDate
  from userData ud
  left join orderData order1
   on order1.orderId = (select max(orderId)
                            from orderData od1
                           where od1.userId = ud.userId
                             and od1.orderType = '1')
  left join orderData order2
   on order2.orderId = (select max(orderId)
                            from orderData od2
                           where od2.userId = ud.userId
                             and od2.orderType = '2')
 where ...[some limiting factors on the selection of users]...;
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top