quarta-feira, 14 de janeiro de 2015

Consulta do CharacterSet do Oracle

O character set do database no Oracle define a forma como os valores serão armazenados no banco de dados.
Para consultar qual o valor que foi configurado na instalação do database execute a instrução:

SELECT * FROM NLS_DATABASE_PARAMETERS
 WHERE PARAMETER='NLS_CHARACTERSET';


PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_CHARACTERSET               WE8ISO8859P1

terça-feira, 25 de junho de 2013

SQL Server 2014 CTP1

A Microsoft disponibilizou em seu site a versão Community Preview 1 da nova versão do SQL Server. No link a seguir é possível iniciar o download do instalador.

http://www.microsoft.com/en-us/sqlserver/sql-server-2014.aspx

No link a seguir tem como fazer o download do guia do produto: http://www.microsoft.com/en-us/download/details.aspx?id=39269

Nos próximos posts vou escrever sobre algumas das novidades desta versão.

sexta-feira, 14 de junho de 2013

Database SQL Server no estado "Restoring"

Quando é realizado o procedimento de restore de uma database no SQL Server o estado do database fica como "restoring", entretanto algumas vezes ele não sai deste estado mesmo tendo terminado o processo normalmente.
Para retornar o database para o estado normal execute a seguinte instrução:

RESTORE DATABASE NomeDatabase WITH RECOVERY

quinta-feira, 6 de dezembro de 2012

Consulta separando Nome e Sobrenome

Abaixo um exemplo para separar o nome e o último sobrenome de um campo com o nome completo:

Select substring(nome,1,patindex('% %',nome)) 'Nome',
          substring(nome,(len(nome) - (patindex('% %',reverse(nome))))+2,patindex('% %',reverse(nome)) ) 'Sobrenome'
From Funcionarios

sexta-feira, 17 de agosto de 2012

SQL Saturday 147

Falta uma semana para o SQL Saturday 147 em Recife - PE.
Todos que trabalham com SQL Server e tiverem condições, devem participar.
O evento é gratuito e os assuntos propostos são bastante relevantes para os profissionais da área.
No dia anterior 24/08/2012 ocorrerão mini-cursos e no dia 25/08/2012 as palestras do evento.
Vale a pena conferir.
Inscreva-se já (http://www.sqlsaturday.com/147/eventhome.aspx)
Abaixo palestras do dia 25/08/2012:

Start Time
Auditorium - Room: Auditorium
Lab 2 - Room: Lab 2
08:30 AM
09:00 AM
10:15 AM
11:30 AM
02:00 PM
03:15 PM
04:30 PM
05:45 PM
07:00 PM

quinta-feira, 28 de junho de 2012

Removendo caracteres de nova linha e tab em consultas no Oracle


Para remover caracteres de nova linha e tabulação (TAB) em textos no Oracle utilize a função REPLACE.

Sintaxe da função:
    REPLACE (String_Original, String_para_Alterar, [String_Destino])

String_Original: Conteúdo original
String_para_Alterar: String que será pesquisa na String_Original
String_Destino: parâmetro opcional que indica qual conteúdo deve ficar em todas as ocorrências de String_para_Alterar na String_Original.

Exemplo: Select Replace('Fusca 1980','1980','1985')     Retorna: 'Fusca 1985'
               Select Replace('Fusca 1980','1980')               Retorna: 'Fusca '

Para resolver o problema proposto neste post são "encadeados" vários replaces para obter o resultado esperado:

REPLACE(REPLACE(REPLACE(String_Original, CHR(10)), CHR(13)), CHR(9)) 

segunda-feira, 25 de junho de 2012

SQL Saturday 147

Nos dias 24 e 25 de agosto de 2012 ocorrerá em Recife a conferência SQL Saturday 147 (http://www.sqlsaturday.com/147/eventhome.aspx).

SQL Saturday são eventos de um dia inteiro de treinamento gratuito, com uma grande variedade de temas e com vários níveis de conhecimento, para atender todos os interessados em SQL Server.

Este evento é organizado pelo SQL Pass (http://www.sqlpass.org/) que é uma organização independente de profissionais SQL Server com mais de 100.000 afiliados no mundo.

No dia 25 as 11:30 estarei apresentando junto com o Marcus Vinicius Bittencourt (@mvbitt) como fazer replicação de informações no SQL Server e demonstrando na prática como replicar informações do Oracle para o SQL Server.

Neste link: http://www.sqlsaturday.com/147/schedule.aspx tem a agenda do evento com as palestras disponíveis.

Este evento é gratuito, portanto se tiveres interesse se inscreva logo para garantir a sua vaga.

sexta-feira, 1 de junho de 2012

SQL Server 2012 Restrições na Sintaxe do Raiserror

   Ao migrar um banco de dados SQL Server 2008 R2 para outro servidor com a versão SQL Server 2012, nos deparamos com alguns cancelamentos logo após a autenticação do aplicativo.

   Após uma análise de onde ocorria o problema chegamos em uma trigger que tinha os tratamentos de erros no formato:

RAISERROR integer 'string'

   Não foi dificil depois disto concluir que este formato foi descontinuado nesta versão do banco de dados, devendo agora ser utilizado como RAISERROR(...).

   Para maiores detalhes sobre as features descontinuadas no SQL Server 2012, acesse o link: http://technet.microsoft.com/en-us/library/ms144262(SQL.110).aspx

quarta-feira, 16 de maio de 2012

Script de backup de todas as databases no SQL Server

Nas versões pagas do SQL Server, existem assistentes que facilitam o agendamento de tarefas de backup que gravem todos os databases existentes, sem a preocupação de que ao criar novas databases precise alterar estes procedimentos.
Em ambientes sem estes recursos, ou onde você prefirar criar manualmente estas atividades, sugiro o script abaixo, que irá selecionar todos os databases existentes na instância do servidor e executar para cada um a instrução de backup.
Para automatizar este procedimento copie o código abaixo e coloque em um arquivo neste exemplo chamado BACKUP.SQL.
Observe no início do script que tem o destino dos arquivos gerados pela rotina, altere conforme a tua necessidade.

DECLARE @name VARCHAR(150) -- Nome do Database  
DECLARE @path VARCHAR(256) -- Caminho do arquivo de backup
DECLARE @fileName VARCHAR(256) -- Arquivo do backup  

-- Define caminho de destino do backup
SET @path = 'D:\Backup\'  

-- Cria um cursor para selecionar todas as databases,  
--  excluindo model, msdb e tempdb
DECLARE db_cursor CURSOR FOR  
   SELECT name 
     FROM master.dbo.sysdatabases 
    WHERE name NOT IN ('model','msdb','tempdb')  

-- Abre o cursor e faz a primeira leitura 
OPEN db_cursor   
FETCH NEXT FROM db_cursor INTO @name   

-- Loop de leitura das databases selecionadas
WHILE @@FETCH_STATUS = 0   
BEGIN   
   SET @fileName = @path + @name + '.BAK'  
   -- Executa o backup para o database
   BACKUP DATABASE @name TO DISK = @fileName WITH FORMAT;  

   FETCH NEXT FROM db_cursor INTO @name   
END   

-- Libera recursos alocados pelo cursor
CLOSE db_cursor   
DEALLOCATE db_cursor 

A seguir crie outro arquivo chamado BACKUP.BAT com o conteúdo abaixo:
osql -E -S Servidor -i "d:\backup\sql\BackupBancos.SQL"

Agora basta criar um agendamento do próprio Windows chamando o arquivo BACKUP.BAT.

sexta-feira, 4 de maio de 2012

Webcast sobre Replicação no SQL Server

No dia 04 de maio de 2012 será realizada uma webcast para tratar de replicação, junto com o Marcus Vinicius @mvbitt serei um dos apresentadores do evento, quem tiver dispobilidade abaixo resumo do evento e link de inscrição:

Sexta – Feira (04/05)

Palestrante: Cesar Blumm (@cesarblumm) e Marcus Vinícius Bittencourt (@mvbitt)
Palestra: Replicação na Prática
Descrição: Uma visão geral sobre replicação e suas formas de publicações. Será criada uma replicação na prática passo-a-passo.
Horário: 20:00 a 21:00
Link para inscrição: https://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032512282&Culture=pt-BR

sábado, 21 de abril de 2012

Reunião do SQL Server RS em Abril/2012

No próximo dia 27/04/2012 será realizado mais um encontro de usuários de SQL Server do RS, na Flexxo (Av. Rio Branco 105, Caxias do Sul - RS).
O Crespi (Especialista SQL Server) falará sobre Troubleshoot (Database Engine).

Maiores informações no site do grupo: http://sqlserverrs.com.br.

segunda-feira, 2 de janeiro de 2012

Insert condicional em várias tabelas

No post anterior mostrei com usar o INSERT para inserir em várias tabelas. Utilizado daquela maneira, cada linha é inserida em todas as tabelas incluídas no comando. Mas outra variação de INSERT permite utilizar expressões lógicas para indicar em qual tabela uma linha deve ser inserida. Sua sintaxe é:

INSERT[ALL]
WHEN
THEN INTO VALUES (lista de valores)

WHEN
THEN INTO VALUES (lista de valores)

...
[ELSE INTO VALUES (lista de valores)]
SELECT .... ;

Cada condição lógica controla se uma linha específica deve ser inserida na respectiva tabela. a palavra chave ALL indica se todas as condições lógicas devem ser avaliadas, permitindo que a linha seja inserida em mais de uma tabela (sempre que a condição for verdadeira). Se ALL for omitida, a linha é inserida na primeira tabela cuja respectiva condição lógica for verdadeira. A claúsula ELSE é opcional permite inserir a linha na tabela caso todas as condições lógicas resultem falso. Uma consulta SELECT é obrigatória e pode ser simples ou tão complexa quanto necessário.

Por exemplo, digamos que há três tabelas VENDAS, VENDAS_2011  e VENDAS_HIST. a primeira armazena vendas realizadas em 2012, a segunda vendas de 2011 e a terceira vendas realizadas em qualquer outro ano. As três tabelas tem exatamente a mesma estrutura. O INSERT abaixo utiliza as condições lógicas para definir em qual tabela cada linha gerada pelo SELECT deve ser inserida.

INSERT 
  WHEN EXTRACT(YEAR FROM data_venda) = 2012 THEN
       INTO vendas VALUES (vendas_id, data_venda, valor)
  WHEN EXTRACT(YEAR FROM data_venda) = 2011 THEN
       INTO vendas_2011 VALUES (vendas_id, data_venda, valor*0.95)
  ELSE INTO vendas_hist VALUES (vendas_id, data_venda, valor*0.9)
SELECT level vendas_id,
       TRUNC(sysdate - dbms_random.value(0, 500)) data_venda,
       TRUNC(dbms_random.value(1000, 2000), 2) valor
  FROM dual
  CONNECT BY level <= 100; 


O SELECT resulta em 100 linhas representando vendas realizadas entre hoje e 500 dias atrás e valor entre 1000 e 2000. As condições lógicas no INSERT controlam em qual tabela cada linha deve ser inserida. Nesta caso, como cada linha deve ser inserida em apenas uma tabela, a palavra chave ALL é desnecessária. Note que o valor de vendas é multiplicado por diferentes fatores em cada INSERT, apenas para demonstrar que é possível alterar os valores. Também seria possível utilizar tabelas com diferentes colunas. 

Há algumas restrições quanto ao SELECT, a mais relevante é quanto ao uso de sequences. O comando abaixo retorna um erro:


INSERT 
  WHEN EXTRACT(YEAR FROM data_venda) = 2012 THEN
       INTO vendas VALUES (vendas_id, data_venda, valor)
  WHEN EXTRACT(YEAR FROM data_venda) = 2011 THEN
       INTO vendas_2011 VALUES (vendas_id, data_venda, valor*0.95)
  ELSE INTO vendas_hist VALUES (vendas_id, data_venda, valor*0.9)
SELECT vendas_seq.nextval vendas_id,
       TRUNC(sysdate - dbms_random.value(0, 500)) data_venda,
       TRUNC(dbms_random.value(1000, 2000), 2) valor
  FROM dual
  CONNECT BY level <= 100;


SQL Error: ORA-02287: sequence number not allowed here
02287. 00000 -  "sequence number not allowed here"
*Cause:    The specified sequence number (CURRVAL or NEXTVAL) is inappropriate
           here in the statement.
*Action:   Remove the sequence number.

Entre as soluções possíveis, uma das simples é criar uma função para encapsular a sequence e depois usar a função no comando SELECT.


CREATE OR REPLACE
  FUNCTION new_vendas_id
    RETURN NUMBER
  IS
    retVal NUMBER;
  BEGIN
    SELECT vendas_seq.nextval INTO retVal FROM dual;
    RETURN retVal;
  END;



INSERT 
  WHEN EXTRACT(YEAR FROM data_venda) = 2012 THEN
       INTO vendas VALUES (vendas_id, data_venda, valor)
  WHEN EXTRACT(YEAR FROM data_venda) = 2011 THEN
       INTO vendas_2011 VALUES (vendas_id, data_venda, valor*0.95)
  ELSE INTO vendas_hist VALUES (vendas_id, data_venda, valor*0.9)
SELECT new_vendas_id vendas_id,
       TRUNC(sysdate - dbms_random.value(0, 500)) data_venda,
       TRUNC(dbms_random.value(1000, 2000), 2) valor
  FROM dual
  CONNECT BY level <= 100;



Esta solução para contornar o erro ORA-02287 pode ser útil em várias outras situações não relacionadas ao INSERT.




sexta-feira, 23 de dezembro de 2011

Insert em várias tabelas

O comando INSERT é amplamente utilizado para inserir dados em tabelas. Com pequenas variações, há duas formas bastante conhecidas:

INSERT INTO Contabil.Vendas(Id, Produto_ID, Qty, Data_Venda)
VALUES (Contabil.Vendas_Seq.Nextval, 1020, 5, Sysdate);

e

INSERT INTO Data_Whse.Vendas
(Produto_ID, Produto_Grupo_ID, Qty, Data_Venda)
SELECT Produto_ID, Produto_Grupo_ID, Qty, trunc(Data_Venda, 'HH') 
  FROM Contabil.Vendas V,
       Contabil.Produtos P
 WHERE V.Produto_ID = P.Produto_ID
   AND Trunc(V.DataVenda) = Trunc(Sysdate)-1;

A primeira forma insere uma única linha na tabela Vendas (esquema Contabil); a segunda forma insere muitas linhas na tabela Vendas (esquema Data_Whse). Tipicamente a primeira seria parte da aplicação, a segunda de um processo diário que copia dados de um esquema para outro (Contabil para Data_Whse).

Mas o Oracle permite uma variação do comando INSERT que poderia ser muito útil numa sitaução semelhante porque permite, em um único comando, inserir dados em várias tabelas. 

INSERT ALL 
  INTO Contabil.Vendas(Id, Produto_ID, Qty, Data_Venda)
       (Vendas_ID, Produto_ID, Qty, Data_Venda)
  INTO Data_Whse.Vendas(Produto_ID, Produto_Grupo_ID, Qty, Data_Venda)
       (Produto_ID, Produto_Grupo_ID, Qty, Trunc(Data_Venda) )
SELECT 20 as Vendas_ID, P.Produto_ID, P.Produto_Grupo_ID, 
       5 as QTY, Sysdate as Data_Venda
  FROM Contabil.Produto P
 WHERE P.Produto_ID = 1;
       
Com este INSERT, duas linhas são inseridas, uma em cada tabela, num único passo. A lista de colunas em cada tabela pode ser diferente e os valores podem ser manipulados, como n caso da coluna Data_Venda: uma tabela receberá o valor Sysdate, outra o valor truncado.

A sintaxe permite inserir em muitas tabelas ao mesmo tempo. O SELECT pode retornar várias linhas. Mas a combinação correta é sempre INSERT ALL/SELECT. 

Há algumas poucas restrições, a mais importante é que o SELECT não pode utilizar uma sequence diretamente. Note que coloquei o valor 20 (fixo) para Vendas_ID. É óbvio que isto funciona porque o SELECT retorna uma única linha neste exemplo.

Mas digamos que 5 itens de cada produto da empresa tenha sido vendido. Neste caso o SELECT teria que retornar muitas linhas e é necessário utilizar a sequence. Eis a solução:


INSERT ALL 
  INTO Contabil.Vendas(Id, Produto_ID, Qty, Data_Venda)
       ( Contabil.Vendas_Seq.Nextval , Produto_ID, Qty, Data_Venda)
  INTO Data_Whse.Vendas(Produto_ID, Produto_Grupo_ID, Qty, Data_Venda)
       (Produto_ID, Produto_Grupo_ID, Qty, Trunc(Data_Venda, 'HH') )
SELECT P.Produto_ID, P.Produto_Grupo_ID, 
       5 as QTY, Sysdate as Data_Venda
  FROM Contabil.Produto P;


Potencialmente, este SELECT reduz a necessidade de escrever código complementar, seja em triggers ou em outras procedures. O dado é inserido a partir de um único ponto em todas as tabelas onde é necessário, facilitando a compreensão e manutenção do sistema. E, claro, trata-se de uma única transação, ou seja, ou insere em todas as tabelas ou não insere em nenhuma. 


terça-feira, 20 de dezembro de 2011

Particionando arquivos de exportação no Oracle

O utilitário exp do oracle é muitas vezes utilizado como uma ferramenta auxiliar para estratégias de backup.
Algumas vezes o espaço disponível em um único disco não é suficiente para armazenar todo o conteúdo exportado pelo utilitário, o texto abaixo explica como resolver esta situação e complementa uma dúvida que recebi por e-mail que perguntava como particionar o arquivo de exportação em vários arquivos.
Como exemplo considere que o exp deva gerar um arquivo de 9G e precisaria quebrar em três arquivos de 3G.

Para resolver esta situação utilize os parâmetros file e filesize:

File - relacione os nomes dos três arquivos que devem ser gerados, separados por vírgulas.
Filesize - indique o tamanho máximo dos arquivos que serão gerados.

Exemplo:
   exp usuario/senha file=/tmp/arq01.dmp,/tmp1/arq02.dmp,/tmp2/arq03.dmp log=/tmp/exporta.log filesize=3G

Quando a exportação precisar gerar um quarto arquivo (exportação maior do que 9G), se não estiver previsto no parâmetro file, o utilitário irá solicitar o nome dele na tela.

terça-feira, 13 de dezembro de 2011

Há 100 anos no Polo Sul

Amundsen e equipe fazendo medições para
confirmar que chegaram ao local correto.
Totalmente fora do assunto deste blog, mas podemos dizer que os protagonistas desta aventura são alguns dos nossos heróis preferidos. Pois amanhã, dia 14 de Dezembro de 2011, completa-se 100 anos da chegada de Amundsen ao Polo Sul!

Amundsen pesquisou e experimentou alternativas para chegar ao Polo Sul. Escolheu trenós puxados por cachorros. Toda equipe e alguns cachorros sobreviveram o percurso completo, ida e volta.

Seu concorrente, Scott, acreditava que com esforço e coragem superaria qualquer obstáculo. Chegou ao Polo Sul na metade de Janeiro, apenas para encontrar a bandeira da Noruega marcando o ponto exato. Não retornou a tempo para o acampamento e morreu congelado.  

Portanto, pode-se colocar muitas horas de esforço na solução de um problema, mas é fundamental ter conhecimento, explorar alternativas e escolher aquelas que garantem maior chance de sucesso. Inclusive em problemas de BD.

sexta-feira, 2 de dezembro de 2011

SELECT FROM SAMPLE

Um recurso muito interessante e pouco conhecido da clausula FROM é a opção SAMPLE. Ela deve ser acompanhada de um número entre (0,100),  mas nunca nos limites 0 e 100. Este número indica a probabilidade individual de cada linha da tabela retornar na resposta da query. Por exemplo:

select obj_id, object_name
  from my_objs sample(10);

Detalhando um pouco, priemiro vou criar uma tabela e verificar o número de linhas.

create sequence my_obj_seq;

create table my_objs as
select my_obj_seq.nextval obj_id, owner, object_name, object_id
  from all_objects;
  
select count(*)
  from my_objs;

No meu Oracle retornou 17.900.  Agora tente algo simples como:

select obj_id, object_name
  from my_objs;

Obviamente todas as linhas da tabela retornarão na resposta. Incluindo o SAMPLE, temos:

select obj_id, object_name
  from my_objs sample(10);

E a chance de cada linha estar na resposta é de apenas 10%. Se executar repetidas vezes, o conjunto resposta será diferente a cada execução por que, para cada linha, o Oracle decide inclui-la ou não na resposta. Ou seja, a resposta passa a ser aleatória.

Neste ponto, você executa:

select count(*) from(  
select obj_id, object_name
  from my_objs sample(10);

E a resposta é 1.755! Opa, mas isto não é 10% de 17.900. Repetindo o mesmo SELECT COUNT(*) a resposta é 1.829! E na terceira vez é 1.793! 

Estaria tudo errado? Não deveria ser exatamente 10% do total de linhas na tabela, nesta caso exatos 1.790? 

O Oracle está correto. A chance de cada linha estar na resposta é 10%, mas eventualmente haverá mais linhas na resposta - digamos que deram sorte - ou menos linhas - estavam com azar. Se você precisar de um número exato, basta utilizar uma consulta aninhada:

select * from(
select obj_id, object_name
  from my_objs sample(100))
where rownum <= 10; 

Eu utilizei este recurso para selecionar um subconjunto aleatório de linhas da tabela. Isto fazia parte de um teste que seria repetido diversas vezes, mas era importante que o conjunto de dados fosse diferente a cada execução, afinal seria desnecessário calcular várias vezes o mesmo valor.


segunda-feira, 28 de novembro de 2011

Sequence no SQL Server 2012

Aproveitando o tema abordado pelo colega Miguel sobre “Alterar valor de uma Sequence” , vou comentar que na versão do SQL Server 2012 foi incluído este recurso já tão utilizado no Oracle.

Para quem não conhece a sequence é um contador que gera valores normalmente utilizados para inicializar chaves primárias de tabelas, seguindo critérios estabelecidos no momento da criação da sequence.

Uma das vantagens sobre colunas do tipo Identity é que uma sequence pode ser utilizada por várias tabelas.

Sintaxe:

CREATE SEQUENCE [schema_name . ] sequence_name
    [ AS [ built_in_integer_type | user-defined_integer_type ] ]
    [ START WITH ]
    [ INCREMENT BY ]
    [ { MINVALUE [ ] } | { NO MINVALUE } ]
    [ { MAXVALUE [ ] } | { NO MAXVALUE } ]
    [ CYCLE | { NO CYCLE } ]
    [ { CACHE [ ] } | { NO CACHE } ]
    [ ; ]

Para explicar o funcionamento deste recurso vou utilizar o exemplo abaixo:
Considere uma tabela com dois campos: MesAno e TotalSalario que não possui chave, vamos incluir uma coluna ID do tipo inteiro, e colocaremos como valor default o valor atual de uma Sequence (IdSalarios) criada abaixo. Também serão atualizados todos os registros previamente existentes com um valor da sequence:

-- Script de criação da Tabela

CREATE TABLE [dbo].[TotalSalarios](
       [MesAno] [datetime] NOT NULL,
       [TotalSalario] [decimal](11, 2) NULL);

MesAno
TotalSalario
2011-01-01 00:00:00.000
89500.00
2011-02-01 00:00:00.000
91200.00
2011-03-01 00:00:00.000
93200.00
2011-04-01 00:00:00.000
97200.00
2011-05-01 00:00:00.000
101250.00
2011-06-01 00:00:00.000
103000.00
2011-07-01 00:00:00.000
103000.00
2011-08-01 00:00:00.000
105020.00
Tabela 1 - Conteúdo atual da tabela
 
-- Criação da Sequence com o nome IdSalarios

CREATE SEQUENCE dbo.IdSalarios AS INT
   MINVALUE 1     -- Menor valor da Sequence
   MAXVALUE 10000 --  Maior valor da sequence 
   START WITH 1   -- Valor inicial da Sequence
   CYCLE;         -- Indica para a sequence reiniciar do menor
                  -- valor (1) quando atingir o
                  -- maior valor (10000)

-- Criação da coluna ID
Alter Table TotalSalarios Add ID int;
 
-- Criando uma constraint para definir o valor da sequence como valor default da coluna ID
ALTER TABLE TotalSalarios 
  ADD CONSTRAINT Seq_IDSalarios DEFAULT (NEXT VALUE FOR IdSalarios) FOR Id
 
-- Inicializando os valores existentes com o próximo valor da sequence
Update TotalSalarios
   set ID = Next Value For IdSalarios;
 
-- Inserindo um registro novo deixando sem informar valor para ID
Insert into TotalSalarios (MesAno, TotalSalario) Values('2011-09-01',11000);


MesAno
TotalSalario
2011-01-01 00:00:00.000
89500.00
2011-02-01 00:00:00.000
91200.00
2011-03-01 00:00:00.000
93200.00
2011-04-01 00:00:00.000
97200.00
2011-05-01 00:00:00.000
101250.00
2011-06-01 00:00:00.000
103000.00
2011-07-01 00:00:00.000
103000.00
2011-08-01 00:00:00.000
105020.00
2011-09-01 00:00:00.000
11000.00
Tabela 2 - Conteúdo final da tabela

Alterar valor de uma sequence

Acho que este cenário é bem comum: a chave primária de uma tabela tem seus valores definidos a partir de uma sequence. Por exemplo, os valores na tabela Customer, coluna Customer_ID são obtidos da sequence Customer_Seq. Se tudo funcionar adequadamente, o valor máximo na coluna é igual ao valor atual da sequence. E na próxima inserção de uma linha, a sequence retorna um valor maior (provavelmente um número acima). 

Pois bem, por diferentes motivos isto pode não ocorrer. Um caso comum ocorre no ambiente de desenvolvimento quando a aplicação ainda em testes iniciais contem um erro, insere muitas linhas sem utilizar a sequence e perde esta sincronia. Em produção já enfrentei um cenário mais complexo envolvendo duas aplicações inserindo dados concorrentemente em um BD. De qualquer modo, aparece uma necessidade: "adiantar a sequence até o ponto máximo da tabela".  

O modo simples seria dropar e recriar a sequence. Mas há problemas de segurança, concorrência, necessidade de recompilar pacotes, etc... que podem tornar a operação não muito simples. 

A solução óbvia é escrever um pequeno loop para adiantar a sequence n vezes. Atenção, se n for grande, isto pode demorar algum tempo.

Bem, depois de ocorrer algumas vezes, desenvolvi uma procedure para me ajudar. São três parâmetros: nome da tabela, nome da coluna e nome da sequence.  A idéia básica é obter a diferença entre o valor máximo na tabela, que suponho ser maior, e o valor atual na sequence. Então, alterar o incremento da sequence para a diferença e incrementar a sequence uma única vez cobrindo toda diferença. Apenas para não afetar a sequence de maneira definitiva, é necessário saber o incremento utilizado no início e redefini-lo no final. 

Vantagens: 
  • em poucos passos o valor da sequence é corrigido 
  • tempo de execução não depende da diferença
  • a sequence continua existindo, bem como seus grants
  • não é necessário recompilar procedures ou funções
  • e uma vez escrita a procedure, basta chamá-la.
Para chamar a procedure, basta uma linha como;

exec move_sequence('customer', 'customer_id', 'customer_seq');

Aqui a procedure, com alguns put_line apenas para ajudar a sua compreensão:


create or replace
procedure move_sequence(p_table_name varchar2, p_column_name varchar2, p_sequence_name varchar2 )
as
  sqlc varchar2(500);
  max_column_value number;
  curr_seq_value number;
  diff number;
  curr_seq_increment number;


begin


  sqlc := 'select max('||p_column_name||') from '||p_table_name;
  execute immediate sqlc into max_column_value;
  
  dbms_output.put_line('Max Current Value: '||max_column_value);


  select us.increment_by
    into curr_seq_increment
    from user_sequences us
   where sequence_name = upper(p_sequence_name); 
  
  sqlc := 'select '||p_sequence_name||'.nextval from dual';
  execute immediate sqlc into curr_seq_value;
  
  dbms_output.put_line('Seq Current Value: '||curr_seq_value);

  diff := max_column_value - curr_seq_value;
  sqlc := 'alter sequence '||p_sequence_name||' increment by '||diff;
  execute immediate sqlc;
  
  sqlc := 'select '||p_sequence_name||'.nextval from dual';
  execute immediate sqlc into curr_seq_value;
  
  dbms_output.put_line('New Seq Current Value: ' ||curr_seq_value);
   
 sqlc := 'alter sequence '||p_sequence_name||' increment by '||curr_seq_increment;
  execute immediate sqlc;
  
end;

Uma variação simples é resetar a sequence para o valor inicial. Esta eu deixo por conta do leitor. Alguns detalhes podem ser incluídos, como um parâmetro para o nome do esquema.