*/
Mostrando postagens com marcador Algoritmos. Mostrar todas as postagens
Mostrando postagens com marcador Algoritmos. Mostrar todas as postagens

quinta-feira, 2 de abril de 2015

Ultimo Dia Util Considerando Feriados em Oracle PL/SQL

/* Geralmente é complicado obter o ultimo dia válido considerando os feriados nacionais,
muito usado no ramo financeiro. */

/* Esse artigo pretende dá uma opção de como obter o ultimo dia util dentre várias soluções
encontradas na net, com o uso de stores functions do Oracle PL/SQL */

/* Mostrando como criar uma função SQL Oracle isto é em PL/SQL para obter o ultimo dia útil, considerando os feriados nacionais dentre outros assuntos correlacionados. */

/* Primeiro criamos uma tabela feriado para melhor elucidar as store functions, conforme script abaixo: */


-- DROP TABLE FERIADO;
CREATE TABLE FERIADO
(
   ID_FERIADO                       NUMBER(10,0)       NOT NULL ENABLE
 , DATA                             DATE               NOT NULL ENABLE
 , DESCRICAO                        VARCHAR2(50 BYTE)  NOT NULL ENABLE
 , ID_TIPO_FERIADO                  NUMBER(3,0)        NOT NULL ENABLE -- 1 NACIONAL, 2 ESTADUAL, 3 MUNICIPAL, 4 OUTROS
 , OBS                              NVARCHAR2(15)
 , CONSTRAINT PK_FERIADO_ID_FERIADO PRIMARY KEY (ID_FERIADO)
 , CONSTRAINT UQ_FERIADO_DATA UNIQUE (DATA)
);

-- DROP SEQUENCE FERIADO_FCSEQ; 
CREATE SEQUENCE FERIADO_FCSEQ
INCREMENT BY 1
START WITH 1
NOCYCLE;

-- DROP TRIGGER TG_FERIADO_ID_FERIADO_BI;
CREATE OR REPLACE TRIGGER TG_FERIADO_ID_FERIADO_BI BEFORE INSERT ON FERIADO
FOR EACH ROW
 WHEN (new.ID_FERIADO IS NULL) BEGIN
  SELECT FERIADO_FCSEQ.NEXTVAL INTO :new.ID_FERIADO FROM dual;
END;
/
ALTER TRIGGER TG_FERIADO_ID_FERIADO_BI ENABLE;


/* Se desejar usar o o arquivo CSV dos feriados chamado FERIADOS.CSV com CRLF no final de cada linha, use o SQL Loader do Oracle para dá carga do arquivo, evitando assim digitação de datas, é possível obtê-lo CLICANDO AQUI! */
/* Criando o arquivo de controle chamado FERIADO.CTL salvo no diretorio C:\TEMP */
load data
  infile 'FERIADO.CSV'
  into table feriado
  fields terminated by "|"
  (
      data  DATE "YYYY-MM-DD"
    , descricao
    , id_tipo_feriado
    , obs
  )
/* Depois executar o SQL Loader, no Linux é igual ao do Windows considerando nome da pasta, no Windows fica assim: */
/* Lembrando de considerar, isto é, permutar seu usuario e senha, ip_servidor e SID do Oracle do seu servidor*/
sqlldr usuario/senha@ip_servidor/SID control=FERIADO.CTL log=X.LOG bad-Z.BAD READSIZE=10000000


/* Depois criamos uma store function para idenficar dias feriados usando a tabela feriado conforme script abaixo: */


CREATE OR REPLACE FUNCTION usf_feriado (d in date) 
RETURN integer 
AS
  
  x integer; 

BEGIN 

  SELECT count(*) 
    INTO x
    FROM feriado 
   WHERE to_char(data,'YYYY-MM-DD') = to_char(d,'YYYY-MM-DD'); 
   
  IF (x >= 1) THEN 
      RETURN 1;
  ELSE 
      RETURN 0; 
  END IF;
  
END usf_feriado; 



/* Logo em seguida criamos um store function para tratar o ultimo dia util, conforme script abaixo: */


CREATE OR REPLACE FUNCTION usf_ultimo_dia_util ( dt_base in date ) 
RETURN date 
AS

  dt_basex date; 
  bo_fimx  boolean; 

BEGIN 

  dt_basex := dt_base; 
  bo_fimx  := false; 
  
  WHILE NOT (bo_fimx) LOOP 
  
      bo_fimx := to_char(dt_basex,'d') NOT IN ('1','7');
      
      IF usf_feriado(dt_basex) = 1 THEN 
          bo_fimx := false;
      END IF; 
      
      IF NOT (bo_fimx) THEN 
          dt_basex := dt_basex - 1; 
      END IF; 
      
  END LOOP; 
  
  RETURN dt_basex; 
  
EXCEPTION 

  WHEN others THEN 
  RAISE; 
  
  
END usf_ultimo_dia_util;


/* Testando o uso da function em SQL puro */


SELECT usf_feriado(sysdate) AS DT FROM DUAL; -- RETORNA 1 PARA FERIADO E 0 PARA OUTROS DIAS 

ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'; -- ALTERA DATA PARA FORMATO 'YYYY-MM-DD'

SELECT usf_ultimo_dia_util ('2015-04-05') from dual; -- RETORNA O ULTIMO DIA UTIL -> 2015-04-02

/* Espero ter ajudado! */

/* Seja Feliz!! */

/* APdSJC */

quinta-feira, 26 de dezembro de 2013

Código, Algoritmo de um Plano de Contas escrito em VB

Complementando um artigo descrito em http://emersonhermann.blogspot.com.br/2012/09/consulta-sql-de-plano-de-contas-query.html Consulta SQL de Plano de Contas - Query Contabil - Query para Centro de Custo, apenas exponho um simples código para elucidar como deveria ser feito, isto é, como seria implementado um algoritmo para totalizar um plano de contas em uma linguagem de programação, a exemplo aqui do VBA, em casos que não é possível usar uma query ou um SGBD sem um suporte mais abrangente ao SQL a exemplo do ACCESS 2010.

'Um código bem básico para uma estrutura de três níveis escrito em 'VB para o Access 2010 
Option Compare Database

Sub centro_custo()
' funciona para uma estrutura de contas em 3 niveis
Dim rs1 As Recordset
Dim rs2 As Recordset
Dim soma_n1, soma_n2, soma_n3 As Double
Dim tamanho_conta As Integer
Dim strSQL As String
' lista todas as contas cadastradas em centro_custo
Set rs1 = CurrentDb.OpenRecordset("SELECT id_centro_custo, descricao, tipo_conta FROM centro_custo ORDER BY id_centro_custo DESC;")
Do While Not rs1.EOF
   ' contas nivel 3
   ' todas contas analiticas, nessa estrutura as contas tem o nivel 3
   If (rs1("tipo_conta") = "A") Then
       ' processa o nivel 3 na query em strSQL
       strSQL = "SELECT cc.id_centro_custo, cc.descricao, sum(m.valor_movimento) AS total_conta FROM centro_custo cc INNER JOIN movimento m  ON m.id_centro_custo LIKE cc.id_centro_custo WHERE cc.id_centro_custo =  '" & rs1("id_centro_custo") & "' GROUP BY cc.id_centro_custo, cc.descricao;"
       Set rs2 = CurrentDb.OpenRecordset(strSQL)
       If Not rs2.EOF Then
           soma_n3 = rs2("total_conta")
       Else
           soma_n3 = 0
       End If
       soma_n2 = soma_n2 + soma_n3
       Debug.Print rs1("id_centro_custo") & " - " & rs1("descricao") & " - " & rs1("tipo_conta") & " - " & soma_n3
   Else

       tamanho_conta = Len(rs1("id_centro_custo"))
       ' contas nivel 2
       If tamanho_conta = 3 Then
           Debug.Print rs1("id_centro_custo") & " - " & rs1("descricao") & " - " & rs1("tipo_conta") & " - " & soma_n2
           soma_n1 = soma_n1 + soma_n2
           soma_n2 = 0
       Else
           ' contas nivel 1
           If tamanho_conta = 1 Then
               Debug.Print rs1("id_centro_custo") & " - " & rs1("descricao") & " - " & rs1("tipo_conta") & " - " & soma_n1
               soma_n1 = 0
           End If
       End If
   End If
   ' avanca proximo registro
   rs1.MoveNext
Loop

End Sub

' Informo ainda que os níveis podem ser configurados via matrizes 
' e código desenvolvido acima é procedural
' Recomendo definir uma mascara do plano de contas

Resultado do Código em VBA Acima Descrito


Mais uma vez espero ter ajudado!
APdSJC!

terça-feira, 29 de janeiro de 2013

Desenvolvendo querys SQL para Razão e Balancete Contábil.

Desenvolvendo querys SQL para Razão e Balancete Contábil.

Em um artigo escrito neste blog no dia 06-09-2012 Consulta SQL de Plano de Contas - Query Contabil - Query para Centro de Custo conforme link http://www.emersonhermann.blogspot.com.br/2012/09/consulta-sql-de-plano-de-contas-query.html, mostrei como desenvolver uma query em uma estrutura de plano de contas ou centro de custo, dessa vez irei apresentar de forma prática, gradual e por exemplos de como desenvolver uma query para relatório de Razão, Razão Sumarizado e Balancete Contábil.

Nivel de Complexidade: Intermediário, Avançado

Quem desenvolve sabe como é complicado criar relatórios para contabilidade ou centro de custos, seja codificando em alguma linguagem ou mesmo tentando encurtar o tempo ou a pressão, usando algum gerador de relatórios, a exemplo do MS Report Service, SAP Crystal Reports, Script Case, etc.

As querys scripts mostradas nesse artigo foram desenvolvidas para os seguintes SGBDRs em ordem alfabética:

Firebird 2.5.1
Oracle 11g R2
Postgres 9.1
SQL Server 2012

A modelagem aqui apresentada não seguiu o rigor acadêmico, a exemplo de implementação de constraints, chaves primárias, etc, bem como os exemplos citados para area Contabil.

O objetivo principal deste artigo é mostrar como desenvolver querys para Sistemas Contabeis, houve uma simplificação com intuíto de facilitar o entendimento, entretanto, com os exemplos expostos é possivel adotar em qualquer modelo relacional.

Houve também uma preocupação em apresentar os scripts desenvolvidos nos SGBDRs citados no inicio deste documento.

Pode-se chegar aos mesmos resultados de uma forma mais performática e simples; estou apto a sugestões.

Seguem os exemplos em scripts SQL:

Criando as tabelas ... (Script para Firebird 2.5.1, Oracle 11g R2, Postgres 9.1, SQL Server 2012)

-- USE tempdb; -- Descomentar caso use SQL Server, recomendo criar um banco de teste para os outros SGBDRs.
--DROP TABLE plano_conta;
CREATE TABLE plano_conta
( 
   id_plano_conta   varchar(12) PRIMARY KEY   -- dados da conta ex. 1.02.01 
 , descricao        varchar(50)               -- descricao da conta cadastrada ex. Vendas Externas
 , tipo_conta       varchar(1)                -- tipo de conta do cc Analitica ou Sintética, dominio discreto: A ou S, em situação de produção merece uma constraint check
); 
 
--DROP TABLE lancamento;
CREATE TABLE lancamento 
(
   id_lancamento    integer   PRIMARY KEY    -- id do lancamento, recomenda-se auto incremento, mas para simplifcar fica sem auto incremento
 , dt_lancamento    date                     -- data do lancamento 
 , numero_doc       varchar(40)              -- numero do documento a ser informado  
 , id_plano_conta   varchar(12)              -- chave estrangeira para a tabela plano_conta, mas para simplificar apenas iremos convencionar, não será habilitado a FK, recomendo colocar not null
 , tipo_lancamento  varchar(1)               -- tipo de lancamento 'E' = Entrada 'S' = Saida 
 , historico        varchar(100)             -- historico do lancamento 
 , valor_lancamento numeric(15,2)            -- valor informado 
);

Povoando os tabelas...
-- Receitas 
 
INSERT INTO plano_conta (id_plano_conta, descricao, tipo_conta) VALUES ('1','Receita','S');
INSERT INTO plano_conta (id_plano_conta, descricao, tipo_conta) VALUES ('1.1','Vendas Internas','S');
INSERT INTO plano_conta (id_plano_conta, descricao, tipo_conta) VALUES ('1.1.1','Escola','A');
INSERT INTO plano_conta (id_plano_conta, descricao, tipo_conta) VALUES ('1.1.2','Escritório','A');
INSERT INTO plano_conta (id_plano_conta, descricao, tipo_conta) VALUES ('1.2','Vendas Externas','S');
INSERT INTO plano_conta (id_plano_conta, descricao, tipo_conta) VALUES ('1.2.1','Livro','A');
INSERT INTO plano_conta (id_plano_conta, descricao, tipo_conta) VALUES ('1.2.2','Brinquedos','A');
 
-- Despesas
 
INSERT INTO plano_conta (id_plano_conta, descricao, tipo_conta) VALUES ('2','Despesas','S');
INSERT INTO plano_conta (id_plano_conta, descricao, tipo_conta) VALUES ('2.1','Fornecedores','S');
INSERT INTO plano_conta (id_plano_conta, descricao, tipo_conta) VALUES ('2.1.1','Nacional','A');
INSERT INTO plano_conta (id_plano_conta, descricao, tipo_conta) VALUES ('2.1.2','Importado','A');
INSERT INTO plano_conta (id_plano_conta, descricao, tipo_conta) VALUES ('2.2','Escritório','S');
INSERT INTO plano_conta (id_plano_conta, descricao, tipo_conta) VALUES ('2.2.1','Materiais de limpeza','A');
INSERT INTO plano_conta (id_plano_conta, descricao, tipo_conta) VALUES ('2.2.2','Materiais de Escritório','A');
 
-- Vamos povoar a tabela lancamento: 
INSERT INTO lancamento (id_lancamento, dt_lancamento, numero_doc, id_plano_conta, tipo_lancamento, historico, valor_lancamento) VALUES (1,'2012-07-03','0000084','1.2.2','E',NULL,10.55); 
INSERT INTO lancamento (id_lancamento, dt_lancamento, numero_doc, id_plano_conta, tipo_lancamento, historico, valor_lancamento) VALUES (2,'2012-07-03','0000084','1.2.2','S',NULL,2.50); 
INSERT INTO lancamento (id_lancamento, dt_lancamento, numero_doc, id_plano_conta, tipo_lancamento, historico, valor_lancamento) VALUES (3,'2012-07-01','0000021','1.1.2','E',NULL,50.00);
INSERT INTO lancamento (id_lancamento, dt_lancamento,numero_doc, id_plano_conta, tipo_lancamento, historico, valor_lancamento) VALUES (4,'2012-07-01','0000042','1.2.2','E',NULL,100.00);
INSERT INTO lancamento (id_lancamento, dt_lancamento, numero_doc, id_plano_conta, tipo_lancamento, historico, valor_lancamento) VALUES (5,'2012-07-04','0000084','1.2.2','E',NULL,160.00);
INSERT INTO lancamento (id_lancamento, dt_lancamento, numero_doc, id_plano_conta, tipo_lancamento, historico, valor_lancamento) VALUES (6,'2012-07-04','0000084','1.2.2','S',NULL,80.00);
 
INSERT INTO lancamento (id_lancamento, dt_lancamento, numero_doc, id_plano_conta, tipo_lancamento, historico, valor_lancamento) VALUES (7,'2012-07-04','0000142','2.2.1','S',NULL,40.00);
INSERT INTO lancamento (id_lancamento, dt_lancamento, numero_doc, id_plano_conta, tipo_lancamento, historico, valor_lancamento) VALUES (8,'2012-07-07','0000210','2.2.2','S',NULL,80.00);
INSERT INTO lancamento (id_lancamento, dt_lancamento, numero_doc, id_plano_conta, tipo_lancamento, historico, valor_lancamento) VALUES (9,'2012-07-13','0000242','2.2.2','S',NULL,20.00);
INSERT INTO lancamento (id_lancamento, dt_lancamento, numero_doc, id_plano_conta, tipo_lancamento, historico, valor_lancamento) VALUES (10,'2012-07-13','0000284','2.2.1','S',NULL,15.00);



Vamos as querys relatórios...

Razão Detalhado (Script para Firebird 2.5.1, Oracle 11g R2, Postgres 9.1, SQL Server 2012)
-- razao detalhado (Script para Firebird 2.5.1, Oracle 11g R2, Postgres 9.1, SQL Server 2012) 

   SELECT lan.id_lancamento
        , lan.dt_lancamento 
        , lan.numero_doc
        , lan.id_plano_conta
        , lan.tipo_lancamento
        , lan.historico 
        , 
          coalesce(
          (
          SELECT sum
                 ( 
                  CASE WHEN tipo_lancamento = 'E' THEN 
                            valor_lancamento
                       WHEN tipo_lancamento = 'S' THEN 
                            valor_lancamento * -1 
                       ELSE 
                            0.00
                  END
                 ) 
            FROM lancamento 
            WHERE dt_lancamento < lan.dt_lancamento 
              AND id_plano_conta  = lan.id_plano_conta 
          ),0)  AS saldo_inicial  
        , CASE WHEN tipo_lancamento = 'E' THEN 
                    valor_lancamento
               ELSE 
                    0.00 
          END AS entrada 
        , CASE WHEN tipo_lancamento = 'S' THEN 
                    valor_lancamento
               ELSE 
                    0.00 
          END AS saida 
        ,  
          coalesce(
          (
          SELECT sum
                 ( 
                  CASE WHEN tipo_lancamento = 'E' THEN 
                            valor_lancamento 
                       WHEN tipo_lancamento = 'S' THEN 
                            valor_lancamento * -1 
                       ELSE 
                            0.00
                  END
                 ) 
            FROM lancamento
           WHERE dt_lancamento <= lan.dt_lancamento 
             AND id_plano_conta  = lan.id_plano_conta 
          ),0)  AS saldo_final
     FROM lancamento AS lan
     JOIN plano_conta AS plc 
       ON plc.id_plano_conta  = lan.id_plano_conta
    WHERE lan.dt_lancamento >= '2012-07-04'
      AND lan.dt_lancamento <= '2012-07-14'
      AND lan.id_plano_conta  = '2.2.2'
 ORDER BY lan.dt_lancamento ASC 
        ;


Razão Sumarizado Por Plano de Conta (Script para Firebird 2.5.1, Oracle 11g R2, Postgres 9.1, SQL Server 2012)
-- razao sumarizado por plano de conta (Script para Firebird 2.5.1, Oracle 11g R2, Postgres 9.1, SQL Server 2012)   
    SELECT x.id_plano_conta
         , pcx.descricao 
         , coalesce(
           (
            SELECT sum
                   ( 
                    CASE WHEN tipo_lancamento = 'E' THEN 
                              valor_lancamento
                         WHEN tipo_lancamento = 'S' THEN 
                              valor_lancamento * -1 
                         ELSE 
                              0.00
                     END
                   ) 
              FROM lancamento 
             WHERE dt_lancamento < '2012-07-04'
               AND id_plano_conta  = x.id_plano_conta 
           ),0)  AS saldo_inicial 
         , sum(x.entrada) AS entrada
         , sum(x.saida)   AS saida
         , coalesce(
           (
            SELECT sum
                   ( 
                    CASE WHEN tipo_lancamento = 'E' THEN 
                              valor_lancamento 
                         WHEN tipo_lancamento = 'S' THEN 
                              valor_lancamento * -1 
                         ELSE 
                              0.00
                    END
                   ) 
              FROM lancamento
             WHERE dt_lancamento <=  '2012-07-14'
               AND id_plano_conta  = x.id_plano_conta 
           ),0)  AS saldo_final
      FROM
         ( 
           SELECT lan.id_plano_conta
                , CASE WHEN tipo_lancamento = 'E' THEN 
                            valor_lancamento
                       ELSE 
                            0.00 
                  END AS entrada 
                , CASE WHEN tipo_lancamento = 'S' THEN 
                            valor_lancamento
                       ELSE 
                            0.00 
                  END AS saida 
             FROM lancamento AS lan
             JOIN plano_conta AS plc 
               ON plc.id_plano_conta  = lan.id_plano_conta
            WHERE lan.dt_lancamento >= '2012-07-04'
              AND lan.dt_lancamento <= '2012-07-14'
              AND lan.id_plano_conta  = '2.2.2'
         ) AS x
      JOIN plano_conta AS pcx
        ON pcx.id_plano_conta = x.id_plano_conta 
  GROUP BY x.id_plano_conta
         , pcx.descricao
  ORDER BY x.id_plano_conta ASC 
      ;


Balancete Contabil (Script para SQL Server 2012)
-- balancete contabil (Script para SQL Server 2012) 
  SELECT pcx.id_plano_conta
       , pcx.descricao 
       , sum(xx.saldo_inicial) AS saldo_inicial 
       , sum(xx.entrada)       AS entrada
       , sum(xx.saida)         AS saida 
       , sum(xx.saldo_final)   AS saldo_final 
    FROM
       (
        SELECT x.id_plano_conta
             , coalesce(
               (
                SELECT sum
                       ( 
                        CASE WHEN tipo_lancamento = 'E' THEN 
                                  valor_lancamento
                             WHEN tipo_lancamento = 'S' THEN 
                                  valor_lancamento * -1 
                             ELSE 
                                  0.00
                        END
                       ) 
                  FROM lancamento 
                 WHERE dt_lancamento < '2012-07-04'
                   AND id_plano_conta  = x.id_plano_conta 
               ),0)  AS saldo_inicial 
             , coalesce(
               (
                SELECT sum
                       ( 
                        CASE WHEN tipo_lancamento = 'E' THEN 
                                  valor_lancamento
                             ELSE 
                                  0.00
                        END
                       ) 
                  FROM lancamento 
                 WHERE dt_lancamento >= '2012-07-04'
				   AND dt_lancamento <= '2012-07-14'
                   AND id_plano_conta  = x.id_plano_conta 
               ),0)  AS entrada
             , coalesce(
               (
                SELECT sum
                       ( 
                        CASE WHEN tipo_lancamento = 'S' THEN 
                                  valor_lancamento 
                             ELSE 
                                  0.00
                        END
                       ) 
                  FROM lancamento 
                 WHERE dt_lancamento >= '2012-07-04'
				   AND dt_lancamento <= '2012-07-14'
                   AND id_plano_conta  = x.id_plano_conta 
               ),0)  AS saida			 
             , coalesce(
               (
                 SELECT sum
                        ( 
                         CASE WHEN tipo_lancamento = 'E' THEN 
                                   valor_lancamento 
                              WHEN tipo_lancamento = 'S' THEN 
                                   valor_lancamento * -1 
                              ELSE 
                                   0.00
                         END
                        ) 
                   FROM lancamento
                  WHERE dt_lancamento <=  '2012-07-14'
                    AND id_plano_conta  = x.id_plano_conta 
               ),0)  AS saldo_final
          FROM
             ( 
               SELECT pla.id_plano_conta
			        , lan.dt_lancamento
                    , CASE WHEN lan.tipo_lancamento = 'E' THEN 
                                lan.valor_lancamento
                           ELSE 
                                0.00 
                      END AS entrada 
                    , CASE WHEN lan.tipo_lancamento = 'S' THEN 
                                lan.valor_lancamento
                           ELSE 
                                0.00 
                      END AS saida 
                 FROM lancamento AS lan
				 JOIN plano_conta AS pla
				   ON pla.id_plano_conta = lan.id_plano_conta 
                WHERE 1=1 
             ) AS x
      GROUP BY x.id_plano_conta
       ) AS xx
    JOIN plano_conta pcx 
      ON xx.id_plano_conta LIKE pcx.id_plano_conta + '%'
GROUP BY pcx.id_plano_conta 
       , pcx.descricao 
ORDER BY pcx.id_plano_conta ASC
       ;   


Balancete Contabil (Script para Firebird 2.5.1, Oracle 11g R2, Postgres 9.1)
  
-- balancete contabil (Script para Firebird 2.5.1, Oracle 11g R2, Postgres 9.1)   
  SELECT pcx.id_plano_conta
       , pcx.descricao 
       , sum(xx.saldo_inicial) AS saldo_inicial 
       , sum(xx.entrada)       AS entrada
       , sum(xx.saida)         AS saida 
       , sum(xx.saldo_final)   AS saldo_final 
    FROM
       (
        SELECT x.id_plano_conta
             , coalesce(
               (
                SELECT sum
                       ( 
                        CASE WHEN tipo_lancamento = 'E' THEN 
                                  valor_lancamento
                             WHEN tipo_lancamento = 'S' THEN 
                                  valor_lancamento * -1 
                             ELSE 
                                  0.00
                        END
                       ) 
                  FROM lancamento 
                 WHERE dt_lancamento < '2012-07-04'
                   AND id_plano_conta  = x.id_plano_conta 
               ),0)  AS saldo_inicial 
             , coalesce(
               (
                SELECT sum
                       ( 
                        CASE WHEN tipo_lancamento = 'E' THEN 
                                  valor_lancamento
                             ELSE 
                                  0.00
                        END
                       ) 
                  FROM lancamento 
                 WHERE dt_lancamento >= '2012-07-04'
				   AND dt_lancamento <= '2012-07-14'
                   AND id_plano_conta  = x.id_plano_conta 
               ),0)  AS entrada
             , coalesce(
               (
                SELECT sum
                       ( 
                        CASE WHEN tipo_lancamento = 'S' THEN 
                                  valor_lancamento 
                             ELSE 
                                  0.00
                        END
                       ) 
                  FROM lancamento 
                 WHERE dt_lancamento >= '2012-07-04'
				   AND dt_lancamento <= '2012-07-14'
                   AND id_plano_conta  = x.id_plano_conta 
               ),0)  AS saida			 
             , coalesce(
               (
                 SELECT sum
                        ( 
                         CASE WHEN tipo_lancamento = 'E' THEN 
                                   valor_lancamento 
                              WHEN tipo_lancamento = 'S' THEN 
                                   valor_lancamento * -1 
                              ELSE 
                                   0.00
                         END
                        ) 
                   FROM lancamento
                  WHERE dt_lancamento <=  '2012-07-14'
                    AND id_plano_conta  = x.id_plano_conta 
               ),0)  AS saldo_final
          FROM
             ( 
               SELECT pla.id_plano_conta
			        , lan.dt_lancamento
                    , CASE WHEN lan.tipo_lancamento = 'E' THEN 
                                lan.valor_lancamento
                           ELSE 
                                0.00 
                      END AS entrada 
                    , CASE WHEN lan.tipo_lancamento = 'S' THEN 
                                lan.valor_lancamento
                           ELSE 
                                0.00 
                      END AS saida 
                 FROM lancamento AS lan
				 JOIN plano_conta AS pla
				   ON pla.id_plano_conta = lan.id_plano_conta 
                WHERE 1=1 
             ) AS x
      GROUP BY x.id_plano_conta
       ) AS xx
    JOIN plano_conta pcx 
      ON xx.id_plano_conta LIKE pcx.id_plano_conta || '%'
GROUP BY pcx.id_plano_conta 
       , pcx.descricao 
ORDER BY pcx.id_plano_conta ASC
       ; 


Figura 01 - Resultado das Querys

Para facilitar, caso haja interesse, recomendo criar views ou functions ou procedures das querys acimas, pois são demasiadamente extensas.

Mais uma vez espero ter ajudado.

APDSJ!

quinta-feira, 6 de setembro de 2012

Consulta SQL de Plano de Contas - Query Contabil - Query para Centro de Custo

A idéia primordial desse artigo é demonstrar como desenvolver uma query sql usando um plano de contas ou centro de custo, o principio é o mesmo, subtotalizando de forma invertida, considerando uma estrutura de balancete ou centro de custo, mas sempre usando um plano de contas com contas analiticas e sintéticas e vários niveis de sub-contas.

O SGBDR testados foram:
 Firebird 2.5.1
 Postgres 9.1
 SQL Server 2012
 Oracle 11g R2

Expondo o problema, exemplo:

Montar uma query relatório que mostre o resultado totalizado por níveis de contas lançadas.
O centro de custo irá ser lançado sempre no ultimo nivel e/ou conta analitica, ou seja 1.1.1 ou 1.2.1, etc. 

Conforme figura Figura 01, abaixo:

Figura 01 - Relatório Sumarizado por Plano de Contas

Temos uma estrutura de centro de custo modelada da seguinte maneira: 

Script para Firebird 2.5.1, Postgres 9.1, SQL Server 2012, Oracle 11g R2



-- USE tempdb; -- Descomentar caso use SQL Server 
--DROP TABLE centro_custo;
CREATE TABLE centro_custo
( 
   id_centro_custo  varchar(12) PRIMARY KEY   -- dados da conta ex. 1.02.01 
 , descricao        varchar(50)               -- descricao da conta cadastrada ex. Vendas Externas
 , tipo_conta       varchar(1)                -- tipo de conta do cc Analitica ou Sintética, dominio discreto: A ou S, em situação de produção merece uma constraint check
); 

--DROP TABLE movimento;
CREATE TABLE movimento 
(
   id_movimento   integer   PRIMARY KEY  -- id do movimento, recomenda-se auto incremento, mas para simplifcar fica sem auto incremento
 , numero_doc   varchar(40)              -- numero do documento a ser informado  
 , id_centro_custo   varchar(12)         -- chave estrangeira para a tabela centro de custo, mas para simplificar apenas iremos convencionar, não será habilitado a FK, recomendo colocar not null
 , valor_movimento numeric(15,2)         -- valor informado 
);

Agora iremos povoar a tabela de centro de custo

-- Receitas 

INSERT INTO centro_custo (id_centro_custo, descricao, tipo_conta) VALUES ('1','Receita','S');
INSERT INTO centro_custo (id_centro_custo, descricao, tipo_conta) VALUES ('1.1','Vendas Internas','S');
INSERT INTO centro_custo (id_centro_custo, descricao, tipo_conta) VALUES ('1.1.1','Escola','A');
INSERT INTO centro_custo (id_centro_custo, descricao, tipo_conta) VALUES ('1.1.2','Escritório','A');
INSERT INTO centro_custo (id_centro_custo, descricao, tipo_conta) VALUES ('1.2','Vendas Externas','S');
INSERT INTO centro_custo (id_centro_custo, descricao, tipo_conta) VALUES ('1.2.1','Livro','A');
INSERT INTO centro_custo (id_centro_custo, descricao, tipo_conta) VALUES ('1.2.2','Brinquedos','A');

-- Despesas

INSERT INTO centro_custo (id_centro_custo, descricao, tipo_conta) VALUES ('2','Despesas','S');
INSERT INTO centro_custo (id_centro_custo, descricao, tipo_conta) VALUES ('2.1','Fornecedores','S');
INSERT INTO centro_custo (id_centro_custo, descricao, tipo_conta) VALUES ('2.1.1','Nacional','A');
INSERT INTO centro_custo (id_centro_custo, descricao, tipo_conta) VALUES ('2.1.2','Importado','A');
INSERT INTO centro_custo (id_centro_custo, descricao, tipo_conta) VALUES ('2.2','Escritório','S');
INSERT INTO centro_custo (id_centro_custo, descricao, tipo_conta) VALUES ('2.2.1','Materiais de limpeza','A');
INSERT INTO centro_custo (id_centro_custo, descricao, tipo_conta) VALUES ('2.2.2','Materiais de Escritório','A');

-- Vamos povoar a tabela movimento: 

INSERT INTO movimento (id_movimento, numero_doc, id_centro_custo, valor_movimento) VALUES (1,'0000021','1.1.2',50.00);
INSERT INTO movimento (id_movimento, numero_doc, id_centro_custo, valor_movimento) VALUES (2,'0000042','1.2.2',100.00);
INSERT INTO movimento (id_movimento, numero_doc, id_centro_custo, valor_movimento) VALUES (3,'0000084','1.2.2',160.00);

INSERT INTO movimento (id_movimento, numero_doc, id_centro_custo, valor_movimento) VALUES (4,'0000142','2.2.1',40.00);
INSERT INTO movimento (id_movimento, numero_doc, id_centro_custo, valor_movimento) VALUES (5,'0000210','2.2.2',80.00);
INSERT INTO movimento (id_movimento, numero_doc, id_centro_custo, valor_movimento) VALUES (6,'0000242','2.2.2',20.00);
INSERT INTO movimento (id_movimento, numero_doc, id_centro_custo, valor_movimento) VALUES (7,'0000284','2.2.1',15.00);

Listando os centros de custos cadastrados ...
-- Listando conteudo de centro_custo 
SELECT * FROM centro_custo ORDER BY 1;
Figura 02 - Lista de Centro Custo

Listando os movimentos cadastrados, referenciando os centros de custos ...
-- Listando conteudo de movimento 
SELECT * FROM movimento;
Figura 03 - Lista de Movimento Script apenas para SQL Server 2012

-- Query SQL Centro de Custo, para SQL Server 2012, exemplo: 
  SELECT cc.id_centro_custo
       , cc.descricao 
       , sum(m.valor_movimento) AS total_conta 
    FROM centro_custo cc 
    JOIN movimento m
      ON m.id_centro_custo LIKE cc.id_centro_custo + '%'  -- O segredo está aqui, o campo id_centro_custo vem depois do LIKE
GROUP BY cc.id_centro_custo 
       , cc.descricao 
ORDER BY cc.id_centro_custo ASC
       ; 
Figura 04 - Query Centro Custo com Resultado em SQL Server 2012

Script apenas para Firebird 2.5.1, Postgres 9.1, Oracle 11g R2
  
-- Query SQL Centro de Custo, para Firebird 2.5.1, Postgres 9.1, Oracle 11g R2, exemplo: 
  SELECT cc.id_centro_custo
       , cc.descricao 
       , sum(m.valor_movimento) AS total_conta 
    FROM centro_custo cc 
    JOIN movimento m
      ON m.id_centro_custo LIKE cc.id_centro_custo || '%'  -- O segredo está aqui, o campo id_centro_custo vem depois do LIKE
GROUP BY cc.id_centro_custo 
       , cc.descricao 
ORDER BY cc.id_centro_custo ASC
       ; 
Figura 05 - Query Centro Custo com Resultado em Oracle 11g R2


A grande dica está na junção dos campos de conta usando like concatenado com '%' no final

Como no exemplo com Oracle 11g R2, abaixo descrito:

ON m.id_centro_custo LIKE cc.id_centro_custo || '%'

Existem outras formas de se chegar ao mesmo resultado, uma delas seria implementar union em contas de grupo e depois criar uma view, mas, fica complicado e não elegante, já que, será necessário alterar a query toda vez que o plano de contas mudar a estrutura.

Outra forma seria usar querys recursivas, que seria bem mais elegante que a opção anterior, entretanto, mais complexa, mas quanto ao uso de recursividade em querys, nem todos os SGBDRs atualmente dão suporte a essa técnica, outro problema reside no fato do desempenho e consumo de recursos, pois quanto mais níveis tiver o plano de contas maior será a pilha, em outras palavras, consumo de memória e processamento no servidor do banco de dados alto.

Eu particularmente já tive a oportunidade fazer parte de uma equipe de desenvolvimento, em que o meu chefe e a própria equipe insistiram em traçar uma modelagem que usava querys recursivas, apesar de não concordar, como era novato na equipe, não me deram crédito, então me reservei a sabedoria do silêncio e mesmo não concordando aproveitei a oportunidade para fazer acontecer e vê como ficaria um sistema de custos com uso maciço de querys recursivas, foi divertido, mas ainda não recomendo, pois a complexidade é alta das querys recursivas e da codificação da aplicação também, tornando o compartilhamento do conhecimento difícil, como também a manutenção da aplicação, considerando ainda o consumo extremo de memória e processamento a nível de infra-estrutura.

Mais uma vez espero ter ajudado.


Fique na Paz do Senhor Jesus Cristo !!!

sexta-feira, 3 de agosto de 2012

Algoritmos, Caixa Eletrônico em SQL SERVER

Anteriormente em setembro/2010, havia escrito um script para caixa eletrônico em linguagem PL/PgSQL do Postgres, agora foi desenvolvido o mesmo algoritmo de caixa eletrônico para a linguagem Transact do SQL SERVER.

O parâmetro é o valor em inteiro no qual retorna as cédulas das notas em Real.

Com poucas adaptações, pode se remover a nota de 1 Real, para ficar com o novo padrão de cédulas da moeda Real do Brasil.

Mais uma vez, espero ter ajudado.

--Retornando notas do caixa eletrônico
--Notas de 1, 2, 5, 10, 20, 50 e 100

IF EXISTS (
            SELECT * 
              FROM sys.objects 
             WHERE object_id = OBJECT_ID(N'[dbo].[usf_caixa_eletro]') 
               AND type IN (N'FN')
           )
 DROP FUNCTION usf_caixa_eletro;

CREATE FUNCTION usf_caixa_eletro (@pvalor int) RETURNS varchar(max) AS 
--
-- Nome Artefato/Programa..: usf_caixa_eletro_sql_server.sql
-- Autor(es)...............: O Peregrino (emersonhermann at gmail.com) 
-- Data Inicio ............: 02/08/2012
-- Data Atual..............: 03/08/2012
-- Versao..................: 0.01
-- Linguagem...............: TRANSACT 
-- Compilador/Interpretador: T-SQL 
-- Sistemas Operacionais...: Windows
-- SGBD....................: SQL SERVER 2005/2008/2012
-- Kernel..................: Nao informado!
-- Finalidade..............: Caixa Eletronico
-- OBS1....................: Caixa Eletronico
 
--
/*
Algoritmo Caixa Eletronico
notasSaída = []                         #guardar as notas que saírão do caixa eletrônico
notas = [100, 50, 20, 10, 5, 1]         #notas que podem ser sacadas
valor = 375                             #valor a ser sacado
 
restante = valor                        #faz uma cópia do valor em "restante"
inota = 0                               #índice da nota: 0 é 100, 1 é 50, 2 é 20, 3 é 10, ...
enquanto restante>0:                    #enquanto restante for maior que 0
  resultado = restante-notas[inota]     #calcula o resultado da subtração entre o valor e a nota
  se resultado<0:                       #se for negativo:
    inota++                             #incrementa o índice para a próxima nota
  senão:                                #se for positivo ou zero:
    restante = resultado                #deixa restante com o novo resultado
    notasSaída.adicionar(notas[inota])  #adiciona a nota utilizada nas que devem sair do caixa
 
para nota em notasSaída:                # escreve as notas que devem sair
  escreva nota
 
*/
BEGIN
 DECLARE
     @sretorno   varchar(max)
    ,@qnota1     integer
    ,@qnota2     integer
    ,@qnota5     integer
    ,@qnota10    integer
    ,@qnota20    integer
    ,@qnota50    integer
    ,@qnota100   integer
    ,@pvalorx    integer
    ,@residual   integer
    ,@restante   integer
    ,@vet_notas1 integer
    ,@vet_notas2 integer
    ,@vet_notas3 integer
    ,@vet_notas4 integer
    ,@vet_notas5 integer
    ,@vet_notas6 integer
    ,@vet_notas7 integer
    ,@i          integer
    ,@resultado  integer

    SET @vet_notas1=100
    SET @vet_notas2=50
    SET @vet_notas3=20
    SET @vet_notas4=10
    SET @vet_notas5=5
    SET @vet_notas6=2
    SET @vet_notas7=1
     
    SET @i         = 1
    SET @qnota1    = 0
    SET @qnota2    = 0
    SET @qnota5    = 0
    SET @qnota10   = 0
    SET @qnota20   = 0
    SET @qnota50   = 0
    SET @qnota100  = 0
    SET @pvalorx   = 0
    SET @resultado = 0
    SET @sretorno  = ''
    SET @restante  = @pvalor
 
    WHILE (@i <= 7) BEGIN
     
    
        IF @i = 1 BEGIN 
            SET @resultado = @restante - @vet_notas1
        END ELSE IF @i = 2 BEGIN 
            SET @resultado = @restante - @vet_notas2 
        END ELSE IF @i = 3 BEGIN 
            SET @resultado = @restante - @vet_notas3
        END ELSE IF @i = 4 BEGIN 
            SET @resultado = @restante - @vet_notas4
        END ELSE IF @i = 5 BEGIN 
            SET @resultado = @restante - @vet_notas5
        END ELSE IF @i = 6 BEGIN 
            SET @resultado = @restante - @vet_notas6
        END ELSE IF @i = 7 BEGIN 
            SET @resultado = @restante - @vet_notas7
        END 

     
        IF (@resultado < 0) BEGIN
          
           SET @i = @i + 1
               
        END ELSE BEGIN -- senao
 
           SET @restante = @resultado                             

           IF @i = 1 BEGIN
               SET @qnota100 = @qnota100 + 1
           END ELSE IF @i = 2 BEGIN
               SET @qnota50 = @qnota50 + 1
           END ELSE IF @i = 3 BEGIN
               SET @qnota20 = @qnota20 + 1
           END ELSE IF @i = 4 BEGIN
               SET @qnota10 = @qnota10 + 1
           END ELSE IF @i = 5 BEGIN
               SET @qnota5  = @qnota5 + 1
           END ELSE IF @i = 6 BEGIN
               SET @qnota2  = @qnota2 + 1
           END ELSE IF @i = 7 BEGIN
               SET @qnota1  = @qnota1 + 1
           END --fim_se 
              
        END -- fim_se 
  
    END -- fim_enquanto
     
     
    SET @sretorno = 'Total: '
                 + cast (@pvalor as varchar(max))
                 + ' ' --chr(10)
                 + 'Notas de 100:'
                 + cast (@qnota100 as varchar(max))
                 + ' ' --chr(10)
                 + 'Notas de 50:'
                 + cast (@qnota50 as varchar(max))
                 + ' ' --chr(10)
                 + 'Notas de 20:'
                 + cast (@qnota20 as varchar(max))
                 + ' ' --chr(10)
                 + 'Notas de 10:'
                 + cast (@qnota10 as varchar(max))
                 + ' ' --chr(10)
                 + 'Notas de 5:'
                 + cast (@qnota5 as varchar(max)) 
                 + ' ' --chr(10)
                 + 'Notas de 2:'
                 + cast (@qnota2 as varchar(max))
                 + ' ' --chr(10)
                 + 'Notas de 1:'
                 + cast (@qnota1 as varchar(max))
                 
    RETURN (@sretorno) -- Retorna as linhas
END;
GO
 
--alguns testes, chamada da function
 
--SELECT dbo.usf_caixa_eletro(2678);
--SELECT dbo.usf_caixa_eletro(1078); 

quinta-feira, 16 de setembro de 2010

Algoritmos, Caixa Eletrônico

Algoritmos, Caixa Eletrônico
--
-- Nome Artefato/Programa..: sp_caixa_eletro.sql
-- Instituicao.............:
-- Autor(es)...............: O Peregrino (emersonhermann at gmail.com) ou (emerson at info.ufrn.br)
-- Data Inicio ............: 29/06/2010
-- Data Atual..............: 29/06/2010
-- Versao..................: 0.01
-- Linguagem...............: PL/pgSQL
-- Compilador/Interpretador: PostgreSql
-- Sistemas Operacionais...: Linux/Windows
-- SGBD....................: PostgreSql 8.x
-- Kernel..................: Nao informado!
-- Finalidade..............: Caixa Eletronico
-- OBS1....................: Caixa Eletronico

--


/*
Algoritmo Caixa Eletronico
notasSaída = []                         #guardar as notas que saírão do caixa eletrônico
notas = [100, 50, 20, 10, 5, 1]         #notas que podem ser sacadas
valor = 375                             #valor a ser sacado

restante = valor                        #faz uma cópia do valor em "restante"
inota = 0                               #índice da nota: 0 é 100, 1 é 50, 2 é 20, 3 é 10, ...
enquanto restante>0:                    #enquanto restante for maior que 0
  resultado = restante-notas[inota]     #calcula o resultado da subtração entre o valor e a nota
  se resultado<0:                       #se for negativo:
    inota++                             #incrementa o índice para a próxima nota
  senão:                                #se for positivo ou zero:
    restante = resultado                #deixa restante com o novo resultado
    notasSaída.adicionar(notas[inota])  #adiciona a nota utilizada nas que devem sair do caixa

para nota em notasSaída:                # escreve as notas que devem sair
  escreva nota

*/

--opcao 1
--Retornando notas do caixa eletrônico
--Notas de 1, 2, 5, 10, 20, 50 e 100
DROP FUNCTION IF EXISTS sp_caixa_eletro(pvalor INTEGER);
CREATE OR REPLACE FUNCTION sp_caixa_eletro (pvalor INTEGER) RETURNS text AS $$
DECLARE
    sretorno  TEXT;
    qnota1    INTEGER;
    qnota2    INTEGER;
    qnota5    INTEGER;
    qnota10   INTEGER;
    qnota20   INTEGER;
    qnota50   INTEGER;
    qnota100  INTEGER;
    pvalorx   INTEGER;
    residual  INTEGER;
    restante  INTEGER;
     vet_notas INTEGER ARRAY[7];
     i         INTEGER;
     resultado INTEGER;
BEGIN
     vet_notas[1]=100;
     vet_notas[2]=50;
     vet_notas[3]=20;
     vet_notas[4]=10;
     vet_notas[5]=5;
     vet_notas[6]=2;
     vet_notas[7]=1;
    
     i         := 1;
     qnota1    := 0;
     qnota2    := 0;
     qnota5    := 0;
     qnota10   := 0;
     qnota20   := 0;
     qnota50   := 0;
     qnota100  := 0;
     pvalorx   := 0;
     resultado := 0;
     sretorno  := '';
     restante  := pvalor;

     WHILE (i <= 7)  LOOP
    
          resultado = restante - vet_notas[i];
    
          IF (resultado < 0) THEN
         
               i := i + 1;
              
          ELSE

               restante := resultado;                              
               RAISE NOTICE 'Restante % -> Nota: R$% Qt: %', restante, vet_notas[i], resultado; 
               IF vet_notas[i] = 100 THEN
                    qnota100 := qnota100 + 1;
               ELSIF vet_notas[i] = 50 THEN
                    qnota50 := qnota50 + 1;
               ELSIF vet_notas[i] = 20 THEN
                    qnota20 := qnota20 + 1;
               ELSIF vet_notas[i] = 10 THEN
                    qnota10 := qnota10 + 1;
               ELSIF vet_notas[i] = 5 THEN
                    qnota5  := qnota5 + 1;
               ELSIF vet_notas[i] = 2 THEN
                    qnota2  := qnota2 + 1;
               ELSIF vet_notas[i] = 1 THEN
                    qnota1  := qnota1 + 1;
               END IF;
             
          END IF;
 
     END LOOP;
    
    
     sretorno := 'Total: '
                 || pvalor
                 || ' ' --chr(10)
                 || 'Notas de 100:'
                 || qnota100
                 || ' ' --chr(10)
                 || 'Notas de 50:'
                 || qnota50
                 || ' ' --chr(10)
                 || 'Notas de 20:'
                 || qnota20
                 || ' ' --chr(10)
                 || 'Notas de 10:'
                 || qnota10
                 || ' ' --chr(10)
                 || 'Notas de 5:'
                 || qnota5
                 || ' ' --chr(10)
                 || 'Notas de 2:'
                 || qnota2                
                 || ' ' --chr(10)
                 || 'Notas de 1:'
                 || qnota1;
                
     RETURN sretorno; -- Retorna as linhas
END;
$$ LANGUAGE plpgsql;

--alguns testes

--SELECT sp_caixa_eletro(2678);
--SELECT sp_caixa_eletro(1078);