Kolejność w zapytaniu z DISTINCT ON

Na problem z nieustawioną kolejnością w zapytaniu zawierającym DISTINCT ON natknąłem się już jakiś czas temu, jednak dopiero teraz zebrałem się, aby go opisać. A warto, bo niby błahy, a jednak ciekawy i sam trochę ignorowałem jego znaczenie.

Otóż korzystając w zapytaniu z DISTINCT ON na zestawie kolumn powinniśmy ustawić kolejność prezentacji wierszy poprzez standardową klauzulę ORDER BY. DISTINCT ON pomija bowiem kolejne wiersze o wartościach, które wystąpiły już wcześniej:

DISTINCT ON ( expression [, …] ) keeps only the first row of each set of rows where the given expressions evaluate to equal. […] Note that the “first row” of each set is unpredictable unless ORDER BY is used to ensure that the desired row appears first. […] The DISTINCT ON expression(s) must match the leftmost ORDER BY expression(s).

http://www.postgresql.org/docs/9.0/static/sql-select.html#SQL-DISTINCT

Bez ustawionej kolejności może być przypadkowa (chodź technicznie to nie do końca prawda) i różna w zależności od sposobu użycia.

Przykład? Opiszę mój przypadek w nieco zmodyfikowanej formie. Miałem zapytanie korzystające z dwóch tabel: pierwsza to tabela z numerami telefonów klientów wraz z etykietami. Jeden klient może posiadać kilka telefonów, nawet z tymi samymi etykietami (ze względu na przypisywanie tych numerów do jego umów, których może być wiele):

id_customer
id_contractnumberlabel
512
600123123
Company Ltd.
513600123123Company Ltd.
514600123123John Poe

Z tabeli pobierane były unikalne numery telefonów dla klienta wraz z pierwszą etykietą – ta bowiem nie była do końca ważna. Korzystałem więc z zapytania:

SELECT DISTINCT ON (id_customer, number)
    id_customer,
    number,
    label
FROM customers

Nakładając na zapytanie ograniczenie na ID żądanego klienta otrzymywałem wiersze z jego telefonami oraz etykietą. Jeżeli do jednego numeru klienta było kilka etykiet, mogła zostać zaprezentowana dowolna z nich, każda bowiem była prawidłowa i w tym zastosowaniu nie miało to znaczenia.

Trzeba było jednak rozszerzyć tabelę o dodatkowe dane (tagi), które nie występowały – jak etykiety – przy każdym wierszu klienta. Jeżeli już jednak istniały, należało je zaprezentować, a jeżeli było ich kilka – najważniejszą z nich według zadanego priorytetu. Pominę przy analizie priorytet, ponieważ nie jest on aż tak istotny. Tabela po zmianach wyglądała więc tak:

id_customer
id_contractnumberlabeltag
512
600123123
Company Ltd.
urgent
513600123123Company Ltd.
514600123123John Poe

Do zapytania dodałem tylko kolumnę z interesującą mnie daną:

SELECT DISTINCT ON (id_customer, number)
    id_customer,
    number,
    label,
    tag
FROM customers

Przy testach wszystko było OK, zawsze zwracane były wiersze z wypełnioną kolumną tag. To nieco uśpiło moją czujność i nie przetestowałem takiej liczb przypadków, abym trafił na pustą wartość mimo istnienia tagu dla numeru.

Sytuacja zmieniła się, gdy zapytanie trafiło do SQL-owego widoku. To zmieniło sposób sortowania wyników, przez co pobierając dane z widoku miałem przypadki zwracania numerów bez tagów mimo, że te ewidentnie istniały. Żeby było zabawniej, wykonanie zapytania bezpośrednio (z pominięciem widoku) działało tak, jak bym chciał.

Wróciłem więc do dokumentacji i stackoverflow. To naprowadziło mnie na podany na początku wpisu fragment dokumentacji. Aby poprawić zapytanie, należało więc użyć ORDER BY:

SELECT DISTINCT ON (id_customer, number)
    id_customer,
    number,
    label,
    tag
FROM customers
ORDER BY tag NULLS LAST

Dzięki temu, wiersze z tagami były zwracane jako pierwsze i ewentualne późniejsze wiersze z pustymi wartościami w tej kolumnie dla tego samego klienta i numeru były pomijane, a nie odwrotnie.

Nie można więc zapominać o klauzuli ORDER BY po kolumnach niewystępujących w DISTINCT ON, bo prędzej czy później się to zemści.

Chrome dla Androida – zdalne debugowanie aplikacji webowej

Dostosowujemy ostatnio aplikację webową do urządzeń mobilnych. Pierwszym pytaniem, które mi się nasunęło, było: “czy przeglądarki mobilne mają w ogóle wbudowane narzędzia deweloperskie?”. Odpowiedź w uproszczeniu brzmi: nie. W tym sensie, że nie uruchomimy tych narzędzi bezpośrednio na urządzeniu, co zresztą byłoby dość kłopotliwe ze względu na ich złożoność.

Krótkie przeszukiwanie internetu naprowadziło mnie na informacje o zdalnym debugowaniu. Brzmiało zbyt pięknie, aby było możliwe bez większego wysiłku. Pracuję na Linuksie, więc już sobie wyobrażałem rozwiązywanie problemów z połączeniem. Postanowiłem jednak przebić się przez ten krótki opis. Efekty były zaskakujące.

Okazało się, że w zasadzie wszystko działa out of the box. Po stronie komputera nie wymagało w zasadzie żadnej konfiguracji – urządzenie było widoczne od razu na liście. Zmian wymagały jedynie ustawienia na telefonie/tablecie.

Ale po kolei: na początku musimy włączyć debugowanie USB w opcjach programisty. W moim telefonie ze starszym Androidem 4.1.2 opcje programisty dostępne są bezpośrednio z ustawień:

Na nowszych należy je uaktywnić poprzez siedmiokrotne stuknięcie w “Build number” (zgodnie z opisem).

Po włączeniu debugowania USB, wystarczy podłączyć urządzenie kablem USB do komputera oraz włączyć Chrome na obu urządzeniach. Na komputerze wystarczy teraz włączyć narzędzia dla programistów (CTRL+SHIFT+I) i z menu wybrać: More tools -> Remote devices:

Na liście powinniśmy zobaczyć podłączone urządzenie. Po kliknięciu możemy wpisać adres do otwarcia na nim w nowej karcie lub debugować którąś z już otwartych kart na urządzeniu mobilnym poprzez kliknięcie na: “Inspect” przy jej nazwie. Pojawi się wtedy nowe okno z podglądem widoku strony na urządzeniu mobilnym oraz panel z narzędziami developerskimi. Zmiany wykonane na urządzeniu widoczne są w podglądzie na komputerze i vice versa:

Z takimi możliwościami rozwijanie aplikacji webowych na urządzenia mobilne to czysta przyjemność. 🙂

Ratchet – WebSockety w PHP w praktyce

WebSockety to jeden z najciekawszych od kilku lat tematów, z którymi przyszło mi zmierzyć się w pracy. Pierwszy raz słyszałem o nich gdzieś w 2013 roku i od tego czasu zdążyły zdobyć ogromną popularność w internecie. YouTube, Stack Overflow, Facebook – tam wszędzie WebSockety wykorzystywane są już od dawna do różnych rzeczy (przesyłanie i odświeżanie komentarzy oraz głosów, komunikacja w czasie rzeczywistym, itp.). To tyle słowem wstępu – jeżeli tu trafiłeś, prawdopodobnie wszystko to wiesz i szukasz informacji, jak wykorzystać WebSockety w praktyce.

Do zeszłego roku “WebSockety” i “node.js” stanowiły w zasadzie synonimy, jeżeli chodzi o ich implementację. O uruchomieniu ich w PHP czytałem same złe rzeczy i nawet najbardziej zagorzali programiści używający tego języka odradzali. Tematu jednak nie zgłębiałem i przyglądałem się nieco z boku. Jednak gdy w pracy pojawił się temat obsługi zdarzeń z centrali telefonicznej w naszym systemie, nie było wyjścia – trzeba zacząć się nimi szybko interesować.

Kolega z sąsiedniego zespołu już jakiś czas wcześniej podejmował ten temat w oparciu właśnie od node.js. Jego opinie nie były życzliwe, zwłaszcza, że nie miał wiele czasu na stworzenie tego rozwiązania. Podobnie z czasem było u mnie. Dodatkowo zniechęcał fakt, że w zasadzie nie mamy żadnej infrastruktury pod node.js, a ostatnią rzeczą, której bym chciał, to zarządzanie tym wszystkim od strony administracyjnej. Zacząłem więc szukać czegoś o WebSocketach w PHP.

Natychmiast trafiłem na Ratchet – bibliotekę do prostej implementacji WebSocketów w aplikacjach PHP. Brzmiało świetnie zwłaszcza, że mogłem korzystać z “dobrodziejstw” całego naszego systemu. W praktyce okazało się, że jest jeszcze lepiej i łatwiej, niż mi się wydawało.

Stwórzmy więc wspólnie skręcaną z drutów aplikację z pomocą Ratchet, która będzie niczym innym, jak prostym echotestem. Dodatkowo, oprócz odpowiadania na otrzymane wiadomości, serwer będzie sam wysyłał do wszystkich wiadomość, np. co 10 sekund. Dzięki temu otrzymasz bazę do większości zastosowań – nasza aplikacja będzie zarówno odpowiadała na komunikaty z inicjatywy klientów, jak i sama w pętli wysyłała coś od siebie.

Najpierw potrzebujemy samego Ratchet. Najłatwiej zlecić pobranie go composerowi. Jeżeli nie korzystałeś jeszcze z composera, spróbuj po prostu odwiedzić stronę pobierania i przejdź do sekcji “Manual download”, skąd możesz ściągnąć najnowszą wersję composer.phar. Umieść ten plik w katalogu głównym projektu, po czym możesz przejść do pobierania Ratcheta:

php composer.phar require cboden/ratchet

Kolejnym krokiem będzie stworzenie klasy obsługującej wszystkie potrzebne zdarzenia (połączenie, rozłączenie, wystąpienie błędu) oraz zawierającej metodę, którą serwer będzie cyklicznie wywoływać. Oto jej treść:

<?php
namespace WebSocket;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class Handler implements MessageComponentInterface
{
    protected $clients;

    public function __construct()
    {
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn)
    {
        $this->clients->attach($conn);

        echo 'Nowe połączenie: ' . $conn->resourceId . PHP_EOL;
    }

    public function onMessage(ConnectionInterface $from, $msg)
    {
        // iteracja po dostępnych połączeniach
        foreach ($this->clients as $client) {
            if ($from !== $client) {
                // bieżące połączenie to nadawca wiadomości
                
                // odsyłamy wiadomość
                $client->send($msg);
            }
        }
    }

    public function onClose(ConnectionInterface $conn)
    {
        $this->clients->detach($conn);

        echo 'Zamknięto połączenie: ' . $conn->resourceId . PHP_EOL;
    }

    public function onError(ConnectionInterface $conn, \Exception $e)
    {
        echo 'Wystąpił błąd: ' . $e->getMessage() . PHP_EOL;

        $conn->close();
    }
    
    public function onLoop($options = array())
    {
        foreach($this->clients as $client) {
            $client->send('Cykliczna wiadomość');
    
            echo 'Wysłano komunikat cykliczny' . PHP_EOL;
        }
    }
}

Klasę umieściłem w pliku handler.php.

Metody onOpen, onClose i onError są raczej oczywiste. W onMessage występuje obsługa zdarzenia pojawiającego się w momencie otrzymania wiadomości od klienta. Iterujemy tam po prostu po tablicy dostępnych połączeń i sprawdzamy, czy trafiliśmy na połączenie zgodne z identyfikatorem zasobu nadawcy. Jeżeli tak, to korzystamy z tego zasobu do odesłania odpowiedzi tej samej treści, którą otrzymaliśmy.

Metodę onLoop wykorzystamy natomiast do wywoływania cyklicznego. Wysyła ona do wszystkich połączeń komunikat o tej samej treści.

Mając taką klasę, możemy stworzyć plik, w którym uruchomimy sam serwer. W moim wypadku jest to server.php o następującej treści:

<?php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use WebSocket\Handler;

require 'vendor/autoload.php';
require 'handler.php';

// utworzenie obiektu klasy obsługującej
$handler = new Handler();

// stworzenie instancji serwera websocketów
$server = IoServer::factory(
    new HttpServer(
        new WsServer($handler)
    ),
    8080    // port
);

// ustawienie pętli
$server->loop->addPeriodicTimer(10, function() use ($server, $handler) {
    // uruchomienie metody obrotu pętli
    $handler->onLoop(array(
        'server' => $server,
    ));
});

echo 'Serwer uruchomiony' . PHP_EOL;

// uruchomienie serwera
$server->run();

Jak widać, tworzymy najpierw instancję serwera nasłuchującego na porcie 8080, podając mu klasę z obsłużonymi zdarzeniami, a następnie dodajemy pętlę, która będzie wywoływana co 10 sekund.

Teraz wystarczy uruchomić serwer poprzez wywołanie:

php server.php

W konsoli powinniśmy dostawać informacje o przebiegu działania serwera.

Jak się połączyć z naszym serwerem? Najprościej uruchamiając konsolę w przeglądarce (w Firefoksie możemy wywołać ją kombinacją CTRL-SHIFT-K) i wklejając:

var conn = new WebSocket('ws://localhost:8080');
conn.onopen = function(e) {
    console.log("Połączono pomyślnie!");
};

conn.onmessage = function(e) {
    console.log(e.data);
};

Po chwili powinniśmy otrzymywać z serwera co 10 sekund wiadomość.

Aby wysłać wiadomość do serwera, wystarczy, że w tej samej konsoli wkleimy:

conn.send('Test');

To wszystko! Mamy już działający serwer.

Oczywiście, docelowa aplikacja musi zawierać obsługę uwierzytelniania, jednak celem tego wpisu było pokazanie prostoty stworzenia serwera WebSocketów.

Wpis był mocno inspirowany oficjalnym tutorialem Ratcheta, który rozbudowałem o cykliczne wywoływanie zdarzeń przez serwer.

Optymalizacja zapytań z GROUP BY w MySQL (i nie tylko)

Przyznam, że do wczoraj sposób działania planera w przypadku zapytań agregujących (z klauzulą GROUP BY) był dla mnie nie do końca jasny.

Akurat trafiłem w mojej aplikacji na zapytanie na tyle proste, a jednocześnie wolne (operujące na dużej tabeli), że łatwo było je analizować. Szukając przyczyny i sposobów optymalizacji, trafiłem na znakomitą odpowiedź na stackoverflow. Pozwolę sobie wkleić jej sedno:

For covering index you add:
1. columns used in where clauses first, then
2. columns used in group by, then
3. columns used in order by, and then
4. columns used in select.

Ze swoim zapytaniem miałem nieco większy problem, ponieważ łączyło ono dwie tabele poprzez JOIN. Jak się okazuje, kolumny z warunków JOIN-owania należy przypisać do pierwszego punktu, co potwierdziło się w moim dzisiejszym pytaniu.

Sprawdziłem działanie tego schematu również na zapytaniach z GROUP BY na dużych tabelach w PostgreSQL i również działało (skutkowało wykonywaniem zapytania w pełni na indeksach), więc można przyjąć, że podobnie działa to dla większości popularnych DBMS.

YouTube – access forbidden, the request may not be properly authorized

Jeżeli ostatnio trafiłeś na tytułowy błąd, a dokładniej:

HTTP 403: youtube.common, Access forbidden. The request may not be properly authorized.

w swojej aplikacji sprzężonej z YouTube poprzez API, to wiedz, że aplikacja działa w pełni poprawnie. Nie szukaj też przyczyny w tokenach autoryzacyjnych.

Przyczyna jest zupełnie inna – Google wprowadził w API YouTube’a zabezpieczenie przed spamem (a raczej floodem). W związku z tym w ciągu jednej doby można przesłać bez ograniczeń do 50 nagrań, potem API będzie blokowało przesyłanie większej liczby nagrań niż jedno na 15 minut. Każdy nadmiarowe będzie wyrzucało właśnie zacytowany wyżej błąd.

Zmiany zostały podobno wprowadzone 24 lutego 2017. Sam trafiłem na to ograniczenie następnego dnia. Podobnych wzmianek jest już w sieci jednak sporo, np. #1, #2, #3, #4, #5, #6, #7, #8. Trochę współczuję ludziom, którzy oparli w swoich aplikacjach magazynowanie dużej ilości multimediów na YouTube.

PostgreSQL – losowe wartości z podzapytania

W PostgreSQL, korzystając z podzapytania używającego funkcji RANDOM() tak, jak np. w MySQL, otrzymamy te same wyniki dla każdego wiersza.

Przykład:

SELECT
    t1.id,
    (SELECT name FROM table2 ORDER BY RANDOM() LIMIT 1) AS random_name
FROM table1 t1

W tym DBMS podzapytanie tego typu wykonuje się raz, więc przy każdym wierszu z tabeli table1 będzie widniała ta sama wartość random_name.

Aby to zmienić, wystarczy sztucznie skorelować podzapytanie z zapytaniem nadrzędnym, np. tak:

SELECT
    t1.id,
    (SELECT name FROM table2 WHERE t1.id = t1.id ORDER BY RANDOM() LIMIT 1) AS random_name
FROM table1 t1

Niby proste, ale jakiś czas temu straciliśmy w pracy na tym trochę czasu (zwłaszcza nie wiedząc, że problem jest związany z podzapytaniami, bo samo zapytanie było bardziej złożone).

Eclipse – ponowne wywołanie informacji kontekstowej

Gdy w Eclipsie wywołujemy metodę/funkcję, pojawia się informacja kontekstowa z listą jej parametrów wraz z wyróżnieniem właśnie wpisywanego:

Gdy jednak wciśniemy escape lub utracimy focus na metodzie, informacja ta nie pojawia się ponownie, co zawsze było dla mnie problemem (trzeba było najechać na nazwę metody/funkcji, aby uzyskać informację o parametrach). Okazuje się jednak, że aby ponownie wywołać tę podpowiedź wystarczy wcisnąć CTRL+SHIFT+spacja.

Źródła: [1], [2]

Automatyczne generowanie opisu commita przy git merge

Krótko, lecz treściwie: odkryłem przełącznik --log=n  przy git merge.

Będąc na feature branchu opisuję dokładnie zmiany wraz ze znacznikami zagadnienia, co potem jest przetwarzane Redmine’a. Niestety, przy wykonywaniu merge’a do mastera sugerowana jest domyślna treść, którą nadpisywałem skopiowanym opisem któregoś z ostatnich commitów w branchu.

Aż do wczoraj. Kolejny raz przeczytałem manual i doszukałem się w nim ww. przełącznika. W moim przypadku wystarczy więc wykonać merge’a poprzez:

git merge --log=n redmine-XXXXX

Dzięki temu do domyślnej treści merge’owego commita dopisywana jest lista opisów ostatnich n commitów z tego brancha.

3-way merge przy aplikowaniu patcha w git

Nigdy nie rozumiałem sensu używania git apply. Kiedyś zastanawiałem się, czy ma jakieś zalety w stosunku do sprawdzonego patcha. Przejrzałem wtedy nawet pobieżnie manuala stwierdzając, że jest tam przecież tylko to, co ma sam patch. W swojej pracy używałem więc sprawdzonego i dobrze znanego patcha.

Dopiero niedawno uświadomiłem sobie, że pominąłem chyba najistotniejszy przełącznik: -3 . Jego użycie powoduje (w przypadku, gdy diff nie aplikuje się wprost) wykonanie 3-way merge’a. W wielu przypadkach samoczynnie rozwiązuje to problem z aplikowaniem diffów, których nie dałoby się zastosować przy użyciu samego patcha.

Firefox – narzędzia dla programistów zamiast Firebuga

Dopiero niedawno dowiedziałem się, że popularny dodatek do Firefoksa – Firebug – jest już od jakiegoś czasu martwy. Autorzy zalecają używanie narzędzi dla programistów wbudowanych w Firefoksa.

Mając świadomość, że z czasem pojawią się problemy (a jeden wystąpił już kilka miesięcy temu, gdy chciałem podejrzeć ruch generowany przez websockety), usunąłem Firebuga i zacząłem korzystać z wbudowanych narzędzi. Jak na przesiadkę z narzędzia, którego używam co najmniej od 5 lat, proces przebiegł zaskakująco łatwo.

Wbudowane narzędzia dla programistów na pierwszy rzut oka są według mnie nieco ładniejsze od Firebuga. Inspektor oraz wyróżnianie nim elementów jest czytelniejsze. Edycja drzewa DOM jest łatwiejsza, zaś zdarzenia JS (które w Firebugu były dość mocno zakamuflowane)  są teraz oznaczone tagiem “ev” przy elemencie, pod który są podpięte.

Na uwagę zasługuje też narzędzie do badania wydajności oraz przebudowany panel “Sieć”, który wyświetla szczegóły żądań w wygodniejszy sposób. Prezentuje on też dane przesyłane przez websockety, czego brakowało Firebugowi.

Narzędzia dla programistów z Firefoksa posiadają też kilka nowych funkcji. Mnie w oczy rzucił się “Brudnopis” otwierany w osobnym oknie (a nie jako część panelu konsoli) oraz wygodny “Próbnik koloru”, automatycznie kopiujący do schowka kod koloru wskazanego piksela. Nie może tu zabraknąć też narzędzia: “Widok trybu responsywnego”, który ułatwia rozwijanie aplikacji z responsywnym layoutem.

Podsumowując – polecam. Przesiadka jest praktycznie bezbolesna, a warto choćby ze względu na wymienione wyżej nowości i usprawnienia.