Translate this page now :



»Programação
»Programação.NET
»Banco de Dados
»Webdesign
»Office
» Certificações Microsoft 4
»Treinamentos4
»Programação 4
»Webdesign«
»Office & User Tips«
»Grupos de Usuários
»Células Acadêmicas«
intcontpiada : 118
Flanelinha
Você já está cadastrado e participa do grupo de usuários de sua cidade ? Se não, comente o porque.
 
 
Faça um pequeno teste com 10 questões de VB
.:.
Teste seus conhecimentos em Visual Basic, SQL Server e ASP 3.0 com nossas provas on-line
.:.
Aprimore seus conhecimentos em programação com nosso treinamento on-line de lógica de programação
.:.
Veja nosso calendário de treinamentos
Gostou da Página?
Então

para um amigo!
 







Pesquisa personalizada
Pesquisar Dicas:

 






Quer saber mais?
Não deixe escapar essa oportunidade!
Faça um treinamento para Webdeveloper na Búfalo Informática

Paginação com ASP e servidores de banco


Muitos de vocês já devem ter lido artigos sobre paginação de dados em outros sites. Todos os artigos sobre esse assunto que vi até hoje sempre explicam o uso das propriedades do recordset (absolutepage,pagecount,pagesize, etc.), mas nenhum deles explica claramente que para essas propriedades funcionarem o cursor tem que estar no client (cursorlocation=3), ou seja, a cada página de 10 registros exibida, todos os dados do select são recuperados do servidor de banco e transmitidos pela rede para o servidor web, que então decide qual conjunto de 10 registros irá exibir.

Quando estamos trabalhando com bancos Access, então não tem alternativa, por mais que se faça um algorítimo melhor o access sempre trabalhará desta forma. Mas quando estamos trabalhando com SQL Server ou Oracle isso é definitivamente um mal uso dos recursos que temos em mãos.

Assim sendo, esse algorítimo de paginação que utiliza as propriedades do recordset para realizar a paginação não deveria nunca ser utilizado com um bom servidor de dados. O impacto em performance, nos casos mais críticos, pode ser considerável.

Como resolver então ?

Temos que fazer a paginação por conta própria. A instrução select não pode ser sempre a mesma, temos que alterar a instrução select de forma a que ela nos traga sempre uma página de registros diferente. A instrução será alterada conforme seja a 1a entrada na página, o usuário esteja indo para a próxima página ou voltando para a anterior.

Precisamos, ainda assim, de um "marcador", de alguma coisa que nos indique em que página estamos, qual é a próxima e qual é a anterior. Esse "marcador" precisará estar no banco de dados, pois com base nele iremos alterar a instrução SELECT para garantir que iremos buscar no servidor de dados a página certa e trazer para o servidor web apenas os 10 registros que serão exibidos.

Esse "marcador", então, precisará ser um campo da tabela para o qual estaremos utilizando um order by. No nosso exemplo faremos a paginação sobre a tabela PRODUCTS do northwind e utilizaremos o PRODUCTID como campo "marcador".

Para complicar um pouco mais o exemplo, vamos também fazer uma filtragem por CategoryID, a categoria do produto. Então precisamos primeiramente de uma página para perguntar ao usuário a categoria do produto. Não há nada de novo neste código :

<HTML>
     <BODY>
     <form method=post action="exibir.asp">
     Categoria : <select name="txtcat">
     <% dim cn,rs
     set cn=createobject("adodb.connection")
     set rs=createobject("adodb.recordset")
     cn.open Application("strConexao")
     rs.open "Select categoryid,categoryname from categories",cn
     while not rs.eof
     response.write("<option value=" & rs.fields("categoryid")      & ">" & rs.fields("categoryname") & "</option>")
     rs.movenext
     wend
     rs.close
     cn.close
     set rs=nothing
     set cn=nothing
     %>
     </select>
     <br>
     <input type=submit value="Pesquisar">
     </form>
     </body>
     </html>
Vamos chamar este arquivo de "Pesquisar.ASP". Observe que neste exemplo estou considerando a string de conexão guardada em uma variável de applicação, o que poderia ser feito no Global.ASA, mas isso é detalhe. O banco é SQL Server.

Neste exemplo é recuperado um recordset com os campos categoryid e categoryname da tabela categories e é feita a montagem de options de uma combo, sendo o value dos options o categoryid.

Montada esta página, vamos partir para a montagem da página "Exibir.asp". Observe como o Action do formulário aponta para esta página.

Antes de entrarmos em detalhes de código é importante entender um pouco a estrutura da página exibir.ASP. Esta página precisará ter botões "próximo" e "anterior". Esses botões irão chamar a própria página Exibir.ASP, mas não são simples links : Eles precisam transmitir informações para a página Exibir.ASP, informações que digam a ela em que página ela estava, qual os marcadores atuais e se a movimentação tem que ser para frente ou para trás.

Essas informações transmitidas serão utilizadas logo no início da Exibir.ASP para decidir qual SELECT deverá ser feito. Mas o código de montagem dos formulários que irão transmitir essas informações só estará no final do Exibir.ASP.

Vamos analisar o código do Exibir.ASP aos poucos. Vamos começar com um pequeno pedaço :

<HTML>
     <body>
<%
     dim cn,rs,tp,a,codprev,codnext
     tp=""
     set cn=createobjecT("adodb.connection")
     set rs=createobject("adodb.recordset")
     cn.open "Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Initial      Catalog=Northwind;Data Source=."
     rs.cursorlocation=2
     rs.cursortype=3
     if request.form("cod")="" then
     rs.open "select top 6 * from products where categoryid=" & request.form("txtcat")      & " order by productid",cn
     else
     if request.form("tp")="prev" then
     rs.open "select top 6 * from products where categoryid=" & request.form("txtcat")      & " and productid<" & request.form("cod") &      " order by productid desc",cn
     tp="prev"
     else
     rs.open "select top 6 * from products a where categoryid=" &      request.form("txtcat") & " and productid>" &      request.form("cod") & " order by productid",cn
 end if
     end if


São definidas variáveis, que serão utilizadas durante o cõdigo. São instanciados os objetos de conexão e recordset, a conexão é aberta e o cursorlocation do recordset definido como 2 (server, por estranho que pareça, é mais ágil que client, mesmo quando pegamos poucos registros) e cursortype 3 (keyset, precisaremos de movimentações no recordset).

Como citei anteriormente, a página Exibir.ASP chama a si mesma, recursivamente. Nestas chamadas, 3 variáveis são transmitidas : COD, que é o marcador utilizado, TP, que é a identificação do botão pressionado, próximo ou anterior, e TXTCAT, que é a categoria escolhida no pesquisar.ASP, que precisa ser continuamente retransmitida para que a filtragem possa ser mantida.

Usando essas variáveis, faz-se a abertura do recordset. Observe que temos 3 opções de abertura, repare primeiramente como elas são identificadas : Se o código estiver vazio, então é a primeira chamada da Exibir.asp, a página foi chamada diretamente da pesquisar e fará seu primeiro SELECT. Caso contrário verifica-se o botão pressionado (Variável TP). Para o botão anterior existe um SELECT, para o botão próximo existe outro SELECT.

Agora observemos os SELECTs. No primeiro caso, o primeiro SELECT, basta usar o top 5 para retorna no máximo 5 registros e filtrar pelo categoryID. No botão próximo, o 3o caso, além da filtragem por categoryID faz-se uma filtragem por productid, procurando o productid que seja maior que o PRODUCTID guardado em COD, que, no caso do botão próximo, é o último PRODUCTID exibido.

Já no select para o botão anterior exite alguma complexidade a mais. O botão anterior tomou o cuidado de manter na variável COD o primeiro código exibido na página que o usuário estava vendo. Então precisamos mostrar ao usuário os 5 registros que tem código imediatamente inferior a este. É como se fosse um top ao contrário, pegar os 5 últimos registros. Para que isso seja possível fazemos um order by desc, mantemos o top 5 e fazemos a filtragem pelos productIDs inferiores ao PRODUCTID contido na variável COD.

Observe que com o order by DESC obteremos os registros ao contrário, do último para o primeiro, problema que teremos que resolver no trecho de código seguinte :


     response.write("<table border=1><tr><td>Produto</td><td>Preço</td></tr>")
     if tp="prev" then
     rs.movelast
     if rs.recordcount=6 then
     rs.moveprevious
     end if
     end if
     codprev=rs.fields("productid")
     for a=1 to 5
     if rs.eof or rs.bof then
     exit for
     end if
     response.write("<tr><td>")
     response.write(rs.fields("productname"))
     response.write("</td><td>")
     response.write(rs.fields("unitprice"))
     response.write("</td></tr>")
     if tp="prev" then
     rs.moveprevious
     else
     rs.movenext
     end if
     next
     if a > 1 then
     if tp<>"prev" then
     rs.moveprevious
     else
     rs.movenext
     end if
     codnext=rs.fields("productid")
     if tp<>"prev" then
     rs.movenext
     else
     rs.moveprevious
     end if
     end if

Nesse trecho de código vamos exibir o recordset. Observe que se o usuário clicou no botão anterior teremos que fazer a exibição ao contrário, por isso começamos com um if que verifica o TP e faz um movelast.

Antes de exibir o primeiro registro já pegamos seu código e guardamos na variável codprev para utilizarmos posteriormente. Em seguida iniciamos o FOR. Nele garantiremos a quantidade de registros que serão exibidos, 5.

Iniciamos o FOR verificando se atingimos BOF ou EOF, caso sim, saimos do FOR pois chegamos ao final dos registros. Caso contrário mostramos os dados, só dois campos, productname e unitprice.

Ao final do FOR passamos para o próximo registro, mas a movimentação também depende do TP : Se for anterior, precisamos fazer moveprevious, se for próximo precisamos fazer movenext.

Após o FOR iniciamos alguns testes para pegar o codnext, código do último registro exibido. Tudo envolto em um if a>1 (tinha algum registro ?). Se o TP for próximo fazemos um moveprevious (estamos no EOF), se for anterior, fazemos um movenext (estamos no bof). Obtemos o código que desejamos e fazemos outro if para voltarmos exatamente para o ponto em que estavamos.

Vamos então montar os botões próximo e anterior :

 if (tp="prev"      and rs.recordcount>5) or (tp<>"prev" and request.form("cod")<>"")      then
     response.write("<form method=post action='exibir.asp'>")
     response.write("<input type=hidden name='tp' value='prev'>")
     response.write("<input type=hidden name='cod' value='" &      codprev & "'>")
     response.write("<input type=hidden name='txtcat' value='" &      request.form("txtcat") & "'>")
     response.write("<input type=submit value='<< Anterior'>")
     response.write("</form>")
     end if
     if tp="prev" or (tp<>"prev" and not rs.eof) then
     response.write("<form method=post action='exibir.asp'>")
     response.write("<input type=hidden name='tp' value='next'>")
     response.write("<input type=hidden name='cod' value='" &      codnext & "'>")
     response.write("<input type=hidden name='txtcat' value='" &      request.form("txtcat") & "'>")
 response.write("<input      type=submit value='Proximo >>'>")
     response.write("</form>")
     end if

Vamos analisar o código aos poucos. Cada botão é cercado de um IF, que tem como objetivo decidir se o botão tem mesmo que aparecer. Veja a lógica :

Anterior : Se o tipo for anterior e existirem mais de 5 registros no recordset (trouxe 6 de propósito) então exibe. Ou se o tipo for próximo (não for anterior e não estivermos na primeira exibição). Então é porque existe uma página anterior e vamos exibir o botão anterior.

Próximo : Se o tipo for anterior, então existe um próximo e o botão tem que ser exibido. Mas se o tipo não for anterior mas ainda não estivermos no EOF (trouxe 6 registros, lembra?) então o botão próximo deve ser exibido.

Vejamos então como os botões são criados. Eles são basicamente formulários onde a única coisa visível é o botão propriamente. Temos no formulário 3 campos hidden, os 3 que já havia citado : TP, COD e TXTCAT, TP é fixo, diferente apenas de um botão para o outro. TXTCAT é a categoria que vem sendo passada em todas as chamadas e COD contém o valor ou de codnext ou de codprev, dependendo do botão.

Por fim, falta apenas a destruição dos objetos e o fechamento das tags. Veja como fica o código completo :

<HTML>
     <body>
<%
     dim cn,rs,tp,a,codprev,codnext
     tp=""
     set cn=createobjecT("adodb.connection")
     set rs=createobject("adodb.recordset")
     cn.open "Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Initial      Catalog=Northwind;Data Source=."
     rs.cursorlocation=2
     rs.cursortype=3
     if request.form("cod")="" then
     rs.open "select top 6 * from products where categoryid=" & request.form("txtcat")      & " order by productid",cn
     else
     if request.form("tp")="prev" then
     rs.open "select top 6 * from products where categoryid=" & request.form("txtcat")      & " and productid<" & request.form("cod") &      " order by productid desc",cn
     tp="prev"
     else
     rs.open "select top 6 * from products a where categoryid=" &      request.form("txtcat") & " and productid>" &      request.form("cod") & " order by productid",cn
 end if
     end if
     response.write("<table border=1><tr><td>Produto</td><td>Preço</td></tr>")
     if tp="prev" then
     rs.movelast
     if rs.recordcount=6 then
     rs.moveprevious
     end if
     end if
     codprev=rs.fields("productid")
     for a=1 to 5
     if rs.eof or rs.bof then
     exit for
     end if
     response.write("<tr><td>")
     response.write(rs.fields("productname"))
     response.write("</td><td>")
     response.write(rs.fields("unitprice"))
     response.write("</td></tr>")
     if tp="prev" then
     rs.moveprevious
     else
     rs.movenext
     end if
     next
     if a > 1 then
     if tp<>"prev" then
     rs.moveprevious
     else
     rs.movenext
     end if
     codnext=rs.fields("productid")
     if tp<>"prev" then
     rs.movenext
     else
     rs.moveprevious
     end if
     end if
 if (tp="prev"      and rs.recordcount>5) or (tp<>"prev" and request.form("cod")<>"")      then
     response.write("<form method=post action='exibir.asp'>")
     response.write("<input type=hidden name='tp' value='prev'>")
     response.write("<input type=hidden name='cod' value='" &      codprev & "'>")
     response.write("<input type=hidden name='txtcat' value='" &      request.form("txtcat") & "'>")
     response.write("<input type=submit value='<< Anterior'>")
     response.write("</form>")
     end if
     if tp="prev" or (tp<>"prev" and not rs.eof) then
     response.write("<form method=post action='exibir.asp'>")
     response.write("<input type=hidden name='tp' value='next'>")
     response.write("<input type=hidden name='cod' value='" &      codnext & "'>")
     response.write("<input type=hidden name='txtcat' value='" &      request.form("txtcat") & "'>")
 response.write("<input      type=submit value='Proximo >>'>")
     response.write("</form>")
     end if
     rs.close
     cn.close
     set rs=nothing
     set cn=nothing
     %>
     </table>
</body>
     </html>

Com a personalização dos SELECTS feita por este código garantimos que estamos fazendo o mínimo de transferência de dados entre servidor web e servidor de banco a cada página de dados que é exibida.


Dennes Torres
MCSD,MCSE,MCDBA



� Búfalo Informática, Treinamento e Consultoria - Rua Álvaro Alvim, 37 Sala 920 - Cinelândia - Rio de Janeiro / RJ
Tel.: (21)2262-1368 (21) 9240-5134 (21) 9240-7281 e-Mail:
contato@bufaloinfo.com.br