A linguagem de banco de dados SQL permite que você retorne um resultado de dados completo de uma única vez, mas as vezes precisamos manipular este resultado em tempo de execução, uma linha de cada vez, deste modo podemos utilizar um recurso chamado Cursor para realizar este procedimento.
Basicamente todos os bancos de dados possuem este recurso, entretanto cada um deles possui também suas peculiaridades e funcionalidades diferenciadas, mas no geral funcionam da seguinte maneira: a instrução DECLARE CURSOR define os atributos de um cursor do servidor SQL, como o seu comportamento de rolagem e a consulta usada para construir o conjunto de resultados no qual o cursor funciona. A instrução OPEN popula o conjunto de resultados e FETCH retorna uma linha do conjunto de resultados. A instrução CLOSE libera o conjunto de resultados atual associado ao cursor. A declaração DEALLOCATE libera os recursos usados pelo cursor.
Preparando o Banco de dados
Utilizaremos uma modelagem básica contendo três tabelas básicas sem chave primaria, sem chave estrangeira, permitido assim que nosso exemplo possua dados duplicados, basicamente utilizaremos apenas a logica relacional para brincar com os dados utilizando uma Stored procedure contendo um cursor.
Relacionamentos
A tabela de FUNCIONARIOS contem dados de cadastro e o salario de cada funcionário, a tabela DESCONTO possui os valores de porcentagens de desconto para cada funcionário, e a tabela SALARIO contem os lançamentos de salários e deve ser alimentada mês a mês para cada funcionário. Utilize os Scripts abaixo para criar as tabelas e os dados iniciais.
Visual Studio
Para efetuar as conexões com os diferentes bancos de dados você precisa primeiro configurar os drivers ODBC necessários no sistema operacional, depois você pode criar um design com 3 componentes RadioButton, um componente Button e um componente CListControl, você encontra um walkthrough de como configurar os drivers ODBC na categoria SQL e C++ deste site, use a figura abaixo para referencia do design:
Algo Extremamente Útil Sobre Cursores
Cursores são mais rápidos do que os looping efetuados dentro da linguagem de programação de sua preferencia, funcionam basicamente da mesma maneira, porem residem no motor do banco de dados dentro de uma Stored Procedure.
Os Cursores utilizam memoria e também necessitam de um laço para rolar registro a registro do resultado de dados e manipula-lo, é de extrema importância que você utilize cursores somente quando necessário. Caso queira que seu programa tenha uma ótima performance, antes de optar por um cursor é necessário saber que os motores dos bancos de dados são projetados para manipular dados em massa, manipulação de dados registro a registro deve ser utilizada somente quando não houver possibilidade de usar instruções SQL, para ilustrar esta explicação vamos escrever um script SQL que executa o mesmo procedimento do cursor utilizando apenas um acesso ao banco de dados, você encontra este script logo após o código C++.
Exemplo:
Neste exemplo criamos um cursor que reside dentro de uma Stored Procedure, calcula o desconto do salario dos funcionários e insere os valores líquidos em uma tabela de lançamento, após efetuar o procedimento que seria mensal, uma query exibe o relatório em uma grade de dados.
Cursor
O cursor seleciona dados da tabela de FUNCIONARIOS e da tabela de DESCONTO, através de um looping, alimenta os dados relevantes dentro de variáveis e insere os lançamentos na tabela de SALARIO efetuando o calculo do desconto.
SQL
Oracle
create table Funcionarios
(
ID_Funcionario NUMBER(5),
Nome VARCHAR2(30),
Sobrenome VARCHAR2(70),
Cargo VARCHAR2(30),
Salario NUMBER(9,2)
);
Insert into FUNCIONARIOS values (1,'Steve','Gates','Programador',2550.56);
Insert into FUNCIONARIOS values (2,'Bill','Jobs','Diretor',5143.71);
-- Cria tabela com a porcentagem de descontos
Create table DESCONTO (
ID_FUNCIONARIO NUMBER,
PORCENTAGEM NUMBER(9,2));
-- Insere porcentagem por funcionario
Insert Into DESCONTO Values (1, 5 );
Insert Into DESCONTO Values (2, 8 );
-- Cria tabela de lançamentos de descontos
Create table SALARIO (
ID_FUNCIONARIO NUMBER,
DATA_LANC DATE,
VDESCONTO NUMBER(9,2));
-- Lista lançamentos por funcionario
select * from salario;
-- Desenvolvimento Aberto - Cursor explicito
create or replace Procedure CalculoDesconto is
-- Declara cursor
Cursor calculo is
Select A.ID_FUNCIONARIO, A.SALARIO, B.PORCENTAGEM
from FUNCIONARIOS A, DESCONTO B
Where
A.ID_FUNCIONARIO = B.ID_FUNCIONARIO;
-- Declara variáveis
pID NUMBER;
pSalario NUMBER(9,2);
pPorcentagem NUMBER(9,2);
-- Abre cursor
begin
open calculo;
-- Cria laço
loop
-- Alimenta variáveis
fetch calculo into pID, pSalario, pPorcentagem;
EXIT WHEN calculo%NOTFOUND;
-- Insere valores da tabela
Insert into SALARIO
values (
pID,
SYSDATE,
(pSalario * pPorcentagem)/100);
end loop;
-- Fecha cursor
close calculo;
end;
DB2
create table Funcionarios
(
ID_Funcionario INTEGER,
Nome VARCHAR(30),
Sobrenome VARCHAR(70),
Cargo VARCHAR(30),
Salario NUMERIC(9,2)
);
Insert into FUNCIONARIOS values (1,'Steve','Gates','Programador',2550.56);
Insert into FUNCIONARIOS values (2,'Bill','Jobs','Diretor',5143.71);
-- Cria tabela com a porcentagem de descontos
Create table DESCONTO (
ID_FUNCIONARIO INTEGER,
PORCENTAGEM DECIMAL(9,2));
-- Insere porcentagem por funcionario
Insert Into DESCONTO Values (1, 5 );
Insert Into DESCONTO Values (2, 8 );
-- Cria tabela de lançamentos de descontos
Create table SALARIO (
ID_FUNCIONARIO INTEGER,
DATA_LANC DATE,
VDESCONTO DECIMAL(9,2));
-- Lista lançamentos por funcionario
select * from desconto;
-- Desencolcimento Aberto - Cursor explicito
CREATE PROCEDURE calculodesconto ()
DYNAMIC RESULT SETS 1
P1: BEGIN
-- Declara variáveis
DECLARE pID INTEGER;
DECLARE pSalario DECIMAL(9,2);
DECLARE pPorcentagem DECIMAL(9,2);
DECLARE eof SMALLINT DEFAULT 0;
-- Declara cursor
DECLARE calculo CURSOR WITH RETURN for
Select A.ID_FUNCIONARIO, A.SALARIO, B.PORCENTAGEM
from FUNCIONARIOS A, DESCONTO B
Where
A.ID_FUNCIONARIO = B.ID_FUNCIONARIO;
-- Declara handler para final de arquivo
DECLARE CONTINUE HANDLER FOR NOT FOUND SET eof = 1;
-- Abre cursor
OPEN calculo;
-- Cria label e executa looping
fim:
LOOP
-- Alimenta valores nas variáveis
FETCH calculo into pID, pSalario, pPorcentagem;
IF eof <> 0 THEN LEAVE fim;
END IF;
-- Insere dados na tabela
Insert into SALARIO values (
pID,
Current date,
(pSalario * pPorcentagem)/100);
END LOOP fim;
CLOSE calculo;
END P1
Mssql
create table Funcionarios
(
ID_Funcionario Int,
Nome VARCHAR(30),
Sobrenome VARCHAR(70),
Cargo VARCHAR(30),
Salario Decimal(9,2)
);
Insert into FUNCIONARIOS values (1,'Steve','Gates','Programador',2550.56);
Insert into FUNCIONARIOS values (2,'Bill','Jobs','Diretor',5143.71);
-- Cria tabela com a porcentagem de descontos
Create table DESCONTO (
ID_FUNCIONARIO INT,
PORCENTAGEM DECIMAL(9,2));
-- Insere porcentagem por funcionario
Insert Into DESCONTO Values (1, 5 );
Insert Into DESCONTO Values (2, 8 );
-- Cria tabela de lançamentos de descontos
Create table SALARIO (
ID_FUNCIONARIO INT,
DATA_LANC DATE,
VDESCONTO DECIMAL(9,2));
-- Lista lançamentos por funcionario
select * from salario;
-- Desenvolvimento Aberto - cursor explicito
Create Procedure CALCULODESCONTO
AS
BEGIN
-- Declara Variáveis
DECLARE @pID INT,
@pSalario DECIMAL(9,2),
@pPorcentagem DECIMAL(9,2);
-- Declara cursor
DECLARE calculo CURSOR FOR
Select A.ID_FUNCIONARIO, A.SALARIO, B.PORCENTAGEM
from FUNCIONARIOS A, DESCONTO B
Where
A.ID_FUNCIONARIO = B.ID_FUNCIONARIO;
-- Abre cursor
Open calculo;
-- Alimenta -
FETCH NEXT FROM calculo INTO @pID, @pSalario, @pPorcentagem;
While @@FETCH_STATUS = 0
BEGIN
Insert into SALARIO values (
@pID,
GETDATE(),
(@pSalario * @pPorcentagem)/100);
FETCH NEXT FROM calculo INTO @pID, @pSalario, @pPorcentagem;
END
-- Fecha conteudo do cursor
Close calculo;
-- Desaloca cursor da memória
Deallocate calculo;
END
C++
Classe: CAcessocpp
Arquivo .h
// Classe gerada automaticamente
// AcessocppDlg.h : header file
//
#pragma once
// Inclui classe de banco de dados MFC
#include "afxdb.h"
#include "afxcmn.h"
class CAcessocppDlg : public CDialogEx
{
public:
CAcessocppDlg(CWnd* pParent = NULL); // standard constructor
enum { IDD = IDD_ACESSOCPP_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
protected:
HICON m_hIcon;
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
// Cria métodos e objetos da classe
CDatabase db;
CString bancodedados;
void conectarDB(CString dns, CString usuario, CString senha);
afx_msg void OnBnHotItemChangeRadio1(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnBnHotItemChangeRadio2(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnBnHotItemChangeRadio3(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnBnClickedButton1();
CListCtrl m_tabela;
};
Arquivo .cpp
// Código gerado automaticamente
// AcessocppDlg.cpp : implementation file
//
#include "stdafx.h"
#include "Acessocpp.h"
#include "AcessocppDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
class CAboutDlg : public CDialogEx
{
public:
CAboutDlg();
enum { IDD = IDD_ABOUTBOX };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()
CAcessocppDlg::CAcessocppDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CAcessocppDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CAcessocppDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_LIST2, m_tabela);
}
BEGIN_MESSAGE_MAP(CAcessocppDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_NOTIFY(BCN_HOTITEMCHANGE, IDC_RADIO1, &CAcessocppDlg::OnBnHotItemChangeRadio1)
ON_NOTIFY(BCN_HOTITEMCHANGE, IDC_RADIO2, &CAcessocppDlg::OnBnHotItemChangeRadio2)
ON_NOTIFY(BCN_HOTITEMCHANGE, IDC_RADIO3, &CAcessocppDlg::OnBnHotItemChangeRadio3)
ON_BN_CLICKED(IDC_BUTTON1, &CAcessocppDlg::OnBnClickedButton1)
END_MESSAGE_MAP()
BOOL CAcessocppDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
// Desenvolvimento Aberto
// Inicializa dialogo
// Define variavel padrão para o banco de dados
bancodedados = "oracle";
// Cria o modelo de exibição de dados
m_tabela.SetView(LV_VIEW_DETAILS);
m_tabela.SendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_GRIDLINES);
return TRUE;
}
void CAcessocppDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
void CAcessocppDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this);
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
HCURSOR CAcessocppDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
// **************************
// * Desenvolvimento Aberto *
// **************************
// Nosso código começa aqui
// Seleciona banco de dados Oracle
void CAcessocppDlg::OnBnHotItemChangeRadio1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMBCHOTITEM pHotItem = reinterpret_cast<LPNMBCHOTITEM>(pNMHDR);
bancodedados = "oracle";
*pResult = 0;
}
// Seleciona banco de dados IBM DB2
void CAcessocppDlg::OnBnHotItemChangeRadio2(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMBCHOTITEM pHotItem = reinterpret_cast<LPNMBCHOTITEM>(pNMHDR);
bancodedados = "db2";
*pResult = 0;
}
// Seleciona banco de dados MSSQL
void CAcessocppDlg::OnBnHotItemChangeRadio3(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMBCHOTITEM pHotItem = reinterpret_cast<LPNMBCHOTITEM>(pNMHDR);
bancodedados = "mssql";
*pResult = 0;
}
// Evento de clique do botão
void CAcessocppDlg::OnBnClickedButton1()
{
// campo de dado da tabela
CString m_campo;
// Abre conexão - ODBC - (DNS,user,pass)
if (bancodedados == "oracle")
{
conectarDB(L"OracleXE", L"user", L"p@55w0rd");
}
if (bancodedados == "db2")
{
conectarDB(L"IBMDB2", L"user", L"p@55w0rd");
}
if (bancodedados == "mssql")
{
conectarDB(L"MSSQLSERVER", L"user", L"p@55w0rd");
}
// Cria um set de dados
CRecordset dados(&db);
if (bancodedados == "mssql")
{
db.ExecuteSQL(_T("EXEC CALCULODESCONTO"));
}
else
{
db.ExecuteSQL(_T("CALL CALCULODESCONTO();"));
}
CString sql = L"Select A.ID_FUNCIONARIO, A.NOME, A.CARGO, A.SALARIO, B.PORCENTAGEM, C.VDESCONTO, C.DATA_LANC, A.SALARIO - C.VDESCONTO AS SLIQUIDO from FUNCIONARIOS A, DESCONTO B, SALARIO C Where A.ID_FUNCIONARIO = B.ID_FUNCIONARIO AND A.ID_FUNCIONARIO = C.ID_FUNCIONARIO";
dados.Open(CRecordset::forwardOnly, sql);
// Cria item
LVITEM lvItem;
// cria estrutura para inserir o item
typedef struct _LVITEM
{
UINT mask;
int iItem;
int iSubItem;
UINT state;
UINT stateMask;
LPTSTR pszText;
int cchTextMax;
int iImage;
LPARAM lParam;
#if (_WIN32_IE >= 0x0300)
int iIndent;
#endif
} LVITEM, FAR *LPLVITEM;
// Define variaveis de itens
int InsertItem(const LVITEM* pItem);
int nItem;
// Converte CString para LPTSTR atraves de um TCHAR
TCHAR sz[1024];
// Verifica colunas
short nFields = dados.GetODBCFieldCount();
int colunas = m_tabela.GetHeaderCtrl()->GetItemCount();
// Verifica colunas
if (colunas == 0)
{
// Lê metadata da tabela
CODBCFieldInfo field;
for (UINT i = 0; i < nFields; i ++)
{
dados.GetODBCFieldInfo(i, field);
m_tabela.InsertColumn(i, field.m_strName, LVCFMT_LEFT, 100);
}
}
// Deleta itens do controle de lista
m_tabela.DeleteAllItems();
// Recupera dados da tabela
while (!dados.IsEOF())
{
for (short index = 0; index < nFields; index++)
{
dados.GetFieldValue(index, m_campo);
// Retorna linha do banco de dados
if (index == 0)
{
// Insere linha
lvItem.mask = LVIF_TEXT;
lvItem.iItem = 0;
lvItem.iSubItem = 0;
lvItem.pszText = lstrcpy(sz, m_campo);
nItem = m_tabela.InsertItem(&lvItem);
}
// Retorna colunas da linha
m_tabela.SetItemText(nItem, index, lstrcpy(sz, m_campo));
}
// Move o cursor para a proxima linha
dados.MoveNext();
}
// Fecha o set de dados e a conexão
dados.Close();
db.Close();
}
void CAcessocppDlg::conectarDB(CString dns, CString usuario, CString senha)
{
// Cria string de conexão ODBC
CString conexao;
// Cria string de conexão
conexao = L"DSN=" + dns + L";UID=" + usuario + L";PWD=" + senha;
// Abre conexão
db.OpenEx(conexao, 0);
}
SQL – A instrução a seguir, substitui o cursor em um único acesso. (Oracle)
-- Este script SQL efetua o mesmo procedimento do cursor -- utilizando apenas um acesso ao banco de dados. -- é importante projetar seu acesso a dados para obter alta performance -- você deve utilizar cursores apenas quando realmente for necessário. -- -- Para os outros bancos substitua o campo SYSDATE -- Para DB2 use Current date -- Para MSSQL use GETDATE() Insert into SALARIO Select A.ID_FUNCIONARIO,SYSDATE, ((A.SALARIO * B.PORCENTAGEM)/100) from FUNCIONARIOS A, DESCONTO B Where A.ID_FUNCIONARIO = B.ID_FUNCIONARIO;



