//DEVGURU

Author's entries:

Obliczanie pozycji w rankingu w języku MySQL

Monday, January 14th, 2008

*Zarys sytuacji

Wyobraźmy sobie następującą sytuację. Mamy tablicę użytkowników z
przypisaną do każdej osoby aktywnością oznaczającą, przyjmijmy w
uproszczeniu, liczbę logowań danego użytkownika do systemu. Chcemy aby
użytkownik mógł się łatwo dowiedzieć o swoim miejscu w rankingu
aktywności.

*Rozwiązanie nie będące głównym bohaterem tego tekstu

Można do problemu podejść w ten sposób, żeby do tablicy użytkowników
wstawić dodatkowe pole rank i za każdym razem, gdy użytkownik się
loguje, sprawdzać wartość pola activity i porównywać ją z wartością
tego pola u użytkownika o poprzedniej i następnej wartości pola rank.
Jeśli np. pole o poprzedniej wartości pola rank ma pole activity o
większej wartości, pola zamieniają się nawzajem wartością pola rank.
Takie rozwiązanie wymaga jednak ciągłej modyfikacji bazy danych, co
jest mniej optymalne, niż nawet dość złożone zapytanie do bazy danych
jedynie odczytujące informacje. Tym bardziej, jeśli informacja o
pozycji w rankingu będzie odczytywana sporadycznie.

*Rozwiązanie – główy bohater tego tekstu

Chcąc uzyskać informację o pozycji w rankingu mając do dyspozycji
jedynie pole activity, możemy skorzystać z mechanizmu zmiennych
użytkownika (User-Defined Variables), nazywanych w innych źródłach
zmiennymi sesyjnymi (Session Variables), lub, mniej poprawnie,
zmiennymi lokalnymi (Local Variables).

SET @m:=0;
SELECT rank FROM
   (SELECT id, activity, @m:=@m+1 AS rank
     FROM `users`
     ORDER BY activity DESC) AS User
WHERE id = $user_id

*O co tu chodzi

Mechanizm zmiennych użytkownika polega na tym, że najpierw przy pomocy
wyrażenia SET ustalamy wartość początkową zmiennej, a potem odwołujemy
się do niej, lub też modyfikujemy ją w kolejnych zapytaniach do bazy
danych. Zasięg tak utworzonej zmiennej obejmuje okres od uzyskania
połączenia do bazy danych, aż do jego zamknięcia.

Zmienne użytkownika nie wymagają deklaracji (self-defined), co
oznacza, że równie dobrze pierwszy wiersz powyższego przykładu mógłby
wyglądać następująco:

SELECT @m := 0;

*Źródła

CakePHP – Odc. I – Wariacje na temat ‘_queryCache’…

Saturday, October 13th, 2007

…czyli jak zadać dwa razy to samo pytanie i uzyskać inną odpowiedź.

akt 1. Cache’owanie zapytań

Cake zapamiętuje wynik każdego zapytania o czym nie można się dowiedzieć z manuala. Zapamiętuje jednak tylko w pamięci podręcznej, a więc jedyna korzyść z tego taka, że jak w tej samej metodzie (akcji) controllera wywołam np.

$user = $this->User->findAllByName('Alojzy');
...
$user = $this->User->findAllByName('Alojzy');

to zapytanie do bazy danych zostanie wykonane tylko raz. Dzieje się tak nawet, jeśli zamiast findAllByName skorzystamy z
$this->User->query(…)

Problem jednak pojawia się, gdy między tymi wywołaniami znajduje się coś jak:

$this->User->save($this->data);


wtedy drugie findAllByName zwróci nieaktualne dane (oczywiście o ile $this->data będzie zawierało informacje o jakimś Alojzym).

Sytuacji takiej oczywiście zwykle można uniknąć, bo jak odczytam dane z bazy, a potem je zmienię, to znaczy, że posiadam komplet wiedzy, żeby zbudować wynik kolejnego odczytu bez odwoływania się do bazy.

akt 2. Jak być powinno

Zgodnie z ideą CakePHP programista powinien dążyć, do traktowania modelu jako raczej reprezentanta konkretnego rekordu, a nie tabeli (oczywiście findAll i temu podobne to wyjątki). Dlatego przy zapisie danych ustalany jest klucz rekordu i wywołując ’saveField’, czy ’save’ bez klucza, odwołujemy się ciągle do tego samego rekordu. Ponieważ bywa to uciążliwe, gdy zapisuje się więcej niż jeden rekord (np. w pętli), Cake’owcy proponują w takim przypadku wywoływać:

$this->[nazwa_modelu]->create();


za każdym razem gdy skończymy odwoływać się do konkretnego rekordu (np. w tej pętli).

Wydaje sie naturalne, że wywołanie takiego resetu na modelu powinno wyczyścić jego cache (czy tylko mi się to wydaje naturalne?)

Niestety cache nie jest trzymany w klasie AppModel (model_php5.php), tylko w klasie DataSource (datasource.php i dbo_source.php). Dzięki temu zapytanie jest cache’owane jako jedno i to samo nawet gdy wywoływane jest poprzez różne modele (IMHO nieco wątpliwa korzyść). Utrudnia to jednak operowanie na cache’u.

akt 3. Rozwiązanie.

W przypadku, gdy chcę odczytać dane z bazy (select) przed i po modyfikacji danych (update), muszę po updacie wyczyścić cały cache zapytań. Na stronie: http://www.benjiegillam.com/serendipity/categories/11-CakePHP Benjie Gillam proponuje dodać do app/app_model.php następującą funkcję:

 function clearAllDBCache() {
   $db =& ConnectionManager::getDataSource($this->useDbConfig);
   $db->_queryCache = array();
  }

i wywoływać ją za każdym razem, gdy w środku controllera zapragniemy aktualnych danych.

Krzysiek Heród