In sql divide row1 by row,row2 by row3.... . .and store the output third column - sql

I have this table, Now I need to perform division on amt column and update the data int CalcAmt
month amt CalcAmt
JAN 10000 NULL
FEB 20000 NULL
MAR 30000 NULL
APR 40000 NULL
eg: (FEB/JAN) then store output in CalcAmt of Feb,
(MAR/FEB) then store output in CalcAmt of MAR,
Expected Output:
month amt CalcAmt
JAN 10000 0
FEB 20000 2
MAR 30000 1.5
APR 40000 1.33

This can be easily done through LEAD/LAG, Since you are using Sql Server 2008you cannot use LEAD/LAG function. Instead use can generate row number for each row and self join the same table with row number + 1 to fetch the previous row data.
Note: You need add the remaining months in the order in case statement if you have any.
As a side note you should not use reserved keywords as object name ex: MONTH
;WITH cte
AS (SELECT Rn=CASE [month]
WHEN 'Jan' THEN 1
WHEN 'feb' THEN 2
WHEN 'mar' THEN 3
ELSE 4
END,
*
FROM YOURTABLE)
SELECT A.month,
A.amt,
CalcAmt = A.amt / NULLIF(b.amt ,0)
FROM cte A
LEFT OUTER JOIN cte B
ON A.rn = b.rn + 1
SQLFIDDLE DEMO
If you want to update the table then use this.
;WITH cte
AS (SELECT Rn=CASE [month]
WHEN 'Jan' THEN 1
WHEN 'feb' THEN 2
WHEN 'mar' THEN 3
ELSE 4
END,
*
FROM YOURTABLE)
UPDATE C
SET C.calcamt = D.calcamt
FROM cte C
INNER JOIN (SELECT A.month,
A.amt,
calcamt=A.amt / b.amt
FROM cte A
LEFT OUTER JOIN cte B
ON A.rn = b.rn + 1) D
ON C.[month] = D.[month]

Related

SQL Server Count Distinct records with a specific condition in window functions

I have a table similar to below:
Group
TradeMonth
flag
A
Jan
1
A
Mar
1
A
Mar
0
A
Jun
0
B
Feb
1
B
Apr
1
B
Sep
1
B
Sep
1
I need to have a column that calculates the number of distinct months with non-zero values (flag=1) for each Group. I prefer using window function (not group by) and I know count distinct is not allowed in window functions in sql server, So any solution on how to calculate that with a window function is highly appreciated.
The results should be as below:
Group
TradeMonth
flag
#of flagged_months(Distinct)
A
Jan
1
2
A
Mar
1
2
A
Mar
0
2
A
Jun
0
2
B
Feb
1
3
B
Apr
1
3
B
Sep
1
3
B
Sep
1
3
Unfortunately you can't do COUNT(DISTINC ...) OVER (), but here is one workaround
with
cte as
(
select *,
dr = dense_rank() over (partition by [Group], flag order by [TradeMonth])
from yourtable
)
select [Group], [TradeMonth], flag,
max(case when flag = 1 then dr end) over (partition by [Group])
from cte
dbfiddle demo
Try this
create table #test([Group] varchar(1), TradeMon Varchar(10), Flag int)
insert into #test values ('A', 'Jan', 1),('A', 'Mar', 1),('A', 'Mar', 0),('A', 'Jun', 0),('B', 'Feb', 1),('B', 'Apr', 1),('B', 'Sep', 1),('B', 'Sep', 1)
With distinctCount AS
(
SELECT [group], COUNT(1)DistinctCount
FROM
(
select distinct [group], TradeMon
from #test
where flag=1
)T GROUP BY [group]
)
SELECT T.[GROUP], T.TradeMon, T.Flag, DC.DistinctCount
FROM #test T
INNER JOIN distinctCount DC ON (T.[GROUP] = DC.[Group])
You can actually do this with a single expression:
select t.*,
(dense_rank() over (partition by group
order by (case when flag = 1 then trademonth end)
) +
dense_rank() over (partition by group
order by (case when flag = 1 then trademonth end) desc
) -
(1 + min(flag) over (partition by trademonth))
) as num_unique_flag_1
from t;
What is the logic here? The sum of dense_rank() with an ascending sort and a descending sort is one more than the number of distinct values.
Under normal circumstances (i.e. calculating the number of distinct months), you would just subtract 1.
In this case, though, you treat 0 values as NULL. These still get counted, but there is only one of them. So, you either subtract 1 or 2 depending on the presence of 0 values. Voila! The result is the number of distinct months with a flag of 1.

How to extract and pivot in sql

I have tables like following
I treid to sum score in pivoted style..
product date score
A 2020/8/1 1
B 2018/8/1 2
B 2018/9/1 1
C 2017/9/1 2
I'd like to transform them to the following pivotedone.
The index is YEAR(t.date) and columns = product
date A B C
2017 0 0 2
2018 0 3 0
2019 0 0 0
2020 1 0 0
Are there any effective way to achieve this?
Thanks
We can handle this by joining a calendar table containing all years of interest to your current table, aggregating by year, and then using conditional aggregation to find the sum of scores for each product.
WITH years AS (
SELECT 2017 AS year FROM dual UNION ALL
SELECT 2018 FROM dual UNION ALL
SELECT 2019 FROM dual UNION ALL
SELECT 2020 FROM dual
)
SELECT
y.year,
SUM(CASE WHEN t.product = 'A' THEN t.score ELSE 0 END) AS A,
SUM(CASE WHEN t.product = 'B' THEN t.score ELSE 0 END) AS B,
SUM(CASE WHEN t.product = 'C' THEN t.score ELSE 0 END) AS C
FROM years y
LEFT JOIN yourTable t
ON y.year = EXTRACT(YEAR FROM t."date")
GROUP BY
y.year
ORDER BY
y.year;
Demo
One option would be using PIVOT Clause after determining the year range, and joining outerly with your data source and setting the null scores as zeroes :
WITH years AS
(SELECT MIN(EXTRACT(year from "date")) AS min_year,
MAX(EXTRACT(year from "date")) AS max_year
FROM tab)
SELECT year, NVL(A,0) AS A, NVL(B,0) AS B, NVL(C,0) AS C
FROM (SELECT l.year, product, SUM(score) AS score
FROM tab t --> your original data source
RIGHT JOIN (SELECT level + min_year - 1 AS year
FROM years
CONNECT BY level BETWEEN 1 AND max_year - min_year + 1) l
ON l.year = EXTRACT(year from "date")
GROUP BY l.year, product)
PIVOT (SUM(score) FOR product IN('A' AS "A", 'B' AS "B", 'C' AS "C"))
ORDER BY year;
YEAR A B C
---- - - -
2017 0 0 2
2018 0 3 0
2019 0 0 0
2020 1 0 0
Demo

SQL Server how to retrieve all numbers between 2 numbers

I have 4 columns- Code, Amount, Start, End. I would like to take the between amounts in the start and end columns and change them into one column with all results. Any suggestions on how to achieve this? Thanks.
Current Results:
Code Amount Start End
1 5000 2015 2016
2 5000 2014 2016
3 20000 2012 2016
Desired Results:
Code Amount StartEnd
1 5000 2015
1 5000 2016
2 5000 2014
2 5000 2015
2 5000 2016
3 20000 2012
3 20000 2013
3 20000 2014
3 20000 2015
3 20000 2016
You can use a recursive cte to generate all the numbers between minimum start and maximum end and join on the generated numbers.
with cte as (select min(start) col,max(end) mx from tablename
union all
select col+1,mx from cte where col < mx)
select t.code,t.amount,c.col
from cte c
join tablename t on c.col between t.start and t.end
or more simply
with cte as (select id,amount,start startend,end from tablename
union all
select id,amount,start+1,end from cte where start<end)
select id,amount,startend
from cte
order by 1,3
You can query like this
SELECT
c.code,
c.amount,
f.yr
FROM #code c
CROSS APPLY fn_yearslist(c.startyr, c.endyr) f
function you cancreate like this
CREATE FUNCTION fn_yearslist (#startyear int, #endyear int)
RETURNS #t TABLE (
yr int
)
AS
BEGIN
WHILE (#startyear <= #endyear)
BEGIN
INSERT INTO #t (yr)
VALUES (#startyear)
SET #startyear += 1
END
RETURN
END
Another option is a UDF. I use this TVF to generate dynamic ranges
Declare #YourTable table (Code int, Amount int, Start int , [End] int)
Insert into #YourTable values
(1,5000 ,2015,2016),
(2,5000 ,2014,2016),
(3,20000,2012,2016)
Select A.Code
,A.Amount
,StartEnd = cast(B.RetVal as int)
From #YourTable A
Cross Apply (Select * from [dbo].[udf-Range-Number](A.Start,A.[End],1)) B
Returns
Code Amount StartEnd
1 5000 2015
1 5000 2016
2 5000 2014
2 5000 2015
2 5000 2016
3 20000 2012
3 20000 2013
3 20000 2014
3 20000 2015
3 20000 2016
The Function
CREATE FUNCTION [dbo].[udf-Range-Number] (#R1 money,#R2 money,#Incr money)
Returns Table
Return (
with cte0(M) As (Select cast((#R2-#R1)/#Incr as int)),
cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a,cte1 b,cte1 c,cte1 d,cte1 e,cte1 f,cte1 g,cte1 h )
Select RetSeq=1,RetVal=#R1 Union All Select N+1,(N*#Incr)+#R1
From cte2
)
/*
Max 100 million observations --
Syntax:
Select * from [dbo].[udf-Range-Number](0,4,0.25)
*/
I am not sure if it works on SQL 2008, but here's a CTE:
;with sel_cte as (
select Code, Amount, start StartEnd
from #tblTest t
union all
select t.code, t.amount, c.StartEnd + 1 StartEnd
from sel_cte c
inner join #tblTest t on t.code = c.code
where c.StartEnd + 1 <= [end]
)
select *
from sel_cte
order by Code, StartEnd
Note: replace #tblTest with the actual table name.
If the requirements allow you to ONLY have successive numbers (like 2014, 2015, then 2016, etc) then the approach above (cte) would work fine. However, if not, you could create a another temp table (say numbers having 1 column) with numbers in successive sequence of what all you want to be the result output, like.
Number
2014
2015
2016
2018 <-- Missing 2017 and jumping on 2018
2019
And then use a right join to get results in the progressive series with a query similar to the one below.
select Code, StartEnd, Amount from numbers
right join inputs on number between Start and End

SQL query count (recursive)

I have the following table on my database which contains some transactions for which I need to calc points and rewards.
Every time a TxType A occurs I should record 10 points.
Then I have to subtract from these points the value of the PP column every time a TxType B occurs.
When the calculation goes to zero a reward is reached.
ID TxType PP
1 A 0
2 B 2
3 B 1
4 B 1
5 B 1
6 B 3
7 B 1
8 B 1
9 A 0
10 B 4
11 B 3
12 B 2
13 B 1
14 A 0
15 B 2
I have created the sql query to calc points as follow
SELECT SUM(
CASE
WHEN TxType = 'A' THEN 10
WHEN TxType = 'B' THEN (PP * -1)
END)
FROM myTable
This query return the value of 8, which is exactly the number of points based on the sample data.
How do I calculate the rewards occurred (2 in the given example)?
thanks for helping
One way to do the calculation (in SQL Server 2008) using a correlated subquery:
select t.*,
(select sum(case when TxType = 'A' then 10
when TxType = 'B' then PP * -1
end)
from mytable t2
where t2.id <= t.id
) as TheSum
from mytable t;
You can then apply the logic of what happens when the value is 0. In SQL Server 2012, you could just use a cumulative sum.
To complete Gordon Linoff's the answer, you just need to count the records where TheSum is 0 to get how many rewards occurred:
SELECT COUNT(1)
FROM (
SELECT ID,
TxType,
PP,
( SELECT SUM(CASE TxType WHEN 'A' THEN 10 WHEN 'B' THEN -PP END)
FROM #myTable t2
WHERE t2.id <= t1.id
) AS TheSum
FROM #myTable t1
) Result
WHERE TheSum = 0

SQL Query for Count value from the latest date

I need to have a query that returns the ff:
Count from the latest Date in each Name
If the value of Count from the latest Date is -1 then it will return the count of the Date before the latest Date
If the value of Count from the latest Date is -1 and the other Date is -1. Then return 0
If the value of Count from the latest Date is -1 and no other Date of that Name. Then return 0
Example Table:
ID Name Date Count
1 Adj 09/29/2012 2
2 Adj 09/30/2012 4
3 Ped 09/29/2012 -1
4 Ped 09/30/2012 5
5 Mel 09/29/2012 3
6 Mel 09/30/2012 -1
7 Rod 09/30/2012 7
8 Ney 09/30/2012 -1
9 Jin 09/29/2012 -1
10 Jin 09/30/2012 -1
Desired Output:
Name Count
Adj 4
Ped 5
Mel 3
Rod 7
Ney 0
Jin 0
I am very confused on how to approach this in SQL since I only knew simple query.
Any idea on how to make a query for this? Thanks.
Btw, I'm sorry I forgot to include this. I am using SQL Server 2000.
Try this
SQL FIDDLE EXAMPLE
select A.name, isnull(T.[Count], 0) as [Count]
from (select distinct T.name from table1 as T) as A
outer apply
(
select top 1 T.[Count]
from table1 as T
where T.name = A.name and T.[Count] <> -1
order by T.[date] desc
) as T
order by A.name asc
UPDATE: for SQL 2000 you can use query like this
SQL FIDDLE EXAMPLE for SQL 2000
select A.name, isnull(T1.[Count], 0) as [Count]
from
(
select T.name, max(case when T.[Count] <> -1 then T.[date] else null end) as [date]
from table1 as T
group by T.name
) as A
left outer join table1 as T1 on T1.name = A.name and T1.[date] = A.[date]
but it relies on suggestion that you have unique constraint on name, [date] columns
an other one
Select * from
(
Select Test.name,[Count]
from TEST
Join(
Select name, MAX(Date) as Date from TEST
where [Count]<>-1
Group by Name) a
on a.Name=test.Name and a.Date=Test.Date
UNION
Select Distinct name,0 from test o where not Exists(Select * from test where name=o.Name and [count]<>-1)
) res
order by Name