Aktualny czas a transakcja w PostgreSQL

Ostatnio miałem ciekawy case’ik – musiałem w obrębie jednej transakcji zmienić dane rekordów. Wszystko odbywało się w kilku funkcjach plpgsql, funkcje mogły się wywoływać rekurencyjnie w triggerach a zmiany dotyczyły między innymi pól typu TIMESTAMP. Wartości pól TIMESTAMP rekordów były zmieniane i ich zmiany automatycznie wpływały na w działanie skryptu.

Jakież było moje zdziwienie gdy skrypt nie chciał działać, a wszystkie rekordy miały taką zamą wartość pola typu TIMESTAMP. Otóż okazało się, że po rozpoczęciu transakcji funkcje zwracające aktualny czas:

SELECT CURRENT_TIME;
SELECT CURRENT_TIMESTAMP;
SELECT NOW();

przestały zwracać aktualny czas a zamiast tego czas rozpoczęcia transakcji.

I o dziwo jest to rzeczywiście poprawne działanie. Na stronie http://www.postgresql.org/docs/8.3/static/functions-datetime.html można przeczytać:

Since these functions return the start time of the current transaction, their values do not change during the transaction. This is considered a feature: the intent is to allow a single transaction to have a consistent notion of the „current” time, so that multiple modifications within the same transaction bear the same time stamp.

Zatem wyjściem z tego problemu jest użycie postgres’owej funkcji timeofday() i zrzutowanie jej wyniku na TIMESTAMP, realizyje to zapytanie:

SELECT cast( timeofday() AS timestamp);

Edit: zamiast timeofday() można uzyć funkcji clock_timestamp(), która zwraca date w postaci TIMESTAMP’a

SELECT clock_timestamp();

Dla ciekawości sprawdziłem jak zachowują się inne dialekty SQL i okazało się, że zarówno MySQL jak i MSSQL realizują tą sytuację odmniennie niż PostgreSQL, zatem:

  • MySQL – w transakcji zwracany jest bieżący czas (nie czas rozpoczęcia transakcji)
  • MSSQL – w transakcji zwracany jest bieżący czas (nie czas rozpoczęcia transakcji)
  • PgSQL – w transakcji zwracany jest czas rozpoczęcia transakcji

Sztuczki w wordpress’ie: przeciążanie standardowych funkcji dostępnych w szablonie

Gdy programujemy w szablonie skórki WordPress’a możemy korzystać z całej masy funkcji dostępnych w engine WordPress’a. Niektóre z nich zwracają wartości, inne od razu drukują na ekran, zwykle są parametryzowane i możliwość konfiguracji jest wystarczająco duża.

Problem

Niestety z czasem zawsze przychodzi pewne 'ale’… chcielibyśmy aby standardowa funkcja wordpressa działała minimalnie inaczej. Zatem musimy zmodyfikować jej kod… ale stop nie możemy tak bezczelnie nadpisywać funkcji wordpress.Takie hamskie haki są niedopuszczalne z kilku powodów:

  • Utrudniamy przyszłe aktualizacje, po każdej aktualizacji będziemy musieli pamiętać aby w pliku XXX w linii YYY wstawić swojego hacka.
  • Ta sama funkcja może być wywoływana w innym pliku szablonu, może to powodować błędne/niechciane działanie. Pozatym gdy zmienimy skórkę na inną to hack pozostanie dalej mimo, że już go nie będziemy potrzebować
  • Hack’owanie kodu jakichkolwiek bibliotek/frameworków czy takich gotowych kombajnów jak wordpress jest sprzeczne z zasadami programowania.

Zatem jak sobie z tym problemem poradzić?

Wszelkie gotowe rozwiązania powinno się rozszerzać nie modyfikując przy tym źródeł samej głównej aplikacji. Jeżeli takie rozszerzenie jest nie możliwe to oznacza, że aplikacja została źle napisana i należy poszukać innej rozszerzalnej.

Czy rozszeżać funkcje w WordPress?

Oczywiście. Posłużymy się w tym celu trochę zapomnianym plikiem szablonów: functions.php. Jeżeli plik functions.php nie istnieje w katalogu głównym naszej skórki to go tworzymy. Plik ten jest automatycznie ładowany wraz z uruchomieniem WordPress’a, można w nim umieszczać swoje dedykowane dla skórki funkcje oraz jak w naszym przypadku funkcje speudo przeciążające.

Zwykle w szablonach nie ma tego pliku, także świadomość programistów wordpress’owych na temat jego jest niewielka. Zatem radze sobie zapamiętać o takiej możliwości!

Dobra, a więc załóżmy, że chcemy zmodyfikować działanie funkcji the_content_rss. Tworzymy zatem w pliku functions.php funkcję my_the_content_rss.

function my_the_content_rss($more_link_text='(more...)', $stripteaser=0, $more_file='', $cut = 0, $encode_html = 0) {
	global $id;

	ob_start();
	the_content_rss('', $stripteaser, $more_file, $cut, $encode_html);
	$bufferContent = ob_get_contents();
	ob_end_clean();

	$bufferContent = preg_replace( '/\.\.\.\s*$/', '', $bufferContent );
	$bufferContent .=  '<span class="read-on"><a href="'. get_permalink() . "#more-$id\" class=\"more-link\">$more_link_text</a></span>";
	
	print $bufferContent;
}

Funkcja my_the_content_rss musi przyjmować co najmniej takie parametry parametry jak oryginalna funkcja the_content_rss. Wywołuje oryginalną funkcję, jako że the_content_rss nie zwraca wartości tylko drukuję ją na ekran musimy zatem poprzez ob_start() włączyć buforowanie, od tej pory wszystkie dane, które miały być drukowane są zapisywane w buforze, następnie poprzez ob_get_contents() odczytujemy ten bufor i kończymy buforowanie ob_end_clean().

Teraz mając w zmiennej $bufferContent wynik działania oryginalnej funkcji możemygo dostosować do naszych indywidualnych wymagań w przykładzie jest to wycięcie „…” oraz wstawienie linka do szczegółów posta.

Tyle. Następnie w szablonie w miejscach, w których potrzebujemy niestandardowy wygląd/dane szablonu wywołujemy zamiast the_content_rss nowo stworzoną funkcję my_the_content_rss.

Postgres 8.3 a zgodność typów danych

Około rok temu została wypuszczona wersja 8.3 systemu bazodanowego postgreSQL, teraz po roku nadszedł wreszcie czas kiedy to firmy hostingowe zaczynają upgrate’ować bazy do tej wersji.

W wersji 8.3 zostało prowadzonych wiele przydatnych funkcjonalności jak chociażby wyszukiwanie pełnotekstowe TSearch2, jednak nie o tym miałem pisać…

Oprócz dodatkowych funkcjonalności w postgres 8.3 została wprowadzona kontrola zgodności typów danych. Po przejściu providera hostingu z pg 8.x.x na pg 8.3.x istnieje realne zagrożenie błędnego działania naszych aplikacji w przypadku gdy nie zachowywaliśmy zgodości typów danych.

W postgreSQL operatory (np. =, > etc..) i funkcje (np. substr(), regexp_replace() etc…) posiadaja zadeklarowane typy danych na których działają np. INT=INT, substr(TEXT, INT, INT).

Problem

W wersjach przed postgreSQL 8.3 jeżeli operator lub funkcja, która przyjmowała wartości tekstowe została wywołana z wartościami o typie nie tekstowym to wartość parametru był automatycznie przekształcania do typu tekstowego TEXT. Od wersji postgreSQL 8.3 automatyczna konwersja typów została zlikwidowana gdyż jej idea nie jest zgodna z poprawnymi zasadami programowania. To my programiści powinniśmy kontrolować przepływ, wartości i typy danych. Pozostawiona została jedynie automatyczna konwersja w obrębie typów tekstowych (CHAR(X), VARCHAR(X) są CAST’owane do TEXT).

Zatem w najnowszej wersji postgreSQL zapytanie:

SELECT substr(id, 1, 1 ) FROM ...;

gdzie id jest typu integer zwróci błąd „function does not exist” gdyż nie ma wbudowanej funkcji substr(INT, INT, INT)

Analogiczny błąd zwróci operator =

SELECT ... WHERE id = foo;

gdzie id jest typu INTEGER, a foo typu CHAR zwróci błąd „operator does not exist” gdyż nie ma wbudowanego operatora INT = CHAR.

Rozwiązanie

Możliwe są trzy rozwiązania:

  1. Poprawimy zły typ kolumn, które powodują błędy
  2. W każdym zapytaniu zrzutujemy zmienną na dobry typ
  3. Napiszemy własne funkcje, operatory obsługujące te niestandardowe operacje.

1. Poprawa typu kolumny

Możemy zmodyfikować typ kolumny np. z CHAR na INT i wtedy zapytanie wykorzystujące operator INT = INT zadziała poprawnie.

ALTER TABLE sample ALTER COLUMN foo TYPE INTEGER;

Jednak operacja ta nie zawsze może być możliwa. W przypadku źle zaaprojektowanej aplikacji, konwersja taka może okazać się niemożliwa. Co w wypadku gdy pole 'foo’ zawiera ciąg liczb zaczynających się od zera? Przed konwersją przykładowy ciąg może wyglądać '0012388′ po konwersji na typ liczbowy ayutomatycznie zmieni się na '12388′ gdyż INTEGER nie może posiadać zer na przedzie. Ta zmiana może spododować, że kolejne zapytania pomimo prawidłowej składni syntaktycznej zwrócą nieprawidłowe wyniki, pozatym możemy stracić w bazie pewne informacje (zera na przedzie).

2. Castowanie typów w zapytaniu

Gdy zmiana typu kolumny jest niemożliwa pozostaje nam dostosować naszą aplikację do wymagań postgresa. W zapytaniu możemy zrzutować wartość zmiennej na żądany typ

SELECT ... WHERE id = foo::integer;

lub odpowiednik

SELECT ... WHERE id = CAST(foo AS integer);

Minusem takiego rozwiązania jest to, że kolumne 'foo’ należy zrzutować w każdym zapytaniu.

3. Własna funkcje lub operator

Zamiast poprawiać wszystkie zapytania, możemy napisać funkcje/operator który obsłuży żądaną operację.

Tworzenie operatora składa się z dwóch etapów: stworzenie funkcji w języku proceduralnym pl/pgSQL, która obsługuje operację i definicję samego operatora.

Funkcja INTEGER = CHAR

CREATE OR REPLACE FUNCTION equal_integer_vs_char(integer, char) RETURNS bool AS $$
DECLARE
    left_arg ALIAS FOR $1;
    right_arg ALIAS FOR $2;
BEGIN
    IF left_arg = CAST( right_arg AS INTEGER ) THEN
        RETURN true;
    ELSE
        RETURN false;
    END IF;
END;
$$ LANGUAGE plpgsql;

Definicja operatora INTEGER = CHAR, przeczytaj także w manualu.

CREATE OPERATOR = (
    LEFTARG = integer ,
    RIGHTARG = char,
    PROCEDURE = equal_integer_vs_char
);

W przypadku potrzeby posiadania funkcji substr(INT, INT, INT) musimy zdefiniować funkcję przyjmującą odpowiednie parametry.

CREATE OR REPLACE FUNCTION substr(integer, integer, integer) RETURNS text AS $$
DECLARE
       ...
BEGIN
         ...

        RETURN foo;
END;
$$ LANGUAGE plpgsql;

CloudBerry – okienkowa aplikacja do zarządzania Amazon S3

Słowem wstępu Amazon s3 (Simple Storage Service) jest usługą online, która umożliwia składowanie danych na przestrzeni dyskowej serwerów Amazon. W zależności od potrzeb, dane mogą być składowane w USA bądź też w Europie. Usługa s3 wyposarzona jest w API dzięki któremu w prosty sposób można operować na składowanych zasobach. Przeczytaj więcej o Amazon s3.

Do tej pory, poza skromnym pluginem do Firefox’a, nie było żadnego wsparcia softwarowego dla tej usługi, praca jak i testowanie nie było dla developerów przejrzyste ani proste, a dla niezaawansowanych użytkowników wręcz niemożliwe. Ale koniec z tym, właśnie powstała pierwsza okienkowa aplikacja wspomagająca usługę Amazon s3 – CloudBerry Explorer.

CloudBerry jest aplikacją okienkową, która umożliwia poprzez swój interface operacje, które dostępne są poprzez Amazon s3 API. Zdalnymi plikami zarządza się tak jak zarządza się plikami na własnym dysku twardym. Można z łatwością tworzyć przeglądać/tworzyć/usuwać/przegrywać pliki i katalogi, zarządzać prawami dostępu. Aplikacja przystosowana jest do połączeń z wieloma kontami jednocześnie.

Program jest całkowicie darmowy, aktualnie dostępna jest jedynie wersja pod Windows. Ściągnij i przetestuj CloudBerry.

Dostępna jest także wersja premium, do kupienia w cenie około 150zł. Wersja premium umożliwia dużo szybszy transfer plików oraz posiada kilka dodatkowych funkcjonalności.

Jak w Google Analytics badać kliknięcia w linki.

Zagadnienie
Posiadamy serwis internetowy, na którym mamy linki wychodzące prowadzące do innych serwisów. W celach optymalizacyjnych, statystycznych lub ze zwykłej ciekawości chcielibyśmy zliczać kliknięcia w takie linki.

Rozwiązanie
Do rozwiązania tego zagadnienia wykorzystamy statystyki Google Analytics, które są nie dość, że są darmowe to również są zdecydowanie najlepsze na rynku.

Aby liczyć kliknięcia stworzymy wirtualną stronę, którą następnie w panelu ustawimy jako goal/cel. W ten sposób będziemy mieli w łatwy i przejrzysty sposób dostęp do szczegółowych statystyk kliknięć w ten link.

1. W kodzie serwisu musimy umieścić kod śledzący Google Analitycs

	<script type="text/javascript">
	var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
	document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
	</script>
	<script type="text/javascript">
	try {
	var pageTracker = _gat._getTracker("UA-TUTAj_TWOJ-ID");
	pageTracker._trackPageview();
	} catch(err) {}</script>

Należy pamiętać, że w kodzie HTML, powyższy kod musi być powyżej kodu trackera z punktu kolejnego.

2. W linku, który chcemy mierzyć, wstawiamy wywołanie poprzez zdarzenie javascript onclick skryptu GA pageTracker._trackPageview

onclick="javascript:pageTracker._trackPageview('/link/UNIKALNA-NAZWA')"

W miejsce '/link/UNIKALNA-NAZWA’ wpisz, tekst, który ma się wyświetlić jako nazwa wirtualnej strony w panelu Google Analitycs. Dobrym zwyczajem jest dodanie prefixu (w przykładzie 'link’). Jest to przydatne gdy w przyszłości chciałbyś wykluczyć statystyki wirtualnych stron w ogólnym raporcie GA.

3. Dodajemy nowy goal/cel

google analitycs goal

Należy pamiętać aby wartość pola 'Goal URL/Adres URL celu’ była taka sama jak wcześniej zdefiniowana wirtualna strona i zaczynała się od slasha '/’.

To wszystko. Można teraz zrobić kilka testowych kliknięć i należy poczekać 24h gdyż statystyki GA aktualizowane są raz dziennie.

4. Aby podglądnąć statystyki kliknięć należy udać się do panelu GA i przejść do zakładki 'Goals/Cele’, mamy tam zestaw gotowych raportów. Jeżeli mamy zdefiniowanych więcej niż jeden goal/cel, to po kliknięciu na 'Goal conversion/Konwersja na cel’ możemy ograniczyć raport do konkretnego celu. Statystyki naszej wirtualnej strony dostępne są również w zakładce 'Content/Zawartość’ gdyż, dla GA jest ona normalną stroną serwisową.

Dla jednego serwisu możemy zdefiniować wiele goal’i, w panelu możemy badać ich statystyki łącznie lub dla każdego osobno.

Podsumowanie
Metoda opisana powyżej działa zarówno dla linków wychodzących jak i dla linków wewnętrznych, wywołanie javascript:pageTracker._trackPageview(’/link/XXX’) może być zaszyte zarówno w javascript jak i we flashu. Prawdę mówiąc metodą tą można mierzyć kliknięcie w dowolny element serwisu (np. blok tekstowy), jednak do tego lepiej wykorzystać 'heat mapy’, ale o tym innym razem.

Jak w XSLT usunąć puste węzły XML’a?

Problem:
Mamy XML:

<root>
  <category>Biznes</category>
  <category>   </category>
  <category></category>
  <category>Hobby</category>
</root>

Poprzez transformacje XSLT, chcemy usunąć puste węzły kategorii.

Rozwiązanie:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:template match="/">
		<root>
		<xsl:for-each select="category">
		  <xsl:if test="normalize-space(.)=''">
	  	<category>
	  		<![CDATA[<xsl:value-of disable-output-escaping="yes" select="normalize-space(.)" />]]>		
	  	</category>	
		  </xsl:if>
		</xsl:for-each>
		</root>
	</xsl:template>
</xsl:stylesheet>

Wyjaśnienie:

  • Instrukcja match=”/” wybiera wszystkie węzły XML’a
  • Instrukcja for-each iteruje po wszystkich węzłach o nazwie 'category’ (selekcja wynika z warunku: select=”category”)
  • Funkcja normalize-space() usuwa białe znaki z początku i końca węzła. (w przypadku białych znaków w środku stringu, zamienia je na pojedynczą spację)
  • Instrukcja if sprawdza czy wartość węzła równa jest pustemu stringowi.
  • W przypadku powodzenia wartość węzła wstawioana jest w tag <category>
  • Instrukcja disable-output-escaping=”yes” powoduje, że znaki szczególne takie jak <,>,& nie zostaną wyescapowane (zamienione na &lt;,&gt;,&amp;)
  • Tagi <![CDATA[…]]> powodują, że niewyscapowane znaki <,>,& nie spowodują błędu walidacji XML’a

Sortowanie względem pola nie będącego w DISTINCT ON

Problem:
Chcemy wyświetlić jedynie unikalne rekordy względem konkretnego pola/pól i posortować innym polu. W naszym przykładzie chcemy otrzymać zestawienie id_user – ostatnie logowanie

Posiadane dane:
Tabela z datami logowań użytkowników aplikacji w tabeli postaci:

CREATE TABLE logs(
id SERIAL PRIMARY KEY, -- unikalny klucz główny
id_user INTEGER, -- klucz obcy tabeli użytkowników
date TIMESTAMP -- data logowania
);

Przykładowe rekordy:

INSERT INTO logs(id_user,date) VALUES(1,'2008-01-01');
INSERT INTO logs(id_user,date) VALUES(1,'2008-01-01');
INSERT INTO logs(id_user,date) VALUES(4,'2008-01-01');
INSERT INTO logs(id_user,date) VALUES(2,'2008-01-01');
INSERT INTO logs(id_user,date) VALUES(3,'2008-04-01');
INSERT INTO logs(id_user,date) VALUES(7,'2008-04-01');
INSERT INTO logs(id_user,date) VALUES(1,'2008-05-01');
INSERT INTO logs(id_user,date) VALUES(4,'2008-05-01');
INSERT INTO logs(id_user,date) VALUES(5,'2008-06-01');
INSERT INTO logs(id_user,date) VALUES(5,'2008-08-01');
INSERT INTO logs(id_user,date) VALUES(7,'2008-10-01');

Błędne zapytanie nr 1:
W pierwszym momencie wielu początkujących programistów pomyśli o zapytaniu:

SELECT
 DISTINCT ON (id_user)
 *
FROM
 logs
ORDER BY
 date DESC;

Niestety nie jest to poprawne zapytanie, postgres wyrzuci błąd:

ERROR:  SELECT DISTINCT ON expressions must match initial ORDER BY expressions

Oznacza to, że pola, które zawiera klauzula ORDER BY muszą się znaleźć na początku klauzuli DISTINCT ON.
W tym przypadku poprawne zapytanie musiało by zawierać DISTINCT ON (date, id_user).
Niestety wtedy nie dostaniemy pożądanego rezultatu (unikalność po kolumnie id_user) gdyż badana będzie
unikalność względem dwóch pól.

Błędne zapytanie nr 2:

SELECT
 *
FROM
 (SELECT
  DISTINCT ON (id_user)
  *
 FROM
  logs
) AS sub
ORDER BY
 sub.date DESC;

Idąc tropem unikalności można użyć podzapytania, które zwraca unikalne względem id_user rekordy a dopiero później, zwrócone przez nie rekordy posortować. Powyższe zapytanie wykona się i zwróci pewne rezultaty.

id id_user date
11 7 2008-10-01 00:00:00
9 5 2008-06-01 00:00:00
8 4 2008-05-01 00:00:00
5 3 2008-04-01 00:00:00
2 1 2008-01-01 00:00:00
4 2 2008-01-01 00:00:00

Niestety nie będą one do końca poprawne gdyż podzapytanie DISTINCT pozostawia pierwszy napotkany rekord zawierający unikalną wartość id_user. Zatem jeżeli dane nie są posortowane względem id_user i daty chronologicznie to uzyskamy wyniki nieprawidłowe. W naszym przypadku id_user=1 została zwrócona data=2008-01-01 gdyż jest ona bliżej początku niż rekord z datą=2008-05-01.

Rozwiązanie:

SELECT
 *
FROM
 (SELECT
  DISTINCT ON (sub2.id_user)
  sub2.*
 FROM
  (SELECT
  *
  FROM
  logs
  ORDER BY
  id_user ASC,
  date DESC
  ) AS sub2
) AS sub
ORDER BY
 sub.date DESC;

W podzapytaniu sub2 sortujemy dane po id_user i date malejąco tak aby każdy id_user na czele miał rekord z datą najpóźniejszą. Takie dane przekazujemy zapytaniu wcześniejszemu, które najpierw wyciąga rekordy unikalne względem kolumny id_user a następnie tak ograniczony zbiór sortuje względem kolumny date. Jako że dostarczyliśmy dane posortowane otrzymane rezultaty są zgodne z prawdą:

id id_user date
11 7 2008-10-01 00:00:00
10 5 2008-08-01 00:00:00
7 1 2008-05-01 00:00:00
8 4 2008-05-01 00:00:00
5 3 2008-04-01 00:00:00
4 2 2008-01-01 00:00:00

Zapytanie grupujące minimalnne wartości w grupie danych

Problem:
Chcemy wyświetlić miesięczne zestawienie liczby nowo zarejestrowanych
użytkowników mając jedynie daty ich logowań.

Posiadane dane:
Tabela z datami logowań użytkowników aplikacji w tabeli postaci:

	CREATE TABLE logs(
		id SERIAL PRIMARY KEY, -- unikalny klucz główny
		id_user INTEGER, -- klucz obcy tabeli użytkowników
		date TIMESTAMP -- data logowania
	);

Przykładowe rekordy:

INSERT INTO logs(id_user,date) VALUES(1,'2008-01-01');
INSERT INTO logs(id_user,date) VALUES(1,'2008-01-05');
INSERT INTO logs(id_user,date) VALUES(4,'2008-01-01');
INSERT INTO logs(id_user,date) VALUES(2,'2008-01-01');
INSERT INTO logs(id_user,date) VALUES(3,'2008-04-01');
INSERT INTO logs(id_user,date) VALUES(7,'2008-04-01');
INSERT INTO logs(id_user,date) VALUES(1,'2008-05-01');
INSERT INTO logs(id_user,date) VALUES(4,'2008-05-01');
INSERT INTO logs(id_user,date) VALUES(5,'2008-06-01');
INSERT INTO logs(id_user,date) VALUES(5,'2008-08-01');
INSERT INTO logs(id_user,date) VALUES(7,'2008-10-01');

Rozwiązanie:
Rejestracje można utożsamić z pierwszymi logowaniami, zatem:
Najpierw grupujemy dane według użytkownika i dla każdego id_user znajdujemy najwcześniejszą datę logowania. Następnie otrzymane dane grupujemy po roku i miesiącu.

Realizuje to podwójnie grupujące zapytanie:

SELECT 
	extract(year from date) || '-' || extract(month from date) AS month, 
	count(*) AS amount 
FROM 
	logs 
WHERE date IN (
	SELECT 
		min(date) 
	FROM 
		logs 
	GROUP BY 
		id_user 
) 
GROUP BY 
	extract(year from date) || '-' || extract(month from date)
ORDER BY
	amount DESC;

Można też ciut lepiej zoptymalizować zapytanie i operować bezpośrednio na wynikach zwróconych
przez podzapytanie:

	
SELECT 
	extract(year from sub.date) || '-' || extract(month from sub.date) AS month, 
	count(*) AS amount 
FROM 
 (
	SELECT 
		min(date) AS date
	FROM 
		logs 
	GROUP BY 
		id_user 
) AS sub
GROUP BY 
	extract(year from sub.date) || '-' || extract(month from sub.date)
ORDER BY
	amount DESC;	

W przypadku gdy zapytanie to będzie wykonywane często warto założyć index na kolumne z datą:

CREATE INDEX date_idx ON logs(date);

FirePHP – przyjazne debug’owanie aplikacji internetowych

Debugowanie to poważny problem programistów

Bez względu na to jakimi dobrymi programistami jesteśmy nigdy nie ustrzeżemy się błędów. To normalne, błędy popełnia każdy, różnica pomiędzy dobrym programistą a złym polega m. in. na szybkości wykrycia błędnego kodu i naprawienia go.

Mówiąc o błędnym kodzie nie mam na myśli jedynie błędów syntaktycznych, ale również błędów logicznych, które nie powodują widocznych error’ów a jedynie błędne działanie aplikacji. Takie błędy są dużo trudniejsze do wykrycia.

Aby wykryć błędny kod niezbędne jest zdebugowanie zmiennych, obiektów, zasobów, które wykorzystywane są w tworzonej przez nas aplikacji. Brzmi banalnie, jednak w rzeczywistości nie jest to takie łatwe gdyż:

  • po pierwsze trzeba wstawić w odpowiednie miejsca funkcje var_dump() lub print_r()
  • po drugie funkcje te nie wyświetlają przejrzyste i łatwe do zinterpretowania dane
  • po trzecie debug’owanie na ekran może spowodować błędy aplikacji, a debugowanie do pliku jest niewygodne w odczycie (szczególnie w przypadku aplikacji flash’owych lub AJAX’owych)
  • po czwarte zdarza się, że format zwracanych danych uniemożliwia swobodne dodawanie własnych danych do debugowania (np. XML, JSon).
  • po piąge po skończeniu debugowania trzeba usunąć wszystkie instrukcje debugujące

Rozwiązaniem problemu debugowania jest FirePHP

Dużo tych wad… całe szczęście jest na to rozwiązanie. FirePHP to darmowa, open source’owa biblioteka, która wraz z pluginem do Firebug’a idealnie sprawdza się w roli debugera.

FirePHP debuguje dane w konsoli Firebug’a, informacje do debugowania są umieszczone w specjalnych nagłówkach HTTP przez co 'output’ aplikacji pozostaje w nienaruszonym stanie. Rewelacja prawda?

Instalacja firePHP

Wymagania FirePHP to PHP 5.2+, Firefox z pluginem Firebug.

  • Instalujemy plugina do firefox’a Firebug
  • Instalujemy plugina do Firebug’a FirePHP
  • Ściągamy źródła FirePHP, warto zauważyć, że na stronie dostępne są informacje, jak zintegrować bibliotekę z wieloma znanymi frameworkami (CakePHP, CodeIgniter, Drupal, Kohana, ExpressionEngine, PRADO, Symfony, TYPO3, Zend Framework).
  • Integrujemy FirePHP z naszą aplikacją
  • Odpalamy stronę aplikacji w Firefox z uruchomionymi plugin’ami Firebug i FirePHP
  • Klikamy na ikonę Firebug’a a następnie przechodzimy do konsoli. W konsoli powinny być zaprezentowane zdebugowane zmienne naszej aplikacji.
  • W przypadku problemów przydać się mogą: mini tutorial how to, forum dyskusyjne o FirePHP.

Przykład działania

Pod adresem http://blog.adiasz.pl/examples/firephp/ stworzyłem prosty skrypt AJAX’owy łączący się z bazą i wykonujący prościutkie operacje. Jest zintegrowany z FirePHP, zachęcam do testów. Naprawdę warto! (Oczywiście musicie najpierw zainstalować plugin FireBug i FirePHP). Wszystkie pliki wykorzystane w tym przykładzie dostępne są tutaj.

Przykład powinien zaprezentować coś w stylu:

Na koniec, bardzo ważna informacja: po opublikowaniu projektu musicie wyłączyć debugowanie FirePHP poprzez dyrektywę:

$fp = FirePHP::getInstance( true );
$fp->setEnabled ( false );

W przeciwnym razie każdy internauta posiadający zainstalowany ten plugin będzie miał dostęp do niebezpiecznych z punktu bezpieczeństwa danych. Najlepiej w konfigu aplikacji sterować to poprzez zmienną typu boolean $development = true/false. Samych instrukcji debugujących nie trzeba kasować gdyż nie wiadomo kiedy się jeszcze przydadzą.

ps. FirePHP dostępne jest także dla innych niż PHP języków programowania aplikacji webowych: ASP, Python, Ruby.

Darmowe narzędzia dla webmasterów: statystyki serwisu

Ostatnio w ramach optymalizacji czasu i automatyzacji wykonywanych przez zemnie zadań stworzyłem mini aplikację sprawdzającą statystyki moich serwisów w wyszukiwarkach.

Aktualnie sprawdzam: Google PR, Google link, Google site, Yahoo link, Yahoo site, MSN site. Monitoringu pozycji słów kluczowych na razie nie robiłem, gdyż jest wiele tego typu dobrych i darmowych sprawdzaczy na necie – więc szkoda czasu.

Aplikacja działa tak:
* skrypty sprawdzające statystyki wywoływane są z automatycznie z cron’a, dane zapisują w bazie
* po zalogowaniu prezentowana jest lista najnowszych statystyk
* generowane są dzienne wykresy poszczególnych statystyk serwisów

Prościutkie, teraz zamiast sprawdzać ręcznie tysiące parametrów wystarczy się zalogować do panelu. Wyniki zostaną zaprezentowane szybki i w przejrzysty sposób.

Dla szerokiego internetu udostępniłem wersję uproszczoną – możliwość sprawdzenia wyżej wymienionych statystyk dla podanego adresu: zapraszam na darmowe narzędzia dla webmasterów. Nic odkrywczego, ale może komuś się przyda.

Aplikacja została napisana we framework’u: Kohana, o którym postaram się napisać niedługo, a już teraz polecam się z nim zapoznać.