domingo, 31 de março de 2013

Subsequência Numérica em Consultas SQL


Inicialmente, acho que preciso explicar o que estou chamando de subsequência numérica. Imaginem duas tabelas, uma mestre e uma de detalhe. Pedidos e itens de pedido, por exemplo. A tabela de Itens é dependente da tabela de pedidos. Precisa dela para existir. Nesses casos, é comum que a tabela de itens possua uma chave primária composta, onde a chave estrangeira para pedido faça parte da composição. Adicionalmente a isso, é criado um campo sequencial que servirá para diferenciar a chave dos itens dentro de um mesmo pedido. A chave estrangeira do pedido e esse campo sequencial formam a chave primária do item, mas o campo sequencial se repete de um pedido a outro. Ou seja, dentro de cada pedido, os itens possuem sequencial de 1 a N, onde N é o número de itens do pedido.
Exemplo:
Tabela Itens Pedido
cod_pedido | sequencial | cod_produto | ...
         1            1          1236   
         1            2          1421   
         1            3          7633   
         1            4          1187   
------------------------------------------         
         2            1          1236   
         2            2          1421   
         2            3          7633   
------------------------------------------         
         3            1          1236   
         3            2          4866   
         3            3          7633   
         3            4          1187   
------------------------------------------      
Note que sequencial e cod_pedido se repetem, mas os dois juntos, nunca. Alem disso, sequencial é sempre iniciado em 1, e incrementado item a item dentro do mesmo pedido.

No nível da aplicação é fácil resolver: para cada pedido, eu inicio uma variável que é incrementada a cada item adicionado. Pra explicar é um pouco complicado e para montar um SQL que gere esse campo sequencial a partir de uma consulta no banco de dados, aí é que é complicado mesmo.
Infelizmente, não existe uma forma padronizada de resolver um problema como esse. Na verdade, a solução para esse problema se assemelha muito à geração de sequência numérica em uma consulta SQL. Tudo vai depender, mais uma vez, do banco de dados que você está usando. Então, como de costume, vou descrever a solução para alguns bancos por ordem de sequelas que cada uma pode gerar.

Oracle
São nessas horas mais difíceis que o Oracle nos enche de orgulho. A solução aqui é tão simples quanto a do post anterior: a função ROW_NUMBER() sozinha resolve o problema. Para tanto, é preciso definir como o Oracle vai particionar o resultado do ROW_NUMBER() através da cláusula PARTITION BY. Seque o SQL:

SELECT cod_pedido, ROW_NUMBER() OVER (PARTITION BY cod_pedido, cod_funcionario ORDER BY nome_produto) AS sequencia, cod_produto
    FROM itens_pedido ORDER BY cod_pedido, cod_produto

Maiores detalhes a respeito dessa função, consulte a farta documentação do Oracle em: http://download.oracle.com/docs/cd/B14117_01/server.101/b10759/functions122.htm

Microsoft SQL Server (versão 2005 ou superior)
Como já dito anteriormente, o SQL Server, a partir de versão 2005, também possui uma função ROW_NUMBER() e com a mesma sintaxe do Oracle! Com isso, a solução é também a mesma do Oracle:


SELECT cod_pedido, ROW_NUMBER() OVER (PARTITION BY cod_pedido, cod_funcionario ORDER BY nome_produto) AS sequencia, cod_produto
    FROM itens_pedido ORDER BY cod_pedido, cod_produto


Maiores detalhes a respeito dessa função, consulte a documentação da Microsoft, que é um pouco diferente da Oracle, mas igualmente bem detalhada: http://msdn.microsoft.com/en-us/library/ms186734.aspx

Se seu banco de dados for SQL Server 2000, sugiro fortemente instalar uma versão superior, como a 2005 ou a 2008. Agora, se não for possível fazer essa atualização, você pode pedir demissão e fazer vestibular para biologia ou tentar a alternativa que descrevo mais abaixo.


SQLite
O SQLite, infelizmente não possui uma função ROW_NUMBER(). Nos aproveitaremos mais uma vez de uma característica bem peculiar do SQLite: Todos os registros de qualquer tabela possuem um rowid, que é um sequencial numérico do registro que segue a sequência de inserção nessa tabela. Dessa forma, podemos criar uma tabela temporária, construída a partir da consulta que queremos adicionar o sequencial. Se inserirmos os registros na ordem desejada, cada um receberá o rowid sequencial, mas ainda não o sequencial que queremos. Inicialmente, vamos criar essa tabela temporária nos aproveitando de outra característica do SQLite: você pode facilmente criar uma tabela a partir de uma consulta.

CREATE TEMP TABLE tmp_itens AS SELECT * FROM itens_pedido ORDER BY cod_pedido, cod_produto;

Note que a tabela é criada a partir do mesmo select anterior, mantendo-se inclusive a ordenação. Note também o "TEMP" na instrução SQL. Isso indica que a tabela criada é temporária e não será visível para outras conexões. A partir de agora, temos uma tabela temporária, com os registros desejados, e com um rowid sequencial, indo de 1 ao número de registros selecionados. Isso ainda não é o que precisamos.

cod_pedido | rowid | cod_produto | ...
         1       1          1236   
         1       2          1421   
         1       3          7633   
         1       4          1187   
-------------------------------------         
         2       5          1236   
         2       6          1421   
         2       7          7633   
-------------------------------------         
         3       8          1236   
         3       9          4866   
         3      10          7633   
         3      11          1187   
-------------------------------------      

Note que o campo rowid é um sequencial, mas ele não inicia em 1 para todos os pedidos. Para o primeiro pedido, inicia em 1, para o segundo, em 5 e para o terceiro, em 8. No entando, se subtrairmos o rowid pelo menor rowid dentro do mesmo pedido e somarmos com 1, teremos o resultado esperado. Para chegarmos a isso, faremos uso de outro recurso do SQL: o nested select ou subselect. Eis a consulta para conseguir o menor rowid para cada pedido:

SELECT cod_pedido, MIN(rowid) AS min_rowid FROM tmp_itens GROUP BY cod_pedido

Usando essa consulta como um subselect da consulta principal, teremos:

SELECT itp.cod_pedido, 1 + itp.rowid - tmp.min_rowid AS sequencial, itp.cod_produto
    FROM tmp_itens itp
    JOIN (SELECT cod_pedido, MIN(rowid) AS min_rowid FROM tmp_itens GROUP BY cod_pedido) tmp
        ON tmp.cod_pedido = itp.cod_pedido

Nessa consulta, a consulta anterior que retorna os menores rowids para cada pedido, está entre parenteses e seu resultado será tratado na consulta principal como se fosse uma tabela, que no caso, estamos chamando de tmp. Essa não é uma forma muito limpa de se resolver, mas o resultado dessa consulta é exatamente o que precisamos. Podemos organizar o código criando outra tabela temporária a partir do resultado da primeira. Mas isso não significará ganho de performance e ainda lhe obrigará a apagar essas duas tabelas temporárias. Em SQLite, as tabelas temporárias existem apenas para a conexão que as criou, e desaparecerão quando a conexão for encerrada. Mesmo assim, é importante apagá-las (com DROP TABLE) sempre que usadas para evitar erros, caso tente-se criar novamente a mesma tabela.

Microsoft SQL Server (versões anteriores à 2005)
Se você está realmente interessado nessa seção, é porque você ainda não foi convencido a atualizar o SQL Server ou ainda não argumentou o suficiente para convencer seus superiores a isso. Dessa forma, a primeira coisa a se fazer é enviar um email aos interessados avisando que vai demorar para resolver o problema porque a versão do SQL está desatualizada. Mas que não se preocupem porque essa demora no desenvolvimento não se equipara às perdas por falta de desempenho por se usar um banco de dados tão velho e problemático. Drama a parte, aí vai a solução:

A solução em versões antigas do SQL Server, é semelhante ao SQLite, sendo que um pouco mais complicado. Primeiro porque o SQL Server não gera um "rowid" automaticamente. Segundo, porque não é possível criar uma tabela a partir de uma consulta como no SQLite. Por isso precisaremos criar uma tabela com os campos necessários, e adicionar um campo identity para gerar o sequencial. Podemos também criar a tabela como temporária, que em SQL Server, basta nomear a tabela iniciando dom "#".

CREATE TABLE #tmp_itens (
    sequencia   INTEGER IDENTITY(1,1),
    cod_pedido  INTEGER,
    cod_produto INTEGER
);

Depois de criar a tabela, inserimos os registros a partir da mesma consulta que queremos adicionar o sequencial. O campo do tipo identity gera um sequencial automaticamente, ou seja, não precisamos passar valor para ele. Basta inserirmos os registros na ordem desejada e cada um receberá o sequencial único.

INSERT INTO #tmp_itens(cod_pedido, cod_produto) 
    SELECT cod_pedido, cod_produto 
        FROM itens_pedido 
        ORDER BY cod_pedido, cod_produto;

Com isso, temos uma tabela temporária, com os registros desejados, e com o campo sequencia que é quase aquilo que queremos. Para gerarmos o resultado que esperamos, fazemos da mesma forma como fizemos em SQLite: usamos um subselect

SELECT itp.cod_pedido, 1 + itp.rowid - tmp.min_rowid AS sequencial, itp.cod_produto
    FROM #tmp_itens itp
    JOIN (SELECT cod_pedido, MIN(rowid) AS min_rowid 
        FROM #tmp_itens 
        GROUP BY cod_pedido
    ) tmp
        ON tmp.cod_pedido = itp.cod_pedido

Note que, como não é possível criar a tabela temporária com base no resultado da consulta, como no SQLite, é preciso criar a tabela com todos os campos que se deseja ou adicionar um outro JOIN com itens_pedido nessa consulta para se obter todos os campos que se deseja.

Por fim, lembro mais uma vez que é importante, apagar a tabela temporária após utilizar o retorno da consulta. Da mesma forma como no SQLite, as tabelas temporárias no SQLServer existem apenas para a conexão que as criou, e desaparecerão quando a conexão for encerrada, mas antes disso, podem gerar erros caso tente-se criar novamente a mesma tabela.

Outros bancos
Novamente, listei aqui as soluções nos bancos de dados em que tenho mais experiência. Em outros bancos, provavelmente a solução será uma variação de uma dessas que apresentei aqui. Se alguém conhecer algum solução para outro banco de dados, ou uma solução melhor do que as apresentadas aqui, enviem-me que terei prazer em atualizar esse post.


terça-feira, 19 de março de 2013

Conexão ao banco de dados SQL Server via JDBC


  • java.sql.SQLException: [Microsoft][SQLServer 2000 Driver for JDBC]Error establishing socket.
  • java.sql.SQLException: Unable to get information from SQL Server
  • java.sql.SQLException: Network error IOException: Connection refused: connect SQL Express
  • e muito mais!

Tudo isso, você e eu encontramos facilmente quando tentamos nos conectar a um banco de dados SQL Server via JDBC. Visando a redução das despesas mundiais com analgésicos, estou escrevendo esse post para que os desenvolvedores Java possam facilmente configurar sua conexão JDBC com SQL Server. Seguem as dicas:
  • Use o JTDS. O Driver da Microsoft é amaldiçoado e fará você perder muito tempo tentando configurar até desistir e ficar frustrado para, só então, resolver usar o JTDS.
  • Existem 4 protocolos de comunicação com o SQL Server: TCP/IP, Named Pipes, VIA e Shared Memory. Os dois primeiros são os mais usados, por isso, vou descrever apenas esses dois casos:
  • TCP/IP: 
    • É o meio limpo e bonito e joiado de se trabalhar, por isso, procure usá-lo sempre.
    • Usa conexões TCP para comunicação com o SQL Server.
    • Garanta que o protocolo TCP/IP esteja habilitado no SQL Server Configuration Manager -> SQL Server 2005 Network Configuration, Protocols for SQL2005  (nome da instancia)
    • Abrindo as propriedades do protocolo TCP/IP, podemos definir quais IPs terão acesso ao banco. Normalmente ativo todos e no final, em IP All, coloco TCP Port: 1433
    • Após modificar os protoclos, é preciso reiniciar o serviço SQLServer para ativar as novas configurações e, infelizmente, algumas vezes elas não funcionam até que se reinicie o servidor.
    • Garanta também que o serviço SQL Server Browser está ativo e na inicialização automática
    • Para acesso externo, é preciso configurar o firewall também para liberar as portas TCP 1433 e UDP 1434
    • Strings de conexão:
      • Driver: net.sourceforge.jtds.jdbc.Driver
      • URL: jdbc:jtds:sqlserver://<nome ou IP do servidor>:1433/<nome da base de dados>;instance=<nome da instância>
      • Exemplo de URL: jdbc:jtds:sqlserver://ceres:1433/Netuno;instance=SQL2005
      • O número da porta só é obrigatório ser o servidor estiver rodando em outra porta
  • Named Pipes: 
    • É a forma nojenta que a Microsoft usa como padrão para o SQL Server.
    • Utiliza troca de arquivos em uma pasta compartilhada para comunicação entre o servidor e os clientes (falei que era nojento)
    • Por ser padrão, normalmente não é preciso configurar o servidor. Se for o caso, basta habilitar o protocolo Named Pipes no SQL Server Configuration Manager -> SQL Server 2005 Network Configuration, Protocols for SQL2005  (nome da instância)
    • Para acesso externo, é preciso garantir que a máquina cliente esteja no mesmo domínio do servidor ou tenha permissão para acessar os compartilhamentos do servidor. Para se certificar disso, basta acessar o servidor pelo Windows Explorer.
    • Strings de conexão:
      • Driver: net.sourceforge.jtds.jdbc.Driver
      • URL: jdbc:jtds:sqlserver://<nome ou IP do servidor>/<nome da base de dados>;instance=<nome da instância>;namedPipe=true
      • Exemplo de URL: jdbc:jtds:sqlserver://ceres/Netuno;instance=SQL2005;namedPipe=true
Seguindo estas instruções, sua conexão JDBC deve funcionar. Se ainda assim houver problema, envie a descrição do teu caso e terei prazer em adicionar as dicas para solução neste post!



domingo, 18 de setembro de 2011

Retornando sequência numérica em uma consulta SQL

O que devemos fazer para que cada registro do resultado de uma consulta SQL possua um campo sequencial ordenado? Essa é uma das perguntas mais simples de resposta mais complicada que conheço a respeito de SQL. Uma das maiores complicações é que a solução muda dramaticamente de banco para banco. Para exemplificar esse problema, vamos imaginar uma tabela clientes, com campos codigo e nome. Queremos que essa tabela seja listada e ordenada por nome, daí executamos:


SELECT codigo, nome FROM clientes ORDER BY nome


E temos o seguinte resultado:


codigo      nome
----------- -----------------------------
482496      AMERCIA MED
4120        AQUARIUS
30528       C NORDESTINO
30530       C PONTE CARV
478919      COM PARANHOS
6131        COM.ITAMBE
485327      CRISTIANE DA
485866      D ARAUJO & S
482649      D VERDE MAR
30529       DIAMANTE C A
483240      E C SILVA G
4580        EDNILSON P
4454        FERREIRA COS
481520      GD LIMA INF
29077       LAJEDO 1
29078       LAJEDO 2
23976       LJ CONDOMINI
480728      LOPES & FONS
30325       M NORDESTIN
43297       N PAES MELO
480350      TUTTO LIMP
29081       VAREJAO BRA


(22 registros)


Agora gostaríamos de adicionar uma coluna nessa consulta que retornasse o número da linha, de 1 à 22, seguindo a mesma sequencia de ordenação da consulta, da sequinte forma:


sequencia   codigo      nome
----------- ----------- -----------------------------
1           482496      AMERCIA MED
2           4120        AQUARIUS
3           30528       C NORDESTINO
4           30530       C PONTE CARV
5           478919      COM PARANHOS
6           6131        COM.ITAMBE
7           485327      CRISTIANE DA
8           485866      D ARAUJO & S
9           482649      D VERDE MAR
10          30529       DIAMANTE C A
11          483240      E C SILVA G
12          4580        EDNILSON P
13          4454        FERREIRA COS
14          481520      GD LIMA INF
15          29077       LAJEDO 1
16          29078       LAJEDO 2
17          23976       LJ CONDOMINI
18          480728      LOPES & FONS
19          30325       M NORDESTIN
20          43297       N PAES MELO
21          480350      TUTTO LIMP
22          29081       VAREJAO BRA
 
Bom, como disse anteriormente, para cada banco de dados, teremos uma solução diferente. Por isso, descreverei a solução em alguns bancos que conheço. Estou descrevendo por ordem de complicação, pois dessa forma, evitamos maiores sequelas...

Oracle
Dizer que a solução é mais simples com Oracle pode assustar aqueles que não conhecem bem o Oracle. Mas a verdade é que muitas coisas que são complicadas em outros bancos apresentam uma solução bem mais simples com o Oracle. Neste caso, o Oracle tem uma função "ROW_NUMBER()", que aplica um número único a cada registro de uma consulta. E isso é exatamente o que precisamos! Eis a consulta que retorna o que precisamos no Oracle:

SELECT ROW_NUMBER() OVER (ORDER BY nome) AS sequencia, codigo, nome FROM clientes ORDER BY nome

Percebam que a função ROW_NUMBER possui uma claúsula "OVER" que nos permite determinar sobre qual critério queremos gerar o seguencial. Maiores detalhes a respeito dessa função, consulte a documentação em: http://download.oracle.com/docs/cd/B14117_01/server.101/b10759/functions122.htm

Microsoft SQL Server (versão 2005 ou superior)
Por mais incrível que pareça, o SQL Server, a partir de versão 2005, também possui uma função ROW_NUMBER(), inclusive com a mesma sintaxe do Oracle! E como essa função já retorna aquilo que precisamos, a solução no SQL Server fica simples também (desde que a versão do banco seja igual ou maior à 2005).

SELECT ROW_NUMBER() OVER (ORDER BY nome) AS sequencia, codigo, nome FROM clientes ORDER BY nome

Da mesma forma como no Oracle, a função ROW_NUMBER possui uma claúsula "OVER" que nos permite determinar sobre qual critério queremos gerar o seguencial. Maiores detalhes a respeito dessa função, consulte a documentação da Microsoft, que é um pouco diferente da Oracle, mas igualmente bem detalhada: http://msdn.microsoft.com/en-us/library/ms186734.aspx

Se seu banco de dados for SQL Server 2000, sugiro fortemente instalar uma versão superior, como a 2005 ou a 2008. Agora, se não for possível fazer essa atualização, você pode chorar, ou tentar a alternativa que descrevo mais abaixo (outras pessoas já choraram muito até chegarem a essa solução).

SQLite
Em SQLite, nao existe uma forma direta de se fazer isso. Utilizamos para tanto, um artifício baseado em uma característica muito interessante do SQLite: Todos os registros de qualquer tabela possuem um rowid, que é um sequencial numérico do registro que segue a sequência de inserção nessa tabela. Dessa forma, podemos criar uma tabela temporária, construída a partir da consulta que queremos adicionar o sequencial. Se inserirmos os registros na ordem desejada, cada um receberá o rowid equivalente ao sequencial que queremos. Em SQLite, você pode facilmente criar uma tabela a partir de uma consulta.

CREATE TEMP TABLE tmp_clientes AS SELECT codigo, nome FROM clientes ORDER BY nome;

Note que a tabela é criada a partir do mesmo select anterior, mantendo-se inclusive a ordenação. Note também o "TEMP" na instrução SQL. Isso indica que a tabela criada é temporária e não será visível para outras conexões. A partir de agora, temos uma tabela temporária, com os registros desejados, e com um rowid equivalente ao sequencial que queremos. Ou seja, basta fazermos a consulta:

SELECT rowid AS sequencia, codigo, nome FROM tmp_clientes ORDER by nome;

É importante, apagar a tabela temporária após utilizar o retorno da consulta. Em SQLite, as tabelas temporárias existem apenas para a conexão que as criou, e desaparecerão quando a conexão for encerrada, mas antes disso, podem gerar erros caso tente-se criar novamente a mesma tabela.

Microsoft SQL Server (versões anteriores à 2005)
Não recebo nenhum tipo de comissão da Microsoft, mas mesmo assim, acho interessante insistir: faça o possível para atualizar seu SQL Server, pois as versões mais recentes, além de disponibilizarem uma maior variedade de recursos, ainda têm um ganho considerável de desempenho em relação as versões anteriores à 2005.
Se mesmo assim, você quiser implementar essa solução no seu banco SQL Server anterior à versão 2005, segue a solução:

A solução em versões antigas do SQL Server, é semelhante ao SQLite, sendo que mais complicado. Primeiro porque o SQL Server não gera um "rowid" automaticamente. Segundo, porque não é possível criar uma tabela a partir de uma consulta como no SQLite. Por isso precisaremos criar uma tabela com os campos necessários, e adicionar um campo identity para gerar o sequencial. Podemos também criar a tabela como temporária, que em SQL Server, basta nomear a tabela iniciando dom "#".

CREATE TABLE #tmp_clientes (
    sequencia   INTEGER IDENTITY(1,1),
    codigo      INTEGER,
    nome        VARCHAR(50)
);

Depois de criar a tabela, inserimos os registros a partir da mesma consulta que queremos adicionar o sequencial. O campo do tipo identity gera um sequencial automaticamente. Se inserirmos os registros na ordem desejada, cada um receberá o sequencial da forma como queremos.

INSERT INTO #tmp_clientes(codigo, nome) SELECT codigo, nome FROM clientes ORDER BY nome;

com isso, temos uma tabela temporária, com os registros desejados, e com o campo sequencia que queremos. Ou seja, basta fazermos a consulta:

SELECT sequencia, codigo, nome FROM #tmp_clientes ORDER by nome;

Mais uma vez lembrando que é importante, apagar a tabela temporária após utilizar o retorno da consulta. Da mesma forma como no SQLite, as tabelas temporárias no SQLServer existem apenas para a conexão que as criou, e desaparecerão quando a conexão for encerrada, mas antes disso, podem gerar erros caso tente-se criar novamente a mesma tabela.

Firebird
Em Firebird, precisaremos criar usar um generator ou um sequence. O generator funciona em qualquer versão do Firebird. Já a partir da versão 2.0, foi introduzido o sequence que é a implementação recomendada para geração de números sequenciais. Resumindo: De preferência, deve-se usar o sequence, mas se for necessário criar códigos compativeis com versões anteriores ao Firebird 2.0, deve ser usado o generator.

Para criar um generator, usamos:

CREATE GENERATOR TMP_GEN;

Para criar um sequence:

CREATE SEQUENCE TMP_SEQUENCE;

A partir daí, a tarefa fica simples. Basta executar a consulta adicionando o campo sequencial. Para generator:

SELECT GEN_ID(TMP_GEN, 1) as sequencia, codigo, nome FROM clientes order by nome;

Para sequence:

SELECT NEXT VALUE FOR TMP_SEQUENCE as sequencia, codigo, nome FROM ta101 order by nome;

Pra finalizar, devemos apagar o generator ou o sequence. Dessa forma, deixamos tudo da mesma forma como encontrado. De certa forma, a implementação fica suja, porque não temos como criar esse sequencial de forma temporária. É preciso ter cuidado para não executar essa mesma operação ao mesmo tempo em duas conexões distintas. Se possível, é recomendado envolver a operação dentro de uma transação. Isso garante a unicidade da operação.

Outros bancos
Listei aqui as soluções nos bancos de dados em que tenho mais experiência. Me lembro de ter feito algo semelhante no PostgresSQL, mas não me lembro exatamente como fiz. Sei que a solução é uma variação de uma dessas que apresentei aqui. E provavelmente em outros bancos, será algo do tipo também. Se alguém conhecer algum solução para outro banco de dados, ou uma solução melhor do que as apresentadas aqui, enviem-me que terei prazer em atualizar esse post.

segunda-feira, 5 de setembro de 2011

Alterando o caminho do arquivo de dados do Oracle

Trabalhar com banco de dados Oracle costuma deixar muita gente sequelada. Muitas vezes as dificuldades que sentimos para gerenciar o Oracle, ofusca toda a robustez e poderio desse banco de dados. Recentemente, vários de nossos maiores clientes estão aderindo a esse SGBD, e com isso, nós da Connect estamos precisando conhecer cada vez mais dele.
O que percebo, sempre que preciso fazer algo novo no Oracle, é que tudo é sempre muito bem documentado. O "Oracle Database SQL Reference" é sem dúvida o mais completo que conheço. Mesmo assim, vou procurar resumir em poucas linhas como realizar algumas tarefas simples, mas que nos tomam muito tempo quando não dominamos o Oracle.
A primeira delas, como o título desse post sugere, é como alterar o caminho de um arquivo de dados do Oracle. Mas por quê alguém precisaria alterar o caminho de um arquivo de dados do Oracle?
Por diversos motivos. Para liberar espaço, para distribuir os arquivos, ou para organizar os dados, não sei. Mas se precisar, é assim que faz:

  1. Pra começar, precisamos saber o nome completo do arquivo que vai mover e a qual tablespace ele pertence. Como tudo em Oracle, conseguimos listar todos os arquivos de dados do banco e seus respectivos tablespaces com uma consulta SQL:
    SELECT FILE_NAME, TABLESPACE_NAME FROM DBA_DATA_FILES 
  2. Com essas informações, devemos alterar o estado do tablespace para 'offline' com o seguinte comando SQL:
    ALTER TABLESPACE TS_DADOS OFFLINE
  3. Depois, precisamos mover o arquivo em questão para sua nova pasta (acredito que isso, todos que estejam lendo esse post sabem fazer)
  4. Só então, executamos o SQL para renomear logicamente o arquivo de dados no banco:
    ALTER TABLESPACE TS_DADOS RENAME DATAFILE 'C:\ORACLEXE\ORADATA\XE\TS_DADOS01.DBF' TO 'E:\DATABASES\ORADATA\XE\TS_DADOS01.DBF'
  5. Por último, tornamos o tablespace 'online' novamente:
    ALTER TABLESPACE TS_DADOS ONLINE


Neste exemplo, usamos caminhos Windows, e o nome do tablespace é TS_DADOS. Mas isso funciona da mesma forma para servidores UNIX e outros tablespaces de dados.

IMPORTANTE: Uma maldição cairá sobre sua cabeça se você tentar alterar os tablespaces de sistema (SYSAUX, UNDO e SYSTEM). Um dia, quando eu fizer o meu curso de DBA Oracle, eu explico o porquê. Até lá, evitem de fazer isso.

quinta-feira, 5 de maio de 2011

Access to Multivalued Fields for Dummies

Olá pessoal!
Como primeiro post, preciso de algo que demonstre exatamente o que é a idéia desse blog. Por isso, caçei no nosso histórico de atualizações aqui da empresa o SQL que motivou a criação do SeQueLado. Essa idéia, na verdade, nasceu da cabeça devastadoramente pensante de Bonifacio Segundo, um amigo e funcionário da Connect, empresa da qual sou sócio.
Pois bem, Bonifacio ficou perturbado durante algumas semanas depois que bolamos um SQL que mapeia um campo multivalorado de uma tabela para registros de uma tabela de relacionamento. Para situar-vos, grande parte do nosso trabalho na Connect está relacionado a integração das nossas soluções móveis com os sistemas de retaguarda dos nossos clientes. E nesse caso, o cliente tinha em seu banco de dados, um campo na tabela de clientes que listava as formas de pagamento permitidas para cada cliente. Esse campo era um campo de texto contendo os códigos das formas permitidas entre aspas e separados por vírgulas. Exemplo: "'001','002','003'" - cliente pode usar as formas de pagamento '001', '002' e '003'.
Não faz parte do nosso trabalho questionar a não-normatização do banco de dados do cliente. Precisamos apenas nos integrar a ele. Na nossa base de dados, essa regra é mapeada através de uma tabela de relacionamento (agradecimentos à Nilton Freire Santos, professor que me ensinou normatização de banco de dados).
Para mapearmos esse campo multivalorado em uma tabela de relacionamento, usamos a seguinte infâmia: fizemos uma junção entre as tabelas de clientes (CadParceiro) e formas de pagamento (CadTipoEvento), usando como critério de junção a ocorrência do código de pagamento (frm.CODIGOTIPOEVENTO) no campo multivalorado de cliente (cli.TIPOSEVENTOSPERMITIDOS). Como esses valores são separados por vírgulas no campo, concatenamos uma vírgula antes e outra depois do campo multivalorado para facilitar a comparação através do operador LIKE do SQL.
A consulta, simplificada para facilitar a compreensão, é essa aí abaixo. A maioria das pessoas vai achar essa consulta feia, mas eu acho que feio mesmo é usar campo multivalorado...

SeQueLem-se!

---

SELECT
        cli.CODIGOPARCEIRO          AS cod_cliente,
        frm.CODIGOTIPOEVENTO        AS cod_frm_pagamento
    FROM CadParceiro cli
    JOIN CADTIPOEVENTO frm
        ON ',' + cli.TIPOSEVENTOSPERMITIDOS + ',' LIKE '%,''' + frm.CODIGOTIPOEVENTO + ''',%'