Related
I'm having some trouble coming up with the CTE way of doing a task. I already have a loop method and it is fast enough, but I wanted to do it in a proper way to better learn and understand CTE usage.
SQL:
DECLARE #Income MONEY=125000.00,
#Active INT=0,
#Year CHAR(4)='2018'
DECLARE #T TABLE ([Year] CHAR(4),Active INT,UpperLimit MONEY,Factor DECIMAL(6,3))
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,5000.0,1.00;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,100000.0,0.85;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,500000.0,0.80;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,999999999.0,0.75;
WITH GradientCTE ([Year], Active, UpperLimit, Factor,[Income],WeightedValue,[Row])
AS
(
SELECT [Year], Active, UpperLimit, Factor
,#Income AS [Income]
,CAST(0.0 AS DECIMAL(16,3))AS WeightedValue
,ROW_NUMBER() OVER(PARTITION BY [Year],Active ORDER BY UpperLimit ASC) AS [Row]
From #T
)
SELECT *
FROM GradientCTE
ORDER BY UpperLimit
TLDR version; current output:
Year Active UpperLimit Factor Income WeightedValue Row
2018 0 5000.000 1.000 125000.000 0.000 1
2018 0 100000.000 0.850 125000.000 0.000 2
2018 0 500000.000 0.800 125000.000 0.000 3
2018 0 999999999.000 0.750 125000.000 0.000 4
What I would like:
Year Active UpperLimit Factor Income WeightedValue Row
2018 0 5000.000 1.000 125000.000 5000.000 1
2018 0 100000.000 0.850 120000.000 85000.000 2
2018 0 500000.000 0.800 20000.000 16000.000 3
2018 0 999999999.000 0.750 0.000 0.000 4
Explained:
Currently, the looping logic goes over the set row by row and reduces #Income by the UpperLimit for each row until no money is left. It uses that to multiply that amount by the Factor to get a weighted amount. So, in the example provided, the starting income is 125,000.00. The first 5000 are at full weight (1.00), so we reduce the income by 5000 and move the the next row saving the summed weighted value. This is done until income is 0. So, 125,000 should come out to (5000 * 1.0) + (100000 * 0.85) + (20000 * 0.80) + (0.00 * 0.75) or 106,000 total if summed.
Thanks to Ross Bush's answer, it led me down the right track to solve the problem. From a maintenance standpoint, I think a looping pattern is easier to understand, so I likely won't implement the CTE version and performance isn't an issue as the data set is tiny.
DECLARE #Income DECIMAL(18,3)=125000.00,
#Active INT=0,
#Year CHAR(4)='2018'
DECLARE #T TABLE ([Year] CHAR(4),Active INT,UpperLimit DECIMAL(18,3),Factor DECIMAL(18,3))
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,5000.0,1.00;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,100000.0,0.85;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,500000.0,0.80;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,999999999.0,0.75;
;WITH GradientCTE
AS
(
SELECT DISTINCT
[YEAR],Active,UpperLimit=0.00, Factor = 0.00, [Row] = 0
FROM #T
UNION ALL
SELECT [Year],Active,UpperLimit, Factor
,ROW_NUMBER() OVER(PARTITION BY [Year],Active ORDER BY UpperLimit ASC) AS [Row]
From #T
)
,Reduce AS (
SELECT
[YEAR],Active,CAST(#Income AS DECIMAL(18,3)) AS [RemainingIncome],
Row,
Factor
,UpperLimit
,CAST(0.00 AS DECIMAL(18,3)) AS WeightedValue
FROM GradientCTE
WHERE UpperLimit=0
UNION ALL
SELECT
g.[YEAR],g.Active,CASE WHEN CAST([RemainingIncome] - G.UpperLimit AS DECIMAL(18,3)) < 0 THEN 0 ELSE CAST([RemainingIncome] - G.UpperLimit AS DECIMAL(18,3)) END AS [RemainingIncome],
G.Row,
g.Factor
,g.UpperLimit
,CAST(CASE WHEN [RemainingIncome]>G.UpperLimit THEN G.UpperLimit * G.Factor ELSE R.[RemainingIncome] * G.Factor END AS DECIMAL(18,3)) AS WeightedValue
FROM GradientCTE G
INNER JOIN Reduce R ON R.Row = G.Row -1
AND g.Year=r.Year
AND g.Active=r.Active
)
SELECT
*
-- [Year],Active,SUM(WeightedValue)
FROM Reduce
WHERE [RemainingIncome] >= 0
--GROUP BY [Year],Active
You can reduce the results within another CTE, recursively. I added a UNION with 0 to in the first set to produce the first line showing the starting income.
DECLARE #Income DECIMAL(18,3)=125000.00,
#Active INT=0,
#Year CHAR(4)='2018'
DECLARE #T TABLE ([Year] CHAR(4),Active INT,UpperLimit DECIMAL(18,3),Factor DECIMAL(18,3))
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,5000.0,1.00;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,100000.0,0.85;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,500000.0,0.80;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,999999999.0,0.75;
;WITH GradientCTE
AS
(
SELECT ReduceAmount = 0, UpperLimit=0.00, Factor = 0.00, Row = 0
UNION ALL
SELECT ReduceAmount = UpperLimit * Factor, UpperLimit, Factor
,ROW_NUMBER() OVER(PARTITION BY [Year],Active ORDER BY UpperLimit ASC) AS [Row]
From #T
)
,Reduce AS (
SELECT
Income = CAST(#Income AS DECIMAL(18,3)),
Row,
ReduceAmount
FROM GradientCTE
WHERE ReduceAmount=0
UNION ALL
SELECT
Income = CASE WHEN CAST(Income - G.ReduceAmount AS DECIMAL(18,3)) < 0 THEN 0 ELSE CAST(Income - G.ReduceAmount AS DECIMAL(18,3)) END,
G.Row,
G.ReduceAmount
FROM GradientCTE G
INNER JOIN Reduce R ON R.Row = G.Row -1
)
SELECT * FROM Reduce
WHERE
Income >= 0
DECLARE #Income MONEY=125000.00,
#Active INT=0,
#Year CHAR(4)='2018',
#vIncome Money = 0
DECLARE #T TABLE ([Year] CHAR(4),Active INT,UpperLimit MONEY,Factor
DECIMAL(6,3))
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT
'2018',0,5000.0,1.00;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT
'2018',0,100000.0,0.85;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT
'2018',0,500000.0,0.80;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT
'2018',0,999999999.0,0.75;
Select * ,ROW_NUMBER() OVER(PARTITION BY [Year],Active ORDER BY
UpperLimit ASC) AS [Row],CAST(0.0 AS DECIMAL(16,3))AS Income,CAST(0.0 AS
DECIMAL(16,3))AS WeightedValue
into #tmp from #T t1
--Select (t2.UpperLimit * t2.Factor),t2.*,t1.Row as prev,t1.UpperLimit
--from #tmp t1
--inner join #tmp t2 on (t1.Row = t2.Row +1)
update t2
set
#vIncome = #Income,
#Income = case when (#Income > t2.UpperLimit) then
#Income - t2.UpperLimit
else
0
end,
t2.Income = #Income,
t2.WeightedValue = case when (#vIncome > t2.UpperLimit) then
(t2.UpperLimit * t2.Factor)
else
#vIncome *t2.Factor
end
from #tmp t1
inner join #tmp t2 on (t1.Row = t2.Row +1)
Select * from #tmp
drop table #tmp
Here is my ask:
Go through the code and understand it.
As first solution, query should complete within 10 secs for 30 input
It should be working with good performance for 100 input as well.
My code:
/**************************************************
Populating the Array values in table variable
**************************************************/
DECLARE #PUZZLE table(
ID int IDENTITY(1,1) NOT NULL,
Value int NOT NULL)
/****Sample 1*****/
INSERT INTO #PUZZLE (value)
--SELECT 0 UNION ALL
--SELECT -22 UNION ALL
--SELECT -33 UNION ALL
--SELECT -44 UNION ALL
--SELECT 55 UNION ALL
--SELECT -100 UNION ALL
--SELECT 100 UNION ALL
--SELECT 10 UNION ALL
--SELECT -30 UNION ALL
--SELECT -60 UNION ALL
--SELECT -60 UNION ALL
SELECT -60 UNION ALL
SELECT -10 UNION ALL
SELECT 10 UNION ALL
SELECT 10 UNION ALL
SELECT -10 UNION ALL
SELECT 0 UNION ALL
SELECT -22 UNION ALL
SELECT -33 UNION ALL
SELECT -44 UNION ALL
SELECT 55 UNION ALL
SELECT -100 UNION ALL
SELECT 100 UNION ALL
SELECT 10 UNION ALL
SELECT -30 UNION ALL
SELECT -60 UNION ALL
SELECT -60 UNION ALL
SELECT -60 UNION ALL
SELECT -10 UNION ALL
SELECT 10 UNION ALL
SELECT 10
/**************************************************
Populating possible hierarchy/path
**************************************************/
DECLARE #puzHierarchy table (parentid int, childid int,value int)
INSERT #puzHierarchy (parentid,childid,value)
SELECT *-- INTO #puzHierarchy
FROM (
SELECT NULL AS ParentId,ID AS ChildId, Value
FROM #PUZZLE
WHERE ID = (SELECT MIN(ID) FROM #PUZZLE)
UNION ALL
SELECT B.Id,C.ID,C.Value
FROM #PUZZLE B
JOIN #PUZZLE C
ON C.ID > B.ID AND C.ID < (B.ID + 7)
) A
--SELECT * FROM #puzHierarchy order by parentid
/*******************************************************
Logic using recursive CTE to get the path with max value
*******************************************************/
;WITH children AS
(
SELECT ParentId
,CAST(ISNULL(CAST(ParentId AS NVARCHAR) + '->' ,'') + CAST(ChildId AS NVARCHAR) AS NVARCHAR(Max)) AS Path
,value As PathValue
FROM #puzHierarchy
WHERE ChildId = (SELECT MAX(ChildId) FROM #puzHierarchy)
UNION ALL
SELECT t.ParentId
,list= CAST(ISNULL(CAST(t.ParentId AS NVARCHAR) + '->' ,'') + d.Path AS NVARCHAR(Max))
,(t.value+d.PathValue) As PathValue
FROM #puzHierarchy t
INNER JOIN children AS d
ON t.ChildId = d.ParentId
)
SELECT [Path],PathValue
FROM children c
WHERE ParentId IS NULL
AND c.PathValue = (SELECT max(PathValue) FROM children WHERE ParentId IS NULL)
A. Your code goes through too many cycles/data unrelated to result you want.
B. After running your sample data, the results are not accurate.
Parentid Path PathValue
NULL 1->3->4->6->10->12->13->19->20 145
NULL 1->3->4->10->12->13->19->20 145
The first result is wrong.
Basically you just want starting from ParentId IS NULL and ChildId = 1, among ParentId = 1 finding which ChildId has the MAX value, this ChildId becomes ParentID to find next MAX value, and so on.
;WITH cte_base AS (SELECT Parentid
, Childid
, Value
, ROW_NUMBER() OVER(PARTITION BY Parentid ORDER BY Value DESC) AS Rownum
FROM #puzHierarchy
), cte_re AS (SELECT ParentId
, Childid
, CAST(CAST(ChildId AS NVARCHAR) AS NVARCHAR(Max)) AS Path
, Value As PathValue
, Rownum
FROM cte_base
WHERE Parentid IS NULL
UNION ALL
SELECT b.parentid, b.childid
, CAST(Path + '->' + ISNULL(CAST(b.parentid AS NVARCHAR) ,'') AS NVARCHAR(Max))
,(b.value + r.PathValue) As PathValue
, b.Rownum
FROM cte_base AS b
INNER JOIN cte_re AS r
ON b.Parentid = r.childid
where b.Rownum = 1
)
SELECT *
FROM cte_re
(I changed your sample table variable to a temporary table.)
I have a following table:
State LAB GROUP DATE CODE ID
UP A I 1-Jan 1 345
UP R S 1-Feb 1 456
UP A S 1-Jan 2 567
DL R S 1-Feb 3 678
DL T S 1-Jan 1 789
DL A S 1-Jan 2 900
MN T S 1-Jan 3 1011
MN R I 1-Feb 1 1122
MN S I 1-Feb 2 1233
I need a pivot table of following type:
STATE A R T TOTAL
UP 2 1 0 3
DL 1 1 1 3
MN 0 1 1 2
DISTINCT COUNT OF ID FOR EACH LAB FOR EACH STATE.
I then need the pivot tables filtered for following columns:
GROUP
DATE
CODE
So 1st table will have the pivot table above counting only those records which have GROUP=S
2nd table will have the pivot table above counting only those records which have CODE=1
and so on, I wish to put multiple conditions. and generate several tables one by one and export them.
If this is possible in SQL please let me know! I ruled out excel vba due to the size of table (source table will have 800,000 records approx).
Try this :-
Select [State],[A],[R],[T],Total = [A] + [R]+ [T]
from
(
Select [State],
[A] = Sum(Case when LAB='A' then 1 else 0 END) ,
[R] = Sum(Case when LAB='R' then 1 else 0 END) ,
[T] = Sum(Case when LAB='T' then 1 else 0 END)
from YourTable
group by [State]
)a
SQL FIDDLE
CREATE TABLE #t(States VARCHAR(10),LAB VARCHAR(5),GROUPs VARCHAR(5),DATEs VARCHAR(10),CODE INT,ID INT)
INSERT INTO #t values('UP','A','I','1-Jan',1,345)
INSERT INTO #t values('UP','R','S','1-Feb',1,456)
INSERT INTO #t values('UP','A','S','1-Jan',2,567)
INSERT INTO #t values('DL','R','S','1-Feb',3,678)
INSERT INTO #t values('DL','T','S','1-Jan',1,789)
INSERT INTO #t values('DL','A','S','1-Jan',2,900)
INSERT INTO #t values('MN','T','S','1-Jan',3,1011)
INSERT INTO #t values('MN','R','I','1-Feb',1,1122)
INSERT INTO #t values('MN','S','I','1-Feb',2,1233)
SELECT States,ISNULL(A,0) A,ISNULL(R,0) R,ISNULL(T,0) T,ISNULL(A,0)+ISNULL(R,0)+ISNULL(T,0) total
FROM
(
SELECT States,LAB,Count(ID) AS cnt FROM #t GROUP BY States,LAB /*apply GROUP DATE CODE condition here*/
) AS PVT
PIVOT(MAX(cnt) FOR LAB IN (A,R,T)) pvt
Another solution using PIVOT :
WITH PivotInUse AS (
SELECT state,lab,COUNT(*) AS cnt
FROM YourTable
GROUP BY state,lab
)
SELECT STATE
,COALESCE([A], 0) AS A
,COALESCE([R], 0) AS R
,COALESCE([T], 0) AS T
,COALESCE([A], 0) + COALESCE([R], 0) + COALESCE([T], 0) AS TOTAL
FROM PivotInUse
PIVOT(SUM(cnt) FOR lab IN ([A],[R],[T])) AS p;
Your sample table
SELECT * INTO #TEMP FROM
(
SELECT 'UP' [State],'A' LAB,'I' [GROUP],'1-Jan' [DATE],1 CODE,345 ID
UNION ALL
SELECT 'UP','R','S','1-Feb',1,456
UNION ALL
SELECT 'UP','A','S','1-Jan',2,567
UNION ALL
SELECT 'DL','R','S','1-Feb',3,678
UNION ALL
SELECT 'DL','T','S','1-Jan',1,789
UNION ALL
SELECT 'DL','A','S','1-Jan',2,900
UNION ALL
SELECT 'MN','T','S','1-Jan',3,1011
UNION ALL
SELECT 'MN','R','I','1-Feb',1,1122
UNION ALL
SELECT 'MN','S','I','1-Feb',2,1233
)TAB
Now you need to get the distinct count of each state and get the sum as the result to show Total
in pivoted result.
SELECT DISTINCT [State],LAB,SUM(CNT) CNT
INTO #NEWTABLE
FROM
(
SELECT DISTINCT
[State],LAB,
CASE WHEN [State] IS NULL THEN NULL ELSE COUNT([State]) OVER(PARTITION BY [State],LAB) END CNT
FROM #TEMP
)TAB
GROUP BY [State],LAB
WITH ROLLUP
Now we need to get the distinct columns for pivot(#cols) and columns to identify and replace null with zero in pivot(#NullToZeroCols)
DECLARE #cols NVARCHAR (MAX)
DECLARE #NullToZeroCols NVARCHAR (MAX)
SET #cols = SUBSTRING((SELECT DISTINCT ',['+LAB+']' FROM #NEWTABLE GROUP BY LAB FOR XML PATH('')),2,8000)
SET #NullToZeroCols = SUBSTRING((SELECT DISTINCT ',ISNULL(['+LAB+'],0) AS ['+LAB+']'
FROM #NEWTABLE GROUP BY LAB FOR XML PATH('')),2,8000)
Join the pivotted query with the #NEWTABLE to get the Total for each State
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT P.State,' + #NullToZeroCols + ',T2.CNT TOTAL FROM
(
SELECT DISTINCT [State],LAB,CNT FROM #NEWTABLE
) x
PIVOT
(
SUM(CNT)
FOR [LAB] IN (' + #cols + ')
) p
JOIN #NEWTABLE T2 ON P.[STATE]=T2.[STATE]
WHERE P.State IS NOT NULL AND T2.LAB IS NULL AND T2.[STATE] IS NOT NULL;'
EXEC SP_EXECUTESQL #query
Here is your result
Here is the SQLFiddle http://sqlfiddle.com/#!3/c2588/1 (If it shows any error while loading the page, just click RUNSQL, it will work)
Now if you want to get the result as you said DISTINCT COUNT OF ID FOR EACH LAB FOR EACH STATE, just change
OVER(PARTITION BY [State],LAB)
to
OVER(PARTITION BY [State],LAB,Id)
which will show the following result after executing the pivot query
I am using mssql 2008 R2,
i have below structure
create table #temp (
product int,
[order] int,
ord_qnty int
)
insert #temp
select 10 ,3,4
now, if ord_qnty is 4 , i want to select same product,order four times but in all four rows thevalue of ord_qnty should be 1 , i.e.
out put should be
Product order ord_qnty
10 3 1
10 3 1
10 3 1
10 3 1
If you have a numbers table, you can use that. If not, you can generate one:
;with Numbers(n) as (
select ROW_NUMBER() OVER (ORDER BY object_id) from sys.objects
)
select product,[order],1 as ord_qnty
from #temp t inner join Numbers num
on t.ord_qnty >= num.n
(In my nearly empty scratch database, the ROW_NUMBER() generates 77 rows. If that's not going to be enough, you can introduce cross-joins or use other tricks to generate more numbers, or you can create and populate a permanent numbers table)
Try this one -
Query:
DECLARE #temp TABLE
(
product INT
, [order] INT
, ord_qnty INT
)
INSERT #temp(product, [order], ord_qnty)
SELECT 10, 3, 4
SELECT
t.product
, t.[order]
, ord_qnty = 1
FROM #temp t
JOIN [master].dbo.spt_values sv ON t.ord_qnty > sv.number
WHERE sv.[type] = 'p'
SELECT
t.product
, t.[order]
, ord_qnty = 1
FROM #temp t
JOIN (
SELECT number = ROW_NUMBER() OVER (ORDER BY (SELECT 1))
FROM sys.system_parameters p
) sv ON t.ord_qnty >= sv.number
Output:
product order ord_qnty
----------- ----------- -----------
10 3 1
10 3 1
10 3 1
10 3 1
Query Cost:
For any "millions value":
SET NOCOUNT ON;
DECLARE #numbers TABLE (number INT)
DECLARE #temp TABLE
(
product INT
, [order] INT
, ord_qnty INT
)
INSERT #temp(product, [order], ord_qnty)
SELECT 10, 3, 4
DECLARE
#i BIGINT = 1
, #max BIGINT = (
SELECT MAX(ord_qnty)
FROM #temp
)
WHILE (#i <= #max) BEGIN
INSERT INTO #numbers (number)
VALUES (#i), (#i+1), (#i+2), (#i+3), (#i+4), (#i+5), (#i+6), (#i+7), (#i+8), (#i+9)
SELECT #i += 10
END
SELECT
t.product
, t.[order]
, ord_qnty = 1
FROM #temp t
CROSS JOIN (
SELECT *
FROM #numbers
WHERE number < #max + 1
) t2
I have the following table in SQL Server 2008
DECLARE #UnitConvert table
(
ID int identity(1,1),
ConvertUnitOne nvarchar(50),
ConvertUnitTwo nvarchar(50)
)
INSERT INTO #UnitConvert
SELECT 100,500
UNION ALL SELECT 200,100
UNION ALL SELECT 500,300
UNION ALL SELECT 2000,1000
UNION ALL SELECT 3000,9000
UNION ALL SELECT 2000,700
UNION ALL SELECT 820,3000
SELECT * FROM #UnitConvert
Here value in UnitConvertOne is equivalent to UnitConvertTwo
So it has a chain of value linking
So i want to display the result like
Group unit
1 100
200
300
500
2 700
1000
2000
3 820
3000
9000
Group value will be autoincrement based on the number of groups can be created
Unit value can be sorted from small to large value
Thanks to Eugene Elutin from sqlservercentral.com
DECLARE #UnitConvert table
(
ID int identity(1,1),
ConvertUnitOne nvarchar(50),
ConvertUnitTwo nvarchar(50)
)
INSERT INTO #UnitConvert
SELECT 100,500
UNION ALL SELECT 200,100
UNION ALL SELECT 500,300
UNION ALL SELECT 2000,1000
UNION ALL SELECT 3000,9000
UNION ALL SELECT 2000,700
UNION ALL SELECT 820,3000
;WITH cteUP AS
(
SELECT ConvertUnitTwo AS childUP, ConvertUnitOne AS unitUP, 0 AS Lvl
FROM #UnitConvert
UNION ALL
SELECT cte.childUP, u.ConvertUnitOne AS unitUP, Lvl = Lvl + 1
FROM #UnitConvert u
INNER JOIN cteUP cte ON cte.unitUP = u.ConvertUnitTwo
)
--select * from cteUP
SELECT c.ConvertUnit
,DENSE_RANK() OVER (ORDER BY ISNULL(cm.unitUP, c.ConvertUnit)) AS GrpNO
FROM (SELECT ConvertUnitOne AS ConvertUnit FROM #UnitConvert
UNION
SELECT ConvertUnitTwo AS ConvertUnit FROM #UnitConvert) c
OUTER APPLY (SELECT TOP 1 unitUP FROM cteUP m WHERE
m.childUP = c.ConvertUnit ORDER BY Lvl DESC) cm