Pregunta

Tengo una vista de base de datos de estado mensual en base a la cual necesito crear un informe.Los datos en la vista se parecen a esto:

Category | Revenue  |  Yearh  |  Month
Bikes      10 000      2008        1
Bikes      12 000      2008        2
Bikes      12 000      2008        3
Bikes      15 000      2008        1
Bikes      11 000      2007        2
Bikes      11 500      2007        3
Bikes      15 400      2007        4


...Etcétera

La vista tiene una categoría de producto, unos ingresos, un año y un mes.Quiero crear un informe comparando 2007 y 2008, mostrando 0 para los meses sin ventas.Entonces el informe debería verse así:

Category  |  Month  |  Rev. This Year  |  Rev. Last Year
Bikes          1          10 000               0
Bikes          2          12 000               11 000
Bikes          3          12 000               11 500
Bikes          4          0                    15 400


Lo clave a tener en cuenta es que el mes 1 sólo tiene ventas en 2008 y, por lo tanto, es 0 para 2007.Además, el mes 4 sólo no tiene ventas en 2008, de ahí el 0, mientras que tiene ventas en 2007 y aún aparece.

Además, el informe es en realidad para el año financiero, por lo que me encantaría tener columnas vacías con 0 en ambos si no hubo ventas en el mes 5, por ejemplo, para 2007 o 2008.

La consulta que recibí se parece a esto:

SELECT 
    SP1.Program,
    SP1.Year,
    SP1.Month,
    SP1.TotalRevenue,
    IsNull(SP2.TotalRevenue, 0) AS LastYearTotalRevenue

FROM PVMonthlyStatusReport AS SP1 
     LEFT OUTER JOIN PVMonthlyStatusReport AS SP2 ON 
                SP1.Program = SP2.Program AND 
                SP2.Year = SP1.Year - 1 AND 
                SP1.Month = SP2.Month
WHERE 
    SP1.Program = 'Bikes' AND
    SP1.Category = @Category AND 
    (SP1.Year >= @FinancialYear AND SP1.Year <= @FinancialYear + 1) AND
    ((SP1.Year = @FinancialYear AND SP1.Month > 6) OR 
     (SP1.Year = @FinancialYear + 1 AND SP1.Month <= 6))

ORDER BY SP1.Year, SP1.Month

El problema con esta consulta es que no devolvería la cuarta fila en los datos de mi ejemplo anterior, ya que no tuvimos ninguna venta en 2008, pero en realidad sí las tuvimos en 2007.

Probablemente esta sea una consulta/problema común, pero mi SQL está oxidado después de desarrollar el front-end durante tanto tiempo.¡Cualquier ayuda es muy apreciada!

Ah, por cierto, estoy usando SQL 2005 para esta consulta, así que si hay alguna característica nueva útil que pueda ayudarme, házmelo saber.

¿Fue útil?

Solución

La declaración de caso es mi mejor amigo de SQL.También necesitas una tabla de tiempo para generar tus 0 rev en ambos meses.

Los supuestos se basan en la disponibilidad de las siguientes tablas:

ventas:Categoría | Ingresos | Año | Mes

y

tm:Año | Mes (poblado con todas las fechas requeridas para informar)

Ejemplo 1 sin filas vacías:

select
    Category
    ,month
    ,SUM(CASE WHEN YEAR = 2008 THEN Revenue ELSE 0 END) this_year
    ,SUM(CASE WHEN YEAR = 2007 THEN Revenue ELSE 0 END) last_year

from
    sales

where
    year in (2008,2007)

group by
    Category
    ,month

DEVOLUCIONES:

Category  |  Month  |  Rev. This Year  |  Rev. Last Year
Bikes          1          10 000               0
Bikes          2          12 000               11 000
Bikes          3          12 000               11 500
Bikes          4          0                    15 400

Ejemplo 2 con filas vacías:Voy a utilizar una subconsulta (pero es posible que otras no) y devolveré una fila vacía para cada combinación de producto y año mes.

select
    fill.Category
    ,fill.month
    ,SUM(CASE WHEN YEAR = 2008 THEN Revenue ELSE 0 END) this_year
    ,SUM(CASE WHEN YEAR = 2007 THEN Revenue ELSE 0 END) last_year

from
    sales
    Right join (select distinct  --try out left, right and cross joins to test results.
                   product
                   ,year
                   ,month
               from
                  sales --this ideally would be from a products table
                  cross join tm
               where
                    year in (2008,2007)) fill


where
    fill.year in (2008,2007)

group by
    fill.Category
    ,fill.month

DEVOLUCIONES:

Category  |  Month  |  Rev. This Year  |  Rev. Last Year
Bikes          1          10 000               0
Bikes          2          12 000               11 000
Bikes          3          12 000               11 500
Bikes          4          0                    15 400
Bikes          5          0                    0
Bikes          6          0                    0
Bikes          7          0                    0
Bikes          8          0                    0

Tenga en cuenta que la mayoría de las herramientas de informes realizarán esta funcionalidad de tabla cruzada o matriz, y ahora que lo pienso, SQL Server 2005 tiene una sintaxis dinámica que también hará esto.

Aquí hay algunos recursos adicionales.CASOhttp://www.4guysfromrolla.com/webtech/102704-1.shtmlPIVOTE DE SQL SERVIDOR 2005http://msdn.microsoft.com/en-us/library/ms177410.aspx

Otros consejos

@Christian - editor de rebajas - UGH;especialmente cuando la vista previa y la versión final de tu publicación no coinciden...@Christian - unión externa completa - la unión externa completa se anula por el hecho de que hay referencias a SP1 en la cláusula WHERE, y la cláusula WHERE se aplica después de JOIN.Para realizar una combinación externa completa con filtrado en una de las tablas, debe colocar su cláusula WHERE en una subconsulta, para que se realice el filtrado. antes la unión, o intente incorporar todos sus criterios WHERE en la cláusula JOIN ON, que es increíblemente fea.Bueno, en realidad no existe una manera bonita de hacer esto.

@Jonas:Considerando esto:

Además, el informe corresponde en realidad al ejercicio financiero, por lo que Me encantaría tener columnas vacías con 0 en ambas si no hubo ventas en el mes 5, por ejemplo, para 2007 o 2008.

y el hecho de que este trabajo no se puede hacer con una consulta bonita, definitivamente intentaría obtener los resultados que realmente desea.No tiene sentido tener una consulta fea y ni siquiera obtener los datos exactos que realmente desea.;)

Entonces, sugeriría hacer esto en 5 pasos:
1.cree una tabla temporal en el formato que desea que coincidan sus resultados
2.Rellénelo con doce filas, con 1-12 en la columna del mes.
3.actualice la columna "Este año" usando su lógica SP1
4.actualice la columna "El año pasado" usando su lógica SP2
5.seleccionar de la tabla temporal

Por supuesto, supongo que estoy partiendo del supuesto de que se puede crear un procedimiento almacenado para lograr esto.Técnicamente, es posible que puedas ejecutar todo este lote en línea, pero ese tipo de fealdad rara vez se ve.Si no puede crear un SP, le sugiero que recurra a la combinación externa completa mediante una subconsulta, pero no obtendrá una fila cuando un mes no haya tenido ventas en ningún año.

Acerca de la rebaja: sí, eso es frustrante.El editor obtuvo una vista previa de mi tabla HTML, pero después de publicarla desapareció. Así que tuve que eliminar todo el formato HTML de la publicación...

@kcrumley Creo que hemos llegado a conclusiones similares.Esta consulta fácilmente se vuelve realmente fea.De hecho, resolví esto antes de leer tu respuesta, usando un enfoque similar (pero diferente).Tengo acceso para crear funciones y procedimientos almacenados en la base de datos de informes.Creé una función de valores de tabla aceptando una categoría de producto y un año financiero como parámetro.En base a eso, la función completará una tabla que contiene 12 filas.Las filas se completarán con datos de la vista si hay ventas disponibles; de lo contrario, la fila tendrá 0 valores.

Luego uno las dos tablas devueltas por las funciones.Como sé que todas las mesas tendrán doce grupos, es mucho más fácil asignarlas y puedo unirme por categoría de producto y mes:

SELECT 
    SP1.Program,
    SP1.Year,
    SP1.Month,
    SP1.TotalRevenue AS ThisYearRevenue,
    SP2.TotalRevenue AS LastYearRevenue
FROM GetFinancialYear(@Category, 'First Look',  2008) AS SP1 
     RIGHT JOIN GetFinancialYear(@Category, 'First Look',  2007) AS SP2 ON 
         SP1.Program = SP2.Program AND 
         SP1.Month = SP2.Month

Creo que su enfoque probablemente sea un poco más limpio ya que la función GetFinancialYear es bastante complicada.Pero al menos funciona, lo que me hace feliz por ahora ;)

El truco consiste en hacer una UNIÓN COMPLETA, con ISNULL para obtener las columnas unidas de cualquiera de las tablas.Normalmente envuelvo esto en una vista o tabla derivada; de lo contrario, también debes usar ISNULL en la cláusula WHERE.

SELECT 
    Program,
    Month,
    ThisYearTotalRevenue,
    PriorYearTotalRevenue
FROM (
    SELECT 
        ISNULL(ThisYear.Program, PriorYear.Program) as Program,
        ISNULL(ThisYear.Month, PriorYear.Month),
        ISNULL(ThisYear.TotalRevenue, 0) as ThisYearTotalRevenue,
        ISNULL(PriorYear.TotalRevenue, 0) as PriorYearTotalRevenue
    FROM (
        SELECT Program, Month, SUM(TotalRevenue) as TotalRevenue 
        FROM PVMonthlyStatusReport 
        WHERE Year = @FinancialYear 
        GROUP BY Program, Month
    ) as ThisYear 
    FULL OUTER JOIN (
        SELECT Program, Month, SUM(TotalRevenue) as TotalRevenue 
        FROM PVMonthlyStatusReport 
        WHERE Year = (@FinancialYear - 1) 
        GROUP BY Program, Month
    ) as PriorYear ON
        ThisYear.Program = PriorYear.Program
        AND ThisYear.Month = PriorYear.Month
) as Revenue
WHERE 
    Program = 'Bikes'
ORDER BY 
    Month

Eso debería proporcionarle sus requisitos mínimos: filas con ventas en 2007 o 2008, o ambos.Para obtener filas sin ventas en cualquier año, solo necesita UNIRSE INTERNAMENTE a una tabla de números del 1 al 12 (lo hace tener uno de esos, ¿no?).

Podría estar equivocado, pero ¿no deberías utilizar una combinación externa completa en lugar de solo una combinación izquierda?De esa manera obtendrás columnas "vacías" de ambas tablas.

http://en.wikipedia.org/wiki/Join_(SQL)#Full_outer_join

Usando pivot y Dynamic Sql podemos lograr este resultado

SET NOCOUNT ON
IF OBJECT_ID('TEMPDB..#TEMP') IS NOT NULL
DROP TABLE #TEMP

;With cte(Category , Revenue  ,  Yearh  ,  [Month])
AS
(
SELECT 'Bikes', 10000, 2008,1 UNION ALL
SELECT 'Bikes', 12000, 2008,2 UNION ALL
SELECT 'Bikes', 12000, 2008,3 UNION ALL
SELECT 'Bikes', 15000, 2008,1 UNION ALL
SELECT 'Bikes', 11000, 2007,2 UNION ALL
SELECT 'Bikes', 11500, 2007,3 UNION ALL
SELECT 'Bikes', 15400, 2007,4
)
SELECT * INTO #Temp FROM cte

Declare @Column nvarchar(max),
        @Column2 nvarchar(max),
        @Sql nvarchar(max)


SELECT @Column=STUFF((SELECT DISTINCT ','+ 'ISNULL('+QUOTENAME(CAST(Yearh AS VArchar(10)))+','+'''0'''+')'+ 'AS '+ QUOTENAME(CAST(Yearh AS VArchar(10)))
FROM #Temp order by 1 desc FOR XML PATH ('')),1,1,'')

SELECT @Column2=STUFF((SELECT DISTINCT ','+ QUOTENAME(CAST(Yearh AS VArchar(10)))
FROM #Temp FOR XML PATH ('')),1,1,'')

SET @Sql= N'SELECT Category,[Month],'+ @Column +'FRom #Temp
            PIVOT
            (MIN(Revenue) FOR yearh IN ('+@Column2+')
            ) AS Pvt

            '
EXEC(@Sql)
Print @Sql

Resultado

Category    Month   2008    2007
----------------------------------
Bikes       1       10000   0
Bikes       2       12000   11000
Bikes       3       12000   11500
Bikes       4       0       15400
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top