How do I sum the records upto a limit - sql-server

I have a table
CREATE TBALE #tableA (PID int, Amount decimal (10,2), CorrectAmount decimal(10,2))
INSERT INTO #TableA (PID, Amount)
VALUES (1,100), (2,100), (3,100), (4,100), (5,100)
Logic to populate correctAmount:
Now I always need the sum of CorrectAmount as 350 (hard coded)
Keep the 'correct amount' as 'Amount' until its 350
Start with the top record until it hits 350.
Expected result:
#tableA:
PID Amount CorrectAmount
-------------------------
1 100 100
2 100 100
3 100 100
4 100 50
5 100 0

;WITH Joined as(
SELECT
t1.PID,
t1.Amount,
RunningTotal = SUM(t2.Amount),
PrevRunningTotal = SUM(t2.Amount) - t1.Amount,
rn = ROW_NUMBER() OVER(ORDER BY SUM(t2.Amount))
FROM #tableA t1
INNER JOIN #tableA t2
ON t2.PID <= t1.PID
GROUP BY
t1.PID, t1.Amount
HAVING
SUM(t2.Amount) > 350
)
UPDATE t
SET t.CorrectAmount =
CASE
WHEN j.PID IS NULL THEN t.Amount
ELSE
CASE
WHEN j.rn = 1 THEN 350 - j.PrevRunningTotal
ELSE 0
END
END
FROM #tableA t
LEFT JOIN Joined j ON j.PID = t.PID

Related

Excel Quartile function in SQL Server SQL Query

I have an set of values like this:
40
50
50
66
83
100
100
100
100
100
100
100
100
100
100
100
100
When I do the quartile function in excel, i get these four values for my first quartile (25), second quartile(50), third quartile(75) and max (100)
Quartile =
83.33, 100, 100, 100
So when I compare a sales rep who got 80% then they will fall in the bottom quartile according to excel calculation.
I need to redo same functionality in sql and have given my code below.
declare #sales table(
salesRepId int,
percentageSales int)
insert into #sales(salesRepId, percentageSales)
values(1,40)
,(2,50)
,(3,50)
,(4,66.7)
,(5,83.33)
,(6,100)
,(7,100)
,(8,100)
,(9,100)
,(10,100)
,(11,100)
,(12,100)
,(13,100)
,(14,100)
,(15,100)
,(16,100)
,(17,100);
with quintile as(
select percentagesales, ntile(4) over(order by percentagesales)
as quintile
from (select distinct percentagesales from #sales) as s
)
select salesrepid, r.percentagesales, q.quintile
from #sales r
join quintile q on r.percentagesales = q.percentagesales
order by q.quintile, percentagesales
When I run this i get the following result set:
Query results
salesrepid percentagesales quintile
1 40 1
2 50 1
3 50 1
4 66 2
5 83 3
6 100 4
7 100 4
8 100 4
9 100 4
10 100 4
11 100 4
12 100 4
13 100 4
14 100 4
15 100 4
16 100 4
17 100 4
Accoridng to sql, the 80% will fall in the medium quartile.
How can I get the four percentile values similar to excel in SQL query
Change int to decimal in percentageSales:
declare #sales table(
salesRepId int,
percentageSales decimal(8,2))
insert into #sales(salesRepId, percentageSales)
values(1,40)
,(2,50)
,(3,50)
,(4,66.7)
,(5,83.33)
,(6,100)
,(7,100)
,(8,100)
,(9,100)
,(10,100)
,(11,100)
,(12,100)
,(13,100)
,(14,100)
,(15,100)
,(16,100)
,(17,100);
with quintile as(
select percentagesales, ntile(4) over(order by percentagesales)
as quintile
from (select distinct percentagesales from #sales) as s
)
select salesrepid, r.percentagesales, q.quintile
from #sales r
join quintile q on r.percentagesales = q.percentagesales
order by q.quintile, percentagesales
I tried percentile_cont suggested Larnu, I get the four quartile values as
83,100,100,100
but technically it should be 83.33,100,100,100
Here is the query i have so far
declare #sales table(
salesRepId int,
percentageSales int)
insert into #sales(salesRepId, percentageSales)
values(1,40.00)
,(2,50.00)
,(3,50.00)
,(4,66.77)
,(5,83.33)
,(6,100.00)
,(7,100.00)
,(8,100.00)
,(9,100.00)
,(10,100.00)
,(11,100.00)
,(12,100.00)
,(13,100.00)
,(14,100.00)
,(15,100.00)
,(16,100.00)
,(17,100.00);
with quintile as(
select percentagesales, ntile(100) over(order by percentagesales) as quintile
from (select distinct percentagesales from #sales) as s
)
select salesrepid, r.percentagesales, q.quintile
from #sales r
join quintile q on r.percentagesales = q.percentagesales
order by q.quintile, percentagesales;
declare #p Decimal(2,2) = 0.25;
with quartile as(
select
salesRepId,
percentageSales
,cast(percentile_cont(.25) within group(order by percentagesales) over() as decimal(36,2)) as quartile25th
,cast(percentile_cont(.5) within group(order by percentagesales) over() as decimal(36,2)) as quartile50th
,cast(percentile_cont(.75) within group(order by percentagesales) over() as decimal(36,2)) as quartile75th
,cast(percentile_cont(1) within group(order by percentagesales) over() as decimal(36,2)) as quartile100th
from #sales
)
select
s.salesRepId
,s.percentageSales
, case
when s.percentageSales < q.quartile25th then 'red'
when s.percentageSales >=q.quartile25th and s.percentageSales <q.quartile75th then 'yellow'
when s.percentageSales >= q.quartile75th then 'green'
end as color
From #sales s
join quartile q on q.percentageSales = s.percentageSales and q.salesRepId = s.salesRepId
How do I achieve quartile values exactly like I get in excel.
Thank you

Compare two tables and retrieve data

I have 2 tables in SQL Server and I want to compare them. I want to take 'NEEDED_AMOUNT' and 'min. 'ID'. I tried the following:
SELECT S_ID, NEEDED_AMOUNT, ID
FROM (
select T1.S_ID
, T2.NEEDED_AMOUNT
, T1.ID
from T1
INNER JOIN T2 MSD ON T1.S_ID = T2.S_ID
) TABLE1
GROUP BY S_ID, NEEDED_AMOUNT, ID
To explain this for example: in T1 table I have S_ID as '1' and its amount '20' and '30'. Also in T2 I have request for S_ID and I need '40' amount. So in T1 table how can I reach 40? I must take first row '20' amount and I split second row '30' to '20'. Below you can see what I want the output.
So here are the tables.
I can call this table T1 (ID is primary key and auto inc.):
ID AMOUNT S_ID
1 20 1
2 30 1
3 10 2
4 20 3
5 5 3
and I can call this table T2:
S_ID NEEDED_AMOUNT DATE
1 40 01.01.2020
2 5 02.01.2020
3 20 03.01.2020
So my output will be like this:
S_ID NEEDED_AMOUNT ID
1 20 1
1 20 2
2 5 3
3 20 4
Thanks for any opinion
I would use recursive approach for this :
with cte as (
select id, amount, s_id, needed_amount,
(case when amount = needed_amount then 1 else cnt end) as cnt
from (select t1.*, t2.needed_amount,
row_number() over (partition by t1.s_id order by t1.id) as seq,
count(*) over (partition by t1.s_id) as cnt
from t1 inner join
t2
on t2.s_id = t1.s_id
) t
where seq = 1
), cte1 as (
select c.needed_amount / c.cnt as amount, c.s_id, 1 as start, c.cnt
from cte c
union all
select amount, s_id, start + 1, cnt
from cte1 c1
where start < cnt
)
select s_id, amount, row_number() over (order by s_id) as id
from cte1;

Update null values by value in same column

I have a table in MS SQL Server, where are some null values in column "value"
Group ID Value
A 1 10
A 2
A 3
A 4 40
B 1
B 2 20
B 3 30
B 4
I want to update null values by not null in the same group with with the first higher ID, or if there is not any higher in same group, first lower. So the result should look like this.
Group ID Value
A 1 10
A 2 40
A 3 40
A 4 40
B 1 20
B 2 20
B 3 30
B 4 30
Thanks!
You can use windowed version of SUM function in order to determine islands of NULL valued records along with the record having the higher ID in the same group:
SELECT [Group], ID, Value,
SUM(CASE WHEN Value IS NULL THEN 0 ELSE 1 END) OVER
(PARTITION BY [Group] ORDER BY ID DESC) AS grp
FROM mytable
Output:
Group ID Value grp
-----------------------
A 4 40 1
A 3 30 2
A 2 NULL 2
A 1 NULL 2
B 4 40 1
B 3 NULL 1
B 2 20 2
B 1 10 3
You can now wrap the above query in a CTE and use another CTE to do the update:
;WITH CTE AS (
SELECT [Group], ID, Value,
SUM(CASE WHEN Value IS NULL THEN 0 ELSE 1 END) OVER
(PARTITION BY [Group] ORDER BY ID DESC) AS grp
FROM mytable
), ToUpdate AS (
SELECT [Group], ID, Value,
MAX(Value) OVER (PARTITION BY [Group], grp) AS group_value
FROM CTE
)
UPDATE ToUpdate
SET Value = group_value
WHERE Value IS NULL
Demo here
Edit:
The above query doesn't handle the edge case where the very last record within a Group slice is NULL. To handle this case as well you can use the following query:
;WITH CTE AS (
SELECT [Group], ID, Value,
SUM(CASE WHEN Value IS NULL THEN 0 ELSE 1 END) OVER
(PARTITION BY [Group] ORDER BY ID DESC) AS grp,
SUM(CASE WHEN Value IS NULL THEN 0 ELSE 1 END) OVER
(PARTITION BY [Group] ORDER BY ID) AS grp2
FROM mytable
), ToUpdate AS (
SELECT [Group], ID, Value,
MAX(Value) OVER (PARTITION BY [Group], grp) AS group_value,
MAX(Value) OVER (PARTITION BY [Group], grp2) AS group_value2
FROM CTE
)
UPDATE ToUpdate
SET Value = COALESCE(group_value, group_value2)
WHERE Value IS NULL
Demo here
Please try this-
DATA GENERATION
DECLARE #T TABLE
(
GroupCd CHAR(1),
Id INT,
Value INT
)
INSERT INTO #T
VALUES('A',1,10),
('A',2,NULL),
('A',3,NULL),
('A',4,40),
('B',1,NULL),
('B',2,20),
('B',3,30),
('B',4,NULL)
SOLUTION
UPDATE a
SET a.Value = b.Value
FROM #T a
INNER JOIN
(
SELECT a.GroupCd,a.Id,Coalesce(a.Value,z.Value,z1.Value) Value
FROM #T a
OUTER APPLY
(
SELECT TOP 1 Value
FROM #T b
WHERE a.GroupCd = b.GroupCd
AND b.Value IS NOT NULL AND a.Id < b.Id
ORDER BY Id
)z
OUTER APPLY
(
SELECT TOP 1 Value
FROM #T b
WHERE a.GroupCd = b.GroupCd
AND b.Value IS NOT NULL AND a.Id > b.Id
ORDER BY Id DESC
)z1
)b ON a.GroupCd = b.GroupCd AND a.Id = b.Id
SELECT * FROM #T
OUTPUT
GroupCd Id Value
------- ----------- -----------
A 1 10
A 2 40
A 3 40
A 4 40
B 1 20
B 2 20
B 3 30
B 4 30
(8 rows affected)
You Can try This simple Method
DECLARE #T TABLE
(
GroupCd CHAR(1),
Id INT,
Value INT
)
INSERT INTO #T
VALUES('A',1,NULL),
('A',2,NULL),
('A',3,30),
('A',4,40),
('B',1,10),
('B',2,20),
('B',3,NULL),
('B',4,40)
SELECT
*,
NewVal = COALESCE(Value,(SELECT TOP 1 Value FROM #T WHERE GroupCd = T.GroupCd AND Id > T.Id AND Value IS NOT NULL ORDER BY Id ASC))
FROM #T T
My Result
update MY_TABLE set [value] = [newValue] from (
select [Group] [newGroup],
[Value] [newValue]
from (
select [Group], [Value],
row_number() over (partition by [group] order by [Id] desc) [rn]
from MY_TABLE
where [Value] is not null
) [a] where [rn] = 1
) where [Group] = [newGroup] and [Value] is null

Allocated Stock Qty based on Order Qty in SQL Server 2008

I have a query below to allocated stock qty to match order qty based on order type. The problem is the stock qty is not updated.
DECLARE #tblOrder TABLE
(DealerCode NVARCHAR(50),
PartCode NVARCHAR(50),
OrderQty INT,
OrderType NVARCHAR(50)
)
INSERT INTO #tblOrder
( DealerCode,
PartCode,
OrderQty,
OrderType )
VALUES ('D1','A',19,'Urgent'),
('D2','B',10,'Normal'),
('D3','C',11,'HotLine'),
('D1','D',20,'Normal'),
('D2','E',12,'Normal'),
('D1','D',40,'Normal');
DECLARE #tblStock TABLE
(PartCode NVARCHAR(50),
StockQty INT)
INSERT INTO #tblStock
( PartCode,
StockQty)
VALUES ('A',20),
('B',15),
('C',9),
('D',30),
('E',0)
;WITH ordertemp AS (
select ord.dealercode,
ord.partcode,
ord.orderqty,
ord.ordertype,
RANK() OVER (ORDER BY case ord.ordertype when 'HotLine' then 1 when 'Urgent' then 2 else 3 end, ord.partcode, ord.dealercode) 'StockPriority',
sto.stockqty 'InitialStock'
from #tblorder ord
left outer join #tblstock sto
on ord.partcode = sto.partcode )
SELECT
Orders.dealercode,
Orders.partcode,
CASE WHEN Backlog.PriorQty > Orders.InitialStock THEN 0
ELSE Orders.InitialStock - Backlog.PriorQty END 'Stock',
Orders.orderqty,
CASE WHEN Backlog.PriorQty + Orders.OrderQty < Orders.InitialStock THEN Orders.OrderQty
WHEN Backlog.PriorQty > Orders.InitialStock THEN 0
ELSE Orders.InitialStock - Backlog.PriorQty END 'Allocated',
Orders.ordertype
FROM
ordertemp Orders
INNER JOIN
(
SELECT A.stockpriority, A.partcode, ISNULL(SUM(B.orderqty),0) 'PriorQty'
from ordertemp A
LEFT OUTER JOIN ordertemp B
on A.partcode = B.partcode
and A.stockpriority > B.stockpriority
group by A.stockpriority, A.partcode ) Backlog
ON Orders.stockpriority = Backlog.stockpriority
ORDER BY Orders.StockPriority
I got result:
dealercode partcode stock orderqty allocated ordertype
D3 C 9 11 9 HotLine
D1 A 20 19 19 Urgent
D2 B 15 10 10 Normal
D1 D 30 20 20 Normal
D1 D 30 40 30 Normal
D2 E 0 12 0 Normal
Actually, the stock (30) of partcode 'D' must be remain 10 after allocated 20.
Any idea please?
The Reason you get stock 30 is because your are using RANK for prioritization. RANK will allocate the same number to two order records if they have the same stock priority as defined by your condition ORDER BY case ord.ordertype when 'HotLine' then 1 when 'Urgent' then 2 else 3 end, ord.partcode, ord.dealercode. If you check the output of ordertemp you will see
dealercode partcode orderqty ordertype StockPriority InitialStock
D3 C 11 HotLine 1 9
D1 A 19 Urgent 2 20
D2 B 10 Normal 3 15
D1 D 20 Normal 4 30
D1 D 40 Normal 4 30
D2 E 12 Normal 6 0
Notice that the StockPriority of partcode D is 4 for both rows 4 and 5.
You can fix this by using ROW_NUMBER() instead of RANK() . You can also add orderqty in ROW_NUMBER() to specify if you want higher priority for smaller/larger orders if they have the same ordertype, partcode and dealercode.
Query with ROW_NUMBER()
DECLARE #tblOrder TABLE
(DealerCode NVARCHAR(50),
PartCode NVARCHAR(50),
OrderQty INT,
OrderType NVARCHAR(50)
)
INSERT INTO #tblOrder
( DealerCode,
PartCode,
OrderQty,
OrderType )
VALUES ('D1','A',19,'Urgent'),
('D2','B',10,'Normal'),
('D3','C',11,'HotLine'),
('D1','D',20,'Normal'),
('D2','E',12,'Normal'),
('D1','D',40,'Normal');
DECLARE #tblStock TABLE
(PartCode NVARCHAR(50),
StockQty INT)
INSERT INTO #tblStock
( PartCode,
StockQty)
VALUES ('A',20),
('B',15),
('C',9),
('D',30),
('E',0)
;WITH ordertemp AS (
select ord.dealercode,
ord.partcode,
ord.orderqty,
ord.ordertype,
ROW_NUMBER() OVER (ORDER BY case ord.ordertype when 'HotLine' then 1 when 'Urgent' then 2 else 3 end, ord.partcode, ord.dealercode) 'StockPriority',
sto.stockqty 'InitialStock'
from #tblorder ord
left outer join #tblstock sto
on ord.partcode = sto.partcode )
SELECT
Orders.dealercode,
Orders.partcode,
CASE WHEN Backlog.PriorQty > Orders.InitialStock THEN 0
ELSE Orders.InitialStock - Backlog.PriorQty END 'Stock',
Orders.orderqty,
CASE WHEN Backlog.PriorQty + Orders.OrderQty < Orders.InitialStock THEN Orders.OrderQty
WHEN Backlog.PriorQty > Orders.InitialStock THEN 0
ELSE Orders.InitialStock - Backlog.PriorQty END 'Allocated',
Orders.ordertype
FROM
ordertemp Orders
INNER JOIN
(
SELECT A.stockpriority, A.partcode, ISNULL(SUM(B.orderqty),0) 'PriorQty'
from ordertemp A
LEFT OUTER JOIN ordertemp B
on A.partcode = B.partcode
and A.stockpriority > B.stockpriority
group by A.stockpriority, A.partcode ) Backlog
ON Orders.stockpriority = Backlog.stockpriority
ORDER BY Orders.StockPriority
He's quite right above - when you first posted I had made the assumption you'd only have one entry per supplier per part / priority rather than duplicates :)
Note if you added orderquantity you would still face a problem if you had 2 entries with the same quantity in each.

Cumulative Counts and the Dates the Counts Become Negative and Positive

I have the below scenario
Date Amount Item
6/17/08 208 1
9/24/08 -48 1
6/15/09 -160 1
9/23/09 40 1
For the same items, I want to get the date where the cumulative amount is less than or equal to 0 the first time
and if the cumulative amount becomes positive later, I want to get the date it turned positive as well.
Date Amount Item Cumulative Amount
6/17/08 208 1 208
9/24/08 -48 1 160
6/15/09 -160 1 0 --This date
9/23/09 40 1 40 --This date
Any suggestions on how to achieve this?
In SQL Server 2012:
SELECT date
FROM (
SELECT *,
SUM(amount) OVER (PARTITION BY item ORDER BY date) AS csum,
SUM(amount) OVER (PARTITION BY item ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS psum
FROM mytable
) q
WHERE (csum <= 0 AND psum > 0)
OR
(csum > 0 AND psum <= 0)
In earlier versions:
SELECT date
FROM (
SELECT *,
(
SELECT SUM(amount)
FROM mytable mi
WHERE mi.item = m.item
AND mi.date < m.date
) AS psum
FROM mytable m
) q
WHERE (psum + amount <= 0 AND psum > 0)
OR
(psum + amount > 0 AND psum <= 0)
For the date when it first turns non-positive:
SELECT MIN(t3.Date) AS dateCumAmountTurnsLTEZero
FROM
(SELECT Date,
(SELECT SUM(Amount)
FROM mytable t1
WHERE t1.Date <= t2.Date) AS cumulativeAmount
FROM mytable t2) t3
WHERE t3.cumulativeAmount <= 0
To get the date it turns positive again you could either inject this result into another query or assuming you want to do it all in SQL it gets a bit hairy! See below:
SELECT MIN(t6.Date)
FROM
(SELECT Date,
(SELECT SUM(Amount)
FROM mytable t4
WHERE t4.Date <= t5.Date) AS cumulativeAmount
FROM mytable t5) t6
WHERE t6.cumulativeAmount >= 0
AND t6.Date >
(SELECT MIN(t3.Date)
FROM
(SELECT Date,
(SELECT SUM(Amount)
FROM mytable t1
WHERE t1.Date <= t2.Date) AS cumulativeAmount
FROM mytable t2) t3
WHERE t3.cumulativeAmount <= 0)
See SQL Fiddle Demo

Resources