Arquivo de etiquetas: function

Função SQL para Validar NIF

Olá,
esta semana necessitei de criar um script para validação do NIF, número de identificação fiscal, mais conhecido por número de contribuinte.

O código foi implementado em numa linguagem diferente mas achei interessante recriar o mesmo em TSQL.

Explicações mais profundas sobre esta matéria podem consultar por ex. aqui:
https://pt.m.wikipedia.org/wiki/N%C3%BAmero_de_identifica%C3%A7%C3%A3o_fiscal

Vou apenas resumir o algoritmo a implementar:

O NIF é constituído por nove dígitos, sendo os oito primeiros sequenciais e o último um dígito de controlo. Adicionalmente, o primeiro dígito do NIF não pode ser zero nem quatro.

Para ser calculado o digito de controlo:

Multiplicar 1º digito por 9, o 2.º dígito por 8, o 3.º dígito por 7, o 4.º dígito por 6, o 5.º dígito por 5, o 6.º dígito por 4, o 7.º dígito por 3 e o 8.º dígito por 2.

Seguidamente somar os resultados.

Calcular o resto da divisão do número por 11: se o resto for 0 (zero) ou 1 (um) o dígito de controlo será 0 (zero); se for outro qualquer algarismo X o dígito de controlo será o resultado da subtracção 11 – X.

Por fim, basta ver a igualdade do dígito de controlo com o último dígito do NIF. No caso de ser igual o NIF está correcto; no caso de ser diferente o NIF não é válido.

O código TSQL foi colocado como uma função. Esta é chamada com um NIF e o retorno será o próprio NIF se estiver válido ou vazio caso não o seja.
Denominei a função como [dbo].[up_check_nif] e codifica-se da seguinte forma:

/*
Função valida NIF,
2021 JG
*/

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

if OBJECT_ID('[dbo].[up_check_nif]') IS NOT NULL       drop function dbo.up_check_nif
GO

CREATE function [dbo].up_check_nif (@nif_input varchar(9))
RETURNS varchar(9)
AS
BEGIN

declare @j int = 9
declare @i int = 1
declare @total int = 0
declare @digit_control int 
declare @result varchar(9)
-- nif_pri_digito   = '1,2,3,5,6,7,8,9'

IF  LEN(@nif_input) = 9 AND @nif_input NOT LIKE '%[^0-9]%' AND LEFT(@nif_input,1) not in (0,4)      
BEGIN   

    -- SUM( primeiros 8 digitos * [9,8,7,6,5,4,3,2])
    WHILE @i < LEN(@nif_input)  
    BEGIN
         SET @total = @total + SUBSTRING(@nif_input,@i,1) * @j               
         SET @j = @j-1 
         SET @i = @i+1
    END             

    IF (@total % 11) = 0 OR (@total % 11) = 1 
         SET @digit_control = 0          
    ELSE            
         SET @digit_control = 11 - (@total % 11) 

    IF @digit_control = RIGHT(@nif_input,1)                                 
         SET @result = @nif_input   /* nif válido */        
    ELSE                    
         SET @result = ''          /* nif inválido */  
END
ELSE

        SET @result = ''           /* nif inválido */  

RETURN @result  

END

A função poderá ser chamada da seguinte forma:

select dbo.up_check_nif(‘123456789’) AS NIF_Validado

Espero que vos seja útil,

JG

SQL Partitioned Tables

Olá,

o assunto de hoje trata um tema importantíssimo relacionado com a  performance do SQL Server, tema referente às partições de tabelas (Partitioned Tables).

Num modo muito simples, poderemos dizer que Partitioned Tables são uma forma de repartir por localizações diferentes os dados de uma tabela.

Uma DB pode ser composta por diversos FileGroups, sendo cada um constituído por diversos Files físicos, podendo ser localizados em volumes de disco diferentes. Isto permite performance melhorada pois o SQL pode responder paralelamente a pedidos de dados, acedendo a diferentes localizações simultaneamente.

O caso das Partitioned Tables é idêntico mas a separação em diferentes ficheiros é realizada mesmo ao nível da tabela,  tornando o seu acesso de leitura/escrita muito melhorado,  necessitando apenas ser especificado determinado campo para a divisão dos dados – normalmente é uma data, para separação dos dados por anos.

Assim, alguns pontos de interesse nas Partitioned Tables:

  • o manuseamento de grandes tabelas é mais eficiente,
  • cada partição pode ser guardada num FileGroup específico, sendo cada um destes colocados em qualquer volume de dados, podendo ser em locais diferentes para a mesma tabela,
  • os índices ficam alinhados automaticamente, isto é, se for criado um índice numa tabela este fica imediatamente activo nas respectivas partições da tabela.

O processo de crição das Partitioned Tables passa pelas seguintes fases:

  1. Criar uma Partition Function, esta define as fronteiras para as partições, por ex. 3;
  2. Criar uma Partition Scheme, esta mapeia as partições  previamente criadas para os Filegroups desejados – serão por ex. 4, de acordo com as 3 limitações anteriores – e define o próximo Filegroup a utlizar, ou seja, no ex. passa a 5;
  3. Criar uma Tabela, esta é criada na Partition Scheme anteriormente tratada, especificando o campo (coluna) desejado para a separação de dados.
  4. Dividir partição (Split Partition) em duas, sendo adicionado uma fronteira;
  5. Juntar partições (Merge Partitions), sendo que duas partições passarão a formar apenas uma – remove uma fronteira;
  6. Alterar partição (Switch Partitions)

 

Iniciemos por verificar a estrutura física da DB DemoDB, criada para o efeito de demonstração:

use Master
GO

create DATABASE DemoDB
GO

Vejamos os ficheiros constituintes da DemoDB:

files

e os respectivos Filegroups:

filegroups

Das imagens anteriores, resume-se que a DemoDB tem apenas um FileGroup (Primary) e dois ficheiros no disco, um para dados e outro para log.

Agora, iremos alterar a DemoDB de forma a contemplar mais FileGroups (FG000, FG2000, FG2001, FG2002 e FG2003) e adicionar ficheiros a cada um destes FileGroups (respectivamente F0000, F2000, F2001, F2002 e F2003), ficando residentes em “C:\Temp”:

use DemoDB;

ALTER DATABASE DemoDB ADD FILEGROUP FG000 GO ALTER DATABASE DemoDB ADD FILE  (NAME=F0000,  FILENAME=’C:\Temp\F0000.ndf’,  SIZE=3MB,  MAXSIZE = 100MB, FILEGROWTH = 3MB  )  TO FILEGROUP FG000;
GO

ALTER DATABASE DemoDB ADD FILEGROUP FG2000 GO ALTER DATABASE DemoDB ADD FILE (NAME=F2000, FILENAME=’C:\Temp\F2000.ndf’, SIZE=3MB, MAXSIZE = 100MB, FILEGROWTH = 3MB  ) TO FILEGROUP FG2000;
GO

ALTER DATABASE DemoDB ADD FILEGROUP FG2001 GO ALTER DATABASE DemoDB ADD FILE (NAME=F2001, FILENAME=’C:\Temp\F2001.ndf’, SIZE=3MB, MAXSIZE = 100MB, FILEGROWTH = 3MB  ) TO FILEGROUP FG2001;
GO

ALTER DATABASE DemoDB ADD FILEGROUP FG2002 GO ALTER DATABASE DemoDB ADD FILE (NAME=F2002, FILENAME=’C:\Temp\F2002.ndf’, SIZE=3MB, MAXSIZE = 100MB, FILEGROWTH = 3MB  ) TO FILEGROUP FG2002;
GO

ALTER DATABASE DemoDB ADD FILEGROUP FG2003 GO ALTER DATABASE DemoDB ADD FILE (NAME=F2003, FILENAME=’C:\Temp\F2003.ndf’, SIZE=3MB, MAXSIZE = 100MB, FILEGROWTH = 3MB  ) TO FILEGROUP FG2003;
GO

Vejamos agora a estrutura de ficheiros da DemoDB:

files2

e seus Filegroups:

filegroups2

Importante aqui salientar que cada um dos ficheiros criados para a DemoDB poderia ficar residente em diferentes volumes de disco.

 

1 – Criar uma Partition Function

Vamos criar agora a Partition Function com o nome de PF, definindo 3 fronteiras, isto é, dividindo os dados por ano contemplando os anteriores a 2000, depois os dados entre 2000 e 2001, entre 2001 e 2002 e maiores que 2002:

CREATE PARTITION FUNCTION PF (int) AS RANGE RIGHT FOR VALUES (20000101, 20010101, 20020101);
GO

 

2 – Criar uma Partition Scheme

Vamos agora criar a Partition Scheme PS dentro da Partition Function PF para os Filegroups criados anteriormente.

O FG000 ficará com os dados anteriores a 2000, o FG2000 ficará com os dados do ano 2000, o FG2001 com os do ano 2001, o FG2002 com os dados do ano 2002 e o FG2003 com os dados superiores a 2002 (este é o próximo Filegroup a utilizar):

CREATE PARTITION SCHEME PS AS PARTITION PF TO (FG000, FG2000, FG2001, FG2002, FG2003);
GO

 

scheme

 

3 – Criar uma Tabela

O próximo passo é criar uma tabela, OrderTable, especificando que a criação deve ser feita na Partition Scheme PS e o campo de divisão de dados é o campo data (datekey).

A tabela é constituída por apenas dois campos – datekey e amount, ambos numéricos.

Criar tabela OrderTable:

CREATE TABLE dbo.OrderTable
 (datekey int, amount int)
ON PS (datekey);
GO

e inserir dados (datekey e amount), alterando sucessivamente as datas:

INSERT dbo.OrderTable VALUES (20000101, 100);
INSERT dbo.OrderTable VALUES (20001231, 100);
INSERT dbo.OrderTable VALUES (20010101, 100);
INSERT dbo.OrderTable VALUES (20010403, 100);
GO

Os registos inseridos contemplaram os anos de 2000 e 2001.

Poderemos utilizar o seguinte código para verificar o resumo do que acabamos de criar:

  • uma Partition Scheme,
  • uma Partition Function com 4 partes, cada uma identificada numa partição do ficheiro do disco
  • ficheiros da DemoDB onde se inscreveram dados
SELECT ps.name as PartitionScheme, pf.name as PartitionFunction, 
p.partition_number as PartitionNumber, fg.name, prv_left.value inicio, 
prv_right.value fim, p.row_count registos
FROM sys.dm_db_partition_stats p 
INNER JOIN sys.indexes i ON i.OBJECT_ID = p.OBJECT_ID and i.index_id = p.index_id
INNER JOIN sys.data_spaces ds ON ds.data_space_id = i.data_space_id
LEFT OUTER JOIN sys.partition_schemes ps ON ps.data_space_id = i.data_space_id
LEFT OUTER JOIN sys.partition_functions pf ON ps.function_id = pf.function_id
LEFT OUTER JOIN sys.destination_data_spaces dds 
ON dds.partition_scheme_id = ps.data_space_id AND dds.destination_id = p.partition_number
LEFT OUTER JOIN sys.filegroups fg ON fg.data_space_id = dds.data_space_id
LEFT OUTER JOIN sys.partition_range_values prv_right 
ON prv_right.function_id = ps.function_id AND prv_right.boundary_id = p.partition_number
LEFT OUTER JOIN sys.partition_range_values prv_left 
ON prv_left.function_id = ps.function_id AND prv_left.boundary_id = p.partition_number - 1
WHERE OBJECT_NAME (p.object_id) = 'OrderTable'
AND i.index_id = 0
ORDER BY partition_number
GO

 

filesresume

Percebe-se que os registos foram inseridos na tabela OrderTable,  internamente no Filegroup FG2000 e FG2001, cujos mapeamentos físicos no disco são c:\Temp\F2000.ndf e c:\Temp\F2001.ndf.

É um método que permite uma performance melhorada na medida em que passará a ler em localizações físicas diferentes consoante os dados que se desejem. A escrita também fica melhorada uma vez que pode ser efetuado paralelismo de escrita em ficheiros diferentes.

Na prática, efectuamos uma divisão de uma tabela utilizando como fronteira a coluna do ano.

Os pontos 4, 5 e 6:

 

  • Dividir partição (Split Partition) em duas, sendo adicionado uma fronteira;
  • Juntar partições (Merge Partitions), sendo que duas partições passarão a formar apenas uma – remove uma fronteira;
  • Alterar partição (Switch Partitions)

serão publicados no próximo artigo.

 

Espero que vos seja útil,

JG