none
Como Agrupar y/o ReAgrupar Datos en Consulta SQL

    Pregunta

  • Saludos Amigos del Foro.

    La Consulta que me trae a este foro para su ayuda es la siguiente:
    1.- Tengo una Tabla en SQL Server 2000, la cual tiene muchas columnas y que para mi consulta me interesan algunas, pero necesito una forma particular de traer la informacion de ella, tratare de explicarme con un ejemplo:
    Tabla de Ejemplo 1
    Fecha-------NumeroD----Serial--NumeroZ--RazonSocial----RIF-----------CompDesde--CompHasta------Monto
    17-09-2009--FA-000197--xxxx---zzzz------Empres1, C.A.--J-000000000--800---------800--------------100.00
    17-09-2009--FA-000198--xxxx---zzzz------Empres2, C.A.--J-000000000--801---------801--------------103.00
    17-09-2009--FA-000199--xxxx---zzzz------Empres3, C.A.--J-000000000--802---------802--------------110.00
    17-09-2009--FA-000200--xxxx---zzzz------Pedro Perez----V-000000000--803---------803--------------100.00
    17-09-2009--FA-000201--xxxx---zzzz------Pedro Perez----V-000000000--804---------804--------------100.00
    17-09-2009--FA-000202--xxxx---zzzz------Empres2, C.A.--J-000000000--805---------805--------------103.00
    17-09-2009--FA-000203--xxxx---zzzz------Empres3, C.A.--J-000000000--806---------806--------------110.00
    17-09-2009--FA-000204--xxxx---zzzz------Pedro Perez----V-000000000--807---------807--------------100.00
    17-09-2009--FA-000205--xxxx---zzzz------Empres1, C.A.--J-000000000--808---------808--------------100.00
    17-09-2009--FA-000206--xxxx---zzzz------Empres2, C.A.--J-000000000--809---------809--------------103.00
    17-09-2009--FA-000207--xxxx---zzzz------Empres3, C.A.--J-000000000--810---------810--------------110.00

    Tengo una columna que me define el tipo de cliente, si es juridico o Natural, con esto puedo agrupar los tipos de facturas, ya que al agrupar los datos debo obtener un resultado similar al que abajo les muestro: (Ojo este es el resultado que realmente quiero)
    Ejemplo Resulatdo Tabla 2 (como quisiera que fuese)
    Fecha-------NumeroD----Serial--NumeroZ--RazonSocial----RIF-----------CompDesde--CompHasta------Monto
    17-09-2009--FA-000197--xxxx---zzzz------Empres1, C.A.--J-000000000--800---------800--------------100.00
    17-09-2009--FA-000198--xxxx---zzzz------Empres2, C.A.--J-000000000--801---------801--------------103.00
    17-09-2009--FA-000199--xxxx---zzzz------Empres3, C.A.--J-000000000--802---------802--------------110.00
    17-09-2009--N/A---------xxxx---zzzz------N/A------------N/A-----------803---------804--------------200.00
    17-09-2009--FA-000202--xxxx---zzzz------Empres2, C.A.--J-000000000--805---------805--------------103.00
    17-09-2009--FA-000203--xxxx---zzzz------Empres3, C.A.--J-000000000--806---------806--------------110.00
    17-09-2009--N/A---------xxxx---zzzz------N/A------------N/A-----------807---------807--------------100.00
    17-09-2009--FA-000205--xxxx---zzzz------Empres1, C.A.--J-000000000--808---------808--------------100.00
    17-09-2009--FA-000206--xxxx---zzzz------Empres2, C.A.--J-000000000--809---------809--------------103.00
    17-09-2009--FA-000207--xxxx---zzzz------Empres3, C.A.--J-000000000--810---------810--------------110.00

    Como ven aca, quisiera se agruparan todas las facturas de los clientes naturales, pero con la diferencia de que si entre dos juridicos existen facturas naturales, estan se deben agrupar solo en ese rango, o sea, entre la factura juridica anterior y la siguiente, no agrupar todas las facturas naturales y mostrarmelas al final o al principio. (ya que esto es lo que realmente obtengo con la conulta sql que he realizado)

    A continuacion les detallo lo que realmente me devuelve la consulta de sql:
    Resultado de la Consulta Tabla 3
    Fecha-------NumeroD----Serial--NumeroZ--RazonSocial----RIF-----------CompDesde--CompHasta------Monto
    17-09-2009--FA-000197--xxxx---zzzz------Empres1, C.A.--J-000000000--800---------800--------------100.00
    17-09-2009--FA-000198--xxxx---zzzz------Empres2, C.A.--J-000000000--801---------801--------------103.00
    17-09-2009--FA-000199--xxxx---zzzz------Empres3, C.A.--J-000000000--802---------802--------------110.00
    17-09-2009--FA-000202--xxxx---zzzz------Empres2, C.A.--J-000000000--805---------805--------------103.00
    17-09-2009--FA-000203--xxxx---zzzz------Empres3, C.A.--J-000000000--806---------806--------------110.00
    17-09-2009--FA-000205--xxxx---zzzz------Empres1, C.A.--J-000000000--808---------808--------------100.00
    17-09-2009--FA-000206--xxxx---zzzz------Empres2, C.A.--J-000000000--809---------809--------------103.00
    17-09-2009--FA-000207--xxxx---zzzz------Empres3, C.A.--J-000000000--810---------810--------------110.00
    17-09-2009--N/A---------xxxx---zzzz------N/A------------N/A-----------803---------807--------------300.00

    Como ven me agrupa todas las que considere como clientes Naturales, para la consulta esto es lo correcto, pero para la practica desearia que de alguna manera pueda controlar los numeros de comprobantes para que si entre una factura juridica y la siguiente, hay varias facturas Naturales, este me agrupe solo las naturales entre este rango y vuelva a colocar la siguiente juridica, y no me coloque todas las Facturas Naturales al final de forma agrupadas, ya que me daria un numero de comprobante errado.

    A continuacion muestro como es mi consulta SQL original, esta me funciona y me entrega los resultados a partir de la tabla original como lo señalo en la tabla de resultado 3

    SELECT     CONVERT(DATETIME, SUBSTRING(CONVERT(VARCHAR, SAFACT.FechaE, 120), 1, 10), 120) AS FechaE, CASE WHEN SAFACT.NumeroP IS NULL 
                          THEN CASE WHEN SAFACT.TipoFac = 'A' THEN 'FA-' ELSE 'NC-' END + SAFACT.NumeroD ELSE CASE WHEN SACLIE.TipoCli = 0 THEN CASE WHEN SAFACT.TipoFac
                           = 'A' THEN 'FA-' ELSE 'NC-' END + SAFACT.NumeroD ELSE 'N/A' END END AS NumeroD, SAFACT.NumeroP AS Serial, 
                          CASE WHEN SAFACT.NumeroP IS NULL THEN NULL ELSE SAFACT.NumeroZ END AS NumeroZ, 
                          CASE WHEN SACLIE.TipoCli = 0 THEN SAFACT.Descrip ELSE 'Resumen diario de ventas' END AS Razon_Social, 
                          CASE WHEN SACLIE.TipoCli = 0 THEN SAFACT.ID3 ELSE 'N/A' END AS RIF, MIN(SAFACT.NumeroF) AS CompDesde, MAX(SAFACT.NumeroF) 
                          AS CompHasta, SUM(SAFACT.Signo * SAFACT.TExento) AS TExento, SUM(SAFACT.Signo * SAFACT.Monto) AS BaseImponible, 
                          SUM(SAFACT.Signo * SAFACT.MtoTax) AS Impuesto, SUM(SAFACT.Signo * (SAFACT.Monto + SAFACT.MtoTax - SAFACT.TExento)) AS MontoTotal
    FROM         SAFACT LEFT OUTER JOIN
                          SACLIE ON SAFACT.CodClie = SACLIE.CodClie
    WHERE     (SAFACT.TipoFac IN ('A', 'B')) AND (YEAR(SAFACT.FechaE) = 2009) AND (MONTH(SAFACT.FechaE) = 9) AND (DAY(SAFACT.FechaE) = 17)
    GROUP BY CONVERT(DATETIME, SUBSTRING(CONVERT(VARCHAR, SAFACT.FechaE, 120), 1, 10), 120), SAFACT.NumeroP, SAFACT.NumeroZ, 
                          CASE WHEN SACLIE.TipoCli = 0 THEN SAFACT.Descrip ELSE 'Resumen diario de ventas' END, 
                          CASE WHEN SACLIE.TipoCli = 0 THEN SAFACT.ID3 ELSE 'N/A' END, CASE WHEN SAFACT.NumeroP IS NULL 
                          THEN CASE WHEN SAFACT.TipoFac = 'A' THEN 'FA-' ELSE 'NC-' END + SAFACT.NumeroD ELSE CASE WHEN SACLIE.TipoCli = 0 THEN CASE WHEN SAFACT.TipoFac
                           = 'A' THEN 'FA-' ELSE 'NC-' END + SAFACT.NumeroD ELSE 'N/A' END END
    Gracia a todos por tomarse el tiempo para Leerlo

    Atentamente.

    Franklin Gonzalez
    Maturin - Edo. Monagas - Venezuela
    martes, 13 de octubre de 2009 23:38

Respuestas

  • Hola.

    Voy a interntar aportarte otra idea, me resisto a pensar que no sea factible y asequible conseguir lo que buscas sin necesidad de recurrir a cursores. Para empezar, parece que el único problema es la ordenación de los registros (cuando te refieres a "agrupación", creo que estás refiriéndote a "poner cerca", no hablas de un "group by"). Comparando el conjunto de registros deseado y cómo se presentan, parece que existen campos, CompDesde y CompHasta, que permitirían obtener la ordenación deseada. Con eso, tu consulta podría ser la siguiente:

    SELECT     CONVERT(DATETIME, SUBSTRING(CONVERT(VARCHAR, SAFACT.FechaE, 120), 1, 10), 120) AS FechaE, CASE WHEN SAFACT.NumeroP IS NULL 
                          THEN CASE WHEN SAFACT.TipoFac = 'A' THEN 'FA-' ELSE 'NC-' END + SAFACT.NumeroD ELSE CASE WHEN SACLIE.TipoCli = 0 THEN CASE WHEN SAFACT.TipoFac
                           = 'A' THEN 'FA-' ELSE 'NC-' END + SAFACT.NumeroD ELSE 'N/A' END END AS NumeroD, SAFACT.NumeroP AS Serial, 
                          CASE WHEN SAFACT.NumeroP IS NULL THEN NULL ELSE SAFACT.NumeroZ END AS NumeroZ, 
                          CASE WHEN SACLIE.TipoCli = 0 THEN SAFACT.Descrip ELSE 'Resumen diario de ventas' END AS Razon_Social, 
                          CASE WHEN SACLIE.TipoCli = 0 THEN SAFACT.ID3 ELSE 'N/A' END AS RIF, MIN(SAFACT.NumeroF) AS CompDesde, MAX(SAFACT.NumeroF) 
                          AS CompHasta, SUM(SAFACT.Signo * SAFACT.TExento) AS TExento, SUM(SAFACT.Signo * SAFACT.Monto) AS BaseImponible, 
                          SUM(SAFACT.Signo * SAFACT.MtoTax) AS Impuesto, SUM(SAFACT.Signo * (SAFACT.Monto + SAFACT.MtoTax - SAFACT.TExento)) AS MontoTotal
    FROM         SAFACT LEFT OUTER JOIN
                          SACLIE ON SAFACT.CodClie = SACLIE.CodClie
    WHERE     (SAFACT.TipoFac IN ('A', 'B')) AND (YEAR(SAFACT.FechaE) = 2009) AND (MONTH(SAFACT.FechaE) = 9) AND (DAY(SAFACT.FechaE) = 17)
    GROUP BY CONVERT(DATETIME, SUBSTRING(CONVERT(VARCHAR, SAFACT.FechaE, 120), 1, 10), 120), SAFACT.NumeroP, SAFACT.NumeroZ, 
                          CASE WHEN SACLIE.TipoCli = 0 THEN SAFACT.Descrip ELSE 'Resumen diario de ventas' END, 
                          CASE WHEN SACLIE.TipoCli = 0 THEN SAFACT.ID3 ELSE 'N/A' END, CASE WHEN SAFACT.NumeroP IS NULL 
                          THEN CASE WHEN SAFACT.TipoFac = 'A' THEN 'FA-' ELSE 'NC-' END + SAFACT.NumeroD ELSE CASE WHEN SACLIE.TipoCli = 0 THEN CASE WHEN SAFACT.TipoFac
                           = 'A' THEN 'FA-' ELSE 'NC-' END + SAFACT.NumeroD ELSE 'N/A' END END
    order by CompDesde

    Por otra parte, deberías intentar no aplicar funciones a los campos, sino a las constantes, por ejemplo, cambiando esto:

    (YEAR(SAFACT.FechaE) = 2009) AND (MONTH(SAFACT.FechaE) = 9) AND (DAY(SAFACT.FechaE) = 17)

    Por esto otro:

    (SAFACT.FechaE >= '20090917' and SAFACT.FechaE < '20090918'

    La forma en que lo utilizas imposibilita la generación de un plan de ejecución que realice una búsqueda, tendrás recorridos, con un notable perjuicio en el rendimiento.


    Volviendo a lo esencial, si la consulta no resuelve tu caso, nos dices.



     

    Alberto López Grande (Visita mi blog en http://qwalgrande.blogspot.es/)
    miércoles, 14 de octubre de 2009 17:11

Todas las respuestas

  • En un caso como este, yo personalmente preferiría procesar los datos mediante un bucle en el lado cliente en lugar de tratar de construir una única sentencia Sql que devuelva los resultados apetecidos. A no ser que tu tabla contenga muchísimos registros de personas naturales y muy pocos de jurídicas, la diferencia de tráfico del servidor al cliente va a ser insignificante, y el coste de procesamiento para agrupar las personas naturales en el código cliente también es despreciable, así que no vale la pena calentarse la cabeza para hacerlo en el servidor.

    Alternativamente, si no tienes más remedio que hacerlo en el servidor, puedes usar una función con un cursor. Aunque habitualmente se desaconseja usar cursores, este es uno de los casos en que puede merecer la pena recurrir a ellos.
    miércoles, 14 de octubre de 2009 7:32
  • Ok Alberto, Gracias por responder

    Bueno te comento que estaba por implementar lo de los cursores, pero debido a que los registros de facturas van en aumento, no quiero que a futuro, este empieze a tornarse lento al aplicar el cursor y se me vean afectados el resto de los usuarios.
    Por otro lado voy a optar por solo construir ya la vista con los datos filtrados y en el generador de reportes de la aplicacion buscarle la vuelta para que me agrupe los clientes naturales y que me los presente respetando el correlativo de los comprobantes.

    Gracias mil por tu respuesta.
    miércoles, 14 de octubre de 2009 15:03
  • Hola.

    Voy a interntar aportarte otra idea, me resisto a pensar que no sea factible y asequible conseguir lo que buscas sin necesidad de recurrir a cursores. Para empezar, parece que el único problema es la ordenación de los registros (cuando te refieres a "agrupación", creo que estás refiriéndote a "poner cerca", no hablas de un "group by"). Comparando el conjunto de registros deseado y cómo se presentan, parece que existen campos, CompDesde y CompHasta, que permitirían obtener la ordenación deseada. Con eso, tu consulta podría ser la siguiente:

    SELECT     CONVERT(DATETIME, SUBSTRING(CONVERT(VARCHAR, SAFACT.FechaE, 120), 1, 10), 120) AS FechaE, CASE WHEN SAFACT.NumeroP IS NULL 
                          THEN CASE WHEN SAFACT.TipoFac = 'A' THEN 'FA-' ELSE 'NC-' END + SAFACT.NumeroD ELSE CASE WHEN SACLIE.TipoCli = 0 THEN CASE WHEN SAFACT.TipoFac
                           = 'A' THEN 'FA-' ELSE 'NC-' END + SAFACT.NumeroD ELSE 'N/A' END END AS NumeroD, SAFACT.NumeroP AS Serial, 
                          CASE WHEN SAFACT.NumeroP IS NULL THEN NULL ELSE SAFACT.NumeroZ END AS NumeroZ, 
                          CASE WHEN SACLIE.TipoCli = 0 THEN SAFACT.Descrip ELSE 'Resumen diario de ventas' END AS Razon_Social, 
                          CASE WHEN SACLIE.TipoCli = 0 THEN SAFACT.ID3 ELSE 'N/A' END AS RIF, MIN(SAFACT.NumeroF) AS CompDesde, MAX(SAFACT.NumeroF) 
                          AS CompHasta, SUM(SAFACT.Signo * SAFACT.TExento) AS TExento, SUM(SAFACT.Signo * SAFACT.Monto) AS BaseImponible, 
                          SUM(SAFACT.Signo * SAFACT.MtoTax) AS Impuesto, SUM(SAFACT.Signo * (SAFACT.Monto + SAFACT.MtoTax - SAFACT.TExento)) AS MontoTotal
    FROM         SAFACT LEFT OUTER JOIN
                          SACLIE ON SAFACT.CodClie = SACLIE.CodClie
    WHERE     (SAFACT.TipoFac IN ('A', 'B')) AND (YEAR(SAFACT.FechaE) = 2009) AND (MONTH(SAFACT.FechaE) = 9) AND (DAY(SAFACT.FechaE) = 17)
    GROUP BY CONVERT(DATETIME, SUBSTRING(CONVERT(VARCHAR, SAFACT.FechaE, 120), 1, 10), 120), SAFACT.NumeroP, SAFACT.NumeroZ, 
                          CASE WHEN SACLIE.TipoCli = 0 THEN SAFACT.Descrip ELSE 'Resumen diario de ventas' END, 
                          CASE WHEN SACLIE.TipoCli = 0 THEN SAFACT.ID3 ELSE 'N/A' END, CASE WHEN SAFACT.NumeroP IS NULL 
                          THEN CASE WHEN SAFACT.TipoFac = 'A' THEN 'FA-' ELSE 'NC-' END + SAFACT.NumeroD ELSE CASE WHEN SACLIE.TipoCli = 0 THEN CASE WHEN SAFACT.TipoFac
                           = 'A' THEN 'FA-' ELSE 'NC-' END + SAFACT.NumeroD ELSE 'N/A' END END
    order by CompDesde

    Por otra parte, deberías intentar no aplicar funciones a los campos, sino a las constantes, por ejemplo, cambiando esto:

    (YEAR(SAFACT.FechaE) = 2009) AND (MONTH(SAFACT.FechaE) = 9) AND (DAY(SAFACT.FechaE) = 17)

    Por esto otro:

    (SAFACT.FechaE >= '20090917' and SAFACT.FechaE < '20090918'

    La forma en que lo utilizas imposibilita la generación de un plan de ejecución que realice una búsqueda, tendrás recorridos, con un notable perjuicio en el rendimiento.


    Volviendo a lo esencial, si la consulta no resuelve tu caso, nos dices.



     

    Alberto López Grande (Visita mi blog en http://qwalgrande.blogspot.es/)
    miércoles, 14 de octubre de 2009 17:11
  • Ok Alberto Gracias por responder y disculpa la tardanza por contestar.

    El punto no se si difiero de ti con respecto a lo de orden con respecto a lo de Group By, es que realmente de acuerdo a lo planteado inicialmente en mi pregunta, lo que se quiere es algo asi: (voy a colocar un ejemplo un poco mas especifico)

    Documento------Cliente----------Comprobante---Monto
    Factura 1--------Juridico----------0801-------------100,00
    Factura 2--------Juridico----------0802------------- 50,00
    Factura 3--------Natural----------0803-------------150,00
    Factura 4--------Natural----------0804-------------110,00
    Factura 5--------Juridico----------0805-------------120,00
    Factura 6--------Natural----------0806-------------125,00
    Factura 7--------Natural----------0807------------- 85,00
    Factura 8--------Juridico----------0808------------- 45,00
    Factura 9--------Natural----------0809-------------100,00
    Factura 10-------Natural----------0810-------------120,00
    Factura 11-------Natural----------0811-------------220,00
    Factura 12-------Natural----------0812-------------150,00
    Factura 13-------Natural----------0813-------------120,00
    Factura 14-------Natural----------0814-------------120,00
    Factura 15-------Natural----------0815-------------120,00
    Factura 16-------Natural----------0816-------------250,00
    Factura 17-------Natural----------0817-------------120,00
    Factura 18-------Natural----------0818-------------320,00
    Factura 19-------Natural----------0819-------------120,00
    Factura 20-------Natural----------0820-------------100,00

    lo que se quiere es generar una vista en la cual se reflejen los valores de la siguiente forma:

    Documento------Cliente----------CompDesde---CompHasta-----Monto
    Factura 1--------Juridico----------0801-----------0801-------------100,00
    Factura 2--------Juridico----------0802-----------0802------------- 50,00
    N/A-----------N/A-----------0803---------0804----------260,00---> Aca agrupa las dos Factura (3 y 4) y suma sus Montos(150 y 110)
    Factura 5--------Juridico----------0805-----------0805-------------120,00
    N/A-----------N/A-----------0806---------0807----------210,00---> Aca agrupa las dos Factura (6 y 7) y suma sus Montos(125 y 85)
    Factura 8--------Juridico----------0808-----------0809------------- 45,00
    N/A-----------N/A-----------0809---------0820---------1860,00---> Aca agrupa el resto ya que son todas Naturales

    Para ello cree una consulta SQL en la Cual me agrupara las factura por tipo de cliente, colocando los Valores N/A para aquellos campos en los que no me interesa la informacion del mismo.
    Para el campo CompDesde y CompHasta use la Funcion Min() y Max() asociada al comprobante, con este logro de que el comprobante inicial y final en una agrupacion sea el rango que necesito, ahora bien cuan creo la consulta SQL, esta me devuelve lo Siguiente:

    Factura 1--------Juridico----------0801-----------0801-------------100,00
    Factura 2--------Juridico----------0802-----------0802------------- 50,00
    Factura 5--------Juridico----------0805-----------0805-------------120,00
    Factura 8--------Juridico----------0808-----------0809------------- 45,00
    N/A-----------N/A-----------0803---------0820---------2330,00---> Aca agrupa todas las Facturas Naturales

    Este es el problema que tengo y en el cual estoy quizas dando vueltas en un circulo, porque mi forma de hacerlo en una unica consulta, sin el uso de cursores, me oriente a que use Group By.....
    Ahora bien necesito el resultado en una vista para llamarla de el generador de reportes de la aplicacion...
    Si se pudiese hacer de otra manera, quizas con stored procedure, ya que he leido que cursores no es lo mas recomendado y sabiendo que la base de datos de donde saco la informacion va a ir en aumento, menos.

    Gracias Mil por todas sus Atenciones......................

    viernes, 16 de octubre de 2009 17:27