*/

sexta-feira, 8 de abril de 2011

A Bíblia do DBA

Neste artigo, irei descrever como instalar e configurar um base de dados, partindo do ponto de que o banco, serviço, já estão instalados.

Serão usados algums recursos práticos de ETL no Postgres, e conversão de encondig de arquivos.

Tomei por base a Bíblia Sagrada na Tradução de João Ferreira de Almeida, Edição Revista e Corrigida em Português-Brasil.

Como estudioso da Bíblia e crente, entendo que o Autor da Bíblia está vivo, assim sempre que tenho alguma dúvida sobre sobre algum tópico deste livro consulto a Ele, o criador de todas as coisas, DEUS.

Essa base em csv pode ser também usada com algumas adaptações em outros SGBDs a exemplo Oracle, SQL Server, MySQL, Firebird ou outro que você queira usar, neste artigo usei
Postgres

Tomei como referencia uns arquivos gerados em csv com encoding em UTF8, neste caso podendo até serem abertos como planilhas Open-Office ou MS-Excel.

Deixo aqui meu profundo agradecimento ao irmãos que voluntariamente, tiveram o trabalho de converter uma base txt para csv, não é fácil, que DEUS os abençõe!

Mãos a massa!

Antes, apenas definindo o que é um arquivo CSV ou melhor lembrando... :)
Retirado do Wikipedia em: http://pt.wikipedia.org/wiki/Comma-separated_values

Comma-separated values (CSV), em português Valores Separados por Vírgula, é um formato de arquivo que armazena dados tabelados, cujo grande uso data da época dos mainframes. Por serem bastante simples, arquivos .csv são comuns em todas as plataformas de computador.

O CSV é um implementação particular de arquivos de texto separados por um delimitador, que usa a vírgula e a quebra de linha para separar os valores. O formato também usa as aspas em campos no qual são usados os caracteres reservados (vírgula e quebra de linha). Essa robustez no formato torna o CSV mais amplo que outros formatos digitais do mesmo segmento.

Caso queira mudar o encoding, você poderá utilizar o iconv do Linux em http://www.gnu.org/software/libiconv/documentation/libiconv/iconv.1.html ou salvar como txt e abrir pelo MS-Excel, que o mesmo pedirá qual encoding utilizar-converter.

Segue os arquivos em formato CSV da Bíblia:

Biblia_JFARC.zip

No nosso caso, o arquivo está zipado e contém três CSV 1-Testamento_JFARC, 2-Livro_JFARC, 3-Palavra_JFARC:

Para que o script SQL funcione sem alterações descompactei o arquivo no Windows em: c:\Biblia_JFARC, no Linux em: /home/postgres/Biblia_JFARC/ lembrando de dá permissões para acesso com usuário postgres

Além dos Scripts SQL para Postgres, também estão a base de dados para SQL Server e Firebird para Bíblia conforme link abaixo:


Biblia_JFARC.zip

Neste artigo iremos abordar a criação de scripts DDL/DML (SQL) para criação da base de dados da Bíblia no Postgres:

--
-- Nome Artefato/Programa..: Biblia_ddl.sql
-- Autor(es)...............: Emerson Hermann (emersonhermann@gmail.com)  
-- Data Inicio ............: 04/02/2011 as 20:45
-- Data Atual..............: 07/02/2011 as 18:50
-- Versao..................: 0.05
-- Compilador/Interpretador: PostgreSql/PLpgSQL
-- Sistemas Operacionais...: Linux/Windows
-- SGBD....................: PostgreSql 8.x/9.x
-- Kernel..................: Nao informado!
-- Finalidade..............: Script de criação de estrutura de base de dados da Bíblia Sagrada  
-- ........................: 
-- OBS.....................:  
--
/*
--Poderia se criar dominio, como exemplo abaixo, já que é uma tabela pequena e imutavél, 
--porém por questão de compatibilidade com outros SGBDRs, deixamos como um tabela.
DROP DOMAIN IF EXISTS id_testamento_dominio;
CREATE DOMAIN id_testamento_dominio AS smallint
CHECK 
 (
 VALUE IN 
  (
    1 --Antigo Testamento
  , 2 --Novo Testamento 
  ) 
 );
COMMENT ON DOMAIN id_testamento_dominio IS 'Dominio do Testamento';
 
*/

DROP TABLE IF EXISTS Testamento;
CREATE TABLE testamento
(
   id_testamento smallint NOT NULL PRIMARY KEY
 , nome varchar(21) NOT NULL
);
COMMENT ON TABLE Testamento IS 'Tabela que armazena os Testamentos da Bíblia';
COMMENT ON COLUMN Testamento.id_testamento IS 'Chave primária da tabela';
COMMENT ON COLUMN Testamento.nome IS 'Nome do Testamento';



DROP TABLE IF EXISTS Livro;
--DROP TABLE IF EXISTS Livro CASCADE;
CREATE TABLE Livro  
(
   id_livro serial 
 , ordem_livro smallint NOT NULL 
 , id_testamento smallint NOT NULL
 , nome varchar(42) NOT NULL
 , abrev varchar(4) NOT NULL
 , CONSTRAINT Livro_pkey PRIMARY KEY (id_livro)
 , CONSTRAINT Livro_id_testamento_fkey FOREIGN KEY (id_testamento)
   REFERENCES Testamento (id_testamento) 
 
);
COMMENT ON TABLE Livro IS 'Tabela que armazena os Livros da Bíblia';
COMMENT ON COLUMN Livro.id_livro IS 'Id do Livro da Bíblia';
COMMENT ON COLUMN Livro.ordem_livro IS 'Número de  ordem do Livro da Bíblia';
COMMENT ON COLUMN Livro.id_testamento IS 'Número do Testamento';
COMMENT ON COLUMN Livro.nome IS 'Nome completo do Livro da Bíblia';
COMMENT ON COLUMN Livro.abrev IS 'Nome abreviado do Livro da Bíblia';



DROP TABLE IF EXISTS Palavra;
CREATE TABLE Palavra
(
   id_palavra serial 
 , id_livro integer NOT NULL 
 , capitulo smallint NOT NULL
 , versiculo smallint NOT NULL
 , texto TEXT
 , CONSTRAINT Palavra_pkey PRIMARY KEY (id_palavra)
 , CONSTRAINT Palavra_id_livro_fkey FOREIGN KEY (id_livro)
   REFERENCES Livro (id_livro) 


);
COMMENT ON TABLE Palavra IS 'Tabela que armazena os versiculos da Bíblia';
COMMENT ON COLUMN Palavra.id_livro IS 'Id do Livro da Bíblia';
COMMENT ON COLUMN Palavra.capitulo IS 'Número do capítulo da Bíblia';
COMMENT ON COLUMN Palavra.versiculo IS 'Número do versículo da Bíblia';
COMMENT ON COLUMN Palavra.texto IS 'Texto do versículo da Bíblia';



INSERT INTO Testamento (id_testamento, nome) VALUES (1,'Velho Testamento');
INSERT INTO Testamento (id_testamento, nome) VALUES (2,'Novo Testamento');

SELECT * FROM Testamento;

--linux
 COPY livro(ordem_livro, id_testamento, nome, abrev) 
 FROM '/home/postgres/Biblia_JFARC/2_Livro_JFARC.csv' 
  CSV 
   HEADER;

--windows
 COPY livro(ordem_livro, id_testamento, nome, abrev) 
 FROM 'c:/Biblia_JFARC/2_Livro_JFARC.csv' 
  CSV 
   HEADER;


SELECT * FROM Livro;

--linux
 COPY palavra (id_livro, capitulo, versiculo, texto)  
 FROM '/home/postgres/Biblia_JFARC/3_Palavra_JFARC.csv' 
  CSV  
   HEADER;

--windows

 COPY palavra (id_livro, capitulo, versiculo, texto)  
 FROM 'c:/Biblia_JFARC/3_Palavra_JFARC.csv' 
  CSV  
   HEADER;


SELECT * FROM Palavra;


Eu sinceramente creio que DEUS fala pela sua Palavra, a Bíblia, além da leitura tradicional, já tive diversas experiências usando um recurso o qual chamo de Consulta a Palavra, no qual oro a DEUS, clamando pelo sangue de JESUS, peço que fale comigo através da sua Palavra; fecho os olhos e logo em seguida abro a Biblia (Livro) apontanto para um versiculo, abro os olhos e leio.

Uma dessas experiências vou citar agora:

Certa vez fui acusado injustamente de roubo no meu trabalho e por relexo meu, perdi o documento que comprovava minha inocência, diante de tal situação fui pra casa, na eminência de perder o emprego e pior, ser acusado de ladrão, mas a primeira coisa que fiz foi pedir socorro a quem podia me socorrer, DEUS, e orei ao Senhor que me desse livramento, pois havia perdido o documento, que iria me inocentar.

Então falei com DEUS da seguinte forma:

Fala comigo DEUS, pelo sangue de Jesus a ti clamo, me dá um livramento dessa situação, pois de alguma forma estou errado, porque não vigiei e acabei perdendo esse documento, agora estou sendo acusado injustamente de roubo e estou na eminência de perder o emprego, depois abri a Bíblia e pedi uma palavra, e DEUS falou comigo, através do seguinte versículo da Bíblia:

havendo riscado a cédula que era contra nós nas suas ordenanças, a qual de alguma maneira nos era contrária, e a tirou do meio de nós, cravando-a na cruz.
CL 2:14

No dia seguinte o fornecedor, reconheceu o erro, enviou a sua documentação comprovando a minha inocência e corrigindo a situação, quitando o débito; o montante que teria que pagar caso não conseguir provar minha inocência, era de aprox. 20 vezes o meu salário; fui inocentado, e ainda trabalhei um bom tempo nessa empresa, e após 3 anos de desligado dessa empresa, já trabalhando em outra lugar, um amigo encontrou o tal documento que finalmente provou a minha inocência.

Concluindo essa experiência... posso dizer que DEUS realmente riscou a escrito de dívida, que de alguma forma era contra mim. DEUS É FIEL!

Muitas sãos as aflições do justo, mas o Senhor o livra de todas. Salmos 34:19

Usando esse mesmo conceito de Consulta a Palavra na Bíblia, no universo digital, usei uma função randômica (function) que traz um versículo da Bíblia, aleatoriamente em PL/pgSQL




--
-- Nome Artefato/Programa..: sp_palavra.sql
-- Autor(es)...............: Emerson Hermann (emersonhermann@gmail.com)  
-- Data Inicio ............: 16/07/2009
-- Data Atual..............: 07/02/2011 as 18:50
-- Versao..................: 0.05
-- Compilador/Interpretador: PostgreSql/PLpgSQL
-- Sistemas Operacionais...: Linux/Windows
-- SGBD....................: PostgreSql 8.x/9.x
-- Kernel..................: Nao informado!
-- Finalidade..............: Store Procedure/Function Funcao de Consulta a Palavra, não é apenas uma 
-- ........................: caixinha de promessa, pois toda a Biblia Sagrada é usada para o mesmo
-- OBS.....................:  
--


DROP FUNCTION IF EXISTS Palavra();
CREATE OR REPLACE FUNCTION Palavra() RETURNS TEXT AS
$body$
  SELECT p.texto
      || ' '
      || rtrim(l.abrev)
      || ' '
      || cast(p.capitulo AS text)
      || ':'
      || cast(p.versiculo AS text)
      AS "Consulta a Palavra - Clame pelo Sangue de Jesus!!!" 
    FROM Palavra p
    JOIN Livro l
      ON l.id_livro = p.id_livro 
    JOIN Testamento te
      ON te.id_testamento  = l.id_testamento
ORDER BY RANDOM() 
   LIMIT 1
       ; 
$body$ 
LANGUAGE 'sql';
COMMENT ON FUNCTION Palavra() IS 'Função de Consulta a Palavra (Bíblia Sagrada) não é apenas uma caixinha de promessa, pois toda a Bíblia Sagrada é usada para o mesmo';

SELECT * FROM Palavra();


A chamada dessa function em PL/pgSQL é:

SELECT * FROM Palavra();


Observações interessantes:

Os seguintes números são baseados na versão Nova Tradução na Linguagem de Hoje

Antigo Testamento | Novo Testamento | Total
Livros | 39 | 27 | 66
Capítulos | 929 | 260 | 1.189
Versículos | 23.146 | 7.957 | 31.103


Comparando com outras traduções, esses números podem ser um pouco diferentes:

A versão Almeida Revista e Atualizada, por exemplo, tem 31.104 versículos (o final de 1Samuel 20.42 se torna o versículo 43)

A versão Almeida Revista e Corrigida tem 31.105 versículos (além de 1Samuel 20, o final de Juízes 5.31 se torna o versículo 32).

A versão King James, por sua vez, tem 31.102 versículos, pois ajunta os versículos 14 e 15 de 3João.

Os textos originais em hebraico e grego, por sua vez, trazem um total de 31.171 versículos (21.213 no Antigo Testamento e 7.958 no Novo Testamento).

A maior diferença está no Livro de Salmos:

Vários deles trazem títulos que aparecem como o versículo 1 no texto hebraico e que, nas traduções, não são numerados.

Fonte: www.sbb.org.br

Este artigo usou a Versão Almeida Revista e Corrigida, porém com compatibilidade de versículos em quantidade com a versão King James, isto é, um total de 31102 versículos, conforme script abaixo:
-- Edição Almeida Revista e Corrigida 
select count(*) from Palavra; -- 31102
select 31105 - 31102; -- 3

select * from Livro;
--o final de Juízes 5.31 se torna o versículo 
select * from Palavra where id_livro = 7 and capitulo= 5 and versiculo = 31; 
--o final de 1Samuel 20.42 se torna o versículo 43
select * from Palavra where id_livro = 9 and capitulo= 20 and versiculo = 42; 
--pois ajunta os versículos 14 e 15 de 3João
select * from Palavra where id_livro = 64 and capitulo= 1 and versiculo = 14; 

mas nós pregamos a Cristo crucificado, que é escândalo para os judeus e loucura para os gregos. 1CO 1:23


Que DEUS te abençõe sempre....

sexta-feira, 1 de abril de 2011

O moido do NOT IN, o Postgres estaria mentindo, seria um bug ?

Esse problema-desafio foi exposto por Leonardo Campos, amigo e companheiro de trabalho, poderia ser mais uma questão de concurso público voltado para SGBD Postgres, estilo casca de banana.

Vamos ao Problema:

O moido dessa vez é saber pq o NOT IN não estava funcionando corretamente em alguns casos, no PG 9.0, seria um bug ou banco estava mentindo ?

A experiencia tem me mostrado que o banco não mente, o irônico é dizer isso no dia primeiro de abril, mas, cito a fato de que SQL tem base matemática na teoria dos conjuntos e algebra relacional, e dizer isso de um o banco SGBD Relacional em particular o Postgres, um projeto de muitos anos; é dá murro em ponta de faca.

Apresentando o desafio...

--Problema exposto 

DROP TABLE IF EXISTS tb1;
CREATE TEMP TABLE tb1 
( 
       id_tb1 integer PRIMARY KEY 
     , descricao varchar(28) NOT NULL
); 

DROP TABLE IF EXISTS tb2;
CREATE TEMP TABLE tb2 
(
       id_tb2 integer PRIMARY KEY 
     , descricao varchar(28) NOT NULL 
     , id_tb1 integer NULL 
     , CONSTRAINT fk_tb2_id_tb1 FOREIGN KEY (id_tb1) REFERENCES tb1 (id_tb1) 
);

INSERT INTO tb1 (id_tb1, descricao) VALUES (1,'TESTE1');
INSERT INTO tb1 (id_tb1, descricao) VALUES (2,'TESTE2');
INSERT INTO tb1 (id_tb1, descricao) VALUES (3,'TESTE3');
INSERT INTO tb1 (id_tb1, descricao) VALUES (4,'TESTE4');
INSERT INTO tb1 (id_tb1, descricao) VALUES (5,'TESTE5');
INSERT INTO tb1 (id_tb1, descricao) VALUES (6,'TESTE6');
INSERT INTO tb1 (id_tb1, descricao) VALUES (7,'TESTE7');

SELECT * FROM tb1;
/* Resultado 
 id_tb1 | descricao 
--------+-----------
      1 | TESTE1
      2 | TESTE2
      3 | TESTE3
      4 | TESTE4
      5 | TESTE5
      6 | TESTE6
      7 | TESTE7
(7 rows)
*/

INSERT INTO tb2 (id_tb2, descricao, id_tb1) VALUES (8,'TESTE1',1);
INSERT INTO tb2 (id_tb2, descricao, id_tb1) VALUES (9,'TESTE2',2);
INSERT INTO tb2 (id_tb2, descricao, id_tb1) VALUES (10,'TESTE3',3);
INSERT INTO tb2 (id_tb2, descricao, id_tb1) VALUES (11,'TESTE4',4);
INSERT INTO tb2 (id_tb2, descricao, id_tb1) VALUES (12,'TESTE5',5);
INSERT INTO tb2 (id_tb2, descricao, id_tb1) VALUES (13,'TESTE7',NULL);

SELECT * FROM tb2;
/* Resultado
 id_tb2 | descricao | id_tb1 
--------+-----------+--------
      8 | TESTE1    |      1
      9 | TESTE2    |      2
     10 | TESTE3    |      3
     11 | TESTE4    |      4
     12 | TESTE5    |      5
     13 | TESTE7    |   NULL   
(6 rows)

*/

--Query 1
     SELECT * 
       FROM tb1 
      WHERE id_tb1 IN 
                         (
                              SELECT id_tb1
                                FROM tb2
                         )
          ;
/* Resultado
 id_tb1 | descricao 
--------+-----------
      1 | TESTE1
      2 | TESTE2
      3 | TESTE3
      4 | TESTE4
      5 | TESTE5
(5 rows)
*/          

--Query 2 (Seria um bug do Postgres? Retornou nada!)      
     SELECT * 
       FROM tb1 
      WHERE id_tb1 NOT IN 
                         (
                              SELECT id_tb1
                                FROM tb2
                         )
          ;
--Nada, aparentemente o correto seria trazer os registros especificados, será o que SGBD (o banco) Postgres está mentindo ?
/* Resultado
 id_tb1 | descricao 
--------+-----------
(0 rows)
*/

--Análise 1, uma consulta equivalente com not in, apenas para testar
     SELECT * 
       FROM tb1 
      WHERE id_tb1 <> ALL
                         (
                              SELECT id_tb1
                                FROM tb2
                         )
          ;
--Idem ao problema apresentado na Query 2 
/* Resultado
 id_tb1 | descricao 
--------+-----------
(0 rows)
*/

--Análise 2, o que está havendo?
     SELECT 
   DISTINCT *
       FROM tb2
   ORDER BY 1
          ;
--Existe um campo NULL no final do resultado. Será que esse NULL se propaga no NOT IN, mas pq não no IN   ?
/* Resultado
 id_tb2 | descricao | id_tb1 
--------+-----------+--------
      8 | TESTE1    |      1
      9 | TESTE2    |      2
     10 | TESTE3    |      3
     11 | TESTE4    |      4
     12 | TESTE5    |      5
     13 | TESTE7    |   NULL   
(6 rows)
*/

-- Exemplo 1, Solução

     SELECT * 
       FROM tb1 
      WHERE id_tb1 NOT IN 
                         (
                              SELECT id_tb1
                                FROM tb2
                               WHERE id_tb1 IS NOT NULL
                         )
          ;
--Com a inclusão da condicional removendo os nulos, deu certo, pq?, Qual a explicação disso?
/* Resultado 
 id_tb1 | descricao 
--------+-----------
      6 | TESTE6
      7 | TESTE7
(2 rows)
*/





Conclusão

No Postgres o null se propaga, isto é, qualquer coisa concatenada com NULL se torna NULL exemplo:

SELECT 'NULL' || NULL AS teste1; 

SELECT 'Isto ' || 'é' ||' apenas '|| ' um ' || NULL || ' teste .... ' AS teste2; 


Esse mesmo conceito se aplica também ao NOT IN.

Então:

Deve ser observado que se o resultado da expressão à esquerda for nulo,
ou se não houver nenhum valor igual à direita e uma das linhas à direita tiver o valor nulo,
o resultado da construção NOT IN será nulo, e não verdade.
Isto está de acordo com as regras normais do SQL para combinações booleanas de valores nulos. [1]
Idem na versões demais versões do Postgres e a ultima 9.0 [2]

Concluindo isso não é um bug mas sim um comportamento padrão do NOT IN, que está em conformidade com as normas SQL/ANSI,
lembrando que uso de NOT IN não é recomendável quando se pensa em desempenho.

E finalmente o banco (SGBDR) não mente! :)

[1]
http://pgdocptbr.sourceforge.net/pg80/functions-subquery.html#FUNCTIONS-NOT-IN-SUBQ
[2]
http://www.postgresql.org/docs/9.0/static/functions-comparisons.html#AEN16842