Query optimisation - takes so long - sql

I have this query that show turnover of the previous 24 months through the current date.
I'm using SQL SERVER 2008, my problem is that my query takes so long to be executed.
I'm using 2 table : Etablissement to get store name, and piece to get sum of Turnover of each etablissement.
result :
enter image description here
select ETAB.ET_ETABLISSEMENT as 'STORE CODE'
[Month-1]=(select CONVERT(DECIMAL(15,2),sum(gl_totalttcdev))
from piece left join ligne on gl_souche=gp_souche and gl_naturepieceg=gp_naturepieceg and gl_numero=gp_numero and gl_indiceg=gp_indiceg
left join etabliss as e on gp_etablissement=et_etablissement
left join ARTICLE on GL_CODEARTICLE = ARTICLE.GA_CODEARTICLE
where gp_naturepieceg='FFO'
and year(gp_datepiece) = year(DATEADD(MONTH,-1,GETDATE()))
and month(gp_datepiece) = MONTH( DATEADD(MONTH,-1,GETDATE()))
and gl_typearticle<>''
and gl_typearticle<>'FI'
and ETAB.ET_ETABLISSEMENT =e.ET_ETABLISSEMENT
and ETAB.ET_LIBELLE =e.ET_LIBELLE
group by gp_etablissement, et_libelle),
[Month-2]=(select CONVERT(DECIMAL(15,2),sum(gl_totalttcdev))
from piece left join ligne on gl_souche=gp_souche and gl_naturepieceg=gp_naturepieceg and gl_numero=gp_numero and gl_indiceg=gp_indiceg left join etabliss as e on gp_etablissement=et_etablissement
left join ARTICLE on GL_CODEARTICLE = ARTICLE.GA_CODEARTICLE
where gp_naturepieceg='FFO'
and year(gp_datepiece) = year(DATEADD(MONTH,-1,GETDATE()))
and month(gp_datepiece) = MONTH( DATEADD(MONTH,-1,GETDATE()))
and gl_typearticle<>''
and gl_typearticle<>'FI'
and ETAB.ET_ETABLISSEMENT =e.ET_ETABLISSEMENT
and ETAB.ET_LIBELLE =e.ET_LIBELLE
group by gp_etablissement, et_libelle),
[Some thing for the other months ..]
[Month-24]..,
from ETABLISS ETAB
Ther's any solution please to optimise my query ?

This is a comment that doesn't fit in the comments section.
I get that you have multiple queries going on and they are becoming slow. Let's take one at a time. For example:
select CONVERT(DECIMAL(15,2),sum(gl_totalttcdev))
from piece
left join ligne on gl_souche = gp_souche
and gl_naturepieceg = gp_naturepieceg and gl_numero=gp_numero
and gl_indiceg = gp_indiceg
left join etabliss as e on gp_etablissement = et_etablissement
left join ARTICLE on GL_CODEARTICLE = ARTICLE.GA_CODEARTICLE
where gp_naturepieceg = 'FFO'
and year(gp_datepiece) = year(DATEADD(MONTH,-1,GETDATE()))
and month(gp_datepiece) = MONTH( DATEADD(MONTH,-1,GETDATE()))
and gl_typearticle <> ''
and gl_typearticle <> 'FI'
and ETAB.ET_ETABLISSEMENT = e.ET_ETABLISSEMENT
and ETAB.ET_LIBELLE = e.ET_LIBELLE
group by gp_etablissement, et_libelle
First of all, in order to optimize it it's critical for us to know where each column is coming from. So... please add the prefix for all the columns you are mentioning. We can't really guess the tables for each column.

For every record those 2 functions get executed, year(DATEADD(MONTH,-1,GETDATE())) and MONTH( DATEADD(MONTH,-1,GETDATE())) it always gives a constant value , stored those value in a variable and use those variable in query will help to improve the speed.

Your query as written is quite confusing and probably why only one other has even offered a solution. Now lets try to simplify and clarify what you are asking for. Correct me if anything is incorrect.
You need a query that shows the totals over the last 13 month period grouped by each
store code. The first month represents the month prior to current and going back 13 months (or 24 as you indicated).
Now your query. You are querying from your ETABLISS table and grouping by the gp_etablisssement, et_libelle.
By doing a google translate of French to English
Etablissement = Establishement
Libelle = Wording
So I am GUESSING your etablissement is more of an ID key that is unique and the Libelle is the label to be shown for the given etablissement.
Ex:
etablissement libelle
1 Business A
2 Business B
3 Business C
So the label will always be a direct correlation with the etablissement (ID) field.
Lets first simplify. You want a sum of all linge values that qualify per etablissement with some filters. Start with that, and the piece table is the basis of the date range criteria. The Piece table has the etablissement field you ultimately want to group by
select
p.gp_etablissement,
sum(l.gl_totalttcdev) SumPerEtablissement
from
Piece P
JOIN Linge L
-- all criteria specifically limiting the LINGE records
on P.gp_souche = L.gl_souche
and p.gp_naturepieceg = l.gl_naturepieceg
and p.gp_numero = l.gl_numero
and p.gp_indiceg = l.gl_indiceg
and l.gl_typearticle <> ''
and l.gl_typearticle <> 'FI'
where
-- simple to get all records for past 13 months for now just to show summary
p.gp_DatePiece > dateadd( month, -13, getdate())
group by
p.gp_etablissement
So from above, we have one result table per establishement with its total over the entire 13 months. Now, you want it broken down per month... add an extra column showing the month/year as a column and group by. While we are here, we can join to the etablissement table to pull the description/label you may want to show.
One extra item I am throwing into this is the months difference from the current date so we always have a constant sequence to organize totals by. Use DateDiff(). This will help for your pivot result pulling all data on one row per etablissement.
select
p.gp_etablissement,
e.et_libelle,
-- Example: 2019-04-27 would return '2019-04-27'
CONVERT(CHAR(7),p.gp_DatePiece,120) as YrMonth,
-- nice thing about datediff by month. It properly detects
-- '2019-04-01' from '2019-03-31' as 1 month apart even though 1 day
-- so ANY Date within the YrMonth will be within the given month.
min( datediff( month, p.gp_DatePiece, getdate() )) as MonthsPrior,
-- now the column getting summed
sum(l.gl_totalttcdev) SumPerEtabliss
from
Piece P
JOIN Linge L
-- all criteria specifically limiting the LINGE records
on P.gp_souche = L.gl_souche
and p.gp_naturepieceg = l.gl_naturepieceg
and p.gp_numero = l.gl_numero
and p.gp_indiceg = l.gl_indiceg
and l.gl_typearticle <> ''
and l.gl_typearticle <> 'FI'
JOIN Etabliss e
on p.gp_etablissement = e.et_etablissement
where
-- simple to get all records for past 13 months for now just to
-- show summary, or change to 24 months as so needed
p.gp_DatePiece > dateadd( month, -13, getdate())
group by
p.gp_etablissement,
e.et_libelle,
CONVERT(CHAR(7),GETDATE(),120)
the above results may have something like...
gp_etablissement et_libelle YrMonth MonthsPrior SumPerEtabliss
establissID1 libell1 2018-12 4 382
establissID1 libell1 2019-01 3 123
establissID1 libell1 2019-02 2 821
establissID1 libell1 2019-03 1 467
establissID2 libell2 2018-12 4 532
establissID2 libell2 2019-01 3 221
establissID2 libell2 2019-02 2 629
establissID2 libell2 2019-03 1 395
Now, you can build a cross-tab query aggregating each column based on how many month away they are from the original month in question. Since you want 24 months, you would have to copy/paste the sum() values below to represent the remaining columns.
select
pq.gp_etablissement,
pq.et_libelle,
sum( pq.SumPerEtabliss * case when pq.MonthsPrior = 1 then 1 else 0 end ) as [Month 1],
sum( pq.SumPerEtabliss * case when pq.MonthsPrior = 2 then 1 else 0 end ) as [Month 2],
sum( pq.SumPerEtabliss * case when pq.MonthsPrior = 3 then 1 else 0 end ) as [Month 3],
sum( pq.SumPerEtabliss * case when pq.MonthsPrior = 4 then 1 else 0 end ) as [Month 4],
sum( pq.SumPerEtabliss * case when pq.MonthsPrior = 5 then 1 else 0 end ) as [Month 5],
sum( pq.SumPerEtabliss * case when pq.MonthsPrior = 6 then 1 else 0 end ) as [Month 6],
sum( pq.SumPerEtabliss * case when pq.MonthsPrior = 7 then 1 else 0 end ) as [Month 7],
sum( pq.SumPerEtabliss * case when pq.MonthsPrior = 8 then 1 else 0 end ) as [Month 8],
sum( pq.SumPerEtabliss * case when pq.MonthsPrior = 9 then 1 else 0 end ) as [Month 9],
sum( pq.SumPerEtabliss * case when pq.MonthsPrior = 10 then 1 else 0 end ) as [Month 10],
sum( pq.SumPerEtabliss * case when pq.MonthsPrior = 11 then 1 else 0 end ) as [Month 11],
sum( pq.SumPerEtabliss * case when pq.MonthsPrior = 12 then 1 else 0 end ) as [Month 12],
sum( pq.SumPerEtabliss * case when pq.MonthsPrior = 13 then 1 else 0 end ) as [Month 13]
from
( THE ENTIRE QUERY ABOVE THAT DOES THE GROUP BY ) PQ
group by
pq.gp_etablissement,
pq.et_libelle
order by
pq.et_libelle

Related

Dynamically SELECT a column based on value of row at first of month

The following query:
SELECT Confirmed, Interim, Declared, Date
FROM Interest_Hist
WHERE Date BETWEEN '2019-08-01' AND '2019-12-04'
ORDER BY Date ASC
Returns the following sample data:
Confirmed Interim Declared Date
Y 0.314 0.0788 2019-08-01
0.317 0 2019-08-02
...
0.245 0 2019-08-31
0.222 0.219 2019-09-01
0.198 0 2019-09-02
...
Y 0.50 0.454 2019-12-01
0.51 0 2019-12-02
0.52 0 2019-12-03
0.53 0 2019-12-04
Where on the first of the month, Confirmed = Y, I need to return the Declared column for that month.
Note, Confirmed = Y will only exist on the first of the month. That column is blank in all other cases
Otherwise, I need to return each Interim column for the month.
Thus far, I have been able to return the SUM of either column, but not the individual values.
SELECT
CASE WHEN SUM(CASE WHEN IRc_Confirmed = 'Y' THEN 1 ELSE 0 END) = 0
THEN Interim
ELSE Declared
END AS Rate
FROM Fund_Interest
WHERE Date BETWEEN '2019-08-01' AND '2019-12-04'
GROUP BY
DATEADD(month, DATEDIFF(month, 0, Date), 0), Interim, Declared
ORDER BY
DATEADD(month, DATEDIFF(month, 0, Date), 0)
The expected output given the data at the top is as follows
0.0788
0
...
0
0.222
0.198
...
0.454
0
0
0
Find all the year months where the first day is Y:
SELECT year([date]) as yr, month([date]) as mo
FROM fund_interest
WHERE DAY([date]) = 1 and confirmed = 'Y'
This gives you a list of years and months where there is a Y on the first eg Aug 2019
Now make it a cte and left join it back to your data on year and month, and where the join succeeds return declared else interim:
WITH x AS(
SELECT year([date]) as yr, month([date]) as mo
FROM fund_interest
WHERE DAY([date]) = 1 and confirmed = 'Y'
)
SELECT
CASE WHEN x.yr IS NOT NULL THEN f.declared ELSE f.interim END AS something
FROM
fund_interest f
LEFT OUTER JOIN x ON x.yr = year(f.[date]) AND x.mo = month(f.[date])
All of the rows of different days from Aug 2019 and Dec 2019 will succeed in the join. They will have a NOT NULL yr value and hence the declared will show. For all Sep 2019 rows there is no match in the join (Sep 2019 is not returned by the query in the CTE), yr is null, interim shows instead
For a better idea of what is going on do a SELECT *
If you want to use just a single column the EOMONTH function could be used to return a consistent date every month. Replace MONTH with EOMONTH. Remove calls to YEAR from the query
Do not use reserved words like DATE as column names, by the way
You can use a CTE to group by month and year and then join to your original table (Interest_Hist) on the month and year parts of your date field. You can then select the Interim or Declared value using a simple case statement
;WITH CTE AS
(
SELECT DATEPART(month, DateFld) Mnth, DATEPART(year, DateFld) Yr,
MAX(Confirmed) ConfirmedVal
FROM Interest_Hist
GROUP BY DATEPART(month, DateFld), DATEPART(year, DateFld)
)
SELECT
CASE WHEN c.ConfirmedVal= 'Y' THEN interest.Declared ELSE interest.Interim END
FROM Interest_Hist interest
INNER JOIN CTE c ON
DATEPART(month, interest.DateFld) = c.Mnth AND
DATEPART(year, interest.DateFld) = c.Yr
You can see the query in action here
This took me way longer than it probably should have.
SELECT IIF( MAX(Confirmed) OVER(PARTITION BY CONVERT(VARCHAR(6), Date, 112)) = 'Y', Declared, Interim) Interest_Rate
FROM Interest_Hist
WHERE DateBETWEEN '01-AUG-2019' AND '04-DEC-2019'
ORDER BY Date

How to calculate prior year sales data in SQL

I'm attempting to build a table summarizing sales data by week. In it, I'm trying to have one of the adjacent columns show the sales figures for the same fiscal week during the prior year (which due to my organizations fiscal calendar, had a 53rd week last year). I also have need to compare (Comp Units/Comp Sales) to a period 52 weeks ago which is an entirely different fiscal week (Think Week 9 of 2019 comparing to Week 10 2018).
I've tried using both unions and full outer joins, but given the way the way my data is, they're inefficient (Because this is weekly data, unions ended up being inefficient as I needed to leave the date information out of the initial query, then updating columns in my table to reflect the week the data is for. This is obviously rife with opportunity for error, but also time consuming to do 105 times), or just didn't work (attempting a full outer join was returning the wrong answers for all columns). I've also tried utilizing CTEs as well, and that's not working for me either. I'm currently trying a CASE Statement, but that's also returning a null value. I'm not quite sure where to go next
#STANDARDSQL
SELECT
DTL.SKU_NBR AS SKU_NBR
, SLS.STR_NBR AS STR_NBR
, CONCAT(TRIM(CAST(SKU_HIER.SKU_NBR AS STRING)), ' ', '-', ' ', TRIM(SKU_HIER.SKU_DESC)) AS SKU
, CONCAT(TRIM(CAST(SKU_HIER.EXT_SUB_CLASS_NBR AS STRING)), ' ', '-', ' ', TRIM(SKU_HIER.SUB_CLASS_DESC)) AS SUB_CLASS
, CONCAT(TRIM(CAST(SKU_HIER.EXT_SUB_SC_NBR AS STRING)), ' ', '-', ' ', TRIM(SKU_HIER.SUB_SC_DESC)) AS SUB_SUB_CLASS
, LOCATION.MKT_NM AS MARKET_NAME
, LOCATION.RGN_NM AS REGION_NAME
, LOCATION.DIV_NM AS DIVISION_NAME
, LOCATION.DIV_NBR AS DIVISION_NUMBER
, LOCATION.RGN_NBR AS REGION_NUMBER
, LOCATION.MKT_NBR AS MARKET_NUMBER
, COMP.STR_COMP_IND AS COMP_IND
, COMP.PY_STR_COMP_IND AS PRIOR_COMP_IND
, CALENDAR.FSCL_WK_DESC AS FISCAL_WEEK
, CALENDAR.FSCL_PRD_DESC AS FISCAL_PERIOD
, CALENDAR.FSCL_WK_END_DT AS END_DATE
, CALENDAR.FSCL_WK_BGN_DT AS BEGIN_DATE
, CALENDAR.FSCL_YR AS FISCAL_YEAR_NBR
, CALENDAR.FSCL_WK_NBR AS WEEK_NUMBER
, CALENDAR.FSCL_YR_WK_KEY_VAL AS FISCAL_KEY
, CALENDAR.LY_FYR_WK_KEY_VAL AS LY_FISCAL_KEY
, SUM(COALESCE(DTL.UNT_SLS,0)) AS UNITS
, SUM(COALESCE(DTL.EXT_RETL_AMT,0) + COALESCE(DTL.TOT_GDISC_DTL_AMT,0))
AS SALES
, SUM(CASE
WHEN 1=1 THEN (COALESCE(DTL.EXT_RETL_AMT,0) + COALESCE(DTL.TOT_GDISC_DTL_AMT,0)) * COMP.STR_COMP_IND
ELSE 0 END) AS COMP_SALES
, SUM(CASE
WHEN 1=1 THEN (COALESCE(DTL.UNT_SLS,0)) * COMP.STR_COMP_IND
ELSE 0 END) AS COMP_UNITS
, SUM(CASE
WHEN 1=1 AND SLS.SLS_DT = DATE_SUB(SLS.SLS_DT, INTERVAL 364 DAY)
THEN (COALESCE(DTL.EXT_RETL_AMT,0) +
COALESCE(DTL.TOT_GDISC_DTL_AMT,0)) * COMP.PY_STR_COMP_IND
ELSE NULL END)
AS LY_COMP_SALES
, SUM(CASE
WHEN 1=1 AND SLS.SLS_DT = DATE_SUB(SLS.SLS_DT, INTERVAL 364 DAY)
THEN (COALESCE(DTL.UNT_SLS,0)) * COMP.PY_STR_COMP_IND
ELSE NULL END)
AS LY_COMP_UNITS
, SUM(CASE
WHEN SLS.SLS_DT = DATE_SUB(SLS.SLS_DT, INTERVAL 371 DAY)
THEN (COALESCE(DTL.EXT_RETL_AMT,0) +
COALESCE(DTL.TOT_GDISC_DTL_AMT,0))
ELSE NULL END)
AS LY_SALES
, SUM(CASE
WHEN SLS.SLS_DT = DATE_SUB(SLS.SLS_DT, INTERVAL 371 DAY)
THEN (COALESCE(DTL.UNT_SLS,0))
ELSE NULL END)
AS LY_UNITS
FROM `pr-edw-views.SLS.POS_SLS_TRANS_DTL` AS SLS
INNER JOIN
UNNEST (SLS.DTL) AS DTL
JOIN `pr-edw-views.SHARED.MVNDR_HIER` AS MVNDR
ON DTL.DERIV_MVNDR.MVNDR_NBR = MVNDR.MVNDR_NBR
JOIN `pr-edw-views.SHARED.SKU_HIER_FD` AS SKU_HIER
ON SKU_HIER.SKU_NBR = DTL.SKU_NBR
AND SKU_HIER.SKU_CRT_DT = DTL.SKU_CRT_DT
JOIN `pr-edw-views.SHARED.LOC_HIER_FD` AS LOCATION
ON LOCATION.LOC_NBR = SLS.STR_NBR
JOIN `pr-edw-views.SHARED.CAL_PRD_HIER_FD` AS CALENDAR
ON CALENDAR.CAL_DT = SLS_DT
JOIN `pr-edw-views.SLS.STR_COMP_DAY` AS COMP
ON COMP.CAL_DT = CALENDAR.CAL_DT
AND COMP.STR_NBR = SLS.STR_NBR
WHERE CALENDAR.FSCL_WK_END_DT BETWEEN '2018-01-29' AND '2019-04-07'
AND SLS.SLS_DT BETWEEN '2018-01-29' AND '2019-04-07'
AND POS_TRANS_TYP_CD in ('S', 'R')
AND SKU_HIER.EXT_CLASS_NBR = '025-004'
AND MVNDR.MVNDR_NBR IN (74798, 60002238, 73059, 206820, 76009, 40263, 12879, 76722, 10830, 206823, 87752, 60052261, 70401, 51415, 51414)
AND SKU_HIER.LATEST_SKU_CRT_DT_FLG = TRUE
GROUP BY
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
I'm currently getting null values in my LY_SALES, LY_UNITS, LY_COMP_SALES and LY_COMP_UNITS columns, though I know there should have been locations with sales of those items from the same period the previous year. What I'm trying to get to is having those prior year values showing up along side the current year values. Any help would be hugely appreciated!
Thanks!
Such a condition can never be fulfilled : SLS.SLS_DT = DATE_SUB(SLS.SLS_DT, INTERVAL 371 DAY). Simply because a SLS_DT is not equal to SLS_DT-371.
You can pre-aggregate the table in a CTE (adding SLS_DT to the group by columns) and then replace the CASE with a join to the pre-aggregated table. Aim at something like this: and it will become something like (notice - no SUM in the case):
CASE WHEN AGGSLS.SLS_DT = DATE_SUB(SLS.SLS_DT, INTERVAL 371 DAY)
THEN (COALESCE(AGGSLS.SUM_EXT_RETL_AMT,0) +
COALESCE(AGGSLS.SUM_TOT_GDISC_DTL_AMT,0))
ELSE NULL END
Two things:
1) WHEN 1=1 can be expressed simply as WHEN TRUE, this way it is easier to move statements around without breaking the AND/OR chaining
2) to get the last year's sales. You can either omit the year from the final query and limit the output with a where clause or create a smaller table that has the sales this year, sales last year per week.
In my humble opinion sales last year for weeknum is the best option, as you can use it elsewhere. But it's pretty similar to what you wr
It would look something like:
SELECT CALENDAR.FSCL_WK_DESC as week_num,
sum(case when year = year(current_date()) then (COALESCE(DTL.UNT_SLS,0)) * COMP.STR_COMP_IND else 0 end) as this_year
sum(case when year = year(current_date())-1 then (COALESCE(DTL.UNT_SLS,0)) * COMP.STR_COMP_IND else 0 end) as last_year
And then you join back to the original table using week_num
Hope you find it useful
Cheers!

SQL Left vs Right Outer Join. Return all values

PROBLEM: Need query to return the MONTH and YIELD for Each Year. For some reason, if the data is not found in a Month a.Month the query will not return the b.Month's Yield. I need the query to return all monthly data regardless of whether or not a.Month contains a month with data in the same months as "b".
THE FOLLOWING RESULT: SHOULD RETURN A VALUE FOR "MONTH 1 YIELD_1". But it doesn't... because "MONTH 1 YIELD_0" does NOT contain a value for month 1.
**DATA RESULTS WITH: LEFT OUTER JOIN:**
Month Yield_1 Yield_0
2 11.44 14
3 NULL 3.21
4 NULL 14.24
7 NULL 10.36
8 NULL 0
9 NULL -9.6
10 NULL 10.35
11 NULL 1.4
12 11.44 -1.18
**DATA RESULTS WITH RIGHT OUTER JOIN:**
Month Yield_1 Yield_0
NULL 11.44 NULL
2 11.44 14
12 11.44 -1.18
QUERY:
SET #ID_CARTERA = 8;
select
a.Month Month,
b.Monthly_Yield Yield_Year_1,
a.Monthly_Yield Yield_Year_0
from
( select
LEFT(A.F_ANOMES, 4) Year,
RIGHT(A.F_ANOMES, 2) Month,
ROUND(A.POR_RENTABILIDAD, 2) Monthly_Yield
from dr_rent_carteras_meses A
where A.ID_CARTERA = #ID_CARTERA
And A.IND_RENTABILIDAD = 1
And LEFT(A.F_ANOMES, 4) = ( select MAX(left(F_ANOMES, 4 ) ) - 0 from dr_rent_carteras_meses where ID_CARTERA = #ID_CARTERA ) ) a
LEFT outer join
( select
LEFT(A.F_ANOMES, 4) Year,
RIGHT(A.F_ANOMES, 2) Month,
ROUND(A.POR_RENTABILIDAD, 2) Monthly_Yield
from dr_rent_carteras_meses A
where A.ID_CARTERA = #ID_CARTERA
And A.IND_RENTABILIDAD = 1
And LEFT(A.F_ANOMES, 4) = ( select MAX(left(F_ANOMES, 4 ) ) - 1 from dr_rent_carteras_meses where ID_CARTERA = #ID_CARTERA ) ) b on ( a.Month = b.Month )
order by month asc
Summary
I don't think you need either left, right, or full join for this specific query as a GROUP BY/CASEbased solution should work just fine and be at least twice as much faster.
Problem Definition
I found this question rather interesting as it seems to arise from a real life situation and I believe that in real life FULL JOIN is seldom if ever necessary. So to me the real question is not how to combine the data from left and right joins but rather why is a full join needed in the first place?
Besides, simple replacing LEFT with FULL would not be enough as that would lead to
Duplicate rows for the same months;
NULL months for when the month is not available in the latest year.
So I decided to go ahead and decipher the query.
Table and Data Setup
Unfortunately, #smileyseven didn't provide table definitions or insert statements, so I had to work them out from the query and sample data. Based on the query syntax I'm assuming that SQL Server was used so that's what I use as well.
Here's my guess at how the table looks:
CREATE TABLE dr_rent_carteras_meses
(
F_ANOMES varchar(6) NOT NULL PRIMARY KEY,
POR_RENTABILIDAD float NOT NULL,
-- These columns are irrelevant
ID_CARTERA INT NOT NULL DEFAULT(8),
IND_RENTABILIDAD INT DEFAULT(1) NOT NULL
)
The important point ist hat F_ANOMES is probably the key column as otherwise we would have to expect multiple rows for each month in the query output and that seems unlikely.
The following insert statements should produce the sample data:
INSERT dr_rent_carteras_meses (F_ANOMES, POR_RENTABILIDAD) VALUES
('201202', 14),
('201203', 3.21),
('201204', 14.24),
('201207', 10.36),
('201208', 0),
('201209', -9.6),
('201210', 10.35),
('201211', 1.4),
('201212', -1.18),
('201101', 11.44),
('201102', 11.44),
('201112', 11.44)
Solution
The first thing to note is that we don't really need to calculate the maximum year two times, so we'll start with this:
declare #Max_Year int
select
#Max_Year = MAX(left(F_ANOMES, 4))
from dr_rent_carteras_meses
where ID_CARTERA = #ID_CARTERA
The a and b tables are virtually same, so we could probably reuse them:
;with
Monthly_Yield_CTE as
(
select
LEFT(F_ANOMES, 4) Year,
RIGHT(F_ANOMES, 2) Month,
ROUND(POR_RENTABILIDAD, 2) Monthly_Yield
from dr_rent_carteras_meses
where ID_CARTERA = #ID_CARTERA
and IND_RENTABILIDAD = 1
and LEFT(F_ANOMES, 4) in (#Max_Year, #Max_Year - 1)
)
and use the FULL JOIN but I think a better option is to simply group it by month:
select
[Month],
SUM(CASE WHEN [Year] = #Max_Year - 1 THEN Monthly_Yield ELSE 0 END) Yield_Year_1,
SUM(CASE WHEN [Year] = #Max_Year THEN Monthly_Yield ELSE 0 END) Yield_Year_0
from Monthly_Yield_CTE
group by [Month]
order by [Month]
One could use either the CTE version or re-write it without CTE as the query is simple enough:
SET #ID_CARTERA = 8
declare #Max_Year int
select
#Max_Year = MAX(left(F_ANOMES, 4))
from dr_rent_carteras_meses
where ID_CARTERA = #ID_CARTERA
select
RIGHT(F_ANOMES, 2) Month,
SUM(CASE
WHEN LEFT(F_ANOMES, 4) = #Max_Year - 1
THEN ROUND(POR_RENTABILIDAD, 2)
ELSE 0
END) Yield_Year_1,
SUM(CASE
WHEN LEFT(F_ANOMES, 4) = #Max_Year
THEN ROUND(POR_RENTABILIDAD, 2)
ELSE 0
END) Yield_Year_0
from dr_rent_carteras_meses
where ID_CARTERA = #ID_CARTERA
and IND_RENTABILIDAD = 1
and LEFT(F_ANOMES, 4) in (#Max_Year, #Max_Year - 1)
group by RIGHT(F_ANOMES, 2)
order by 1
The only drawback I'm aware of is that we get 0 instead of NULL for the months where the data is missing and I'm not sure if that's important.
Performance
The performance of this query seems to be quite a bit better; in my setup the relative combined cost of the GROUP BY/CASE query with a separated MAX is about 30% vs 70% for FULL JOIN solution.
Try FULL OUTER JOIN. See here:
http://en.wikipedia.org/wiki/Join_%28SQL%29#Full_outer_join

SQL Query in CRM Report

A "Case" in CRM has a field called "Status" with four options.
I'm trying to
build a report in CRM that fills a table with every week of the year (each row is a different week), and then counts the number of cases that have each Status option (the columns would be each of the Status options).
The table would look like this
Status 1 Status 2 Status 3
Week 1 3 55 4
Week 2 5 23 5
Week 3 14 11 33
So far I have the following:
SELECT
SUM(case WHEN status = 1 then 1 else 0 end) Status1,
SUM(case WHEN status = 2 then 1 else 0 end) Status2,
SUM(case WHEN status = 3 then 1 else 0 end) Status3,
SUM(case WHEN status = 4 then 1 else 0 end) Status4,
SUM(case WHEN status = 5 then 1 else 0 end) Status5
FROM [DB].[dbo].[Contact]
Which gives me the following:
Status 1 Status 2 Status 3
2 43 53
Now I need to somehow split this into 52 rows for the past year and filter these results by date (columns in the Contact table). I'm a bit new to SQL queries and CRM - any help here would be much appreciated.
Here is a SQLFiddle with my progress and sample data: http://sqlfiddle.com/#!2/85b19/1
Sounds like you want to group by a range. The trick is to create a new field that represents each range (for you one per year) and group by that.
Since it also seems like you want an infinite range of dates, marc_s has a good summary for how to do the group by trick with dates in a generic way: SQL group by frequency within a date range
So, let's break this down:
You want to make a report that shows, for each contact, a breakdown, week by week, of the number of cases registered to that contact, which is divided into three columns, one for each StateCode.
If this is the case, then you would need to have 52 date records (or so) for each contact. For calendar like requests, it's always good to have a separate calendar table that lets you query from it. Dan Guzman has a blog entry that creates a useful calendar table which I'll use in the query.
WITH WeekNumbers AS
(
SELECT
FirstDateOfWeek,
-- order by first date of week, grouping calendar year to produce week numbers
WeekNumber = row_number() OVER (PARTITION BY CalendarYear ORDER BY FirstDateOfWeek)
FROM
master.dbo.Calendar -- created from script
GROUP BY
FirstDateOfWeek,
CalendarYear
), Calendar AS
(
SELECT
WeekNumber =
(
SELECT
WeekNumber
FROM
WeekNumbers WN
WHERE
C.FirstDateOfWeek = WN.FirstDateOfWeek
),
*
FROM
master.dbo.Calendar C
WHERE
CalendarDate BETWEEN '1/1/2012' AND getutcdate()
)
SELECT
C.FullName,
----include the below if the data is necessary
--Cl.WeekNumber,
--Cl.CalendarYear,
--Cl.FirstDateOfWeek,
--Cl.LastDateOfWeek,
'Week: ' + CAST(Cl.WeekNumber AS VARCHAR(20))
+ ', Year: ' + CAST(Cl.CalendarYear AS VARCHAR(20)) WeekNumber
FROM
CRM.dbo.Contact C
-- use a cartesian join to produce a table list
CROSS JOIN
(
SELECT
DISTINCT WeekNumber,
CalendarYear,
FirstDateOfWeek,
LastDateOfWeek
FROM
Calendar
) Cl
ORDER BY
C.FullName,
Cl.WeekNumber
This is different from the solution Ben linked to because Marc's query only returns weeks where there is a matching value, whereas you may or may not want to see even the weeks where there is no activity.
Once you have your core tables of contacts split out week by week as in the above (or altered for your specific time period), you can simply add a subquery for each StateCode to see the breakdown in columns as in the final query below.
WITH WeekNumbers AS
(
SELECT
FirstDateOfWeek,
WeekNumber = row_number() OVER (PARTITION BY CalendarYear ORDER BY FirstDateOfWeek)
FROM
master.dbo.Calendar
GROUP BY
FirstDateOfWeek,
CalendarYear
), Calendar AS
(
SELECT
WeekNumber =
(
SELECT
WeekNumber
FROM
WeekNumbers WN
WHERE
C.FirstDateOfWeek = WN.FirstDateOfWeek
),
*
FROM
master.dbo.Calendar C
WHERE
CalendarDate BETWEEN '1/1/2012' AND getutcdate()
)
SELECT
C.FullName,
--Cl.WeekNumber,
--Cl.CalendarYear,
--Cl.FirstDateOfWeek,
--Cl.LastDateOfWeek,
'Week: ' + CAST(Cl.WeekNumber AS VARCHAR(20)) +', Year: ' + CAST(Cl.CalendarYear AS VARCHAR(20)) WeekNumber,
(
SELECT
count(*)
FROM
CRM.dbo.Incident I
INNER JOIN CRM.dbo.StringMap SM ON
I.StateCode = SM.AttributeValue
INNER JOIN
(
SELECT
DISTINCT ME.Name,
ME.ObjectTypeCode
FROM
CRM.MetadataSchema.Entity ME
) E ON
SM.ObjectTypeCode = E.ObjectTypeCode
WHERE
I.ModifiedOn >= Cl.FirstDateOfWeek
AND I.ModifiedOn < dateadd(day, 1, Cl.LastDateOfWeek)
AND E.Name = 'incident'
AND SM.AttributeName = 'statecode'
AND SM.LangId = 1033
AND I.CustomerId = C.ContactId
AND SM.Value = 'Active'
) ActiveCases,
(
SELECT
count(*)
FROM
CRM.dbo.Incident I
INNER JOIN CRM.dbo.StringMap SM ON
I.StateCode = SM.AttributeValue
INNER JOIN
(
SELECT
DISTINCT ME.Name,
ME.ObjectTypeCode
FROM
CRM.MetadataSchema.Entity ME
) E ON
SM.ObjectTypeCode = E.ObjectTypeCode
WHERE
I.ModifiedOn >= Cl.FirstDateOfWeek
AND I.ModifiedOn < dateadd(day, 1, Cl.LastDateOfWeek)
AND E.Name = 'incident'
AND SM.AttributeName = 'statecode'
AND SM.LangId = 1033
AND I.CustomerId = C.ContactId
AND SM.Value = 'Resolved'
) ResolvedCases,
(
SELECT
count(*)
FROM
CRM.dbo.Incident I
INNER JOIN CRM.dbo.StringMap SM ON
I.StateCode = SM.AttributeValue
INNER JOIN
(
SELECT
DISTINCT ME.Name,
ME.ObjectTypeCode
FROM
CRM.MetadataSchema.Entity ME
) E ON
SM.ObjectTypeCode = E.ObjectTypeCode
WHERE
I.ModifiedOn >= Cl.FirstDateOfWeek
AND I.ModifiedOn < dateadd(day, 1, Cl.LastDateOfWeek)
AND E.Name = 'incident'
AND SM.AttributeName = 'statecode'
AND SM.LangId = 1033
AND I.CustomerId = C.ContactId
AND SM.Value = 'Canceled'
) CancelledCases
FROM
CRM.dbo.Contact C
CROSS JOIN
(
SELECT
DISTINCT WeekNumber,
CalendarYear,
FirstDateOfWeek,
LastDateOfWeek
FROM
Calendar
) Cl
ORDER BY
C.FullName,
Cl.WeekNumber

PIVOT SQL Server Assistance

Given the following table structure:
CrimeID | No_Of_Crimes | CrimeDate | Violence | Robbery | ASB
1 1 22/02/2011 Y Y N
2 3 18/02/2011 Y N N
3 3 23/02/2011 N N Y
4 2 16/02/2011 N N Y
5 1 17/02/2011 N N Y
Is there a chance of producing a result set that looks like this with T-SQL?
Category | This Week | Last Week
Violence 1 3
Robbery 1 0
ASB 3 1
Where last week shuld be a data less than '20/02/2011' and this week should be greater than or equal to '20/02/2011'
I'm not looking for someone to code this out for me, though a code snippet would be handy :), just some advice on whether this is possible, and how i should go about it with SQL Server.
For info, i'm currently performing all this aggregation using LINQ on the web server, but this requires 19MB being sent over the network every time this request is made. (The table has lots of categories, and > 150,000 rows). I want to make the DB do all the work and only send a small amount of data over the network
Many thanks
EDIT removed incorrect sql for clarity
EDIT Forget the above try the below
select *
from (
select wk, crime, SUM(number) number
from (
select case when datepart(week, crimedate) = datepart(week, GETDATE()) then 'This Week'
when datepart(week, crimedate) = datepart(week, GETDATE())-1 then 'Last Week'
else 'OLDER' end as wk,
crimedate,
case when violence ='Y' then no_of_crimes else 0 end as violence,
case when robbery ='Y' then no_of_crimes else 0 end as robbery,
case when asb ='Y' then no_of_crimes else 0 end as asb
from crimetable) as src
UNPIVOT
(number for crime in
(violence, robbery, asb)) as pivtab
group by wk, crime
) z
PIVOT
( sum(number)
for wk in ([This Week], [Last Week])
) as pivtab
Late to the party, but a solution with an optimal query plan:
Sample data
create table crimes(
CrimeID int, No_Of_Crimes int, CrimeDate datetime,
Violence char(1), Robbery char(1), ASB char(1));
insert crimes
select 1,1,'20110221','Y','Y','N' union all
select 2,3,'20110218','Y','N','N' union all
select 3,3,'20110223','N','N','Y' union all
select 4,2,'20110216','N','N','Y' union all
select 5,1,'20110217','N','N','Y';
Make more data - about 10240 rows in total in addition to the 5 above, each 5 being 2 weeks prior to the previous 5. Also create an index that will help on crimedate.
insert crimes
select crimeId+number*5, no_of_Crimes, DATEADD(wk,-number*2,crimedate),
violence, robbery, asb
from crimes, master..spt_values
where type='P'
create index ix_crimedate on crimes(crimedate)
From here on, check output of each to see where this is going. Check also the execution plan.
Standard Unpivot to break the categories.
select CrimeID, No_Of_Crimes, CrimeDate, Category, YesNo
from crimes
unpivot (YesNo for Category in (Violence,Robbery,ASB)) upv
where YesNo='Y'
Notes:
The filter on YesNo is actually applied AFTER unpivoting. You can comment it out to see.
Unpivot again, but this time select data only for last week and this week.
select CrimeID, No_Of_Crimes, Category,
Week = sign(datediff(d,CrimeDate,w.firstDayThisWeek)+0.1)
from crimes
unpivot (YesNo for Category in (Violence,Robbery,ASB)) upv
cross join (select DATEADD(wk, DateDiff(wk, 0, getdate()), 0)) w(firstDayThisWeek)
where YesNo='Y'
and CrimeDate >= w.firstDayThisWeek -7
and CrimeDate < w.firstDayThisWeek +7
Notes:
(select DATEADD(wk, DateDiff(wk, 0, getdate()), 0)) w(firstDayThisWeek) makes a single-column table where the column contains the pivotal date for this query, being the first day of the current week (using DATEFIRST setting)
The filter on CrimeDate is actually applied on the BASE TABLE prior to unpivoting. Check plan
Sign() just breaks the data into 3 buckets (-1/0/+1). Adding +0.1 ensures that there are only two buckets -1 and +1.
The final query, pivoting by this/last week
select Category, isnull([1],0) ThisWeek, isnull([-1],0) LastWeek
from
(
select Category, No_Of_Crimes,
Week = sign(datediff(d,w.firstDayThisWeek,CrimeDate)+0.1)
from crimes
unpivot (YesNo for Category in (Violence,Robbery,ASB)) upv
cross join (select DATEADD(wk, DateDiff(wk, 0, getdate()), -1)) w(firstDayThisWeek)
where YesNo='Y'
and CrimeDate >= w.firstDayThisWeek -7
and CrimeDate < w.firstDayThisWeek +7
) p
pivot (sum(No_Of_Crimes) for Week in ([-1],[1])) pv
order by Category Desc
Output
Category ThisWeek LastWeek
--------- ----------- -----------
Violence 1 3
Robbery 1 0
ASB 3 3
I would try this:
declare #FirstDayOfThisWeek date = '20110220';
select cat.category,
ThisWeek = sum(case when cat.CrimeDate >= #FirstDayOfThisWeek
then crt.No_of_crimes else 0 end),
LastWeek = sum(case when cat.CrimeDate >= #FirstDayOfThisWeek
then 0 else crt.No_of_crimes end)
from crimetable crt
cross apply (values
('Violence', crt.Violence),
('Robbery', crt.Robbery),
('ASB', crt.ASB))
cat (category, incategory)
where cat.incategory = 'Y'
and crt.CrimeDate >= #FirstDayOfThisWeek-7
group by cat.category;