PostgreSQL - zabawa z tablicami

dodał: DDarko (2009-12-27 12:23),   tagi: postgresql

PostgreSQL IMHO jest najlepszą i najprzyjemniejszą w obsłudze darmową bazą danych. Już dawno temu zostawił w tyle swojego konkurenta MySQL. W tym artykule chciałem przybliżyć temat tablic w postgresie.

Na początek tworzymy nową bazę „test”:
$ createdb -U postgres test

Łączymy się z nową bazą przy pomocy standardowego klienta:
$ psql -U postgres test

Tworzymy przykładową tabelę „tab” na dane typu teksotowego (można robić tablice dowolnych typów):
test=# CREATE TABLE tab (id serial, t text[]);

Sprawdzamy czy wszystko zostało utworzone poprawnie:
test=# \d
             List of relations
 Schema |    Name    |   Type   |  Owner   
--------+------------+----------+----------
 public | tab        | table    | postgres
 public | tab_id_seq | sequence | postgres
(2 rows)

test=# \d tab
                         Table "public.tab"
 Column |  Type   |                    Modifiers                     
--------+---------+--------------------------------------------------
 id     | integer | not null default nextval('tab_id_seq'::regclass)
 t      | text[]  | 

Dodajemy kilka przykładowych wpisów:
test=# INSERT INTO tab (t) VALUES ('{ddarko}');
INSERT 0 1
test=# INSERT INTO tab (t) VALUES ('{ddarko,karol,ola}');
INSERT 0 1
test=# INSERT INTO tab (t) VALUES ('{ania,michał,zosia}');
INSERT 0 1

Sprawdzamy czy dane zostały dodane pomyślnie:
test=# SELECT * FROM tab;
 id |          t          
----+---------------------
  1 | {ddarko}
  2 | {ddarko,karol,ola}
  3 | {ania,michał,zosia}
(3 rows)

Wszystko wygląda fajnie :] Co teraz możemy z tym zrobić ?!
Np. chcemy wydobyć drugi element z tablicy dla każdego wiersza:
test=# SELECT t[2] FROM tab;
   t    
--------
 
 karol
 michał
(3 rows)
Jak widać pierwszy wiersz nie ma drugiego elementu i dostaliśmy NULL, w pozostałych przypadkach jest to jakiś pojedynczy wyraz.

Przykładowo: chcemy uzyskać id dla wpisów, które zawierają słowo „ddarko”.
test=# SELECT id FROM tab WHERE 'ddarko' = ANY (t);

Często napotykałem na potrzebę skorzystania z ILIKE do szukania wewnątrz tablicy.
Chciało by się napisać:
test=# SELECT id FROM tab WHERE 'DD%' ILIKE any (t);

niestety nie działa to poprawnie :( , aby uzyskać pożądany efekt stworzyłem własny operator:
test=# create function rilike(text,text) returns bool as 'select $2 ilike $1' language sql strict immutable;
test=# create operator ~~~ (procedure = rilike, leftarg = text, rightarg = text, commutator = ~~);

Po wykonaniu dwóch powyższych linijek uzyskujemy dostęp do nowego operatora ~~~, przykład jego wykorzystania:
test=# SELECT id FROM tab WHERE 'DD%' ~~~ ANY (t);

Gdy chcemy odszukać wiersze zawierające literkę np. „ł”:
test=# SELECT * FROM tab WHERE '%ł%' ~~~ ANY (t);
 id |          t          
----+---------------------
  3 | {ania,michał,zosia}
(1 row)

Jeżeli chcemy odszukać wiersze nie zawierające litery np. „ł”:
test=# SELECT * FROM tab WHERE NOT '%ł%' ~~~ ANY (t);
 id |         t          
----+--------------------
  1 | {ddarko}
  2 | {ddarko,karol,ola}
(2 rows)

Kolejny przykład:
Mamy dwa wyrazy np.: „ania”, „zosia” i chcemy znaleźć wiersze, które zawierają oba wyrazy.
test=# select * from tab where '{ania,zosia}' <@ t;
 id |          t          
----+---------------------
  3 | {ania,michał,zosia}
(1 row)

Jeżeli chcemy znaleźć wiersze, które zawierają dowolny wyraz z podanej przez nas tablicy (np.: „ania”, „ola”):
test=# select * from tab where '{ania,ola}' && t;
 id |          t          
----+---------------------
  2 | {ddarko,karol,ola}
  3 | {ania,michał,zosia}
(2 rows)

Niby wszystko dobrze, jednak nie wiemy, który wyraz został odnaleziony w danym wierszu.
Ponieważ w standardzie nie ma funkcji, która to realizuje musimy stworzyć taką funkcję sami:
CREATE LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION array_common_elements (ANYarray, ANYarray) RETURNS ANYarray AS
$$
	DECLARE
		out ALIAS FOR $0;
	BEGIN
		FOR idx IN array_lower($1, 1)..array_upper($1, 1) LOOP
			IF $1[idx] = any($2) THEN
				out = array_append(out, $1[idx]);
			END IF;
		END LOOP;
	RETURN out;
	END;
$$
LANGUAGE plpgsql;

i tutaj zapytanie rozwiązujące przykładowy problem:
test=# select id, array_common_elements('{ania,ola}',t) from tab where array_common_elements('{ania,ola}',t) is not null;
 id | array_common_elements 
----+-----------------------
  2 | {ola}
  3 | {ania}
(2 rows)


Spis operatorów dostępnych na tablicach:

Operator Opis Przykład wykorzystania
= równość
SELECT array[1.1,2.1,3.1]::int[] = array[1,2,3];
 t
		
<> różność
SELECT array[1,2,3] <> array[1,2,4];
 t
		
< mniej niż
SELECT array[1,2,3] < array[1,2,4];
 t
		
> więcej niż
SELECT array[1,4,3] > array[1,2,4];
 t
		
<= mniej niż lub równość
SELECT array[1,2,3] <= array[1,2,3];
 t
		
>= więcej niż lub równość
SELECT array[1,4,3] >= array[1,4,3];
 t
		
@> zawieranie (cała tablica zawiera się w innej)
SELECT array[1,4,3] @> array[3,1];
 t
		
<@ zawarte w
SELECT array[2,7] <@ array[1,7,4,2,6];
 t
		
&& część wspólna (co najmniej jeden element wspólny)
SELECT array[1,4,3] && array[2,1];
 t
		
|| łączenie połączenie dwóch tablic:
SELECT array[1,2,3] || array[4,5,6];
{1,2,3,4,5,6}
dodanie elementu do tablicy:
SELECT 3 || array[4,5,6];
{3,4,5,6}

SELECT array[4,5,6] || 7;
{4,5,6,7}


Spis funkcji dostępnych na tablicach:

Funkcja Wynik funkcji Opis Przykład wykorzystania
array_append(anyarray, anyelement) anyarray dodaje element na koniec tablicy
SELECT array_append(array[1,2], 3);
{1,2,3}
		
array_cat(anyarray, anyarray) anyarray łączy dwie tablice ze sobą
SELECT array_cat(array[1,2,3], array[4,5]);
{1,2,3,4,5}
		
array_dims(anyarray) text zwraca rozmiar/wielkość tablicy
SELECT array_dims(array[[1,2,3], [4,5,6]]);
[1:2][1:3]
		
array_lower(anyarray, int) int zwraca mniejszy wymiar tablicy
array_lower('[0:2]={1,2,3}'::int[], 1);
0
		
array_prepend(anyelement, anyarray) anyarray dodanie elementu na początek tablicy
SELECT array_prepend(1, array[2,3]);
{1,2,3}
		
array_to_string(anyarray, text) text łączenie elementów tablicy przy pomocy ciągu znaków
SELECT array_to_string(array[1, 2, 3], '~^~');
1~^~2~^~3
		
array_upper(anyarray, int) int zwraca największy wymiar tablicy
SELECT array_upper(array[1,2,3,4], 1);
4
		
string_to_array(text, text) text[] zamiana ciągu znaków na tablicę, dzielenie po podanym ciagu znaków
SELECT string_to_array('xx~^~yy~^~zz', '~^~');
{xx,yy,zz}
		




Spis ciekawych sztuczek: tutaj.