I had got stuck few hours back on around something similar and worked out a less messy code for outputting 25th, 50th, 75th percentiles in a single Teradata query. Can be further extended to produce a "5 point summary". For minimum and maximum change static values according to your population estimate.
Somewhere someone had asked for an elegant approach. Sharing mine.
Here's the code:
SELECT MAX(PER_MIN) AS PER_MIN,
MAX(PER_25) AS PER_25,
MAX(PER_50) AS PER_50,
MAX(PER_75) AS PER_75,
MAX(PER_MAX) AS PER_MAX
FROM (SELECT CASE WHEN ROW_NUMBER() OVER(ORDER BY DURATION_MACRO_CURR ASC) = CAST(COUNT(*) OVER() * 0.01 AS INT) THEN DURATION_MACRO_CURR END AS PER_MIN,
CASE WHEN ROW_NUMBER() OVER(ORDER BY DURATION_MACRO_CURR ASC) = CAST(COUNT(*) OVER() * 0.25 AS INT) THEN DURATION_MACRO_CURR END AS PER_25,
CASE WHEN ROW_NUMBER() OVER(ORDER BY DURATION_MACRO_CURR ASC) = CAST(COUNT(*) OVER() * 0.50 AS INT) THEN DURATION_MACRO_CURR END AS PER_50
CASE WHEN ROW_NUMBER() OVER(ORDER BY DURATION_MACRO_CURR ASC) = CAST(COUNT(*) OVER() * 0.75 AS INT) THEN DURATION_MACRO_CURR END AS PER_75
CASE WHEN ROW_NUMBER() OVER(ORDER BY DURATION_MACRO_CURR ASC) = CAST(COUNT(*) OVER() * 0.99 AS INT) THEN DURATION_MACRO_CURR END AS PER_MAX
FROM PROD_EXP_DL_CVM.PROD_CVM
WHERE PW_END_DATE = '2016-10-18'
) BASE
Here's the desired output:
I would do this using conditional aggregation:
select min(DURATION_MACRO_CURR) as min_val,
min(case when seqnum / 0.25 >= cnt then DURATION_MACRO_CURR end) as 25_percentile,
min(case when seqnum / 0.50 >= cnt then DURATION_MACRO_CURR end) as 50_percentile,
min(case when seqnum / 0.75 >= cnt then DURATION_MACRO_CURR end) as 75_percentile,
max(DURATION_MACRO_CURR) as max_val
from (select pc.*,
row_number() over (order by DURATION_MACRO_CURR) as seqnum,
count(*) over () as cnt
from PROD_EXP_DL_CVM.PROD_CVM pc
where pc.PW_END_DATE = '2016-10-18'
) pc;
How to use LAG function to get the updated previous row value (without using Recursive CTE). Please check the screenshot for sample output
Query Tried
Declare #Tbl as Table(SNO Int,Credit Money,Debit Money,PaidDate Date)
Insert into #Tbl
SELECT * FROM (VALUES (1,0,12,'7Jan16'), (2,10,0,'6Jan16'), (3,15,0,'5Jan16'), (4,0,5,'4Jan16'), (5,0,3,'3Jan16'), (6,0,2,'2Jan16'), (7,20,0,'1Jan16')) AS X(SNO,Credit,Debit,PaidDate)
Select
T.SNO,
T.Credit,
T.Debit,
TotalDebit = Case When Credit < LAG(T.Debit, 1, 0) OVER (ORDER BY SNO) Then Debit + (LAG(T.Debit, 1, 0) OVER (ORDER BY SNO)-Credit) Else Debit End,
Amount = Case When Credit < LAG(T.Debit, 1, 0) OVER (ORDER BY SNO) Then 0 Else Credit-LAG(T.Debit, 1, 0) OVER (ORDER BY SNO) End,
T.PaidDate
From #Tbl T
UPDATE:
Can get the expected result using recursive CTE, but when i convert the query to function and when i join the function with 3000 record, takes long time to execute. That's why i am trying to convert the query without recursive CTE part.
Recursive CTE Query:
Declare #Tbl as Table(SNO Int,Credit Money,Debit Money,PaidDate Date)
Insert into #Tbl
SELECT * FROM (VALUES (1,0,12,'7Jan16'), (2,10,0,'6Jan16'), (3,15,0,'5Jan16'), (4,0,5,'4Jan16'), (5,0,3,'3Jan16'), (6,0,2,'2Jan16'), (7,20,0,'1Jan16')) AS X(SNO,Credit,Debit,PaidDate)
;With Temp As(/* Detect Debited amount */
Select Top 1 SNO,Credit,Debit,Debit As TotalDebit,Credit As Amount,PaidDate From #Tbl
Union All
Select
R.SNO,
R.Credit,
R.Debit,
TotalDebit = Case When R.Credit < RP.TotalDebit Then R.Debit + (RP.TotalDebit-R.Credit) Else R.Debit End,
Amount = Case When R.Credit < RP.TotalDebit Then 0 Else R.Credit-RP.TotalDebit End,
R.PaidDate
From #Tbl R
Inner Join Temp RP ON R.SNO-1=RP.SNO
)
Select * From Temp
Spreadsheet sample:
https://docs.google.com/spreadsheets/d/1FNwzgGxmLiLFS_R5QANnfd16Iw64xhF0gWTc4ZocKsk/edit?usp=sharing
Performance here is suffering from recursive CTE. CTE on it's own is just syntactic sugar.
Just for this particular sample data this works without recursion:
Declare #Tbl as Table(SNO Int,Credit Money,Debit Money,PaidDate Date)
Insert into #Tbl
SELECT * FROM (VALUES (1,0,12,'7Jan16'), (2,10,0,'6Jan16'), (3,15,0,'5Jan16'), (4,0,5,'4Jan16'), (5,0,3,'3Jan16'), (6,0,2,'2Jan16'), (7,20,0,'1Jan16')) AS X(SNO,Credit,Debit,PaidDate);
With CTE1 As (
Select *
, CASE WHEN Credit > 0 THEN LEAD(1 - SIGN(Credit), 1, 1) OVER (ORDER BY SNO) ELSE 0 END As LastCrPerBlock
From #Tbl
), CTE2 As (
Select *
, SUM(LastCrPerBlock) OVER (ORDER BY SNO DESC ROWS UNBOUNDED PRECEDING) As BlockNumber
From CTE1
), CTE3 As (
Select *
, SUM(Credit - Debit) OVER (PARTITION BY BlockNumber) As BlockTotal
, SUM(Credit - Debit) OVER (PARTITION BY BlockNumber ORDER BY SNO ROWS UNBOUNDED PRECEDING) As BlockRunningTotal
From CTE2
)
Select SNO, Credit, Debit
, CASE WHEN BlockRunningTotal < 0 THEN -BlockRunningTotal ELSE 0 END As TotalDebit
, CASE WHEN BlockRunningTotal > 0 THEN CASE WHEN Credit < BlockRunningTotal THEN Credit ELSE BlockRunningTotal END ELSE 0 END As Amount
, PaidDate
From CTE3
Order By SNO;
This can help evaluate performance, but it will fail if in any block total of Debits exceed total of Credits. If BlockTotal is negative then it must be merged with one or several following blocks and that can't be done without iteration or recursion.
In real life I would dump CTE3 into temporary table and cycle over it merging blocks until there are no more negative BlockTotals.
From Y.B's answer, added recursive CTE to handle if any BlockTotal have negative. Cannot use while loop for recursion because i converted this query to inline table valued function.(Multi-statement table valued function is very slow)
Declare #Tbl as Table(ReceiptNo varchar(50),Credit Money,Debit Money,PaidDate Date)
Insert into #Tbl
SELECT * FROM (VALUES ('R1',20,0,'1Jan16'),('R2',0,2,'2Jan16'),('R3',0,3,'3Jan16'),('R4',0,5,'4Jan16'),('R5',10,0,'5Jan16'),('R6',0,1,'6Jan16'),('R7',0,10,'7Jan16')) AS X(ReceiptNo,Credit,Debit,PaidDate);
With Receipts As (
Select
SNO = ROW_NUMBER() OVER(ORDER BY PaidDate Desc),ReceiptNo,Credit,Debit,PaidDate,
LastCrPerBlock = CASE WHEN Credit > 0 THEN LEAD(1 - SIGN(Credit), 1, 1) OVER (ORDER BY PaidDate DESC) ELSE 0 END
From #Tbl
), Blocks As (
Select *
, SUM(LastCrPerBlock) OVER (ORDER BY SNO DESC ROWS UNBOUNDED PRECEDING) As BlockNumber
From Receipts
), BlockTotal As (
Select *
, SUM(Credit - Debit) OVER (PARTITION BY BlockNumber) As BlockTotal
, SUM(Credit - Debit) OVER (PARTITION BY BlockNumber ORDER BY SNO ROWS UNBOUNDED PRECEDING) As BlockRunningTotal
From Blocks
),
ReceiptAmount As (
Select ReceiptNo,
Amount = CASE WHEN BlockRunningTotal > 0 THEN CASE WHEN Credit < BlockRunningTotal THEN Credit ELSE BlockRunningTotal END ELSE 0 END,
Debit = IIF(BlockNumber<>LEAD(BlockNumber) OVER(ORDER BY SNO) and BlockRunningTotal<0,ABS(BlockRunningTotal),0),
PaidDate
From BlockTotal
),
FinalReceipt2012 As (
Select
SNO = ROW_NUMBER() OVER(ORDER BY PaidDate Desc),ReceiptNo,Amount,Debit,PaidDate,
Recur = IIF(Exists(Select Top 1 R1.Amount From ReceiptAmount R1 Where Debit>0),1,0)
From ReceiptAmount
Where Amount>0 or Debit>0
),
FinalReceipt As (
Select * From FinalReceipt2012 Where Recur=0 OR SNO=1
Union All
Select
R.SNO,R.ReceiptNo,
Amount = Case When R.Amount < RP.Debit Then 0 Else R.Amount-RP.Debit End,
Debit = Case When R.Amount < RP.Debit Then R.Debit + (RP.Debit-R.Amount) Else R.Debit End,
R.PaidDate,0 As Recur
From FinalReceipt2012 R
Inner Join FinalReceipt RP ON R.SNO=RP.SNO+1
Where R.Recur=1
)
Select ReceiptNo,Amount,PaidDate From FinalReceipt Where Amount>0
Input:
Output:
I have the following MS SQL query which cross references three tables, (tTeam, tPlayer and tScores) to get the total "Net score", "Gross score" and "Position" ordered by Net score and Team.
SELECT TeamID, Team, NetScore, Gross,
CASE WHEN cnt > 1 THEN 'T' + CAST(rnk AS VARCHAR(5))
ELSE CAST(rnk AS VARCHAR(5))
END Pos
FROM (
SELECT tTeam.TeamID,
tTeam.Title AS Team,
SUM(CONVERT(INT, tScores.Net_Score)) AS NetScore,
SUM(CONVERT(INT, tScores.Out_Score) + CONVERT(int, tScores.In_Score)) AS Gross,
rank() OVER ( ORDER BY SUM(CONVERT(INT, tScores.Net_Score))) rnk,
COUNT(*) OVER ( PARTITION BY SUM(CONVERT(INT, tScores.Net_Score))) cnt
FROM tScores INNER JOIN tPlayer ON tScores.PlayerID = tPlayer.PlayerID INNER JOIN tTeam ON tPlayer.TeamID = tTeam.TeamID
WHERE tTeam.TournamentID = 13
GROUP BY tTeam.TeamID, tTeam.Title ) temp
ORDER BY NetScore, Team
The query works great but (and here is where i need some help), it is calculating all of the players Net and Gross scores by team when all I need it to do is calculate the "4 lowest Player's Net and Gross Scores" by team only.
I have spent the last day and a half pulling my hair out with this one and any help will be greatly appreciated.
Thanks in advance!
If I understood correctly that you want to sum four lowest scores per player only, you might use another set of row_numbers to isolate lowest scores. I don't think that rn_gross is necessary (based on rank() function) but I included it nevertheless. If there is no need for separate numbering remove conditionals from sums and add and lowestScores.rn_net <= 4 to where clause.
; with lowestScores as
(
select *,
ROW_NUMBER() over (PARTITION by PlayerID
order by CONVERT(INT, Net_Score)) rn_net,
ROW_NUMBER() over (PARTITION by PlayerID
order by CONVERT(INT, Net_Score) + CONVERT(int,In_Score)) rn_gross
from tScores
),
temp as
(
SELECT tTeam.TeamID,
tTeam.Title AS Team,
SUM(CASE WHEN rn_net <= 4 THEN CONVERT(INT, lowestScores.Net_Score) END) AS NetScore,
SUM(CASE WHEN rn_gross <= 4 THEN CONVERT(INT, lowestScores.Out_Score) END
+ CASE WHEN rn_gross <= 4 THEN CONVERT(int, lowestScores.In_Score) END) AS Gross,
rank() OVER ( ORDER BY SUM(CASE WHEN rn_net <= 4 THEN CONVERT(INT, lowestScores.Net_Score) END)) rnk,
COUNT(*) OVER ( PARTITION BY SUM(CASE WHEN rn_net <= 4 THEN CONVERT(INT, lowestScores.Net_Score) END)) cnt
FROM lowestScores
INNER JOIN tPlayer
ON lowestScores.PlayerID = tPlayer.PlayerID
INNER JOIN tTeam
ON tPlayer.TeamID = tTeam.TeamID
WHERE tTeam.TournamentID = 13
GROUP BY tTeam.TeamID, tTeam.Title
)
SELECT TeamID, Team, NetScore, Gross,
CASE WHEN cnt > 1
THEN 'T' + CAST(rnk AS VARCHAR(5))
ELSE CAST(rnk AS VARCHAR(5))
END Pos
FROM temp
ORDER BY NetScore, Team
I have a table containing the following line:
I'd like to create a view that displays the following result (without changing my original table) :
For each line having the same id,day,month and year I'd like to leave a single line with the cost and count and insert 0 in the others.
Here is a portable approach not requiring PARTITION. I have assumed you will not have the same datetimeIN value for more than one row in a group:
select t.id, t.day, t.month, t.year,
case when tm.id is null then 0 else t.cost end as cost,
case when tm.id is null then 0 else t.Count end as Count,
t.datetimeIN, t.datetimeOUT
from MyTable t
left outer join (
select id, day, month, year, min(datetimeIN) as minIN
from MyTable
group by id, day, month, year
) tm on t.id = tm.id
and t.day = tm.day
and t.month = tm.month
and t.year = tm.year
and t.datetimeIN = tm.minIN
You can do something like this:
SELECT id, day, month, year,
CASE WHEN nNum = 1 then cost else 0 end as cost,
CASE WHEN nNum = 1 then "Count" else 0 end as "Count",
datetimeIN, datetimeOUT
FROM (
SELECT id, day, month, year,
cost, "Count", datetimeIN, datetimeOUT,
row_number() OVER (PARTITION BY id, day, month, year
ORDER BY datetimeIN) as nNum
FROM TableName
) A
It uses row_number() to number the rows, and then a CASE statement to single out the first one and make it behave differently.
See it working on SQL Fiddle here.
or, using a common table expression:
with commonTableExp ([day], [month], [year], minDate) as (
select [day], [month], [year], min(datetimeIn)
from #temp
group by [day], [month], [year])
select id,
dt.[day],
dt.[month],
dt.[year],
case when datetimein = minDate then [cost] else 0 end,
case when datetimein = minDate then [count] else 0 end,
dateTimeIn
from #temp dt join commonTableExp cte on
dt.[day] = cte.[day] and
dt.[month] = cte.[month] and
dt.[year] = cte.[year]
order by dateTimeIn
Query
Select id, [day], [month], [year], Case When K.RowID = 1 Then [cost] Else 0 End as Cost, Case When K.RowID = 1 Then [count] Else 0 End as [count], [DateTimeIN], [DateTimeOut] From
(
select ROW_NUMBER() Over(Partition by id, [day], [month], [year] Order by ID ) as RowID, * From Testing
)K
Drop table Testing
Click here to see SQL Profiler details for Red Filter's Query
Click here to see SQL Profiler details for my Query
For More Information You can see SQL Fiddle