SQL: WITH ROLLUP per department without totals - sql

I have some SQL needed for creating a file for export. I have a lot of it working as it should. The one major stumbling block is with the WITH ROLLUP portion.
I need to get totals by VendorNo(store) and by DepartmentID(dept), but not with overall totals. The Department is gleaned by getting the first 3 digits of a column as you can see below. Grouping on that as well as the VendorNo is giving me what I need but also throwing overall totals in there.
How can I either pare out the overall totals from WITH ROLLUP OR rewrite some portions to accomplish the same totaling per VendorNo, Dept?
So far, I have:
select
'VDP' as [Record Id],
'TRU' as [Client Id],
o.vendorNo as [Store Number],
CASE WHEN LEFT(HVI.EncodeData, 3) IS NULL THEN 'S' ELSE 'D' END as [Feed Level],
LEFT(HVI.EncodeData, 3) AS [DepartmentId],
CONVERT(Date, pto.PrintBatch) as [Metric Date],
CASE WHEN pto.pickbit = 1 THEN 'ISPU_ORDER_PICKUP' ELSE 'ISPU_ORDER_CREATED' END as [Metric ID], -- 15 chars ISPU_ORDER_CREATED or ISPU_ORDER_PICKUP TOTO: VALIDATE THIS WORKS !!
'A' AS [Metric Category],
'D' AS [Metric Granularity],
REPLICATE('0', 9 - len(CAST(SUM(OI.Quantity) AS int))) + CAST(CAST(SUM(OI.Quantity) as Int)as VARCHAR) AS [Data Value 1], -- needed to get the left-padded 9 char max value
NULL as [Data Value 2],
NULL as [Data Value 3],
NULL as [Data Value 4],
NULL as [Data Value 5],
NULL as [Data Value 6],
NULL as [Operation Code]
FROM
pickticketsorders pto
inner join orders o
on pto.orderno = o.orderno
and o.rank = 3
and pto.printbatch >= '6-12-2015'
and pto.printbatch < '6-13-2015'
INNER JOIN HostVendorItems HVI
on HVI.ItemId = pto.ItemId
INNER JOIN OrderItems OI
on OI.OrderNo = o.OrderNo
GROUP BY
o.VendorNo,
LEFT(HVI.EncodeData, 3),
pto.PrintBatch,
pto.PickBit
WITH ROLLUP
ORDER BY
o.VendorNo,
DepartmentId ,
pto.PrintBatch
Which gives me: (the line that has StoreNo as NULL needs to be removed)

Use GROUPING SETS. I am not sure exactly which groups you want, but this seems to match what you are asking for:
GROUP BY GROUPING SETS ((o.VendorNo, LEFT(HVI.EncodeData, 3), pto.PrintBatch, pto.PickBit),
(o.VendorNo, LEFT(HVI.EncodeData, 3)),
(o.VendorNo)
)
You might want to read more about GROUPING SETS in the documentation.

You could also add :
HAVING GROUPING(o.VendorNo) = 0

Related

SQL Server stored procedure taking a LONG time to run after minor changes

I have a stored procedure that takes info from two tables I created to generate a summary table that is then used with several views.
Previously this took between 60-90 seconds to run. I had two calls to functions for different costs, and a third that makes another call for cost * qty. I removed all 3 and replaced with a new function that is almost an exact copy of one of the other cost functions
I wrote this as I was working through it, so it's evolved a bit. I improved on the speed, but it's still nowhere near as fast as it was before and I'm not sure why.
ALTER FUNCTION [dbo].[fn_getFactoryStdCost]
(#PartID int)
RETURNS decimal(20, 4)
AS
BEGIN
DECLARE #pureID int = 0
SET #pureID = (SELECT TOP(1) PURE_COST_ID
FROM visuser.PART_COST
WHERE EN_PART_ID = #partID
ORDER BY EN_REV_MASTER_ID DESC, IC_WAREHOUSE_ID DESC)
RETURN (SELECT TOP(1) (TOT_MATERIAL_N + TOT_MATERIAL_OVERHEAD_N)
FROM visuser.PURE_COST
WHERE PURE_COST_ID = #pureID
ORDER BY (TOT_MATERIAL_N + TOT_MATERIAL_OVERHEAD_N) DESC)
END
Replaced with. I added the WITH INLINE = OFF after it first got stuck to rule that out. The function by itself works just fine.
ALTER FUNCTION [dbo].[fn_getFactoryStdCost]
(#PartID int)
RETURNS decimal(20,4)
WITH INLINE = OFF
AS
BEGIN
DECLARE #pureID int = 0
SET #pureID = (SELECT TOP(1) PURE_COST_ID
FROM visuser.PART_COST
WHERE EN_PART_ID = #partID
ORDER BY EN_REV_MASTER_ID DESC, IC_WAREHOUSE_ID DESC)
RETURN (SELECT TOP(1) (TOT_MATERIAL_N + TOT_MATERIAL_OVERHEAD_N + TOT_RUN_VALUE_N + TOT_FIXED_OVERHEAD_N) FROM visuser.PURE_COST WHERE PURE_COST_ID = #pureID ORDER BY (TOT_MATERIAL_N + TOT_MATERIAL_OVERHEAD_N) DESC)
END
The other changes that I made was adding [Qty] > 0 AND to the [Part Count] line
And replacing the string based entries for the Commondity ID to ints (which is more appropriate) as the COMMODITY_ID is a reference to the COMMODITY_CODE which is what the strings were.
I expected it to run faster, not run indefinitely. The procedure is now taking forever to run. I'm now on 38min and counting. I also tried just copying the code in the procedure itself and running it and it is also taking forever, so it's something in the code itself.
The AllPartsList table has 1.04m lines, as does the bomBreakdown table. The bomBreakdown table is far more complex and takes 40-60s to generate. The bomSummary table will have 4,100 lines. The AllPartsList table has appropriate indexes, bomBreakdown doesn't.
ALTER PROCEDURE [dbo].[createBOMSummary]
AS
DECLARE #processTime int=0, #begin datetime, #end datetime
SET #begin = SYSDATETIME()
IF OBJECT_ID(N'dbo.bomSummary', N'U') IS NOT NULL
DROP TABLE bomSummary
SELECT
DISTINCT ap.[SourcePartID] AS [Assembly Part ID],
p.[PART_X] AS [Assembly Part #],
p.[DESCR_X] AS [Assembly Part Description],
(SELECT COUNT(DISTINCT [Component Part #]) FROM [bomBreakdown] WHERE [Qty] > 0 AND [Component Part ID] IS NOT NULL AND SourcePartID = ap.SourcePartID GROUP BY [SourcePartID]) AS [Part Count],
(SELECT SUM([Qty]) FROM [bomBreakdown] WHERE [Component Part ID] IS NOT NULL AND SourcePartID = ap.[SourcePartID] GROUP BY [SourcePartID]) AS [Total # of Parts],
([dbo].[fn_getFactoryStdCost](ap.[SourcePartID])) AS [Factory Std Cost],
COALESCE(
(SELECT COUNT(DISTINCT ComponentPartID)
FROM AllPartsList apl
LEFT JOIN visuser.EN_PART p1
ON p1.[EN_Part_ID] = apl.[ComponentPartID]
WHERE
apl.ComponentPartID IS NOT NULL AND
apl.SourcePartID = ap.SourcePartID AND
p1.Commodity_ID IN (15, 84, 85, 87, 81, 92) -- Commodity Codes: 009, 072, 073, 075, 079, 082
GROUP BY SourcePartID
), 0) AS [# of Docs], --0sec
COALESCE(
(SELECT COUNT(DISTINCT ComponentPartID)
FROM AllPartsList apl
LEFT JOIN visuser.EN_PART p1
ON p1.[EN_Part_ID] = apl.[ComponentPartID]
WHERE
apl.ComponentPartID IS NOT NULL AND
apl.SourcePartID = ap.SourcePartID AND
p1.Commodity_ID IN (28) -- Commodity Code 034
GROUP BY SourcePartID
), 0) AS [# of Software], --0sec
COALESCE(
(SELECT COUNT(*)
FROM visuser.[PART_COST]
WHERE [STD_PO_Cost_N] > 0 AND
EN_PART_ID IN
(SELECT DISTINCT ComponentPartID FROM AllPartsList WHERE ComponentPartID IS NOT NULL AND SourcePartID = ap.SourcePartID)
), 0) AS [# of Std Cost Items], --0sec
COALESCE(
(SELECT COUNT(DISTINCT ComponentPartID)
FROM AllPartsList apl
LEFT JOIN visuser.EN_PART p1
ON p1.[EN_Part_ID] = apl.[ComponentPartID]
WHERE
apl.ComponentPartID IS NOT NULL AND
apl.SourcePartID = ap.SourcePartID AND
p1.Commodity_ID IN (11) -- Commodity Code: 002
GROUP BY SourcePartID), 0
) AS [# of HR Devices] ,--0sec
COALESCE(
(SELECT COUNT(DISTINCT ComponentPartID)
FROM AllPartsList apl
LEFT JOIN visuser.EN_PART p1
ON p1.[EN_Part_ID] = apl.[ComponentPartID]
WHERE
apl.ComponentPartID IS NOT NULL AND
apl.SourcePartID = ap.SourcePartID AND
p1.Commodity_ID IN (5) -- Commodity Code: 007
GROUP BY SourcePartID), 0
) AS [# of 3rd Party Devices], --0sec
COALESCE(
(SELECT COUNT(DISTINCT ComponentPartID)
FROM AllPartsList apl
LEFT JOIN visuser.EN_PART p1
ON p1.[EN_Part_ID] = apl.[ComponentPartID]
WHERE
apl.ComponentPartID IS NOT NULL AND
apl.SourcePartID = ap.SourcePartID AND
p1.Commodity_ID IN (13) AND -- Commodity Code: 005
p1.MAKE_BUY_C = 'B'
GROUP BY SourcePartID
), 0) AS [# of Robots], --0sec
COALESCE(
(SELECT COUNT(*)
FROM visuser.[PART_COST] c
LEFT JOIN visuser.[EN_PART] p
ON p.[EN_PART_ID] = c.[EN_PART_ID]
WHERE
c.[STD_PO_Cost_N] > 0 AND
p.[MAKE_BUY_C] = 'B' AND
c.[EN_PART_ID] IN
(SELECT DISTINCT ComponentPartID FROM AllPartsList WHERE ComponentPartID IS NOT NULL AND SourcePartID = ap.SourcePartID)
), 0) AS [# of Buy Parts], --0sec
COALESCE(
(SELECT COUNT(*)
FROM visuser.[PART_COST] c
LEFT JOIN visuser.[EN_PART] p
ON p.[EN_PART_ID] = c.[EN_PART_ID]
WHERE
c.[STD_PO_Cost_N] > 0 AND
p.[MAKE_BUY_C] = 'M' AND
c.[EN_PART_ID] IN
(SELECT DISTINCT ComponentPartID FROM AllPartsList WHERE ComponentPartID IS NOT NULL AND SourcePartID = ap.SourcePartID)
), 0) AS [# of Make Parts]
INTO bomSummary
FROM AllPartsList ap
LEFT JOIN visuser.EN_PART p
ON p.[EN_Part_ID] = ap.[SourcePartID]
ORDER BY [PART_X]
SET #end = SYSDATETIME()
SET #processTime = DATEDIFF(s, #begin, #end)
PRINT #end
PRINT CHAR(10)+CHAR(13)
PRINT 'bomSummary Processing Time: ' + CONVERT(varchar, #processTime)
GO
Here's how the bomBreakdown table looks:
And the AllPartsList table:
If I comment out the function line two records takes 1m 20s to process, here's part of the execution plan. It looks like each COALESCE I have adds 4-6 seconds to the process time.
If I remove all the COALESCE then it takes 2min 50sec to process all 4981 records. Here's the execution list for it:
The execution plans suggested a couple additional indexes, so I added those and now 1 record takes 0 seconds, 2 took 5 secs, 10 took 1 sec, 100 took 2sec, 1000 took 28, and all 4981 took 4min 17sec.
The additional indexes certainly helped, I no longer see %s over 1000%, there are several still over 100% which makes me think there is some more optimization that could be done, I'm just not sure where. The execution plan is huge, so here just a few shots:
Not sure what was up with the 2 records. It's not the 90sec it was before, but it at least finishes now.
Odd thing I see is that it has (1000 rows affected), then (1 row affected). I have no idea what that 1 row is or where it's coming from. And I'd still like to know why making those few changes made such a hug difference.
I'm using:
SQL Server 2019 (v15.0.2070.41)
SSMS v18.5
Here are the results of my modifications based on allmhuran's suggestions:
SELECT
DISTINCT ap.[SourcePartID] AS [Assembly Part ID],
p.[PART_X] AS [Assembly Part #],
p.[DESCR_X] AS [Assembly Part Description],
oa2.[Part Count],
oa2.[Total # of Parts],
([dbo].[fn_getFactoryStdCost](ap.[SourcePartID])) AS [Factory Std Cost],
oa2.[# of Docs],
oa2.[# of Software],
'Logic Pending' AS [# of Std Cost Items],
oa2.[# of HR Devices],
oa2.[# of 3rd Party Devices],
oa2.[# of Robots],
oa2.[# of Buy Parts],
oa2.[# of Make Parts]
FROM AllPartsList ap
LEFT JOIN visuser.EN_PART p
ON p.[EN_Part_ID] = ap.[SourcePartID]
OUTER APPLY (
SELECT
[Part Count] = COUNT( DISTINCT IIF( [Qty] = 0, null, [Component Part #]) ),
[Total # of Parts] = SUM([Qty]),
[# of Docs] = COUNT( DISTINCT IIF( [Commodity Code] IN ('009', '072', '073', '075', '079', '082'), [Component Part #], null) ), -- Commodity Codes: 009, 072, 073, 075, 079, 082 : Commodity ID: 15, 84, 85, 87, 81, 92
[# of Software] = COUNT( DISTINCT IIF( [Commodity Code] IN ('034'), [Component Part #], null) ), -- Commodity Code 034 : Commodity ID: 28
[# of HR Devices] = COUNT( DISTINCT IIF( [Commodity Code] IN ('002'), [Component Part #], null) ), -- Commodity Code 002 : Commodity ID: 11
[# of 3rd Party Devices] = COUNT( DISTINCT IIF( [Commodity Code] IN ('007'), [Component Part #], null) ), -- Commodity Code 007 : Commodity ID: 5
[# of Robots] = COUNT( DISTINCT IIF( ( [Commodity Code] IN ('005') AND [Make/Buy] = 'B' ), [Component Part #], null) ), -- Commodity Code 005 : Commodity ID: 13
[# of Buy Parts] = COUNT( DISTINCT IIF( [Make/Buy] = 'B', [Component Part #], null) ),
[# of Make Parts] = COUNT( DISTINCT IIF( [Make/Buy] = 'M', [Component Part #], null) )
FROM bomBreakdown
WHERE
[Component Part ID] IS NOT NULL AND
[SourcePartID] = ap.[SourcePartID] AND
--[SourcePartID] = ap.[AssemblyPartID] AND
ap.SourcePartID = 964
GROUP BY [SourcePartID]
) oa2
OK, snuck in a bit of time to go through this.
Scalar function refactor
As mentioned in my comment, scalar functions do bad things to set based operations. In general, if you have a pattern like
create function scalar_UDF(#i int) returns int as begin
return #i * 2;
end
select c = scalar_UDF(t.c)
from t;
Then this turns your select into a row-by-agonising-row (RBAR) operation under the covers.
You can improve the performance by sticking with set based operations. One way to do this is to mark the scalar UDF as inline, which basically tells SQL it can rewrite your query to this before generating a query plan:
select c = t.c * 2
from t;
But scalar function inlining is a difficult thing for microsoft to solve, and is still a bit buggy. Another way is to handle it yourself, by using an inline table valued function and cross apply or outer apply
create function inline_TVF(#i int) returns table as return
(
select result = #i * 2
)
select c = u.result
from t
outer apply inline_TVF(t.c) u;
Actual factorization refactor
Part of your existing procedure looks like this:
select [Part Count] =
(
select count(distinct [Component Part #])
from bomBreakdown
where Qty > 0
and [Component Part ID] is not null
and SourcePartID = ap.SourcePartID
group by SourcePartID
),
[Total # of Parts] =
(
select sum(Qty)
from bomBreakdown
where [Component Part ID] is not null
and SourcePartID = ap.SourcePartID
group by SourcePartID
)
-- , more ...
Those two subqueries look really similar. It's this sort of pattern:
select a = (
select x1 from y where z
),
b = (
select x2 from y where almost_z
)
What we'd really like to do is something like the following. If we could, then the query only needs to hit the y table once, instead of hitting it twice. But of course the syntax wouldn't be valid:
select a = t.x1,
b = t.x2
from (
select x1 where z,
x2 where almost_z
from y
) t
Aha, but perhaps we can be a bit clever. If we look back to your specific case, we might change it into something like this:
select oa1.[Part Count],
oa1.[Total # of Parts]
into bomSummary
from AllPartsList ap
left join visuser.EN_PART p on p.EN_Part_ID = ap.SourcePartID
outer apply (
select [Part Count] = count
(
distinct iif
(
Qty = 0, null, [Component Part #]
)
),
[Total # of Parts] = sum(qty)
from bomBreakdown
where [Component Part ID] is not null
and SourcePartID = ap.SourcePartID
group by SourcePartID
)
oa1
Here, the iif(Qty = 0, null, [Component Part #]) will make the column null if the quantity is zero. Count will ignore those nulls. And we get the distinct, just like before. So we have sneakily managed to get a where clause in here: "count the distinct component part # values where the quantity is not equal to zero". Now we can just sum the Qty column as well, and we're done refactoring this.
The same kind of refactoring can be done in many places in this stored procedure. It would actually be a great learning exercise for refactoring SQL. I'm not going to do all of them, but just try to identify the patterns, and follow a factorisation process - the same kind you would do in algebra. Because, in many ways, this is algebra!
Please excuse any typos/syntax errors. I haven't been able to check this through an actual query window, my intent here is to demonstrate a few ideas, not to actually rewrite the original query.

Query optimisation - takes so long

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

Merge two SQL data tables in asp.net website using vb.net in code behind

I can't seem to find a solution that fits.
Here is the situation. I have a two SQL queries from the same table each with a different where clause.
Table A uses this SQL statement:
Select jo.AssemblySeq As Assm,
jo.OprSeq As [OP Center #],
jo.WCCode As WC,
Convert(Varchar, jo.DueDate, 101) As [Due Date],
ja.RequiredQty As Qty,
jo.QtyCompleted As [Qty Comp],
jo.OpComplete As OpComplete
From JobOper jo
Join JobAsmbl ja
On jo.JobNum = ja.JobNum
And jo.AssemblySeq = ja.AssemblySeq
Where jo.JobNum Like '236087.%'
And ja.AssemblySeq <> 0
Order By jo.AssemblySeq;
And returns this data:
Table B uses this SQL query:
Select jo.AssemblySeq As Assm,
jo.OprSeq As [OP Center #],
jo.WCCode As WC,
Convert(Varchar, jo.DueDate, 101) As [Due Date],
jo.QtyPer As QTY,
jo.QtyCompleted As [Qty Comp],
jo.OpComplete As OpComplete
From JobOper jo
Join JobAsmbl ja
On jo.JobNum = ja.JobNum
And jo.AssemblySeq = ja.AssemblySeq
Where jo.JobNum Like '236087.%'
And ja.AssemblySeq = 0
Order By jo.AssemblySeq;
And returns this data:
What I need to do is merge these two tables so that I have columns called Assm, OP Center #, WC, Due Date, Qty, Qty Completed, OpComplete. My problem is that the where clause for the query for table A has ja.AssemblySeq <> 0 and the where clause for the query for table B has ja.AssemblySeq = 0.
I need all the lines from both queries. I am not sure if I need some type of Join or if it would involve sub queries?
A simple UNION ALL will help as below:
Select * from Query1
Union All
Select * from Query2
You don't need to do a UNION ALL at all. You can do this with one single query, without the need to hit the table twice.
Your queries are identical with the exception of the column selected based on the value of ja.AssemblySeq. You can just remove the WHERE clause altogether and make the Qty column a CASE expression.
Select jo.AssemblySeq As Assm,
jo.OprSeq As [OP Center #],
jo.WCCode As WC,
Convert(Varchar, jo.DueDate, 101) As [Due Date],
Case When ja.AssemblySeq = 0
Then jo.QtyPer
Else ja.RequiredQty
End As Qty,
jo.QtyCompleted As [Qty Comp],
jo.OpComplete As OpComplete
From JobOper jo
Join JobAsmbl ja
On jo.JobNum = ja.JobNum
And jo.AssemblySeq = ja.AssemblySeq
Where jo.JobNum Like '236087.%'
Order By jo.AssemblySeq;

Dividing two SQL query results

I'm just trying to learn how to take a value from a column, in this case how much JJ spent on product a, and divide it by the sum of the total Product A sales and turn it into a percentage.
My SQL understanding is pretty low level right now, so the simpler the response the better.
SELECT
JJ / Result * 100 AS percentage
FROM
(SELECT
([Product A] AS JJ
FROM [Test].[dbo].[TableA]
WHERE [Customer Name] = 'JJ'
SELECT SUM([Product A]) AS Result
FROM [Test].[dbo].[TableA]
)
--JJ/Result * 100 = ProdAPercentSales)
You could use a case expression to find JJ's purchases, and divide their sum with the total sum:
SELECT SUM(CASE [Customer name] WHEN 'JJ' THEN [Product A] ELSE 0 END) /
SUM([Product A]) * 100 AS [Percentage]
FROM [Test].[dbo].[TableA]

sum divided values problem (dealing with rounding error)

I've a product that costs 4€ and i need to divide this money for 3 departments.
On the second column, i need to get the number of rows for this product and divide for the number of departments.
My query:
select
department, totalvalue,
(totalvalue / (select count(*) from departments d2 where d2.department = p.product))
dividedvalue
from products p, departments d
where d.department = p.department
Department Total Value Divided Value
---------- ----------- -------------
A 4 1.3333333
B 4 1.3333333
C 4 1.3333333
But when I sum the values, I get 3,999999. Of course with hundreds of rows i get big differences...
Is there any chance to define 2 decimal numbers and round last value? (my results would be 1.33 1.33 1.34)
I mean, some way to adjust the last row?
In order to handle this, for each row you would have to do the following:
Perform the division
Round the result to the appropriate number of cents
Sum the difference between the rounded amount and the result of the division operation
When the sum of the differences exceeds the lowest decimal place (in this case, 0.01), add that amount to the results of the next division operation (after rounding).
This will distribute fractional amounts evenly across the rows. Unfortunately, there is no easy way to do this in SQL with simple queries; it's probably better to perform this in procedural code.
As for how important it is, when it comes to financial applications and institutions, things like this are very important, even if it's only by a penny, and even if it can only happen every X number of records; typically, the users want to see values tie to the penny (or whatever your unit of currency is) exactly.
Most importantly, you don't want to allow for an exploit like "Superman III" or "Office Space" to occur.
With six decimals of precision, you would need about 5,000 transactions to notice a difference of one cent, if you round the final number to two decimals. Increasing the number of decimals to an acceptable level would eliminate most issues, i.e. using 9 decimals you would need about 5,000,000 transactions to notice a difference of a cent.
Maybe you can make a forth row that will be Total - sum(A,B,C).
But it depends on what you want to do, if you need exact value, you can keep fractions, else, truncate and don't care about the virtual loss
Also can be done simply by adding the rounding difference of a particular value to the next number to be rounded (before rounding). This way the pile remains always the same size.
Here's a TSQL (Microsoft SQL Server) implementation of the algorithm provided by Martin:
-- Set parameters.
DECLARE #departments INTEGER = 3;
DECLARE #totalvalue DECIMAL(19, 7) = 4.0;
WITH
CTE1 AS
(
-- Create the data upon which to perform the calculation.
SELECT
1 AS Department
, #totalvalue AS [Total Value]
, CAST(#totalvalue / #departments AS DECIMAL(19, 7)) AS [Divided Value]
, CAST(ROUND(#totalvalue / #departments, 2) AS DECIMAL(19, 7)) AS [Rounded Value]
UNION ALL
SELECT
CTE1.Department + 1
, CTE1.[Total Value]
, CTE1.[Divided Value]
, CTE1.[Rounded Value]
FROM
CTE1
WHERE
Department < #departments
),
CTE2 AS
(
-- Perform the calculation for each row.
SELECT
Department
, [Total Value]
, [Divided Value]
, [Rounded Value]
, CAST([Divided Value] - [Rounded Value] AS DECIMAL(19, 7)) AS [Rounding Difference]
, [Rounded Value] AS [Calculated Value]
FROM
CTE1
WHERE
Department = 1
UNION ALL
SELECT
CTE1.Department
, CTE1.[Total Value]
, CTE1.[Divided Value]
, CTE1.[Rounded Value]
, CAST(CTE1.[Divided Value] + CTE2.[Rounding Difference] - ROUND(CTE1.[Divided Value] + CTE2.[Rounding Difference], 2) AS DECIMAL(19, 7))
, CAST(ROUND(CTE1.[Divided Value] + CTE2.[Rounding Difference], 2) AS DECIMAL(19, 7))
FROM
CTE2
INNER JOIN CTE1
ON CTE1.Department = CTE2.Department + 1
)
-- Display the results with totals.
SELECT
Department
, [Total Value]
, [Divided Value]
, [Rounded Value]
, [Rounding Difference]
, [Calculated Value]
FROM
CTE2
UNION ALL
SELECT
NULL
, NULL
, SUM([Divided Value])
, SUM([Rounded Value])
, NULL
, SUM([Calculated Value])
FROM
CTE2
;
Output:
You can plug in whatever numbers you want at the top. I'm not sure if there is a mathematical proof for this algorithm.