Tempo de execução muito longo com ArrayList + MySql com JDBC

E aí pessoal, tudo bem? Tô ficando doido e não aguento mais ficar doido sozinho, por isso vim recorrer a vocês… :smiley:

Tenho um código em um JFrame que calcula o número de taxas emitidas, as pagas e não pagas por categoria (Enfermeiro, Técnico e Auxiliar). As duas tabelas consultadas são profisisonais (92.000 registros) e movboletos (2.100.000).

Para armazenar esta listagem, utilizei ArrayList, Para conexão com o banco MySQL usei JDBC com o drive mysql-connector-java-5.1.34-bin.jar

O problema é que para executar a query está demorando 10 segundos porém para retornar o resultado “na tela”, aproximadamente 25 minutos (impossível esperar que o usuário aguente tamanha espera).
Como não consigo imaginar a SQL de outra forma, nem conheço à fundo a linguagem em Java (é meu primeiro programa após 5 anos de PHP estruturado), recorro a vocês para entender e resolver o porquê de tanta demora. O problema é com o ArrayList, minha programação ou a SQL?
Segue abaixo as classes utilizadas:

1 - Classe que gera o ArrayList com os boletos:


public class BoletoDAO {
    
    private Connection connection;

    public BoletoDAO(){
        this.connection = new ConnectionFactory().getConnection();
    }
    
        public ArrayList<Boleto> listaInadimplentes(int ano){
    
           String sql = "SELECT M.CDCATEGORIA, M.NUINSCPROF, M.CDTIPO, M.CDTAXA, M.NUPARCELA, M.CDSTATUSP FROM MOVBOLETOS M "
                    + "WHERE "
                        + "M.ANO = "+ano+" AND ( "
                            + "M.CDCATEGORIA like '01' OR M.CDCATEGORIA like '03' OR M.CDCATEGORIA like '04'"
                            + ") AND "
                        + "M.CDTIPO like '04' AND "
                        + "M.NUINSCPROF = ("
                            + "SELECT P.NUINSCPROF FROM PROFISSIONAIS P "
                            + "WHERE "
                                + "M.CDCATEGORIA = P.CDCATEGORIA AND "
                                + "M.CDTIPO = P.CDTIPO AND "
                                + "M.CDSUBTIPO = P.CDSUBTIPO AND "
                                + "M.NUINSCPROF = P.NUINSCPROF AND "
                                + "P.CDSITUACAO = 1 "
                            + ") "
                    + "ORDER BY "
                        + "M.CDCATEGORIA, M.NUINSCPROF, M.CDTIPO, M.CDTAXA, M.NUPARCELA ASC";
            
            try{
			
		//prepara a SQL para ser executada
		PreparedStatement ps = this.connection.prepareStatement(sql);
		
		//executa a query
		ResultSet rs = ps.executeQuery();
		
		//cria uma List que ira armazenar os profissionais
		ArrayList<Boleto> boletos = new ArrayList<Boleto>();
		
                
		//enquanto houver registros, faca:                
		while(rs.next()){
                    
			Boleto boleto = new Boleto();
                        
                        boleto.setChave(rs.getString("NUINSCPROF")+"-"+rs.getString("CDCATEGORIA")+"-"+rs.getString("CDTIPO")+"-"+rs.getInt("CDTAXA"));
			boleto.setNuInscProf(rs.getString("NUINSCPROF"));
			boleto.setCdCategoria(rs.getString("CDCATEGORIA"));
			boleto.setCdTipo(rs.getString("CDTIPO"));
			boleto.setCdTaxa(rs.getInt("CDTAXA"));
                        boleto.setNuParcela(rs.getInt("NUPARCELA"));
                        boleto.setCdStatusP(rs.getString("CDSTATUSP"));
			
			//adicione este boleto encontrado na lista de boletos
			boletos.add(boleto);
		}

		//finaliza o PreparedStatement
		ps.close();
		//finaliza o ResultSet
		rs.close();
		
		//retorna a LIST para quem chamar a funcao "inadimplentes"
		return boletos;
		
		}catch(SQLException e){
			throw new RuntimeException(e);
		}
    
        }
    
    
}

O código acima demora 10 segundos para rodar no código JAVA em localhost, sendo que via phpmyadmin o tempo de execução da SQL é de 6 segundos.

2 - método initComponents da JFrame que exibe na tela o número de registros:


public void initComponentsTrue(){
..
..
..
        
        //abaixo fazemos a lógica dos inadimplentes:
        
        
        //variaveis Globais para calculo dos inadimplentes:
    int enfNum = 0;
    int enfPago = 0;
    int enfNaoPago = 0;
    int tecNum = 0;
    int tecPago = 0;
    int tecNaoPago = 0;
    int auxNum = 0;
    int auxPago = 0;
    int auxNaoPago = 0;
    int totNum = 0;
    int totPago = 0;
    int totNaoPago = 0;
        
        
    //inicializa o DAO correspondente
    BoletoDAO dao = new BoletoDAO();
    //armazena na variavel boletos de 2014.
    ArrayList<Boleto> boletos = dao.listaInadimplentes(2014);
    
    String chaveAnterior = "";
    
    for(Boleto bol : boletos){            
       
                String chaveFor = bol.getNuInscProf()+"-"+bol.getCdCategoria()+"-"+bol.getCdTipo()+"-"+bol.getCdTaxa();
                
                //se a chaveAnterior for diferente da chaveFor, é uma nova chave, portanto:
                if(!chaveAnterior.equals(chaveFor)){
                    
                    //cria variáveis para controlar o tipo de profissional
                    boolean enf = false;
                    boolean tec = false;
                    boolean aux = false;
                    //verifica se a categoria é 01 (enfermeiro)
                    if(bol.getCdCategoria().equals("01")){
                        enf=true;
                        ++enfNum;
                    }
                    //verifica se a categoria é 03 (técnico)
                    if(bol.getCdCategoria().equals("03")){
                        tec=true;
                        ++tecNum;
                    }
                    //verifica se a categoria é 04 (auxiliar)
                    if(bol.getCdCategoria().equals("04")){
                        aux=true;
                        ++auxNum;
                    }
                    
                    ArrayList<Boleto> boletosChave = new ArrayList<>();
                    boletosChave = bol.getChavesIguais(chaveFor,boletos);
                    
                    for(Boleto b : boletosChave){
                    
                        //variaveis de registro para os if's
                        int cdTaxa          = b.getCdTaxa();
                        int nuParcela       = b.getNuParcela();
                        String cdStatusP    = b.getCdStatusP();
                        
                        if(cdTaxa == 0 && nuParcela == 0 && cdStatusP != null){
                            //cria variável para controlar se as parcelas foram pagas
                            boolean todasParcelasPagas = true;
                            //cria novo array que será varrido para ver se há alguma parcela não paga dessa chave
                            ArrayList<Boleto> varreBoletos = new ArrayList<>();
                            varreBoletos = b.getChavesIguais(chaveFor,boletos);
                            //para cada boleto dentro desse ArrayList
                            for(Boleto outrosBoletos : varreBoletos){
                                //verifica se algum tem o CDSTATUSP == NULL, pois aí não foram todos pagos
                                if(outrosBoletos.getCdStatusP() == null){
                                    todasParcelasPagas = false;
                                }
                            }
                            //se todas parcelas foram pagas, soma como mais uma taxa paga, se não, como não paga
                            if(todasParcelasPagas){
                                if(enf) ++enfPago;
                                if(tec) ++tecPago;
                                if(aux) ++auxPago;                                
                            }else{
                                if(enf) ++enfNaoPago;
                                if(tec) ++tecNaoPago;
                                if(aux) ++auxNaoPago;
                            }

                            //sai desse for, passando pra próxima chave
                            break;
                        }

                        //se há somente uma parcela única (cdtaxa=0), não paga e nenhuma outra parcela adicionamos como mais um registro não pago
                        if(cdTaxa==0 && nuParcela==0 && cdStatusP==null){
                            //se o núm. de registros dessa chave for < 2, 
                            //quer dizer que só há um único registro,portanto, some mais um não pago
                            if(boletosChave.size()<2){
                                if(enf) ++enfNaoPago;
                                if(tec) ++tecNaoPago;
                                if(aux) ++auxNaoPago;                                        
                                //sai desse for, passando pra próxima chave
                                break;
                            }
                        }
                        
                        //se anuidade (taxa=0) e parcela normal (numparcela!=0)
                        if(cdTaxa == 0 && nuParcela != 0){
                                    
                            //cria variável para controlar se as parcelas foram pagas
                            boolean todasParcelasPagas = true;

                            //cria novo array que será varrido para ver se há alguma parcela não paga dessa chave
                            ArrayList<Boleto> varreBoletos = new ArrayList<>();
                            varreBoletos = b.getChavesIguais(chaveFor,boletos);

                            //para cada boleto dentro desse ArrayList
                            for(Boleto outrosBoletos : varreBoletos){
                                //se nuparcela!=0 (parcela normal) e cdstatusp==NULL (não paga)
                                if(outrosBoletos.getNuParcela() != 0 && outrosBoletos.getCdStatusP() == null){
                                    todasParcelasPagas = false;
                                }
                            }

                           //se todas parcelas foram pagas, soma como mais uma taxa paga, se não, como não paga
                            if(todasParcelasPagas){
                                if(enf) ++enfPago;
                                if(tec) ++tecPago;
                                if(aux) ++auxPago;                                
                            }else{
                                if(enf) ++enfNaoPago;
                                if(tec) ++tecNaoPago;
                                if(aux) ++auxNaoPago;
                            }

                            //sai desse for, passando pra próxima chave
                            break;
                        }
                        //qualquer taxa em que cdtaxa!=0 não é preciso nenhuma outra verificação, 
                        //só se verifica se o StatusP é == OU != de NULL
                        if(cdTaxa != 0){
                                    
                            //se cdStatusP == NULL, esta taxa não foi paga
                            if(cdStatusP==null){
                                if(enf) ++enfNaoPago;
                                if(tec) ++tecNaoPago;
                                if(aux) ++auxNaoPago;
                            }else{
                                if(enf) ++enfPago;
                                if(tec) ++tecPago;
                                if(aux) ++auxPago;   
                            }

                            //sai desse for, passando pra próxima chave
                            break;
                        }
                        
                    }//fim do for(Boleto b : boletosChave){
                    
                }//fim do if(!chaveAnterior.equals(chaveFor)){
                
                //ao final, seta que a chaveAnterior vira a chaveFor, para comparação com o próximo registro
                chaveAnterior = chaveFor;
        
    }
    
    totNum = enfNum + tecNum + auxNum;
    totPago = enfPago + tecPago + auxPago;
    totNaoPago = enfNaoPago + tecNaoPago + auxNaoPago;
    
    //fim da lógica para cálculo dos Inadimplentes.
   

        jTable1.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {
                {"Enfermeiro", enfNum, enfPago, enfNaoPago},
                {"Técnico", tecNum, tecPago, tecNaoPago},
                {"Auxiliar", auxNum, auxPago, auxNaoPago},
                {"Total", totNum, totPago, totNaoPago}
            },
            new String [] {
                "Categoria", "Emitidas", "Pagas", "Não Pagas"
            }
        ) 
...
...
...
    }

Então… eu não sei mais o que fazer, será que utilizar matriz ao invés do ArrayList é melhor? O que devo fazer? Me ajudem hehehe

Um abraço! Obrigado![/b]

O código, na sua máquina, leva aproximadamente 10 segundos pra executar e remotamente, demora aquela eternidade, é isso?
ps: No sql, eu trocaria o like pelo = mesmo.

M.CDCATEGORIA  = '01' OR M.CDCATEGORIA = '03' OR M.CDCATEGORIA = '04'

Aparentemente, o SQL em si está correto. Já chegou a executar o SQL direto no banco de dados? Quanto tempo levou?

[quote=b2000]
Então… eu não sei mais o que fazer, será que utilizar matriz ao invés do ArrayList é melhor? O que devo fazer? Me ajudem hehehe

Um abraço! Obrigado![/quote]

Você ja pensou em utilizar paginação??? Quem sabe pode lhe ajudar…

Esse link foi de um usuário que estava dando estouro na HEAP por exibir muitos itens na resposta, o que parece ser seu caso.
Java heap space no Hibernate

Abraços

[quote=bomba544]O código, na sua máquina, leva aproximadamente 10 segundos pra executar e remotamente, demora aquela eternidade, é isso?
ps: No sql, eu trocaria o like pelo = mesmo.

M.CDCATEGORIA  = '01' OR M.CDCATEGORIA = '03' OR M.CDCATEGORIA = '04'

Aparentemente, o SQL em si está correto. Já chegou a executar o SQL direto no banco de dados? Quanto tempo levou?[/quote]

Não… os testes estão sendo feitos em localhost, a SQL talvez demore “mais do que eu imaginava”, mas é a melhor performance se comparada à fazer 2 SQLs e salvá-las de um jeito ordenado no ArrayList.
Já o seu esquema do = ao invés de like, testei a performance e nesse caso, o like ficou com média de 3.50 segs enquanto que o = ficou com 3.60. Nenhuma diferença praticamente… ;/

[quote=schiefler]
Você ja pensou em utilizar paginação??? Quem sabe pode lhe ajudar…

Esse link foi de um usuário que estava dando estouro na HEAP por exibir muitos itens na resposta, o que parece ser seu caso.
Java heap space no Hibernate

Abraços[/quote]

Bom, a páginação não vejo como “mal necessário” pois a SQL em si, como mostrei no caso acima, leva menos de 4 segundos para rodar no banco, o problema é a forma de verificar os dados trazidos.
Vi no link que passasse a utilização de serialização com java.io.ObjectInputStream & ObjectOutputStream, mas será que nesse caso é necessário?

O maior problema não tem sido a SQL mas sim o tratamento dos dados.
Verifiquei que minha forma de verificar num ArrayList se uma chave existe está demorando ANOS para ser executada, talvez isso seja o maior problema, veja o trecho do código que faz isso:

Função que é chamada para trabalhar com cada chave dentro do ArrayList de boletos (323.000 linhas de dados, aproximadamente umas 46.000 chaves):

    public ArrayList<Boleto> getChavesIguais(String chave, ArrayList<Boleto> array){
                
        ArrayList<Boleto> listaChaves = new ArrayList<>();
        
        for(Boleto b : array){
            if(b.getChave().equals(chave)){
                listaChaves.add(b);
            }
        }
        
        return listaChaves;
    }

Vi que a cada iteração dessa, demoramos 15 milissegundos, para 323.000 registros dá uma soma terrível.
Por isso, como faço para armazenar com a melhor performance em um ArrayList/outra Collection/ de acordo com uma chave (como pode ser visto no código acima)?

Nesse caso meu problema é que tenho vários boletos e preciso trabalhar com todas as linhas e separá-los em chaves para fazer cálculos p/ descobrir se É MAIS UM BOLETO E ( FOI PAGO ou NÃO FOI PAGO ).
Vou exibir algumas linhas de dados para verem o problema (ver printscreen anexado).

Agradeço MUITO a atenção de vocês! Abraços.

Porque não tratar se foi pago ou não, diretamente na consulta? Algo do tipo:

SELECT coluna,coluna2,coluna3, (CASE WHEN vl_total = valor_pago THEN 'S' else 'N' end) as pago

Eu acredito, que toda operação de cálculo que você consiga fazer no SQL, é bom fazer.
O restante você trata diretamente. Ja tentou usar outro tipo de coleção ao inves do ArrayList?

[quote=bomba544]Porque não tratar se foi pago ou não, diretamente na consulta? Algo do tipo:

SELECT coluna,coluna2,coluna3, (CASE WHEN vl_total = valor_pago THEN 'S' else 'N' end) as pago

Eu acredito, que toda operação de cálculo que você consiga fazer no SQL, é bom fazer.
O restante você trata diretamente. Ja tentou usar outro tipo de coleção ao inves do ArrayList?
[/quote]

Bom, vai ser meio punk porque preciso fazer 3 coisas:
1 - O número de taxas emitidas, tipo um count geral mas só para DISTINCT (CDCATEGORIA, NUINSCPROF, CDTIPO, CDTAXA)
2 - Dentre essas emitidas, quais das DISTINCT (CDCATEGORIA, NUINSCPROF, CDTIPO, CDTAXA) em que todos registros dela possuem CDSTATUSP != NULL (pagas)
3 - e quais dessas DISTINCT possuem ao menos um CDSTATUSP = NULL (não paga)

Não consigo imaginar isso numa SQL, ficaria tenso, não achas?
(Obs.: neste momento estou tentando ver um jeito de fazer essa SQL = tô pirando) kk

É muita informação pra entender exatamente o que você trazer na consulta. Mas pelo que vi, dois dos três que você citou, dá de fazer no SQL sim.
Qualquer coisa chama pvt etento te ajudar, aí depois postamos a solução aqui…

[quote=bomba544]É muita informação pra entender exatamente o que você trazer na consulta. Mas pelo que vi, dois dos três que você citou, dá de fazer no SQL sim.
Qualquer coisa chama pvt etento te ajudar, aí depois postamos a solução aqui…[/quote]

Mandei o PVT, pra galera ajudar se possível, lá vai aqui também:

Vou passar a SQL para criar a tabela e registrar dados de teste:

CREATE TABLE boletosTeste (
NUINSCPROF varchar( 7 ) DEFAULT NULL ,
CDCATEGORIA varchar( 2 ) DEFAULT NULL ,
CDTIPO varchar( 2 ) DEFAULT NULL ,
CDTAXA int( 11 ) DEFAULT NULL ,
NUPARCELA int( 11 ) DEFAULT NULL ,
CDSTATUSP int( 11 ) DEFAULT NULL
)

INSERT INTO boletosteste (NUINSCPROF, CDCATEGORIA, CDTIPO, CDTAXA, NUPARCELA, CDSTATUSP) VALUES
(‘0001499’, ‘01’, ‘04’, 0, 0, -1),
(‘0001499’, ‘01’, ‘04’, 0, 0, -1),
(‘0001499’, ‘01’, ‘04’, 0, 1, 1),
(‘0001499’, ‘01’, ‘04’, 0, 2, NULL),
(‘0001499’, ‘01’, ‘04’, 0, 3, 1),
(‘0001499’, ‘01’, ‘04’, 0, 4, 1),
(‘0001499’, ‘01’, ‘04’, 0, 5, 1),
(‘0001515’, ‘01’, ‘04’, 0, 0, 1),
(‘0001515’, ‘01’, ‘04’, 0, 0, 1),
(‘0001515’, ‘01’, ‘04’, 0, 1, -1),
(‘0001515’, ‘01’, ‘04’, 0, 2, -1),
(‘0001515’, ‘01’, ‘04’, 0, 3, -1),
(‘0001515’, ‘01’, ‘04’, 0, 4, -1),
(‘0001515’, ‘01’, ‘04’, 0, 5, -1),
(‘0001525’, ‘01’, ‘04’, 0, 0, NULL),
(‘0001525’, ‘01’, ‘04’, 0, 0, NULL),
(‘0001525’, ‘01’, ‘04’, 0, 1, NULL),
(‘0001525’, ‘01’, ‘04’, 0, 2, NULL),
(‘0001525’, ‘01’, ‘04’, 0, 3, NULL),
(‘0001525’, ‘01’, ‘04’, 0, 4, NULL),
(‘0001525’, ‘01’, ‘04’, 0, 5, NULL),
(‘0004997’, ‘01’, ‘04’, 0, 0, NULL),
(‘0004997’, ‘01’, ‘04’, 0, 0, NULL),
(‘0004997’, ‘01’, ‘04’, 0, 1, NULL),
(‘0004997’, ‘01’, ‘04’, 0, 2, NULL);

Agora que temos uma tabela igual dá pra conversar hehehe
Então, dentre essas 25 linhas de registros, temos 4 chaves diferentes.
A chave é formada por (NUMINSCPROF+CDCATEGORIA+CDTIPO+CDTAXA).

Cada chave dessa corresponde a um boleto emitido (por isso consigo resolver facilmente os emitidos), porém, desses 4:
a 1º chave foi paga parcialmente, por isso conta como 1 boleto não pago;
a 2º chave foi paga integralmente, conta como 1 pago;
a 3º e 4º chave não foram pagas, portanto conta como mais 2 não pagas.

Então foram 4 geradas, 3 não pagas e 1 paga.
Como fazer isso numa SQL? Ferrou! kkkk

Creio que essas ações de retirar da App e mandar para o SQL só vão melhorar um pouco seu problema. O problema maior, que é a demora de visualização ainda vai existir, pois um ArrayList desse tamanho SEMPRE será problemático.

Fazendo uma analogia com a vida real, até mesmo uma lista de vestibular com tudo isso de registro demoraria pra ser percorrida e exibida. A pergunta é, na sua visualização, você realmente precisa que todos os registros apareçam logo de primeira? Em minha opinião, tudo o que alcança esse Status, ou se transforma em pesquisa, ou é paginado. Sinceramente não vejo outra forma de solucionar esse problema sem gerar “transtornos” em algum ponto da App. :-/

[quote=adriano_si]Creio que essas ações de retirar da App e mandar para o SQL só vão melhorar um pouco seu problema. O problema maior, que é a demora de visualização ainda vai existir, pois um ArrayList desse tamanho SEMPRE será problemático.

Fazendo uma analogia com a vida real, até mesmo uma lista de vestibular com tudo isso de registro demoraria pra ser percorrida e exibida. A pergunta é, na sua visualização, você realmente precisa que todos os registros apareçam logo de primeira? Em minha opinião, tudo o que alcança esse Status, ou se transforma em pesquisa, ou é paginado. Sinceramente não vejo outra forma de solucionar esse problema sem gerar “transtornos” em algum ponto da App. :-/[/quote]

Adriano, o engraçado é que em PHP eu faço isso com vetores e não demora mais que 1 minuto para exibir o resultado esperado… Como que em JAVA, uma linguagem dita “mais poderosa”, não posso processar uma lista de 323.000 registros e depois “brincar” com eles?

Achei que o ArrayList fosse mais “robusto” pra esse tipo de coisa. Não há nada que possa ser mais indicado do que “dividir” os dados em paginações? Até por quê, iria dar mais dor de cabeça para manutenção do código no futuro…

Obs.: ainda acho que uma SQL que conseguisse responder as questões dadas no último post meu resolveria o problema…

Talvez não.
Como falei, já tentou usar outro tipo de coleção para tratar essa quantidade de registros?

[quote=bomba544]Talvez não.
Como falei, já tentou usar outro tipo de coleção para tratar essa quantidade de registros?[/quote]

Só usei LinkedHashMap, pois dentre as Maps é a única que mantêm a ordenação, mas ficou tão lento quanto… Você sugere alguma collection que eu possa usar para esse caso?

Abs.

O problema da demora é o seu algoritmo para contar taxas pagas e não pagas, que é da ordem de O(n³). Veja só:


 for(Boleto bol : boletos){  //você abre um for para percorre uma lista de bolets +- 2.000.000 como você disse
     //...
    
     boletosChave = bol.getChavesIguais(chaveFor,boletos);  //daí, para cada boleto você faz uma nova busca na mesma lista buscando chaves repetidas
     
         varreBoletos = b.getChavesIguais(chaveFor,boletos);  //e mais uma vez, para cada boleto outra busca linear

ora, se você aninha 3 buscas lineares em uma lista da ordem de 10⁶ registros o total de operações é da ordem de 10¹⁸ operações. Isso é demorado para qualquer linguagem em qualquer máquina.

Outra pergunta, se (NUMINSCPROF,CDCATEGORIA,CDTIPO,CDTAXA) é uma chave, porque essa tupla se repete ?

rmendes08, boa pergunta. Essa tupla se repete pois, a “chave” é um código que faz referência à uma taxa, e cada registro dessa taxa, corresponde à uma parcela da taxa (boleto). A taxa em si é o que se forma dessa tupla, porém, há vários registros. Como primeira parcela, segunda, terceira, integral e etc…

[quote=rmendes08]O problema da demora é o seu algoritmo para contar taxas pagas e não pagas, que é da ordem de O(n³).

ora, se você aninha 3 buscas lineares em uma lista da ordem de 10⁶ registros o total de operações é da ordem de 10¹⁸ operações. Isso é demorado para qualquer linguagem em qualquer máquina.[/quote]

Muito bem observado rmendes08. Também percebi que é aí que está meu gargalo, porém não consegui sair dele por enquanto. Dado o cenário abaixo, bem simples, como conseguir:

1 - todos os boletos emitidos, sendo que cada chave representa 1 boleto emitido (A chave é formada por NUMINSCPROF+CDCATEGORIA+CDTIPO+CDTAXA).
2 - desses emitidos, os boletos pagos integralmente, sendo que para cada chave, é preciso que todos os CDSTATUSP estejam diferente de NULL.
3 - desses emitidos, os boletos não pagos, uma vez que para ser considerado não pago, ao menos um registro da chave deve possuir CDSTATUSP = NULL

CREATE TABLE `boletosTeste` ( 
`NUINSCPROF` varchar( 7 ) DEFAULT NULL , 
`CDCATEGORIA` varchar( 2 ) DEFAULT NULL , 
`CDTIPO` varchar( 2 ) DEFAULT NULL , 
`CDTAXA` int( 11 ) DEFAULT NULL , 
`NUPARCELA` int( 11 ) DEFAULT NULL , 
`CDSTATUSP` int( 11 ) DEFAULT NULL 
) 

INSERT INTO `boletosteste` (`NUINSCPROF`, `CDCATEGORIA`, `CDTIPO`, `CDTAXA`, `NUPARCELA`, `CDSTATUSP`) VALUES 
('0001', '01', '04', 0, 0, -1), 
('0001', '01', '04', 0, 0, -1), 
('0001', '01', '04', 0, 1, 1), 
('0001', '01', '04', 0, 2, NULL), 
('0001', '01', '04', 0, 3, 1), 
('0001', '01', '04', 0, 4, 1), 
('0001', '01', '04', 0, 5, 1), 
('00015', '01', '04', 0, 0, 1), 
('00015', '01', '04', 0, 0, 1), 
('00015', '01', '04', 0, 1, -1), 
('00015', '01', '04', 0, 2, -1), 
('00015', '01', '04', 0, 3, -1), 
('00015', '01', '04', 0, 4, -1), 
('00015', '01', '04', 0, 5, -1), 
('00015', '01', '04', 0, 0, NULL), 
('00015', '01', '04', 0, 0, NULL), 
('00015', '01', '04', 0, 1, NULL), 
('00015', '01', '04', 0, 2, NULL), 
('00015', '01', '04', 0, 3, NULL), 
('00015', '01', '04', 0, 4, NULL), 
('00015', '01', '04', 0, 5, NULL), 
('00049', '01', '04', 0, 0, NULL), 
('00049', '01', '04', 0, 0, NULL), 
('00049', '01', '04', 0, 1, NULL), 
('00049', '01', '04', 0, 2, NULL
); 

Para compreender melhor, dado os dados acima, as respostas automatizadas devem ser:
4 boletos gerados.
3 não foram pagos.
1 pago.

Obs.: não estou pedindo a resposta, peço a lógica em Java, já que não consigo fugir da varredura por chaves, gargalo encontrado por você no meu script…

e STATUS = -1 , o que siginifica ?

Consulta SQL que já traz isso pronto:

SELECT 
   NUINSCPROF, CDCATEGORIA, CDTIPO, CDTAXA,
   COUNT(NUPARCELA) AS PARCELAS, 
   SUM( CASE CDSTATUSP = 1 THEN 1 ELSE 0) as PARCELAS_PAGAS,
FROM boletosteste
GROUP BY NUINSCPROF, CDCATEGORIA, CDTIPO, CDTAXA

Se PARCELAS_PAGAS = PARCELAS entã o boleto foi pago integralmente, não ?

Obs: crie um índice para a chave NUINSCPROF, CDCATEGORIA, CDTIPO, CDTAXA, porque uma tabela de 2mi registros sem índice será demorada mesmo.

Todos os pagos:

SELECT distinct NUINSCPROF,CDCATEGORIA,CDTIPO,CDTAXA from boletosTeste t1 where not exists (SELECT 1 from boletosTeste t2 where t2.CDSTATUSP is null and t1.NUINSCPROF=t2.NUINSCPROF and t1.CDCATEGORIA=t2.CDCATEGORIA and t1.CDTIPO=t2.CDTIPO and t1.CDTAXA=t2.CDTAXA )
Todos os não pagos:

SELECT distinct NUINSCPROF,CDCATEGORIA,CDTIPO,CDTAXA from boletosTeste where CDSTATUSP is null

Status -1 é como se fosse 1, 0, 99, ou qualquer outro número, o que difere se é pago ou não é o Status ser NULL (não pago) ou diferente de NULL (pago)

[quote=rmendes08]Consulta SQL que já traz isso pronto:

SELECT 
   NUINSCPROF, CDCATEGORIA, CDTIPO, CDTAXA,
   COUNT(NUPARCELA) AS PARCELAS, 
   SUM( CASE CDSTATUSP = 1 THEN 1 ELSE 0) as PARCELAS_PAGAS,
FROM boletosteste
GROUP BY NUINSCPROF, CDCATEGORIA, CDTIPO, CDTAXA

Se PARCELAS_PAGAS = PARCELAS entã o boleto foi pago integralmente, não ?

Obs: crie um índice para a chave NUINSCPROF, CDCATEGORIA, CDTIPO, CDTAXA, porque uma tabela de 2mi registros sem índice será demorada mesmo.[/quote]

Isso, se NUM_PARCELAS = NUM_PARCELAS_PAGAS, então OK, está pago.
No entanto acho que esse THEN não funciona em MySQL, rodei o comando aqui e deu:
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘THEN 1 ELSE 0) as PARCELAS_PAGAS, FROM boletosteste GROUP BY NUINSCPROF, CDC’ at line 4

[quote=pmlm]Todos os pagos:

SELECT distinct NUINSCPROF,CDCATEGORIA,CDTIPO,CDTAXA from boletosTeste t1 where not exists (SELECT 1 from boletosTeste t2 where t2.CDSTATUSP is null and t1.NUINSCPROF=t2.NUINSCPROF and t1.CDCATEGORIA=t2.CDCATEGORIA and t1.CDTIPO=t2.CDTIPO and t1.CDTAXA=t2.CDTAXA )
Todos os não pagos:

SELECT distinct NUINSCPROF,CDCATEGORIA,CDTIPO,CDTAXA from boletosTeste where CDSTATUSP is null

pmlm, sua SQL de PAGOS travou meu MySQL quando executado na base de dados original (323.000 registros) e a de NÃO PAGOS retornou 25,699 registros, quando o correto deveria ser 14860 pela lógica que fiz em PHP mas não consigo replicar pra Java…

É… o esquema tá punk!

Corrija a query por favor:

CASE WHEN CDSTATUSP IS NOT NULL THEN 1 ELSE 0