Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [MySQL][PHP] Zapis do bazy przy relacji wiele do wielu
Forum PHP.pl > Forum > Przedszkole
martest
Mam pytanie,
Posiadam taką przykładową strukturę tabel (relacja wiele do wielu):

CREATE TABLE User (
UserId int NOT NULL AUTO_INCREMENT PRIMARY KEY,
FirstName VARCHAR(50) NULL,
LastName VARCHAR(50) NULL,
Email VARCHAR(50) NULL,
Password VARCHAR(130) NULL
);

/* TABELA ROLE */
CREATE TABLE Role(
RoleId int NOT NULL AUTO_INCREMENT PRIMARY KEY,
RoleName VARCHAR(30) NULL
);

/* TABELA DO POWIAZANIA - USER z ROLE */
CREATE TABLE UserRole(
UserId int,
RoleId int,
FOREIGN KEY(UserId) references User(UserId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY(RoleId) references Role(RoleId) ON DELETE CASCADE ON UPDATE CASCADE
);

Mam taką sytuację:
Poprzez formularz rejestracyjny użytkownik podaje wszystkie dane z tabeli "user" (FirstName, LastName, Email, Password) oraz określa swoją rolę (czyli de facto podaje "RoleName" z tabeli "Role"). I teraz mam takie pytanie, jak powinien wyglądać (od strony teoretycznej) zapis tych danych do bazy?

Poniżej podaje jak ja to widze ale chciałem sie poradzić kogoś bardziej doświadczonego:
1. Zapisuję pierw "FirstName, LastName, Email, Password" do tabeli "User",
2. Po tym zapisie pobieram SELECTem "UserId" tego dodanego przed chwila użytkownika, (do wykorzystania w punkcie 4)
3. Nastepnie sprawdzam podane "RoleName" z istniejącymi rolami w tabeli "Role" i pobieram ich "RoleId"
4. Używając INSERT INTO wstawiam odpowiednie pary zawierające "UserId" użytkownika z odpowiednim "RoleId" do tabeli "UserRole"

Byłbym wdzięczny za podpowiedzenie jak to sie poprawnie powinno robić, czy można tę operację jakoś uprościć i czy struktura tabel jest odpowiednia?
nospor
punkt 2 nie do konca tak. w mysql last_insert_id zwraca ID ostatnio dodanego rekordu i to z tego masz skorzystac
http://pl1.php.net/manual/en/function.mysql-insert-id.php

ps: haslo 130 znakow? Nie hashujesz hasla?
martest
dzieki za odpowiedz smile.gif
co do hasła to korzystam z funkcji "hash" i sha512 które zapisuje je na 128 znakach, dlatego dałem na nie na okrągło 130 znaków
nospor
jesli cos jest stale to sie uzywa char
U ciebie wiec powinno byc char(128)
Zas wszystkie pola INT powinny miec jeszcze UNSIGNED
No i ID roli mozna spokojnie zmieniejszyc z INT do SMALLINT

Cytat
na okrągło 130 znaków
Pamietaj, ze komputerowe "okrągło" to inne "okrągło" niz czlowieka.
Dla komputera 128 jest bardziej "okrągłe" niz 130 smile.gif
Crozin
Całość powinna być objęta transakcją: (pseudokod)
Kod
START TRANSACTION;
rid = SELECT id FROM roles WHERE name = :name;
INSERT INTO users VALUES (...);
uid = SELECT LAST_INSERT_ID();
INSERT INTO users_roles VALUES(uid, rid);
COMMIT;
Cytat
Zas wszystkie pola INT powinny miec jeszcze UNSIGNED
No i ID roli mozna spokojnie zmieniejszyc z INT do SMALLINT
Mogą, wcale nie muszą. O ile nie ma konkretnych przesłanek by skorzystać z któregoś z modyfikatorów wygodniej jest używać zwykłego [BIG]INT - lepsza kompatybilność z zew. narzędziami. wink.gif
nospor
@Crozin na czym ma polegac wygoda i szybkosc?
Crozin
Teraz dopiero zauważyłem, że wątek dotyczy stricte MySQL-a, ale część "problemów" będzie się tyczyć również go. MySQL jest dosyć wyrozumiały i często stara się myśleć za programistę, co skutkować może dziwnymi "błędami" (powinieneś to doskonale znać z PHP - i stawiam na to, że nie uznałbyś tego za pożyteczne w dłuższej perspektywie). Przede wszystkim chodzi o niemieszanie typów.
1. Jeżeli jedna kolumna jest typu UNSIGNED INT, a druga SIGNED INT nie za bardzo da się porównywać/kopiować wartości pomiędzy tymi kolumnami. Podobną sytuację mamy w przypadku, gdy jeden z typów jest mniejszy/większy od drugiego.
2. Wiele popularnych języków (np. PHP czy Java) nie wspiera typów UNSIGNED i pojawia się problem przy mapowaniu danych. UNSIGNED INT musi być mapowany w Javie na LONG, a w PHP jeszcze gorzej... 64-bitwy pomieści taki typ danych, 32-bitowy albo potraktuje to jako STRING albo utnie przesłaną wartość do SIGNED INT - oba przypadki mogą być katastrofalne w skutkach.
3. Korzystanie z tych modyfikatorów jest często po prostu bezcelowe. Bo co zyskamy poprzez nadanie dla kolumny ID modyfikatora UNSIGNED? Nic. A jeżeli zależałoby nam na możliwości pomieszczenia większego zakresu danych powinniśmy skorystać z BIGINT.
4. UNSIGNED o ile dobrze pamiętam nie jest częścią standardu SQL, więc i przenośność kodu spada. IIRC Postgres tego nie wspiera.

Co do szybkości:
W sumie tutaj nie mam pewności, bo pewnie wewnętrzne optymalizatory BD same lepiej wiedzą co robić i kiedy zignorować polecenia użytkownika, jednak podobnie jak z CPU/pamięcią operacje na danych o "okrągłych" rozmiarach (32/64 bity) są szybsze od operacji na mniejszych fragmentach (np. 16 bitów typu SMALLINT). Jednak tutaj nie mam kompetencji by się szczegółowo wypowiadać. Na dysku takie dane zapewne zapisywane są jako INT16, a w pamięci BD operuje już zapewne na INT32/64.

Podsumowując:
Korzystanie z UNSIGNED czy typów TINYINY/SMALLINT/MEDIUMINT w standardowych przypadkach nie niesie za sobą żadnych specjalnych oszczędności, a potrafi wprowadzić sporo zamieszania. O ile nie wie się co się dokładnie robi... lepiej używać "podstawowych" INT/BIGINT.
nospor
Ja tam jestem zwolennikiem teorii, ze jezeli dana kolumna moze max przyjmowac 200, to nie daje SMALLINT czy INT, tylko TINYINT UNSIGNED, gdyz taki typ doskonale do tego pasuje i zajmuje najmniej miejsca.

Z opisanymi przez Ciebie problemami na szczescie jeszcze sie nie spotkalem smile.gif
martest
mam jeszcze jedno pytanie, jak zrobić taką transakcje jeżeli przy wykonywaniu zapytań używam funkcji "prepare", bo próbowałem cos napisać ale nie chce mi to działać.
Poniżej wklejam fragment kodu (oczywiscie nie działa on tak jak należy i prosiłbym kogoś bardziej zorientowanego aby spojrzał i powiedział jak to prawidłowo powinno wyglądać).

function registerUser($FirstName, $LastName, $Email, $Password, $Role) {
global $MO;

$MO->Database->autocommit(FALSE);

if ($idDlaRoli = $MO->Database->prepare("SELECT RoleId FROM role WHERE RoleName = ?")) {
$idDlaRoli->bind_param('s', $Role);
$idDlaRoli->execute();
} else {
echo 'blad przy pobieraniu id roli';
}

if ($stmt = $MO->Database->prepare("INSERT INTO user (FirstName, LastName, Email, Password) VALUES (?,?,?,?)")) {
$stmt->bind_param('ssss', $FirstName, $LastName, $Email, $Password);
$stmt->execute();
} else {
echo 'blad przy dodawaniu usera';
}

$idDlaUsera = mysql_insert_id();

if ($res = $MO->Database->prepare("INSERT INTO userrole (UserId,RoleId) VALUES (?,?)")) {
$res->bind_param('ii', $idDlaUsera, $idDlaRoli);
$res->execute();
} else {
echo 'problem z dodawaniem usera do roli';
}

$MO->Database->commit();
}

Crozin
Najważniejsze: włącz raportowanie (dyrektywa display_error) wszystkich (error_reporting(-1)) błędów.

1. Zakładam, że $MO->Database to obiekt PDO, tak? W takim razie spójrz do manuala na PDOStatement - nie ma metody bind_param, jest bindParam/Value, spójrz na to co zwraca PDOStatement::execute i ::fetch[All].
2. Mieszasz PDO i mysql_*() - użyj PDO::lastInsertId()
3. Transakcję powinieneś rozpocząć jawnie PDO::beginTransaction().
martest
Już wcześniej korzystałem z "prepare" przed modyfikacją funkcji registerUser. Wyglądało to mniej wiecej tak:

  1. function registerUser($FirstName, $LastName, $Email, $Password)
  2. {
  3. global $MO;
  4.  
  5. if($stmt = $MO->Database->prepare("INSERT INTO user (FirstName,LastName,Email,Password) VALUES (?,?,?,?)"))
  6. {
  7. $stmt->bind_param('ssss', $FirstName, $LastName, $Email, $Password);
  8. $stmt->execute();
  9. $stmt->store_result();
  10.  
  11. if($stmt->num_rows > 0)
  12. {
  13. $stmt->close();
  14. return TRUE;
  15. }
  16. else
  17. {
  18. $stmt->close();
  19. return FALSE;
  20. }
  21. }
  22. else
  23. {
  24. die();
  25. }
  26.  
  27. }


Do połączenia z baza uzywam:
  1. $serwer = 'localhost';
  2. $user = 'root';
  3. $pass = '';
  4. $db = 'newDB';
  5. $MO = new MO_Core($serwer, $user, $pass, $db);


w klasie MO_Core poprzez konstruktor wywoluje:

  1. class MO_Core{
  2. function __construct($serwer, $user, $pass, $db) {
  3. $this->Database = new mysqli($serwer, $user, $pass, $db);
  4. $this->Database->set_charset('utf8');
  5. ...}


W przypadku gdy było jedno "bind_param" oraz jedno "execute" powyższy kod działał. Problem jest właśnie w sytuacji kiedy chce wykonać transakcję zawierającą kilka nastepujących po sobie zapytań które przedstawiłem w moim poprzednim poście
nospor
Czyli nie uzywasz PDO tylko jakiejs wlasnej klasy, ktora skolei korzysta z mysqli_. Tak czy siak nadal mieszasz, bo uzywasz mysqli i mysql, a to są dwie zupelnie rozne rzeczy. To po pierwsze.

A po drugie: napiasz moze laskawie na czym polega to niedzialanie, bo do tej pory nie moge wyczytac tego w twoich postach
martest
Cytat(martest @ 28.04.2014, 17:39:15 ) *
W przypadku gdy było jedno "bind_param" oraz jedno "execute" powyższy kod działał. Problem jest właśnie w sytuacji kiedy chce wykonać transakcję zawierającą kilka nastepujących po sobie zapytań które przedstawiłem w moim poprzednim poście


Tu jest opisany mój problem.
Jak wykonuje się pierwsze zapytanie z "prepare", "bind_param" i "execute" wsystko jest ok, ale jak przechodzi do następnego to zapytanie się nie wykonuje, tylko przeskakuje odrazu do 'else' i wyrzuca (w moim przypadku) 'blad przy dodawaniu usera' oraz kolejne również się nie wykonuje, wchodzi w blok 'else' i wyrzuca 'problem z dodawaniem usera do roli'.
Problem jest z tym jak powinno się zapisywać kolejne zapytania w transakcji korzystając z "prepare".
nospor
Dopiero teraz opisales poprawnie problem. W czesniej tylko pisales ze masz problem. A to znacząca roznica w opisie, nie sądzisz?

1) Uzywasz klasy, ktorej nie znamy, Nie wiemy czy bind_param w twojej klasie pozwala na takie przypisywanie parametrow.
2) Juz ci pisalem ze mieszasz mysqli z mysql.

edit:
ok, widze, ze bind_param pochodzi od mysqli
http://pl1.php.net/manual/en/mysqli-stmt.bind-param.php
tak wiec mozna teoretycznie tak przypisac. Zamiast wiec nic nie mowiacego komunikatu, ze sie nie udalo, wyswietlaj BLAD BAZY, a sie dowiesz co konkretnie sie stalo

Tu masz napisane jak sie wyswietla blad
http://pl1.php.net/manual/en/mysqli-stmt.error.php
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.