Assuntos relacionados à programação, pedaços de códigos, pequenas dicas, pequenos tutoriais, alguns vídeos, algumas fotos e etc.

Triggers no SQLITE

Posted: março 6th, 2009 | Author: | Tags: , , , , , | 2 Comments »

Esta semana um grande amigo twitou uma pergunta que me chamou atenção. Já tinha estudado sobre o assunto a alguns anos atrás para utilizar em um sistema. Foi dai que veio a idéia para este post, peguei os meus rascunhos e comecei a escrever.

O banco de dados SQLITE, em sua versão atual, não possui suporte a integridade referencial. Nunca consegui entender o motivo, já que bancos similares como h2 e HSQLDB possuem. Talvez não implementaram a funcionalidade ainda por que existe uma outra alternativa. Uma forma de driblar essa limitação é através de triggers. Um trigger é disparado quando um evento ocorre. É possível deletar os registros filhos relacionados com uma tabela pai, por exemplo. O evento neste caso, para ficar bem claro, é a remoção de um registro da tabela pai.

A sintaxe básica para a criação de um trigger é:

CREATE TRIGGER nome_da_trigger
{ BEFORE | AFTER | INSTEAD OF }
{ INSERT | UPDATE OF | DELETE }
ON { TABLE }
BEGIN
{ SQL STATEMENT }
END;

Para praticar, criei as tabelas abaixo. Se você for preguiçoso como eu, pode baixar o arquivo do banco de dados direto daqui. É um exemplo bem simples, se você já for um expert em banco de dados (ou um dba), nada do que vou mostrar é novidade.

BEGIN TRANSACTION;

-- users table
CREATE TABLE users (id INTEGER PRIMARY KEY, login text, password text);
-- tasks table
CREATE TABLE tasks (id INTEGER PRIMARY KEY, user_id INTEGER, name text);
-- logs table
CREATE TABLE logs(id INTEGER PRIMARY KEY, event text, created_at datetime DEFAULT (datetime('now', 'localtime')));
-- counts table
CREATE TABLE counts(id INTEGER PRIMARY KEY, name text, VALUE INTEGER);

-- data
INSERT INTO "users" VALUES(1,'admin','admin');

INSERT INTO "tasks" VALUES(1,1,'Entender triggers no sqlite');
INSERT INTO "tasks" VALUES(2,1,'Aprender a voar');

INSERT INTO "counts" VALUES(1,'users',0);
INSERT INTO "counts" VALUES(2,'tasks',0);

COMMIT;

Agora vem a parte interessante. Vejamos como inserir um registro na tabela logs toda vez que um usuário for criado. O trigger é “colocado” na tabela users e, toda vez, após ocorrer um insert o trigger é disparado.

-- after insert user trigger
CREATE TRIGGER user_insert
after INSERT ON users
BEGIN
INSERT INTO logs (event) VALUES ("New user created");
END;

Faça o teste. Termine de inserir outros usuários e verifique a tabela logs.

INSERT INTO "users" VALUES(2,'user','user');
INSERT INTO "users" VALUES(3,'test','test');

SELECT * FROM logs;

O trigger acima monitora a operação insert e adiciona um registro em outra tabela. Agora um exemplo que monitora a mesma operação, mas realiza o update em outra tabela.

-- update users count
CREATE TRIGGER update_users_count
after INSERT ON users
BEGIN
UPDATE counts SET VALUE = VALUE + 1 WHERE name = "users";
END;

-- test again
INSERT INTO "users" VALUES(4,'delete','me');
INSERT INTO "users" VALUES(5,'highlander','therecanbeonlyone');

Para o exemplo de integridade referencial, maiores informações no início do post, o trigger realiza o delete em todos os registros (for each row) relacionados com a tabela pai. Neste exemplo a tabela pai é users e a filha é tasks. A referência feita a old significa o valor atual do iterator no loop.

-- before delete user trigger
CREATE TRIGGER user_delete
BEFORE DELETE ON users
FOR each ROW
BEGIN
DELETE FROM tasks WHERE tasks.user_id=OLD.id;
END;

-- test
INSERT INTO "tasks" VALUES(3,4,'Ganhar na mega-sena');
INSERT INTO "tasks" VALUES(4,4,'Delete me');
INSERT INTO "tasks" VALUES(5,4,'Dominar o mundo');
INSERT INTO "tasks" VALUES(6,3,'Test task');
INSERT INTO "tasks" VALUES(7,3,'Hello world');
INSERT INTO "tasks" VALUES(8,2,'Buy new car');
INSERT INTO "tasks" VALUES(9,2,'Drink more');

Delete o usuário de id número 4 e veja o que acontece.

Pra finalizar, recomendo a todos baixar o arquivo de exemplo e fuçar. Esta solução pode não atender a todos, já que para deletar 1000000 registros filhos, dependendo o seu hardware, o processo pode ser custoso. Não tenho um conhecimento tão profundo sobre engines de banco de dados para explicar como seria feito utilizando um banco com suporte a integridade referencial, portanto, leve isso em consideração ao interpretar a afirmação acima.

De qualquer forma, o uso do SQLITE só deve ser feito em protótipos ou em aplicações cujo os dados não são de muita importância. Caso os dados de sua aplicação sejam muito importantes, geralmente são, você deveria procurar outra alternativa.

Enfim, pelo menos, deu pra brincar um pouco.

Referências:


Ruby Learning novos cursos

Posted: novembro 17th, 2008 | Author: | Tags: , , , , | 1 Comment »

Ruby Learning é um website que oferece cursos baseados em Ruby online, em inglês. O curso em si é muito divertido e muito bem feito para ser um curso online e de graça, até os exercícios são bem bolados. Há a possibilidade dos alunos se interagirem uns com os outros através de fóruns e com os Assistentes de professor, que nada mais são do que alunos mais experientes. O conteúdo de um lição é liberado todo o Sábado, o aluno tem a semana inteira para estudar e fazer os exercícios, é bem tranquilo. Outro incentivo é que o curso é curto, ou seja, tem duração de somente 2 meses.

Aproveitando, o Satish criou uma pesquisa para a criação de novos cursos. O Ruby Learning já tem algumas sugestões e deseja saber o que a comunidade pensa sobre isto. Portanto, citando o Satish em seu blog:

We’re rapidly expanding our course offerings here at RubyLearning, trying to keep up with the enormous and ever-growing interest in Ruby. But we need your YOUR help because, as you know, Ruby is a big subject, and we’d like to be sure to focus in on the areas of most interest to you. (link para a pesquisa).