Entwickler-Ecke

Datenbanken (inkl. ADO.NET) - SQL Freundschaftstabelle


Csharp-programmierer - So 30.10.16 21:34
Titel: SQL Freundschaftstabelle
Hallo Forum,

in der C# Applikation ist es jetzt möglich, Freundschaftsanfragen zu versenden. Wird eine Freundschaftsanfrage versendet, wird in einer Tabelle `Freundschaftsanfragen` ein Datensatz angelegt, der in etwa so aussieht. Wenn der Benutzer, an den die FA gesendet wurde, sich einloggt, kann er diese Anfrage annehmen oder ablehnen. Nimmt er diese an, dann wird der Datensatz aus der Tabelle `Freundschaftsanfragen` gelöscht. Nun wird aber ein neuer Datensatz in die Tabelle `Freundesliste` eingetragen. Diese Tabelle sieht ungefähr so aus:

ID | One | Two |
--------------------------------
1 | User1 | User2 |
--------------------------------

Soo das funktioniert schon mal. Wenn sich jetzt User 1 einloggt, muss User2 als Freund dastehen. Aber wenn User2 sich einloggt, muss User1 als Freund auftauchen.
Wie kann man diese SQL Abfrage verallgemeinern, dass die alle Freunde von dem jeweiligen eingeloggten Benutzer ausgibt?

Mit freundlichen Grüßen :)


Delete - So 30.10.16 22:08

- Nachträglich durch die Entwickler-Ecke gelöscht -


Csharp-programmierer - So 30.10.16 22:31

Oder man erzeugt pro Freundschaft 2 Datensätze. Beispielsweise so:

ID | UserEingeloggt | UserAnderer |
-------------------------------------------------------
1 | Cshrp-programmierer | User2 |
-------------------------------------------------------
2 | User2 | Csharp-programmierer |
-------------------------------------------------------

Dann könnte man diese Abfrage durchführen:

SQL-Anweisung
1:
SELECT * FROM `Freundschaften` WHERE `UserEingeloggt`=VALUE                    


Aber dann würde ja mein 80 GB Webspace Speicher sicher irgendwann überlasten :(


Christian S. - So 30.10.16 22:50

user profile iconCsharp-programmierer hat folgendes geschrieben Zum zitierten Posting springen:
Aber dann würde ja mein 80 GB Webspace Speicher sicher irgendwann überlasten :(
Auf Grund welcher Abschätzung der benötigten Datenmengen vermutest Du das?


Csharp-programmierer - So 30.10.16 22:53

Naja...
Sagen wir mal 1000 User sind befreundet, dann sind in der Datenbank 2000 Datensätze. Ist bestimmt schon relativ viel


Christian S. - So 30.10.16 23:20

Wie kommst Du da drauf, dass das viel ist?

Du speicherst (höchstens) drei Zahlen. Eine ID für den Datensatz, eine ID für den ersten Benutuzer, eine ID für den zweiten Benutzer. Drei Zahlen mit je 32 Bit = 4 Byte. Sind also 12 Byte pro Datensatz. Sagen wir, es gibt noch einen gigantisch unrealistischen Overhead und es sind pro Datensatz 1 KB (also das 85-fache der eigentlichen Daten!). Bei 2000 Datensätzen wärst Du also bei 2 MB. Wo genau siehst Du die Grenze von 80 GB erreicht?


Delete - Mo 31.10.16 00:22

- Nachträglich durch die Entwickler-Ecke gelöscht -


Christian S. - Mo 31.10.16 00:27

user profile iconFrühlingsrolle hat folgendes geschrieben Zum zitierten Posting springen:
[...]oder du erstellst für jeden Namen eine eigene Tabelle, in der dessen Freunde aufgelistet werden.
Was wäre der Vorteil davon?


Delete - Mo 31.10.16 00:29

- Nachträglich durch die Entwickler-Ecke gelöscht -


Christian S. - Mo 31.10.16 00:36

Du meinst, wenn ich dann bei 1000 Nutzern 1000 Tabellen habe?

Und wie frage ich dann z.B. ab, wer alles Benutzer X "gefreundet" hat?


Palladin007 - Mo 31.10.16 00:45

In der heutigen Zeit ist der Datenspeicher die billigste Resource, um die würde ich mir als letztes Gedanken machen ^^
Klar, solltest Du einige Grundregeln beachten, aber solange Du nicht zig Daten an zig verschiedenen Stellen redundant hältst, sollten deine 80GB eigentlich locker und mit links ausreichen.
Kommt natürlich drauf an, was Du noch so für Daten sammelst


Und was die Freundschaften angeht:

Variante 1:
Wie Du schon gesagt hast:
Für jede neue Freundschaft bekommt jeder User einen Freundschaft-Eintrag.
Das hätte den Vorteil, dass pro Person eigene Daten pro Freundschaft gespeichert werden können.
Einen Vorteil in der Abfrage sehe ich nicht.

Variante 2:
Jede Freundschaft zwischen zwei Personen ist "eindeutig"
Pro Freundschaft gibts dann genau einen Datensatz.
Gemeinsame Daten pro Freundschaft sind dann leichter zu verwalten, eine Person kann aber keine eigenen Daten für eine Freundschaft haben.

In SQL ungefähr so:

SQL-Anweisung
1:
2:
3:
SELECT *
FROM Freundschaften
WHERE User1Id = @userId OR User2Id = @userId



Die wichtigste Frage bleibt also:
Was genau willst Du machen?
Braucht eine Freundschaft pro Person eigene Daten?
Oder soll jede Freundschaft eigene Daten gemeinsam für beide Beteiligten haben?
Je nachdem, was Du brauchst, musst Du die Variante wählen.

Die Komplexität der SQL-Abfrage, der Umfang der Daten oder Performance sind bei einem so kleinen Rahmen eher drittrangig.

Aber mach bitte nicht so Spielereien wie pro Freund eine neue Spalte oder pro Person eine neue Freundes-Tabelle.
Potentielle Probleme dabei:

Hab ich was vergessen?


Delete - Mo 31.10.16 01:11

- Nachträglich durch die Entwickler-Ecke gelöscht -


Th69 - Mo 31.10.16 10:27

Also das mit den eigene Tabellen paßt nun wirklich nicht zu einer normalisierten Datenbank.

Ich würde die Abfrage ähnlich wie Palladin007 machen, nur daraus zwei machen und diese beiden vereinen:

SQL-Anweisung
1:
2:
3:
4:
5:
6:
7:
8:
9:
SELECT User2Id
FROM Freundschaften
WHERE User1Id = @userId

UNION

SELECT User1Id
FROM Freundschaften
WHERE User2Id = @userId


Palladin007 - Mo 31.10.16 11:10

Zitat:
1000 Benutzer / 1000 Tabellen, oder 1 Tabelle und ein vielfaches an Datensatzen, also mehr als 1000!


Wenn 1000 Benutzer 1000 Freunde haben, dann brauchst Du 1000 Tabellen mit 1000 Datensätzen.
1000 Tabellen tun weh in der Performance

Alles in einer Tabelle würde bedeuten, dass für jede Person und für jede Freundschaft zwei Datensätze angelegt werden.
Sprich: 1000 * 1000 * 2 = 2000000 Datensätze
Das wäre in der Tat viel, allerdings hast Du die Datenmenge bei 1000 Tabellen auch plus die Daten, die der Server braucht um die Tabellen zu verwalten.
Und bei 1000 Tabellen kann der Server nicht über alle Daten indizieren.

Und DAS, der letzte Punkt, der sollte eigentlich ein KO-Kriterium sein ;)
Wenn Du 2 Millionen Datensätze in einer Tabelle hast, kann der Server auf alle Datensätze indizieren, was einen deutlichen Performance-Vorteil bedeuten kann.
Wenn Du diese 2 Millionen Datensätze auf 1000 Tabellen auf teilst, dann indiziert der Server über 1000 Datensätze pro Tabelle, was witzlos wäre.


Csharp-programmierer - Mo 31.10.16 12:55

Jetzt habe ich gestern Abend noch bin tief in die Nacht an dem PHP Script rumgewerkelt, dass die Usernames in IDs verwandelt werden. Jetzt haut alles so hin, dass die C# Anwendung die Usernames zur PHP Datei schickt und diese demnach die zugehörige ID raussucht und in die Datenbank schreibt. Im Moment läuft es noch so, dass der doppelte Datensatz eingetragen wird.

Was soll noch passieren?
Die eine Datenbank soll lediglich alle Freundschaften speichern. Heute versuche ich noch einen PHP Script zu basteln, der dann die ganzen Freunde, samt anderen Daten ausgibt...

Im Moment habe ich ja noch den doppelten Datensatz für eine Freundschaft.


SQL-Anweisung
1:
2:
3:
4:
5:
6:
7:
8:
9:
SELECT User2Id
FROM Freundschaften
WHERE User1Id = @userId

UNION

SELECT User1Id
FROM Freundschaften
WHERE User2Id = @userId


Und mit diesem SQL Befehl bekommt man dann alle Freundschaften anhand eines Datensatzes?


Delete - Mo 31.10.16 14:44

- Nachträglich durch die Entwickler-Ecke gelöscht -


jfheins - Mo 31.10.16 15:04

Was meinst du damit? Die Abfrage?
Das liegt halt daran, dass die Freundschaft kommutativ ist. Man kann auch ein System implementieren, bei dem man Leute einseitig de-frienden kann. Dann hätte die Reihenfolge wieder eine Bedeutung.

Aber so ist es egal, und bevor du einfach die doppelte Menge an Daten in die DB kippst, ist es doch einfach eine Abfrage wie "... WHERE User1Id = @userId OR User2Id = @userId" abzusetzen.


Delete - Mo 31.10.16 19:06

- Nachträglich durch die Entwickler-Ecke gelöscht -


Csharp-programmierer - Mo 31.10.16 21:08

Ich habe es jetzt so gemacht, dass pro Freundschaft nur ein Datensatz angelegt wird. Ich habe folgenden Code zusammengestellt:


PHP-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
$CurrentUser = mysqli_real_escape_string($conn$_POST["currUsr"]);
    $sqlQuery = "SELECT `To_ID` FROM `MainlysoftFreundesliste` WHERE `To_ID`='$CurrentUser' OR `From_ID`='$CurrentUser'";
    $result = mysqli_query($conn$sqlQuery);
    if($result)
    {
      $newQuery = "SELECT `name`, `vorname`, `language`, `gender` FROM `MainlysoftBenutzer` WHERE `ID`='$CurrentUser'";
      if ($result->num_rows > 0) {
          while($row = $result->fetch_assoc()) {
            echo $row["name"]. ";" . $row["vorname"]. ";" .$row["language"]. ";" .$row["gender"]. "";
          }
      } else {
        echo "0 results";
      }  
    }
    else
    {
      echo "Error in SQL statement";
    }


Jetzt wird mir ja nur "To_ID" rausgesucht. Aber wenn ich jetzt auch "Von_ID" raussuchen lass, weiß ich ja nicht, welcher von den beiden Usern sich eingeloggt hat. Verseht ihr den logischen Fehler?


Ralf Jansen - Mo 31.10.16 21:21

Zitat:
Jetzt wird mir ja nur "To_ID" rausgesucht. Aber wenn ich jetzt auch "Von_ID" raussuchen lass, weiß ich ja nicht, welcher von den beiden Usern sich eingeloggt hat. Verseht ihr den logischen Fehler?


Nein. Warum solltest du, weil das SQL änderst, ~vergessen~ wer in $CurrentUser steht?


Th69 - Di 01.11.16 10:00

Ich möchte noch mal auf meinen UNION-Vorschlag hinweisen ;-)

Aber mit einer einfachen if-Abfrage läßt sich natürlich auch die OR-Query auflösen...


Csharp-programmierer - Di 01.11.16 15:19

Okay. Ich habe jetzt diesen Code:


PHP-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
$CurrentUser = mysqli_real_escape_string($conn$_POST["currUsr"]);
    $sqlQuery = "SELECT `To_ID` FROM `MainlysoftFreundesliste` WEHRE `From_ID`='$CurrentUser' UNION SELECT `From_ID` FROM `MainlysoftFreundesliste` WHERE `To_ID`='$CurrentUser'";
    $result = mysqli_query($conn$sqlQuery);
    if($result)
    {  
      if($result->num_rows > 0) {
        while($row1 = $result->fetch_assoc())
        {
          $zwischenVariable = $row1["To_ID"];
          $newQuery = "SELECT `name`, `vorname`, `language`, `gender` FROM `MainlysoftBenutzer` WHERE `ID`='$zwischenVariable'";
          $newResult = mysqli_query($conn$newQuery);
          if($newResult)
          {
            if ($newResult->num_rows > 0) {
              while($row = $newResult->fetch_assoc()) {
                echo $row["name"]. ";" . $row["vorname"]. ";" .$row["language"]. ";" .$row["gender"]. "";
              }
            } else {
              echo "0 results";
            }  
          }
        }
      }
    }
    else
    {
      echo "Error in SQL statement";
    }


Aber nun bekomme ich den Fehler: Error in SQL statement...

Die Datenbanktabelle sieht jetzt so aus:
ID | From_ID | To_ID |
------------------------------
1 | 1 | 2 |


jfheins - Di 01.11.16 16:03

Der Fehler ist so natürlich wenig hilfreich.
Informativer wird's, wenn du die Zeile im else-Zweig so änderst:


PHP-Quelltext
1:
2:
3:
4:
else
    {
      echo "Error in SQL statement: " . mysqli_error($conn);
    }


Bitte mal probieren und rückmelden ;-)


Th69 - Di 01.11.16 16:16

'WEHRE't den Anfängen. :lol:


Csharp-programmierer - Di 01.11.16 16:47

Hupps. WHERE ist aber auch ein doofes Wort :(


PHP-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
$CurrentUser = mysqli_real_escape_string($conn$_POST["currUsr"]);
    $sqlQuery = "SELECT `To_ID` FROM `MainlysoftFreundesliste` WHERE `From_ID`='$CurrentUser' UNION SELECT `From_ID` FROM `MainlysoftFreundesliste` WHERE `To_ID`='$CurrentUser'";
    $result = mysqli_query($conn$sqlQuery);
    if($result)
    {  
      if($result->num_rows > 0)
      {
        while($row1 = $result->fetch_assoc())
        {
          $zwischenVariable = $row1["To_ID"];
          $newQuery = "SELECT `name`, `vorname`, `language`, `gender` FROM `MainlysoftBenutzer` WHERE `ID`='$zwischenVariable'";
          $newResult = mysqli_query($conn$newQuery);
          if($newResult)
          {
            if ($newResult->num_rows > 0) {
              while($row = $newResult->fetch_assoc()) {
                echo $row["name"]. ";" . $row["vorname"]. ";" .$row["language"]. ";" .$row["gender"]. "";
              }
            } else {
              echo "0 results";
            }  
          }
          else
          {
            echo "error here";
          }
        }
      }
      else
      {
        echo "now here error";
      }


Jetzt kommt nur noch der Error: now error here. Das heißt ja, dass die Datenbank keine passenden Datensätze findet, oder wie?

Moderiert von user profile iconChristian S.: C#- durch PHP-Tags ersetzt


Delete - Di 01.11.16 17:18

- Nachträglich durch die Entwickler-Ecke gelöscht -


Palladin007 - Mi 02.11.16 01:22

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Ich möchte noch mal auf meinen UNION-Vorschlag hinweisen ;-)


Wo genau wäre überhaupt der Vorteil, die zwei Selects per Union zu verknüpfen anstatt die UserId mit beiden IDs in der Tabelle zu vergleichen?

Also entweder das:


SQL-Anweisung
1:
2:
3:
SELECT *
FROM Friends
WHERE FromUserId = 123 OR ToUserId = 123


Oder:


SQL-Anweisung
1:
2:
3:
4:
5:
6:
7:
8:
9:
SELECT *
FROM Friends
WHERE FromUserId = 123

UNION

SELECT *
FROM Friends
WHERE ToUserId = 123


Am Ende kommen alle Datensätze raus, bei denen sowohl FromUserId oder ToUserId gleich 123 sind.
Oder übersehe ich da was?


Th69 - Mi 02.11.16 11:38

Hallo Palldadin007,

bei der OR-Query müßtest du aber noch explizit die beiden Felder FromUserId und ToUserId überprüfen - bei der UNION-Version (in meiner Variante!) hat man sofort in einer Spalte alle Freunde stehen.

Abfragen mit * sollte man generell im Code vermeiden.


Palladin007 - Mi 02.11.16 13:07

Stimmt, damit bekomme ich dann eine flache Liste mit nur einer Spalte.
Endlich hab ich auch mal ein Beispiel, wo Union sinnvoll ist, hab ich bisher noch nie verwendet :D

Zitat:
Abfragen mit * sollte man generell im Code vermeiden.

Klar, war auch nur ein Beispiel weil wegen spät und so :D


Csharp-programmierer - Do 03.11.16 17:53

Also ich verstehe den Fehler jetzt nicht ganz.


PHP-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
$CurrentUser = mysqli_real_escape_string($conn$_POST["currUsr"]);
    $sqlQuery = "SELECT `To_ID` FROM `MainlysoftFreundesliste` WHERE `From_ID`='$CurrentUser' UNION SELECT `From_ID` FROM `MainlysoftFreundesliste` WHERE `To_ID`='$CurrentUser'";
    $result = mysqli_query($conn$sqlQuery);
    if($result)
    {  
        while($row1 = $result->fetch_array())
        {
          $zwischenVariable = $row1["To_ID"];
          $newQuery = "SELECT `name`, `vorname`, `language`, `gender` FROM `MainlysoftBenutzer` WHERE `ID`='$zwischenVariable'";
          $newResult = mysqli_query($conn$newQuery);
          if($newResult)
          {
            if ($newResult->num_rows > 0) {
              while($row = $newResult->fetch_assoc()) {
                echo $row["name"]. ";" . $row["vorname"]. ";" .$row["language"]. ";" .$row["gender"]. "";
              }
            } else {
              echo "0 results";
            }  
          }
          else
          {
            echo "error here";
          }
        }
        
    }
    else
    {
      echo "Error in SQL statement";
    }


Wenn ich diesen Script jetzt ausführe, bekommt ich keine Antwort (oder eine leere). Ich möchte nur, dass die Tabelle "Freundesliste" durchsucht wird. Und für jeden Freund, der mit dem aktuell eingeloggten User befreundet ist, soll nochmal in der Tabelle "Benutzer" gesucht werden und dann Daten rausgegeben werden.


Th69 - Do 03.11.16 20:57

Nur eine Vermutung: passen die Datentypen?
Stehen in den Datenbankspalten From_ID und To_ID denn wirklich Strings drin (oder Integer) - du verwendest ja Hochkommata?

Und bei einer UNION bin ich mir nicht sicher, was der genauer Spaltenname der Query ist (d.h. laß dir doch mal $zwischenVariable ausgeben) - evtl. nimm mal $row1[0].


Csharp-programmierer - Fr 04.11.16 18:04

In der Datenbank `Freundesliste` sind nur Datentypen vom Typ INT.
Ich habe mir mal die zwischenVariable und row[0] geechoed. Aber die Serverantwort war leer :(

Moderiert von user profile iconTh69: PHP-Tags hinzugefügt


Th69 - Fr 04.11.16 18:42

Was steht denn in $CurrentUser drin? Und ich wiederhole meine Frage:
Warum verwendest du bei deren Benutzung dann die Hochkommata (Zahlen (IDs) werden direkt verwendet - wie in C# auch ;- )?

s.a. das $limit-Beispiel in How to include a PHP variable inside a MySQL insert statement [http://stackoverflow.com/questions/7537377/how-to-include-a-php-variable-inside-a-mysql-insert-statement]


Csharp-programmierer - Fr 04.11.16 20:18

Ups, mir ist ein kleiner großer Fehler passiert. In der C# Anwendung habe ich der NameValueConnection einen falschen Parametername gegeben. Jetzt funktioniert es halbwegs.
Wenn sich jetzt der User einloggt, bekommt dieser eingeloggte User sich selbst als Freund, das liegt wahrscheinlich daran, dass dass der eingeloggte User mit einem Freund an einem Datensatz hängt. SQL gibt jetzt wahrscheinlich den einen User aus, der in dem Fall der eingeloggte User ist.
Wie kann man das so programmieren, dass genau der andere Datensatz als der eingeloggte User ausgegeben wird?


Delete - Fr 04.11.16 20:41

- Nachträglich durch die Entwickler-Ecke gelöscht -


Th69 - Fr 04.11.16 20:53

Bei der UNION-Query kann das aber nur passieren, wenn in einem Datensatz derselbe Wert in beiden Spalten steht.


Csharp-programmierer - Fr 04.11.16 21:10

Vielen Dank Leute,
jetzt funktioniert es. Der Fehler war, dass ich in der SQL Abfrage * statt `From_ID` genommen habe. Jetzt funktionierts :)

Vielen Dank, an die Helfer :)


Csharp-programmierer - Fr 04.11.16 21:36

Ups, das Thema ist doch noch offen. Ich habe noch ein kleines Problem.
In der Datenbank `Freundesliste` sind nun 3 Datensätze. Jetzt ist aber nur das Problem, dass die PHP Datei nur einen von 2 Freunden raussendet. Wieso?


PHP-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
if($conn)
  {
    $CurrentUser = mysqli_real_escape_string($conn$_POST["currUsr"]);
    $sqlQuery = "SELECT `To_ID` FROM `MainlysoftFreundesliste` WHERE From_ID='$CurrentUser' UNION SELECT `From_ID` FROM `MainlysoftFreundesliste` WHERE To_ID='$CurrentUser'";
    $result = mysqli_query($conn$sqlQuery);
    if($result)
    {  
      if ($result->num_rows > 0) {
        while($row1 = $result->fetch_array())
        {
          $zwischenVariable = $row1["To_ID"];
          
          $newQuery = "SELECT `name`, `vorname`, `language`, `gender` FROM `MainlysoftBenutzer` WHERE `ID`='$zwischenVariable'";
          $newResult = mysqli_query($conn$newQuery);
          if($newResult)
          {
            if ($newResult->num_rows > 0) {
              while($row = $newResult->fetch_assoc()) {
                echo $row["name"]. ";" . $row["vorname"]. ";" .$row["language"]. ";" .$row["gender"]. "";
              }
            } else {
              echo "0 results";
            }  
          }
          else
          {
            echo "0 results";
          }
        }
      } else {
        echo "o results";
      }
    }
    else
    {
      echo "Error in SQL statement";
    }
  }


Delete - Sa 05.11.16 03:56

- Nachträglich durch die Entwickler-Ecke gelöscht -


Csharp-programmierer - Sa 05.11.16 15:02

Das Problem war, dass ich im PHP Script | vergessen habe. Die C# Applikation splittet die Serverantwort dann an dem |. Jetzt funktioniert aber wirklich alles :)


Delete - Sa 05.11.16 15:21

- Nachträglich durch die Entwickler-Ecke gelöscht -