INNER JOIN e OUTER JOIN
Se você é um DBA ou trabalha com programação e usa muito banco de dados SQL, este tópico não é você, a menos que não conheça as cláusulas INNER JOIN e OUTER JOIN, muito comuns em SGBDs SQL.
Não sabe o que é SGBD? Os Sistemas de Gerenciamento de Banco de Dados são programas que auxiliam na manipulação dos dados armazenados. O MySQL, PostgreSQL, MS SQL Server ou Oracle, que você tem em sua casa ou empresa, são SGBDs.
Para este tutorial você precisa ter, pelo menos, o conhecimento básico de SQL.
Chega de enrolação, vamos ao que interessa!
INNER JOIN
A cláusula INNER JOIN é usada quando se quer recuperar dados em mais de uma tabela através da igualdade de suas foreign keys.
Por exemplo, pense que você quer criar um programa que sirva como agenda telefônica. Você cadastra as pessoas e os contatos telefônicos, como uma pessoa poderá ter nenhum até N números de telefones, você separa em duas entidades como neste modelo:

Olhando rapidamente você percebe que a entidade TELEFONE tem o atributo NUM_PESSOA que é o número de identificação do registro na entidade PESSOA.
Vamos supor que as tabelas estejam preenchidas com os seguintes registros:
+----------------------------+ +---------------------------+ | PESSOA | | CONTATO | +------------+---------------+ +------------+--------------+ | NUM_PESSOA | NOME_PESSOA | | NUM_PESSOA | NUM_TELEFONE | | 1 | José | | 1 | 6589-3666 | | 2 | Arnaldo | | 3 | 9888-6699 | | 3 | Maria | | 3 | 8956-6666 | | 4 | Elverdelando | | 2 | 2888-9877 | +------------+---------------+ +------------+--------------+
Para recuperar os telefones de uma pessoa você deve usar INNER JOIN, implicitamente ou explicitamente. Vou explicar os dois casos.
O método explícito é usar a cláusula explicitamente (duh!), que é formada por INNER JOIN <tabela filha> ON <atributos de identificação>;, no nosso exemplo:
SELECT nome_completo, num_telefone FROM pessoa INNER JOIN telefone ON pessoa.num_pessoa = telefone.num_pessoa.
Estamos dizendo: "me traga o nome e os números dos telefones da pessoa onde o número da pessoa em TELEFONE seja igual ao número da pessoa em PESSOA".
Na forma implícita você não usa a cláusula INNER JOIN, o tratamento de igualdade é feito na cláusula WHERE. Usando o mesmo exemplo:
SELECT nome_completo, num_telefone FROM pessoa, telefone WHERE pessoa.num_pessoa = telefone.num_pessoa.
Em ambos os casos o resultado será:
+---------------------------+ | CONTATO | +------------+--------------+ | NOM_PESSOA | NUM_TELEFONE | | José | 6589-3666 | | Maria | 9888-6699 | | Maria | 8956-6666 | | Arnaldo | 2888-9877 | +------------+--------------+
"Que simples! Hummmm espera um pouco... Onde está o Elverdelando (que nominho, hein?!)."
Lembra que dissemos ao banco "me traga o nome e os números dos telefones da pessoa onde o número da pessoa em TELEFONE seja igual ao número da pessoa em PESSOA"? Então! O Elverdelando não esta na tabela TELEFONE, por isso não foi retornado no resultado.
Quando você precisar retornar os dados, mesmo não estando na tabela filha, usamos o OUTER JOIN.
OUTER JOIN
A clásula OUTER JOIN tem duas variantes, LEFT OUTER JOIN e RIGHT OUTER JOIN, sendo que estas cláusula devem ser declaradas sempre de maneira explícita. Alguns SGBDs facilitam a utilização do OUTER JOIN te polpando de declarar toda a sintaxe, como é o caso do Oracle que utiliza o símbolo (+) na identificação fraca, mas NÃO É ANSI, ou seja, não funcionará em qualquer SGBD.
Para que apareçam todas as pessoas, inclusive as que não têm número de telefone cadastrado usamos LEFT OUTER JOIN ou RIGHT OUTER JOIN.
A sintaxe é parecida com a INNER JOIN, mudando apenas o nome da cláusula: LEFT[RIGHT] OUTER JOIN <tabela filha> ON <atributos de identificação>;
Assim, faremos...
SELECT nome_completo, num_telefone FROM pessoa LEFT OUTER JOIN telefone ON pessoa.num_pessoa = telefone.num_pessoa
ou então
SELECT nome_completo, num_telefone FROM telefone RIGHT OUTER JOIN pessoa ON pessoa.num_pessoa = telefone.num_pessoa
Em ambos os casos o resultado será:
+-----------------------------+ | CONTATO | +--------------+--------------+ | NOM_PESSOA | NUM_TELEFONE | | José | 6589-3666 | | Maria | 9888-6699 | | Maria | 8956-6666 | | Arnaldo | 2888-9877 | | Elverdelando | | +--------------+--------------+
Como você pôde perceber, a única diferença entre o LEFT OUTER JOIN e o RIGHT OUTER JOIN é só a indicação de qual é o lado forte do relacionamento, ou seja, em qual lado da cláusula está a tabela que pode não ter valores na tabela filha. No nosso caso, ambos apontando para a tabela PESSOA.
INNER JOIN é muito usado para consulta em banco de dados, quase que totalmente em seu método implícito. Já as cláusulas OUTER JOIN também são usadas, mas não com tanta frequência, posso dizer que é raro encontra-las por aí.
Espero que este tutorial os ajude!
Recuperando cursor de uma procedure do Oracle no Java
Percebi que muitas pessoas acabam chegando aqui pesquisando uma forma de recuperar cursores através de procedures do Oracle, para estas pessoas eu coloco à disposição uma maneira de fazê-lo.
Para que o cursor possa ser retornado é preciso declara-lo como REF CURSOR no spec da Package.
--Criando o tipo REF CURSOR que será o cursor type g_cursor is ref cursor;
Em ambos, spec e body, você precisa declarar uma variável out do tipo do REF CURSOR mencionado acima.
procedure PRO_RETORNA_LISTA_CARROS(
i_id in tbl_car.car_id%type,
o_cursor in out g_cursor);
Para devolver o cursor com os resultados (caso haja), é necessário abri-lo no body da procedure, desta forma:
open o_cursor for
select car_id, company, model, color, hp, price
from tbl_car
where car_id = i_id;
A Package completa ficará assim:
create or replace package PAC_CURSOR is
--Criando o tipo REF CURSOR que será o cursor
type g_cursor is ref cursor;
--Procedure que retornará o cursor
procedure PRO_RETORNA_LISTA_CARROS(
i_id in tbl_car.car_id%type,
o_cursor in out g_cursor); -- Nosso cursor
end PAC_CURSOR;
/
create or replace package body PAC_CURSOR is
procedure PRO_RETORNA_LISTA_CARROS(
i_id in tbl_car.car_id%type,
o_cursor in out g_cursor) is
begin
--Abrindo o cursor para retornar os valores
open o_cursor for
select car_id, company, model, color, hp, price
from tbl_car
where car_id = i_id;
end PRO_RETORNA_LISTA_CARROS;
end PAC_CURSOR;
Temos o lado do Oracle pronto, agora precisamos tratar a chamada no Java.
Como o cursor está sendo retornado por uma procedure, usaremos um java.sql.CallableStatement.
CallableStatement cs = conn.prepareCall("{call PAC_CURSOR.PRO_RETORNA_LISTA_CARROS(?,?)}");
O registerOutParameter receberá o tipo oracle.jdbc.OracleTypes.CURSOR e retornará um java.sql.ResultSet. Iteraremos o ResultSet do mesmo modo que iteramos um Iterator.
Cada coluna retornada pelo SELECT será representado como um mapa, usando o getter correpondente. Por exemplo, chamaremos o método getString(<nome coluna>) quando retornar um varchar, getDate(<nome coluna>) quando retornar um date e etc.
O código completo fica assim:
//Chamando o procedure
CallableStatement cs = conn.prepareCall("{call PAC_CURSOR.PRO_RETORNA_LISTA_CARROS(?,?)}");
//Definindo o tipo do retorno, no caso o cursor
cs.registerOutParameter("o_cursor", OracleTypes.CURSOR);
cs.setLong("i_id", id);
cs.execute();//Executando a chamada
//Recuperando o cursor como um Resultset
ResultSet rs = (ResultSet)cs.getObject("o_cursor");
//Iterando as linhas retornadas
while(rs.next()){
//Obtendo o valor das colunas
System.out.println("ID: " + rs.getLong("car_id"));
System.out.println("Marca: " + rs.getString("company"));
System.out.println("Modelo: " + rs.getString("model"));
System.out.println("Cor: " + rs.getString("color"));
System.out.println("HP: " + rs.getString("hp"));
System.out.println("Preco: " + rs.getFloat("price"));
}
No final você conseguirá obter qualquer valor retornado em um SELECT.
Até a próxima!
Recuperando coleção de objetos de uma procedure Oracle
Como prometido há muito tempo atrás (e bota tempo nisso) no tópico "Passando Objetos Java para uma Procedure do Oracle", mostrarei como recuperar objetos Java contendo uma coleção de outro objetos através de uma procedure do Oracle. Para quem não leu, é altamente recomendado ler o post anterior.
Para este tutorial, precisamos incluir a tabela TBL_CLASS e adicionar sua primary key como foreign key na TBL_USER.
--num class será a PK e o desc_class será a descrição create table TBL_CLASS (num_class number, desc_class varchar(100)); alter table TBL_CLASS add primary key(num_class); alter table TBL_USER add num_class number; alter table TBL_USER add constraint FK_CLASS foreign key(num_class) references tbl_class(num_class);
Agora precisamos incluir os novos tipos:
create or replace type class_type as object (num_class number, desc_class varchar2(100), users arr_users); / create or replace type arr_class as table of class_type; /
O tipo class_type será o objeto. Repare que na sua assinatura incluímos o tipo arr_users, que será nossa coleção de user_type (vide post anterior), o tipo arr_class será uma coleção de class_type.
Agora incluiremos a procedure responsável por retornar nossa coleção de class_type.
procedure pro_select_class(clas in class_type, class_return in out arr_class)is
class_ref_cur ref_cur;
--Coleção de classes
classes arr_class := arr_class();
begin
open class_ref_cur for
select cast(
multiset(
select num_class,
desc_class,
(select cast(
multiset(
select user_name,
height,
b_date
from tbl_user
--Fazendo o JOIN com a TBL_USER
where tbl_user.num_class = tbl_class.num_class
) as arr_users)
from dual) users
from tbl_class
--Usando o atributo num_class do parâmetro de entrada
where num_class = clas.num_class) as arr_class
) classes
from dual;
--incluindo ao retorno no array
fetch class_ref_cur into classes;
--passando o array para a variável out
class_return := classes;
end pro_select_class;
Repare que a procedure recebe o tipo class_type como parâmetro in e retorna o tipo arr_class.
Separando trecho responsável por resgatar e montar nossos objetos teremos:
--Montará a coleção de retorno
select cast(
multiset(
--Retornará os objetos class_type e seus atributos
select num_class,
desc_class,
--Populará a coleção com user_type
(select cast(
multiset(
select user_name,
height,
b_date
from tbl_user
where tbl_user.num_class = tbl_class.num_class
) as arr_users)
from dual) users
from tbl_class
where num_class = clas.num_class) as arr_class
) classes
from dual;
Os objetos de banco estão prontos, agora para o Java!
Criaremos o objeto que será interpretado pelo Oracle. Vamos chamá-lo de TypeClass:
public class TypeClass implements SQLData{
public static final String ORACLE_OBJECT_NAME = "CLASS_TYPE"; //Nome do tipo no Oracle
public static final String ORACLE_CLASS_ARRAY_NAME = "ARR_CLASS"; //Nome do array no Oracle
//campos criados na tabela TBL_CLASS
private Long number;
private String desc;
private Array users; //Esta será a coleção de user_type (ou TypeUser no Java)
public String getSQLTypeName() throws SQLException {
return ORACLE_OBJECT_NAME;
}
public void readSQL(SQLInput stream, String typeName) throws SQLException {
setNumber(stream.readLong());
setDesc(stream.readString());
setUsers(stream.readArray());//Usado pelo JDBC driver para ler a coleção
}
public void writeSQL(SQLOutput stream) throws SQLException {
stream.writeLong(getNumber());
stream.writeString(getDesc());
stream.writeArray(getUsers());//Usado pelo JDBC driver para armazenar a coleção
}
//Getters e setters omitidos
}
Precisamos mapear os dois tipos já que ambos serão interpretados na requisição, desta forma:
Map> typeMaps = connection.getTypeMap(); typeMaps.put(TypeUser.ORACLE_OBJECT_NAME, TypeUser.class); typeMaps.put(TypeClass.ORACLE_OBJECT_NAME, TypeClass.class);
Também precisaremos mapear os dois arrays:
typeMaps.put(TypeClass.ORACLE_CLASS_ARRAY_NAME, TypeClass[].class);//retornado pela procedure typeMaps.put(TypeUser.ORACLE_USER_ARRAY_NAME, TypeUser[].class);//retornado pela coleção de class_type
Para a requisição faremos:
cs = conn.prepareCall("{call PAC_BEAN.PRO_SELECT_CLASS(?,?)}");
//registrando o tipo de saída, que será um array de TypeClass
cs.registerOutParameter("class_return", OracleTypes.ARRAY, TypeClass.ORACLE_CLASS_ARRAY_NAME);
//passando o objeto de parâmetros da query
cs.setObject("clas", classQry);
cs.execute();
//recuperando e iterando o array de TypeClass
Object[] array = (Object[])cs.getArray("class_return").getArray();
for(Object obj : array){
TypeClass objClass = ((TypeClass)obj);
System.out.println("Description: "+objClass.getDesc());
//Aqui pegamos a coleção (ou array) de user_type(TypeUser) retornada pela query.
Object[] userArray = (Object[])objClass.getUsers().getArray();
for(Object user : userArray){
System.out.println("\tName: " + ((TypeUser)user).getName());
System.out.println("\tHeight: " + ((TypeUser)user).getHeight());
System.out.println("\tBirth: " + sdf.format(((TypeUser)user).getBirth())+ "\r\n");
}
}
O final você terá um TypeClass que contém um java.sql.Array de TypeUser no atributo getUsers().
E aqui cumpro minha promessa. Baixe o código fonte com o exemplo contendo este e o post anterior aqui.
Até a próxima!
Passando Objetos Java para uma procedure do Oracle
Na empresa onde trabalho houve uma discussão sobre a possibilidade de passar um objeto Java para dentro de uma procedure ou function do Oracle, então resolvi pesquisar e aqui está uma forma bem simples de atingir este objetivo.
Este tutorial só funciona com a versão 9i do Oracle, ou superior, e usando o driver JDBC ojdbc14g, ou superior.
Primeiro precisaremos criar as tabelas, objetos e procedures. Lembrando que os tipos tbl_users e user_type deverão ser declarados como globais para funcionar, ou seja, fora de packages:
-- Criando a tabela create table tbl_user(user_name varchar2(100), height number, b_date date); / --Criando o tipo user_type (nosso bean) create or replace type user_type as object (user_name varchar2(100), height number, birth_date date); / --Criando o tipo arr_users, que é do tipo table of user_type (array de user_type) create or replace type arr_users as table of user_type; /
Criando o spec e body da package que conterá as procedures
--Spec create or replace package PAC_BEAN is -- REF CURSOR deve ser declarado dentro da package type ref_cur is ref cursor; -- Procedure usada para o insert procedure pro_insert_user(usu in user_type); -- Procedure usada para o select procedure pro_select_user(usu in user_type, user_return in out arr_users); end PAC_BEAN; /
--Body
create or replace package body PAC_BEAN is
--A procedure de insert receberá o tipo user_type (nosso bean)
--e o cadastrará na tabela tbl_user
procedure pro_insert_user(usu in user_type) is
begin
insert into tbl_user (user_name, height, b_date)
values (usu.user_name, usu.height, usu.birth_date);
commit;
exception
when others then
rollback;
end pro_insert_user;
--Procedure de select que receberá um user_type com a informação do nome
--(cláusula where) e retornará o array arr_users (array de user_type)
procedure pro_select_user(usu in user_type, user_return in out arr_users)is
user_ref_cur ref_cur;
--Instanciando o array
users arr_users := arr_users();
begin
--Abrindo o cursor que retornará nosso array
open user_ref_cur for
select cast(
multiset(
select user_name,
height,
b_date
from tbl_user
where user_name like '%'||usu.user_name||'%'
) as arr_users
) arr
from dual;
--Jogando o retorno do cursor dentro da instância de arr_users
fetch user_ref_cur into users;
--Retornando a instância através da variável OUT
user_return := users;
end pro_select_user;
end PAC_BEAN;
/
Consulte sobre o funcionamento do CAST e MULTISET.
Construído os objetos de banco precisamos prepara o JavaBean. Ele será uma implementação de java.sql.SQLData, por isso será necessário implementar os métodos:
getSQLTypeName() - É o getter usado para obter o nome do tipo.
readSQL(SQLInput, String) - Usado para converter o objeto SQL em objeto Java.
writeSQL(SQLOutput stream) - Usado para montar o objeto SQL, usado pelo Driver JDBC.
public class TypeUser implements SQLData{
//O nome do tipo declarado no Oracle
public static final String ORACLE_OBJECT_NAME = "USER_TYPE";
//O nome do array declarado no Oracle
public static final String ORACLE_USER_ARRAY_NAME = "ARR_USERS";
//Os atributos
private String name;
private Float height;
private Date birth;
public TypeUser() {
height = 0F;
}
//Getter retorna o nome do tipo ao JDBC
public String getSQLTypeName() throws SQLException {
return ORACLE_OBJECT_NAME;
}
public void readSQL(SQLInput stream, String typeName) throws SQLException {
setName(stream.readString());
setHeight(stream.readFloat());
setBirth(stream.readDate());
}
public void writeSQL(SQLOutput stream) throws SQLException {
stream.writeString(getName());
stream.writeFloat(getHeight());
stream.writeDate(getBirth() != null ?
new java.sql.Date(getBirth().getTime()) : null);
}
//getters e setters omitidos
}
Para definir que um tipo poderá ser enviado à procedure é necessário adicioná-lo ao mapa de tipos através de Connection.getTypeMap(). Este método retorna um Map<String,Class<?>>, onde a chave é o nome do tipo e o valor será a classe SQLData implementada, no nosso caso a TypeUser definida acima. Exemplo:
Map> typeMaps = connection.getTypeMap(); typeMaps.put(TypeUser.ORACLE_OBJECT_NAME, TypeUser.class);
A conexão ficará desta forma:
//Fazendo a conexão
Class.forName("oracle.jdbc.driver.OracleDriver");
connection = DriverManager.getConnection("jdbc:oracle:thin:@<host>:<porta>:<bd>","<usuario>","<senha>");
//Mapeando o tipo necessário
Map<String,Class<?>> typeMaps = connection.getTypeMap();
typeMaps.put(TypeUser.ORACLE_OBJECT_NAME, TypeUser.class);
Agora que temos a nossa conexão podemos fazer o método de insert, que receberá uma instância TypeUser e uma Connection:
CallableStatement cs = null;
try {
//chamando a procedure de insert
cs = conn.prepareCall("{call PAC_BEAN.PRO_INSERT_USER(?)}");
//definindo a instância de TypeUser como paramêtro "usu" da procedure
cs.setObject("usu", typeUser);
cs.execute();
} catch (SQLException e) {
e.printStackTrace();
}
Agora que temos o método de insert, partiremos para o método de select. Como este método retorna um array de objetos, é necessário inserir o tipo do array TypeMap da conexão. O nome passado como chave deve ser o nome do tipo do array no Oracle e o valor será a classe do array que esperamos, desta forma:
connection.getTypeMap().put(TypeUser.ORACLE_USER_ARRAY_NAME, TypeUser[].class);
Para a chamada da procedure e registerOutParameter:
cs = conn.prepareCall("{call PAC_BEAN.PRO_SELECT_USER(?,?)}");
cs.registerOutParameter("user_return", OracleTypes.ARRAY, TypeUser.ORACLE_USER_ARRAY_NAME);
cs.setObject("usu", typeUserQry);
cs.execute();
O método para recuperar o array deverá ser chamado desta forma:
//user_return é o nome da variável OUT da procedure
Object[] array = (Object[])cs.getArray("user_return").getArray();
Se até aqui deu tudo certo fique contente, para resgatar os valores do array é só iterá-lo e fazer cast para a classe TypeUser.
for(Object obj : array){
System.out.println("Nome: " + ((TypeUser)obj).getName());
System.out.println("Altura: " + ((TypeUser)obj).getHeight());
System.out.println("Data de Nascimento: " + sdf.format(((TypeUser)obj).getBirth()));
}
Desta forma poderemos passar e resgatar objetos simples de uma procedure/function do Oracle.
Próximo passo: Recuperando coleção de objetos de uma procedure Oracle.
Até lá!
Baixe o código fonte deste tutorial aqui.