Korbinian Bachl: (PHP/ MySQL) - kunden die X kauften kauften auch y......

Hallo,

im currently developing a Webshop system. One of the new features of it
should be a "Costumers that bought this product also bought...." feature -
concerning this im currently unsure how to implement it - (LAMPS) - my current thought is following:

Costumer A buys Products with NO: 12, 13 , 25 -> system says OK, looks for
Tables 12,13,25 -> finds nothing creates table 12, inserts 13 and 25 and
sets sold of each to 1, then creates table 13 and 25 and inserts like it did
in table 12

now cosumter B buys products 13,12,19 -> system says OK, looks for tables
13,12,19 and finds only 12 created, adds 19 into table 12 and updates sold
from 13 in table 12 -> then does this with table 13 and finally creates
table 19 (because new) and inserts like in Cosumter A's way...

now the question: is MySQL aware of being with over 1500 tables ??? - is
there a better way or more efficent way to do the same ?

Hope sb. knows a trick :)

Yours Sincerely

Korbinian Bachl
www.whiskyworld.de

  1. Hallo Korbinian,

    now the question: is MySQL aware of being with over 1500 tables ???

    wenn mein Depidginizer richtig arbeitet, interessiert Dich, ob MySQL mehr als 1.500 Tabellen handhaben kann. Keine Ahnung - ist allerdings auch eine Frage, die sich nicht stellen sollte. Selbst wenn das DBMS eine solche Unmenge bewältigen könnte, welche Performance erwartest Du von einem derartigen System?

    Sollte ich die Aufgabenstellung halbwegs richtig verstanden haben, so könnt ein zielführender Ansatz z.B. so aussehen:

    Eine Tabelle beinhaltet die Kunden-DS, die über einen Primärschlüssel KID verfügen. In einer zweiten Tabelle sind die Artikel mit ihrem Primärschlüssel AID enthalten. Jetzt stellst Du mit einer dritten Tabelle eine n:m-Relation zwischen den beiden her; diese Tabelle beinhaltet mindestens zwei Spalten, in der für jeden verkauften Artikelposten ein DS eingetragen wird, der die KID der entsprechenden AID gegenüberstellt.

    Zur Beantwortung der Frage, welche weiteren Artikel von Kunden gekauft wurden, die Artikel x kauften, ermittelst Du aus der zuletzt genannten Tabelle alle KID, der die entsprechende AID zugeordnet ist. Mit diesem Ergebnis kannst Du - evtl. im gleichen SELECT - in derselben Tabelle feststellen, welche AIDs von diesen KIDs gekauft wurden.

    Alles klar?

    HTH Robert

    P.S.: Dein Lösungsansatz läßt erahnen, daß Mengenlehre in Deiner mathematischen Ausbildung nicht zu Deinen Lieblingsthemen gehörte. Vielleicht solltest Du vor der Annäherung an Aufgaben wie die gerade diskutierte mal einen Ausflug in die Grundlagen des relationalen Datenbankentwurfs machen...

  2. Moin!

    Hallo,

    ^Das heißt 'Hello', oder die nachfolgende Sprache paßt nicht (zuviel Whisky getestet?). :)

    Costumer A buys Products with NO: 12, 13 , 25 -> system says OK, looks for
    Tables 12,13,25 -> finds nothing creates table 12, inserts 13 and 25 and
    sets sold of each to 1, then creates table 13 and 25 and inserts like it did
    in table 12

    now cosumter B buys products 13,12,19 -> system says OK, looks for tables
    13,12,19 and finds only 12 created, adds 19 into table 12 and updates sold
    from 13 in table 12 -> then does this with table 13 and finally creates
    table 19 (because new) and inserts like in Cosumter A's way...

    Bad idea.

    now the question: is MySQL aware of being with over 1500 tables ??? - is
    there a better way or more efficent way to do the same ?

    Try this:

    Die Bestellung von Artikel Nr. 12, 13 und 19 wird in eine einzige Tabelle eingetragen, die im Prinzip nur zwei Spalten enthält: "bestellt_wurde" und "bestellt_wurde_auch". Diese Tabelle enthält aufgrund der Bestellung folgende Einträge:

    12 -> 13
    12 -> 19
    13 -> 12
    13 -> 19
    19 -> 12
    19 -> 13

    Du kannst dann ganz simpel abfragen, welche Produkte auch gekauft wurden, wenn gewisse Produkte gekauft wurden.

    Ich würde die Tabelle sogar bei jedem Kauf ergänzen, und nicht vorher prüfen, ob die Kombinationen schon mal gekauft wurden, denn dann kannst du statistisch arbeiten und zählen, wieviele Kaufkombinationen vorher schon gemacht wurden. Das würde die Aussagekraft der Information möglicherweise heben.

    Wenn ich allerdings mal auf meine vergangenen Einkaufszettel schaue, dann ist eine Kombination beliebiger Produkte kaum sinnvoll: Wer Karotten kauft, kauft auch Senf (damit er was zum Würzen hat?). Wer Senf kauft, kauft auch T-Shirts (weil er sich vollkleckert und keinen Bock zum Waschen hat?). Wer T-Shirts kauft, kauft auch Toilettenpapier (Ähm...???).

    - Sven Rautenberg

    1. Hallo Sven,

      Hallo,
         ^Das heißt 'Hello', oder die nachfolgende Sprache paßt nicht (zuviel Whisky getestet?). :)

      Ich glaube da war eher jemand zu faul, sein Crossposting in ein englischsprachiges Forum zu übersetzen ;-).

      Wenn ich allerdings mal auf meine vergangenen Einkaufszettel schaue, dann ist eine Kombination beliebiger Produkte kaum sinnvoll: Wer Karotten kauft, kauft auch Senf (damit er was zum Würzen hat?). Wer Senf kauft, kauft auch T-Shirts (weil er sich vollkleckert und keinen Bock zum Waschen hat?). Wer T-Shirts kauft, kauft auch Toilettenpapier (Ähm...???).

      Deswegen ja auch "Customer who bought..." usw., Dein Datenbankschema würde ja nur die gleichzeitigen Bestellungen erfassen, bei sowas wie z.B. Amazon wird aber in der Datenbank erfaßt, was die Kunden, die diesen Artikel bestellt haben, irgendwann einmal gekauft haben. Das geht nur so wie es srob gesagt hat ("nur" natürlich in Anführungszeichen, es gehen sicher auch noch andere Datenbankschemen, aber man braucht die Kunden->Artikel Verknüpfung). Und dann funktioniert das statistisch wunderbar, wenn man genügend Datensätze hat, um Zufälligkeiten unter ein bestimmtes Niveau zu drücken. Die Empfehlungen, die man bei Amazon bekommst, sind sehr selten unpassend, ich nehme an, die zeigen das "Kunden, die dieses Buch gekauft haben, haben auch diese Bücher gekauft" nur an, wenn ein bestimmter Schwellenwert an Korrelation überschritten ist.
      Also a la:
      "select count(*) as anzahl from kunden_artikel as ka left join kunden_artikel as ka2 on ka.kundenid=ka2.kundenid where ka.artikelid=$gekaufter_artikel and ka2.artikelid!=$gekaufter_artikel group by ka2.artikelid having anzahl>5 order by anzahl desc";

      Viele Grüße
      Stephan

      1. Moin!

        Ich glaube da war eher jemand zu faul, sein Crossposting in ein englischsprachiges Forum zu übersetzen ;-).

        Hopefully he doesn't forget his posting here. :)

        Wenn ich allerdings mal auf meine vergangenen Einkaufszettel schaue, dann ist eine Kombination beliebiger Produkte kaum sinnvoll: Wer Karotten kauft, kauft auch Senf (damit er was zum Würzen hat?). Wer Senf kauft, kauft auch T-Shirts (weil er sich vollkleckert und keinen Bock zum Waschen hat?). Wer T-Shirts kauft, kauft auch Toilettenpapier (Ähm...???).

        Deswegen ja auch "Customer who bought..." usw., Dein Datenbankschema würde ja nur die gleichzeitigen Bestellungen erfassen, bei sowas wie z.B. Amazon wird aber in der Datenbank erfaßt, was die Kunden, die diesen Artikel bestellt haben, irgendwann einmal gekauft haben. Das geht nur so wie es srob gesagt hat ("nur" natürlich in Anführungszeichen, es gehen sicher auch noch andere Datenbankschemen, aber man braucht die Kunden->Artikel Verknüpfung).

        Das Datenbankschema von srob ist grausam! Das muß anders gehen.

        Und btw: Ich hab nur schöner umgesetzt, was srob angedacht hat. Wenn er alle Käufe eines Kunden, die jemals getätigt wurden verknüpfen und auswerten will, wird es extrem viel komplizierter. Und auch nicht mehr einfach abfragbar.

        Wenn man einen Artikel ansieht, dann muß man, um die gewünschte Info zu kriegen, erstmal alle Kunden abfragen, die den Artikel bestellt haben, dann alle Artikel, die diese Kunden ebenfalls schon bestellt haben, und das dann sinnvoll zusammenfassen. Wenn hundert Kunden den ersten Artikel bestellt haben, und jeder Kunde insgesamt schon 20 Artikel, dann entstehen 2000 Artikel, die man auswerten muß - ist natürlich noch kein wirklicher Aufwand, Amazon wird so aber nicht arbeiten können. Wenn man mehr als 100 Kunden hat, und mehr als 20 Artikel, dann wird das IMO langsam ziemlich performance-aufwendig. Besser ist, regelmäßig mal ein Skript durch die Datenbank zu jagen, um diese Verknüpfungen zu generieren, die dann relativ einfach abgefragt werden können.

        Und dann funktioniert das statistisch wunderbar, wenn man genügend Datensätze hat, um Zufälligkeiten unter ein bestimmtes Niveau zu drücken. Die Empfehlungen, die man bei Amazon bekommst, sind sehr selten unpassend, ich nehme an, die zeigen das "Kunden, die dieses Buch gekauft haben, haben auch diese Bücher gekauft" nur an, wenn ein bestimmter Schwellenwert an Korrelation überschritten ist.
        Also a la:
        "select count(*) as anzahl from kunden_artikel as ka left join kunden_artikel as ka2 on ka.kundenid=ka2.kundenid where ka.artikelid=$gekaufter_artikel and ka2.artikelid!=$gekaufter_artikel group by ka2.artikelid having anzahl>5 order by anzahl desc";

        Naja, wenigstens legst du pro Kauf keine gesonderte Tabelle an. ;)

        - Sven Rautenberg

        1. Hallo Swen,

          Das Datenbankschema von srob ist grausam! Das muß anders gehen.

          Nein, das ist nicht grausam, sondern die zugrundeliegende Struktur der Daten, sozusagen speicherplatzoptimiert. Alle Daten, die man für die Abfrage braucht, stehen sowieso schon in der Datenbank, weil in jedem vernünftigen Shop die einzelnen Artikel einer Bestellung in sowas wie kunden_artikel gespeichert werden.
          Alles, was dann noch kommt, ist Performance-Optimierung durch Caching, entweder durch zusätzliche Tabellen in der db, oder durch die Generierung irgendwelcher Dateien.
          Dann ist nur noch die Frage, ob es schneller geht, wenn bei jeder Bestellung die entsprechenden Datensätze in irgendeiner Cache-Tabelle aktualisiert werden. Denn dazu braucht man dann wieder so einen select wie mein Beispiel, und mindestens einen Insert oder Update.

          Und, meine (zugegeben etwas ketzerische) Standardantwort: in 90% der Fälle ist ein zusätzlicher Prozessor und ein paar Bänke RAM billiger und effektiver als ein Programmierer, der sich Gedanken über das Caching von Datenbanken macht, die für solche Sachen gemacht und optimiert werden. (Wie gesagt, 90% der Fälle, nämlich die, wo sich die Antwort wie hier mit einem einzigen recht unkomplizierten select rausfinden läßt, beim db-design nicht gepfuscht wurde, und die Zugriffszahlen nicht astronomische Höhen - >50k pro Tag - erreichen).
          Falls die Zugriffszahlen wirklich extrem hoch sind, würde ich sowieso die Produktseiten als "statisches PHP" cachen (die sessionids müssen ja immer noch dynamisch erzeugt werden), dann braucht es keine Sonderbehandlung für die "Kunden, die x kauften..."-Links, und der Aufwand für die Generierung der gecachten Seiten ist gegenüber den Zugriffen in der Regel vernachlässigbar.

          Aber nachdem ich zum Wohle aller hoffe, daß Bücherbestellungen andere Größenordnungen als Whiskeybestellungen haben (nicht, daß da jetzt noch mehr pidgin-englisch auf uns zukommt...), würde ich meine Standardantwort auch hier anwenden ;-).

          Viele Grüße
          Stephan

          1. Hallöchen!

            Ich muß Sven Recht geben! Das ist nicht ganz so einfach abfragbar! Nur Svens erste Version wäre da einfach möglich. Bie Scobs Ansatz sehe ich zwar nicht, warum man überhaupt eine neue Tabelle brauchen soll, denn die Verknüpfung von Kundennummer und Artikelnummer steht auch in der Bestellungen-Tabelle.
            Aber das Problem ist und bleuibt: Du mußt zuerst alle Kunden Filtern, die dieses Artikels X bereits gekauft haben, und dann weiß ich auch nichtz so recht was man am besten macht. Ich würde es probieren, die ganzen Kundennummern in die Where-Bedingung zu schreiben, dann könnte man nämlich direkt in der Abfrage die Zahlen der Artikel Y  ermitteln. Sonst braucht man ne Schleife, udn muß mit Variablen mitzählen.

            Die sauberste Lösung wäre wohl ein Subselect, aber das kann ich nicht, und MySQL (noch) ebensoweinig. Naja, egal was man macht, wenn das viele Kunden sind, wird das ganze recht lahm, befürchte ich!

            Ein ganz anderer Ansatz, wäre wenn man zu Svens alter Version zurückkommt, und immer Nachts per Cronjob nicht immer nur die Daten der aktuellen Bestellung eingibt, sondern das so mit allen bisherigen Bestellungen macht. Nur leider potenziert sich das, und gerade in einem Büchershop wie amazon bräuchte so ein Script bestimmt Stunden oder Tage zum durchlaufen! Aber auch der andere Ansatz müte bei vielen Bestellungen pro Person sehr langsam werden.

            Ich denke, die sind doch noch etwas schlauer als wir, bzw. als ich, und machen das irgendwie besser!

            Viele Grüße
            Andreas

            1. Hallo Andreas,

              Aber das Problem ist und bleuibt: Du mußt zuerst alle Kunden Filtern, die dieses Artikels X bereits gekauft haben, und dann weiß ich auch nichtz so recht was man am besten macht. Ich würde es probieren, die ganzen Kundennummern in die Where-Bedingung zu schreiben, dann könnte man nämlich direkt in der Abfrage die Zahlen der Artikel Y  ermitteln. Sonst braucht man ne Schleife, udn muß mit Variablen mitzählen.

              Warum so kompliziert? Mit dem select, den ich oben angegeben hatte ("select count(*) as anzahl from kunden_artikel as ka left join kunden_artikel as ka2 on ka.kundenid=ka2.kundenid where ka.artikelid=$gekaufter_artikel and ka2.artikelid!=$gekaufter_artikel group by ka2.artikelid having anzahl>5 order by anzahl desc"), kriegt man doch alles, was man braucht, wenn die kundenid auch bei den Bestelldetails mit gespeichert wird. Das Filtern der Kunden macht der Join der Bestelldetails-Tabelle mit sich selbst, weil auf der linken Seite nur die kundenids der Kunden übrigbleiben, die Artikel X gekauft haben.

              Die sauberste Lösung wäre wohl ein Subselect, aber das kann ich nicht, und MySQL (noch) ebensoweinig. Naja, egal was man macht, wenn das viele Kunden sind, wird das ganze recht lahm, befürchte ich!

              Bei 10.000 bestellten Artikeln im Monat sind das nach zwei Jahren 240.000 Datensätze in der Tabelle, die mit sich selbst gejoined wird, auf einem vernünftigen Server mit genügend RAM ist das kein Problem, sind ja alles int-Spalten, und man kriegt einen sehr viel kleineren Wert für die gejointen Datensätze als das volle Kreuzprodukt, ungefähr "absolute Verkaufszahl für Artikel X" * "Artikel pro Kunde", die Zeit für den Query sollte also linear mit der Anzahl der Bestellungen wachsen, nicht exponentiell, wie Du wohl befürchtest.

              Und falls es wirklich irgendwann zu langsam wird, ist es doch kein Problem, für jeden Artikel eine Cache-Datei anzulegen, in der die "verwandten Artikel" gespeichert werden. Beim Aufruf der Artikelseite wird nachgeschaut, ob die Cachedatei älter als 24h ist, wenn nicht, wird die zum User geschickt, sonst wird der query ausgeführt und die Datei neu geschrieben.

              Viele Grüße
              Stephan