Curiosidades que você (talvez) não sabia e se sabia vale a pena lembrar

Post on 14-Feb-2017

573 views 1 download

Transcript of Curiosidades que você (talvez) não sabia e se sabia vale a pena lembrar

Curiosidades quevocê (talvez) nãosabia e se sabia …

Dickson S. Guedes@guediz

PGBR 2015 - Porto Alegre, RS

O aninhador frenético

ProblemaSELECT ..., CASE WHEN sobrenome IS NULL THEN nome WHEN sobrenome IS NOT NULL THEN sobrenome || ',' || nome ENDFROM ...

SoluçãoSELECT ..., COALESCE(sobrenome || ',', '') || nomeFROM ...

O aninhador frenético II

ProblemaSELECT ..., CASE WHEN COALESCE(endereco, '') <> '' THEN CASE WHEN COALESCE( COALESCE(endereco, '') || ' ' || COALESCE(bairro, '') ) <> ' ' THEN endereco ||' '|| bairro ELSE COALESCE(cidade, '') END ELSE COALESCE(cidade,'SEM CIDADE') ENDFROM ...LEFT JOIN ...LEFT JOIN ...

Possível soluçãoSELECT ..., COALESCE(endereco ||' '|| bairro, cidade, 'SEM CIDADE'),FROM ...LEFT JOIN ...LEFT JOIN ...

Opa! Três parâmetros?SELECT ..., COALESCE(endereco ||' '|| bairro, cidade, 'SEM CIDADE'), ̂^̂^̂^̂^̂^̂^̂^̂^̂^̂^̂^̂ ̂^̂^̂ ̂ ̂^̂^̂^̂^̂^FROM ...LEFT JOIN ...LEFT JOIN ...

Qual é o resultado disto?SELECT ..., COALESCE(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'SEM VALOR'), COALESCE(NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'SEM VALOR'), COALESCE(NULL, NULL, NULL, NULL, NULL, NULL, 'SEM VALOR'), COALESCE(NULL, NULL, NULL, NULL, NULL, 'SEM VALOR'), COALESCE(NULL, NULL, NULL, NULL, 'SEM VALOR'), COALESCE(NULL, NULL, NULL, 'SEM VALOR'), COALESCE(NULL, NULL, 'SEM VALOR'), COALESCE(NULL, 'SEM VALOR'), ...FROM ...

Como isso é possível?

VARIADICCREATE FUNCTION menor_de_todos(VARIADIC valores numeric[])RETURNS numeric AS$$ SELECT min($1[valor]) FROM generate_subscripts($1, 1) g(valor);$$LANGUAGE SQL;

SELECT menor_de_todos(10, 11, 12, 30, -20, -30) as menor;

menor -------- -30(1 row)

Parâmetros! Não é para passar um ARRAYnão!

SELECT menor_de_todos(ARRAY[10, 11, 12, 30, -20, -30]) as menor; ̂^̂^̂^ ̀-- assim da ruim

O esbanjador de SELECTs

Tudo bunitinhoCREATE TYPE situacao_cheque AS ENUM ('compensado', 'devolvido', 'sustado', 'voador');

CREATE TABLE cheque (numero integer not null, valor integer not null, situacao situacao_cheque);

INSERT INTO cheque VALUES (1, 100.00, NULL);

INSERT INTO cheque VALUES (2, 500.00, 'compensado'), (3, 1000.00, 'devolvido'), (4, 900.00, 'devolvido'), (5, 3000.00, 'compensado'), (6, 145.00, 'sustado'), (7, 45.00, 'voador'), (8, 66.00, 'voador'), (9, 96.00, 'sustado'), (10, 45.00, 'sustado');

ai você vê isso …SELECT (SELECT COUNT(*) FROM cheque WHERE situacao = 'compensado') as total_compensados, (SELECT COUNT(*) FROM cheque WHERE situacao = 'devolvido') as total_devolvidos, (SELECT COUNT(*) FROM cheque WHERE situacao = 'voador') as total_voadores;

mas esperava isso …SELECT SUM(CASE WHEN situacao = 'compensado' THEN 1 ELSE 0 END) as total_compensados, SUM(CASE WHEN situacao = 'devolvido' THEN 1 ELSE 0 END) as total_devolvidos, SUM(CASE WHEN situacao = 'voador' THEN 1 ELSE 0 END) as total_voadoresFROM cheque;

Ei! isso também é possívelSELECT SUM( int4( situacao = 'compensado' ) ) as total_compensados, SUM( int4( situacao = 'devolvido' ) ) as total_devolvidos, SUM( int4( situacao = 'voador' ) ) as total_voadoresFROM cheque;

CASTCAST('a' = 'b' AS int4) = 0int4('a' = 'b') = 0

CAST('a' = 'a' AS int4) = 1int4('a' = 'a') = 1

int4('B' = 'B') + int4('A' = 'A') = 2

Outros CASTs que funcionamSELECT text(1); => '1'SELECT date '2014-01-01'; => '2014-01-01'SELECT numeric '1'; => 1SELECT int4 '100000000000000'; => out of rangeSELECT int8 '100000000000000'; => 100000000000000

Então da para fazer algo assimSELECT SUM( int4( situacao = 'compensado' ) ) as total_compensados, SUM( int4( situacao = 'devolvido' ) ) as total_devolvidos, SUM( int4( situacao = 'voador' ) ) as total_voadores ̂^̂^̂ ̂^̂^̂^̂ ,̂̂ ^̂^̂^̂^̂^FROM cheque; ̀~~~~~~~~~~́~~~~~ transforma boolean para integer

Mas qual é "o" problema?todos os cheque acima de R$ 1.000,00 foram compensados?algum cheque abaixo de R$ 800,00 foi devolvido?

Declarativo e explicito MELHOR QUEimplícito.

SELECT BOOL_AND(situacao = 'compensado') FILTER (WHERE valor > 1000) as "todos > 1000 foram compensados?", BOOL_OR(situacao = 'devolvido') FILTER (WHERE valor < 800) as "algum < 800 foi devolvido", EVERY(situacao = 'voador') as "algum cheque voador?"FROM cheque;

SELECT bool_and(situacao = 'compensado') as "todos compensados acima de R$ 1.000"FROM cheque WHERE valor > 1000;

SELECT bool_or(situacao = 'devolvido') as "Algum cheque < 800 devolvido?"FROM chequeWHERE valor < 800;

O explorador do desconhecido

NULL e a aritiméticaSELECT NULL = NULL;SELECT NULL > NULL;SELECT NULL < NULL;

NULL e a aritiméticaSELECT NULL = NULL; => NULLSELECT NULL > NULL; => NULLSELECT NULL < NULL; => NULL

O que é NULL?SELECT NULL IS NULL; => trueSELECT NULL IS DISTINCT FROM NULL; => falseSELECT NULL IS NOT DISTINCT FROM NULL; => true

SELECT 1 IS NULL; => falseSELECT 1 IS DISTINCT FROM NULL; => trueSELECT 1 IS NOT DISTINCT FROM NULL; => false

TESTE DE ATENÇÃO!

Lembram da função menor_de_todos?SELECT menor_de_todos(10, 11, 12, 30, -20, -30) as menor;

menor -------- -30(1 row)

E se eu passar NULL?SELECT menor_de_todos(10, 11, 12, NULL, -20, -30) as menor;

menor -------- ????? (1 row)

-30SELECT menor_de_todos(10, 11, 12, NULL, -20, -30) as menor;

menor -------- -30 (1 row)

menor_de_todos(...) usa a funçãomin(...) que é uma função de agregação

funções de agregação ignoram NULL

O fabricador de registros

Um registro!SELECT ROW(10, 'JOAO', 1500.50);

Dois registros!SELECT ROW(10, 'JOAO', 1500.50) = ROW(10, 'JOAO', 1500.50); => true

SELECT ROW(10, 'JOAO', 1500.50) > ROW(9, 'PEDRO', 500.50); => true

SELECT ROW(1, 'MARIA', 1000.10) > ROW(1, 'MARIA', 1000.11); => false

SELECT ROW(100, 'JOANA', 10.60) < ROW(100, 'JOANA', NULL); => ?????

NULL!

Lembra o INSERT?INSERT INTO ... VALUES (1, 'JOAO', 1500.10);

Pensa no VALUES!/* INSERT INTO ... */ VALUES (1, 'JOAO', 1500.10); -- FUNCIONA!

VALUES é um comando sozinho!INSERT INTO ... SELECT codigo, nome, valor FROM tabela_temporaria;

INSERT INTO ... VALUES (1, 'JOAO', 1500.10);

VALUES (1, '...', 1500.10), (2, '...', 500.00), (3, '...', 100.00), (4, '...', 50.50), (5, '...', 3500.00),;

O masoquista escrivinhador

SIM! Isto funciona; pode testar!SELECT 1 OPERATOR(pg_catalog.+) 2 = 1 + 2; => true

Operadores são funções disfarçadasSELECT 1 + 1; ̂ ̂ ̂__ integer na direita | |____ operador '+' |______ integer na esquerda

SELECT oprname, oprcode, CAST(oprleft AS regtype), -- 23 é integer CAST(oprright AS regtype) -- 23 é integerFROM pg_operator WHERE oprname = '+' AND oprleft = 23 AND oprright = 23;

oprname | oprcode | oprleft | oprright ---------+---------+---------+---------- + | int4pl | integer | integer

Operadores são funções disfarçadasSELECT 1 + 1; ̂ ̂ ̂__ integer na direita | |____ operador '+' |______ integer na esquerda

SELECT oprname, oprcode, CAST(oprleft AS regtype), -- 23 é integer CAST(oprright AS regtype) -- 23 é integerFROM pg_operatorWHERE (oprname, oprleft, oprright) = ('+', 23, 23); ̂^̂^̂^̂^̂^̂^̂^̂^̂^̂^̂^̂^̂^̂^̂^̂^̂^̂^̂^̂^̂^ ̀- este é um outro jeito de escrever o WHERE sem os AND e obter o mesmo resultado...

oprname | oprcode | oprleft | oprright ---------+---------+---------+---------- + | int4pl | integer | integer ̂^̂^̂ .̂___ o postgres chamará esta função para somar integer com integer

Logo …SELECT 1 + 1 = 2;

SELECT int4pl(1, 1) = 2;

Para o infinito, e além

Sim, tudo isso é possívelSELECT '+Infinity'::float > 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999; => true

SELECT '-Infinity'::float < 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999; => true

Infinito para datas, também, claro!SELECT 'Infinity'::date > current_date; => true

INSERT INTO elemento(nome, validade) VALUES ('urânio', 'Infinity'); isto é um date _.̂^̂^̂^̂^1SELECT 'today'::interval = current_date; => true

SELECT 'yesterday'::date = 'today'::date - interval '1 day'; => true

SELECT 'tomorroy'::date = 'today'::date + interval '1 day'; => true

SELECT current_date + 'allballs'::time; => '2015-09-18 00:00:00'

E tudo pode ser rescrito como …SELECT date 'Infinity' > current_date; => true

INSERT INTO elemento(nome, validade) VALUES ('urânio', 'Infinity'); isto é um date _.̂^̂^̂^̂^

SELECT interval 'today' = current_date; => true

SELECT date 'yesterday' = date 'today' - interval '1 day'; => true

SELECT date 'tomorroy' = date 'today' + interval '1 day'; => true

SELECT current_date + time 'allballs'; => '2015-09-18 00:00:00'

NaNaNaNaNaNaNaNSELECT 'NaN'::numeric + 1; => 'NaN'

Já que estamos falando dedatas

já viram isso?SELECT ... CAST( CAST(ano AS text) || '-' || CAST(mes AS text) || '-' || CAST(dia AS text), AS date) ), ...FROM ...;

ou isso?SELECT ... FROM ...WHERE ... data_nascimento = CAST( CAST(ano AS text) || '-' || CAST(mes AS text) || '-' || CAST(dia AS text), AS date) );;

que se faça a dataSELECT ..., make_date(ano, mes, dia), ...FROM ...;

Algumas fontes são True Type

SERIAL NÃO!

São equivalentesCREATE TABLE tabela ( coluna SERIAL);

OU

CREATE SEQUENCE tabela_coluna_seq;CREATE TABLE tabela ( coluna integer NOT NULL DEFAULT nextval('tabela_coluna_seq'));ALTER SEQUENCE tabela_coluna_seq OWNED BY tabela.coluna;

Chamando funções

Uma função pode ser chamada assimSELECT upper('nome');

upper ------- NOME(1 registro)

Uma função pode ser chamada assimtambém

SELECT * FROM upper('nome');

upper ------- NOME(1 registro)

E também pode ser assimSELECT ('nome').upper;

upper ------- NOME(1 registro)

Uma tabela comum, uma consulta comumCREATE TABLE pessoa(nome text, data_nascimento date);

INSERT INTO pessoa VALUES('JOAO', now() - interval '35 years');

SELECT p.nome, p.data_nascimento FROM pessoa p;

e uma consulta não tanto comumSELECT nome(p), data_nascimento(p) from pessoa p;

nome | data_nascimento ------+----------------- JOAO | 1980-09-18(1 registro)

Mas uma função também pode ter seuretorno como uma tabela

CREATE FUNCTION duplica(IN int, OUT valor_original int, OUT valor_convertido text)AS$$ SELECT $1, CAST($1 AS text) || ' eh texto'$$LANGUAGE SQL;

SELECT * FROM duplica(42);

valor_original | valor_convertido ----------------+------------------ 42 | 42 eh texto(1 registro)

Tempo: 1,684 ms

PSQL

isso ajuda\set v_nome 'JOAO'\set v_idade 18

\connect desenv usuario 192.168.1.1 5432

SELECT * FROM pessoa WHERE nome = :v_nome AND idade > :v_idade \g

\connect teste usuario 192.168.1.2 5432

SELECT * FROM pessoa WHERE nome = :v_nome AND idade > :v_idade ;

isso também ajuda\set v_cpf 1234567890

\connect desenv usuario 192.168.1.1 5432

SELECT nome as v_nome, idade as v_idadeFROM pessoa WHERE cpf = :v_cpf \gset

\connect teste usuario 192.168.1.2 5432

SELECT * FROM pessoaWHERE nome = :v_nome AND idade > :v_idade ;

Talvez num futuro isso funcioneSELECT ... FROM banco.esquema.tabela;

Bom, não é só isso…

ainda tem muito mais coisas…

Leia o manualhttp://www.postgresql.org/docs/manuals/

Visite o IRC, é divertido …

Numa terça-feira monótona, AndrewGierth (a.k.a. RhodiumToad) escreveu isto

SELECT oid, lowrite(lo_open(oid,131072), a)FROM (VALUES (lo_create(0))) v(oid), generate_bytea() as x(a);

-- (note the oid returned)

\lo_export 828133392 'image.png'

SELECT lo_unlink(828133392);

e é isso, obrigado! :)Conferencia Brasileira de PostgreSQL

18 a 20 de Novembro

email: guedes@guedesoft.netgithub: twitter:

http://pgbr.postgresql.org.br/

http://github.com/guedeshttp://twitter.com/guediz