SQL Splitting Rows Into Multiple Rows Based on Condition - sql

I have the below table:
Account
Credit
Debt
Net
ItemCount
InvoiceNumber
AAA
1300
150
1150
2
10
AAA
500
50
450
12
20
AAA
1800
650
1150
29
30
No record can have a higher count than 10 items so anything over 10 must be split into multiples of 10 until the last remaining value (which could be less than 10)
I am trying to automate this in SQL and have not been able to come up with an idea that works, I would like something to look at the above table and spit out the following:
Account
Credit
Debt
Net
ItemCount
InvoiceNumber
AAA
1300
150
1150
2
10
AAA
500
50
450
10
20
AAA
500
50
450
2
30
AAA
1800
650
1150
10
10
AAA
1800
650
1150
10
20
AAA
1800
650
1150
9
30
Any thoughts on how can this be accomplished?

I would try to use a cursor to iterate through the records and generate as many inserts as you need based on the value that you have

If you work with SQL Server, this should do the trick for you
CREATE TABLE #Table1
(
[Account] varchar(3),
[Credit] int,
[Debt] int,
[Net] int,
[ItemCount] int,
[InvoiceNumber] int
);
INSERT INTO #Table1 ([Account], [Credit], [Debt], [Net], [ItemCount], [InvoiceNumber])
VALUES
('AAA', 1300, 150, 1150, 2, 10),
('AAA', 500, 50, 450, 12, 20),
('AAA', 1800, 650, 1150, 29, 30)
;
WITH cte_numbers([Account] , [Credit] , [Debt] , [Net] , [ItemCount] , [InvoiceNumber] , itemcountzero, currentitmecount)
AS
(
SELECT
*,
(itemcount % 10) AS itemcountzero,
(itemcount) AS currentitmecount
FROM
#Table1
UNION ALL
SELECT
t.[Account] , t.[Credit] , t.[Debt] , t.[Net] , t.[ItemCount] , t.[InvoiceNumber] , c.itemcountzero, (c.currentitmecount-10)
FROM
#Table1 t
INNER JOIN
cte_numbers c ON t.[InvoiceNumber] = c.[InvoiceNumber]
AND (c.currentitmecount-10) >= c.itemcountzero
-- AND (currentitmecount-10) > 0
)
SELECT
[Account] , [Credit] , [Debt] , [Net] , IIF(currentitmecount > 10, 10 , currentitmecount) as [ItemCount] , [InvoiceNumber]
FROM
cte_numbers
ORDER BY
[InvoiceNumber], currentitmecount DESC;

Related

Running assignment of values with break T-SQL

With the below table of data
Customer
Amount Billed
Amount Paid
Date
1
100
60
01/01/2000
1
100
40
01/02/2000
2
200
150
01/01/2000
2
200
30
01/02/2000
2
200
10
01/03/2000
2
200
15
01/04/2000
I would like to create the next two columns
Customer
Amount Billed
Amount Paid
Assigned
Remainder
Date
1
100
60
60
40
01/01/2000
1
100
40
40
0
01/02/2000
2
200
150
150
50
01/01/2000
2
200
30
30
20
01/02/2000
2
200
10
10
10
01/03/2000
2
200
15
10
-5
01/04/2000
The amount paid on each line should be removed from the amount billed and pushed onto the next line for the same customer. The process should continue until there are no more records or the remainder is < 0.
Is there a way of doing this without a cursor? Maybe a recursive CTE?
Thanks
As I mentioned in the comments, this is just a cumulative SUM:
WITH YourTable AS(
SELECT *
FROM (VALUES(1,100,60 ,CONVERT(date,'01/01/2000')),
(1,100,40 ,CONVERT(date,'01/02/2000')),
(2,200,150,CONVERT(date,' 01/01/2000')),
(2,200,30 ,CONVERT(date,'01/02/2000')),
(2,200,10 ,CONVERT(date,'01/03/2000')),
(2,200,15 ,CONVERT(date,'01/04/2000')))V(Customer,AmountBilled,AmountPaid,[Date]))
SELECT Customer,
AmountBilled,
AmountPaid,
AmountBilled - SUM(AmountPaid) OVER (PARTITION BY Customer ORDER BY [Date] ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Remainder,
[Date]
FROM YourTable
ORDER BY Customer,
[Date];
Note this returns -5 for the last row, not 5, as 200 - 205 = -5. If you want 5 wrap the whole expression in an absolute function.
You can achieve this using recursive CTE as well.
DECLARE #customer table (Customer int, AmountBilled int, AmountPaid int, PaidDate date)
insert into #customer
values
(1 ,100, 60 ,'01/01/2000')
,(1 ,100, 40 ,'01/02/2000')
,(2 ,200, 150 ,'01/01/2000')
,(2 ,200, 30 ,'01/02/2000')
,(2 ,200, 10 ,'01/03/2000')
,(2 ,200, 15 ,'01/04/2000');
;WITH CTE_CustomerRNK as
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY customer order by paiddate) AS RNK
from #customer),
CTE_Customer as
(
SELECT customer, AmountBilled, AmountPaid, (amountbilled-amountpaid) as remainder, paiddate ,RNK FROM CTE_CustomerRNK where rnk = 1
union all
SELECT r.customer, r.AmountBilled, r.AmountPaid, (c.remainder - r.AmountPaid) as remainder, r.PaidDate, r.rnk
FROM CTE_CustomerRNK as r
inner join CTE_Customer as c
on c.Customer = r.Customer
and r.rnk = c.rnk + 1
)
SELECT customer, AmountBilled, AmountPaid, remainder, paiddate
FROM CTE_Customer order by Customer
customer
AmountBilled
AmountPaid
remainder
paiddate
1
100
60
40
2000-01-01
1
100
40
0
2000-01-02
2
200
150
50
2000-01-01
2
200
30
20
2000-01-02
2
200
10
10
2000-01-03
2
200
15
-5
2000-01-04

SQL query- turning daily data to average for month, cumulative sum until end of month?

SQL - turning daily data to average for month, cumulative sum until end of month
I have daily data coming from a database.
It has a table that contains many columns - Date, Entity Name, Value1, value2, value3, ..
I want to work out the average value per month for each entity. Also for certain values, i need the cumulative sum value from the beginning to until end of the month (cumulative sum by end of the month).
Example:
Date Entity Value1 Value2 Value3 Value4
01/01/2017 'ZZ-01' 5 10 25 10
01/01/2017 'BB-01' 5 10 25 10
02/01/2017 'ZZ-01' 2 1 5 0
02/01/2017 'BB-01' 2 1 5 0
03/01/2017 'ZZ-01' 5 10 25 10
03/01/2017 'BB-01' 5 10 25 10
.....
.....
31/01/2017 'ZZ-01' 5 10 25 10
31/01/2017 'BB-01' 5 10 25 5
01/02/2017 'ZZ-01' 5 10 25 15
01/02/2017 'BB-01' 5 10 25 11
02/02/2017 'ZZ-01' 5 10 25 15
.......
28/02/2017 'ZZ-01' 5 10 25 10
28/02/2017 'BB-01' 5 10 25 10
....
and so on
What I would like to create is a table that looks like:
Month Entity AvgValue1 AvgValue2 CumValue2 AvgValue3 AvgValue4
Jan-2017 'ZZ-01' Avg1 Avg2 CumV2 Avg3 Avg4
Jan-2017 'BB-01'
Feb-2017 'ZZ-01'
Feb-2017 'BB-01'
Mar-2017 'ZZ-01'
...
Average for the given month for each values. For some values, i need cumulative sum until end of month since the beginning of the data in the table.
from searching the stackoverflow, I found out that i can use the below to get the sum and cumulative
SELECT Date, Name, Sum, Avg,
(SELECT SUM(Value1)
FROM mytable as t2
Where t2.Date <=t1.Date) as CumVal
from mytable as t1
order by t1.DateO ASC, Name
and below for average for month
SELECT
dateadd(month, datediff(month, 0, t2.Date), 0) as Month,
AVG(t2.Value1) as AvgValue1
FROM mytable as t2
group by dateadd(month, datediff(month, 0, t2.Date), 0)
But I am not able to combine these things to make one single query that helps in creating the table. your help is appreciated.
I tried to create small test code with one value column:
IF OBJECT_ID('dbo.mytable', 'U') IS NOT NULL
BEGIN
DROP TABLE dbo.mytable
END
CREATE TABLE mytable
([DateOn] datetime, [Name] varchar(10), [Rate1] float, [Rate2] float,
[Rate3] float)
;
INSERT INTO mytable
([DateOn], [Name], [Rate1])
VALUES
-- MM/DD/YYYY format
-- value for first 4 days is shown. assume that the rest days are zero
-- average will be (sum /31) in January
('01/01/2017' , 'AA-01', 10),
('01/01/2017' , 'BB-01', 100),
('01/02/2017' , 'AA-01', 15),
('01/02/2017' , 'BB-01', 200),
('01/03/2017' , 'AA-01', 20),
('01/03/2017' , 'BB-01', 300),
('01/04/2017' , 'AA-01', 25),
('01/04/2017' , 'BB-01', 400),
('02/01/2017' , 'AA-01', 10),
('02/01/2017' , 'BB-01', 100),
('02/02/2017' , 'AA-01', 15),
('02/02/2017' , 'BB-01', 200),
('02/03/2017' , 'AA-01', 20),
('02/03/2017' , 'BB-01', 300),
('02/04/2017' , 'AA-01', 25),
('02/04/2017' , 'BB-01', 400)
;
Select dateadd(month, datediff(month, 0, t1.DateOn), 0) as Month, Name,
AVG(t1.Rate1) as AvgRate1,
(
SELECT SUM(Rate1)
FROM mytable as t2
Where t2.DateOn <=t1.DateOn
)
as CumRate
from mytable as t1
--order by t1.DateOn ASC,Name
group by dateadd(month, datediff(month, 0, t1.DateOn), 0)
DROP TABLE dbo.mytable
I am running this test on
https://rextester.com/CFHEW85984
but getting an error:
Error(s), warning(s):
Column 'mytable.DateOn' is invalid in the select list because it is not
contained in either an aggregate function or the GROUP BY clause.
Column 'mytable.Name' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
expected result table format:
Month Name AvgRate CumRate
Jan-17 AA-01 2.258 70
Jan-17 BB-01 32.258 1000
Feb-17 AA-01 2.5 140
Feb-27 BB-01 35.714 2000
so for Jan-17 for name AA-01, i add up the daily figures (only 4 days in this example, rest assumed 0) and divide by 31 to get avg for the month. I then sum them up to get cumulative. For February, average is done same way w, but cumulative is cum_jan+cum_feb. so on
I am struggling how to combine this with join also.
Thanks

Multiple joins on same table giving duplicate data

Here is the sale_order table which contains CCNID
sale_orderid client_channel_nameid
1 1
2 1
3 2
4 2
5 2
6 2
7 1
8 1
sale_order_item Table has sale_orderid as a foreign key
sale_order_itemid sale_orderid order_date selling_price
42219 1 2018-03-21 00:00:00 200
28948 2 2018-03-21 16:17:55 100
42220 3 2018-03-21 00:00:00 300
13194 4 2018-03-21 13:33:58 400
42839 5 2018-03-20 07:54:29 550
42840 6 2018-03-20 07:58:20 600
42086 7 2018-03-20 00:00:00 700
11691 8 2018-03-20 05:32:31 500
And I want to get the sum of price of soid of 21 and 20 dates in different columns grouped by CCNID
client_channel_nameid 21 20
1 300 1200
2 700 1150
I am joining Sale order twice which is giving me wrong results
select ccn.client_channel_nameid,
round (sum(soi.selling_price)),
round(sum(soi1.selling_price))
from app.client_channel_name ccn
join app.sale_order so on so.client_channel_nameid = ccn.client_channel_nameid
inner join app.sale_order_item soi on soi.sale_orderid = so.sale_orderid
join app.sale_order so1 on so1.client_channel_nameid = ccn.client_channel_nameid
inner join app.sale_order_item soi1 on soi1.sale_orderid = so1.sale_orderid
where ccn.clientid = 1
and to_char(soi.order_date, 'DD-MM-YYYY') = '20-03-2018'
and to_char(soi1.order_date, 'DD-MM-YYYY') = '21-03-2018'
group by client_channel_nameid;
You can group the data by CCNID and then only sum selling_price when the order_date day is the 21 or 20.
SELECT client_channel_nameid
, SUM(CASE
WHEN EXTRACT(day FROM order_date) = 21 THEN selling_price
ELSE 0
END) AS "21"
, SUM(CASE
WHEN EXTRACT(day FROM order_date) = 20 THEN selling_price
ELSE 0
END) AS "20"
FROM sale_order so
JOIN sale_order_item soi
ON soi.sale_orderid = so.sale_orderid
GROUP BY so.client_channel_nameid
Below query produce the desired result. From your above tried solution and question you asked I think you are looking for exactly 21 and 20 dates. The below will need slight changes with extra filters for add more dates ex.22,23,24...
with sale_order(
sale_orderid, client_channel_nameid
) as (
select
*
from
(
values
(1, 1),
(2, 1),
(3, 2),
(4, 2),
(5, 2),
(6, 2),
(7, 1),
(8, 1)
) as x(
sale_orderid, client_channel_nameid
)
),
sale_order_item(
sale_order_itemid, sale_orderid,
order_date, selling_price
) as (
select
*
from
(
values
(
42219, 1, '2018-03-21 00:00:00' :: timestamp,
200
),
(
28948, 2, '2018-03-21 16:17:55' :: timestamp,
100
),
(
42220, 3, '2018-03-21 00:00:00' :: timestamp,
300
),
(
13194, 4, '2018-03-21 13:33:58' :: timestamp,
400
),
(
42839, 5, '2018-03-20 07:54:29' :: timestamp,
550
),
(
42840, 6, '2018-03-20 07:58:20' :: timestamp,
600
),
(
42086, 7, '2018-03-20 00:00:00' :: timestamp,
700
),
(
11691, 8, '2018-03-20 05:32:31' :: timestamp,
500
)
) as x(
sale_order_itemid, sale_orderid,
order_date, selling_price
)
)
select
client_channel_nameid,
sum(selling_price) filter (where to_char(order_date, 'dd-mm-yy') = '21-03-2018') as date_21,
sum(selling_price) filter (where to_char(order_date, 'dd-mm-yy') = '20-03-2018') as date_20
from
sale_order so
join sale_order_item soi on soi.sale_orderid = so.sale_orderid
group by
so.client_channel_nameid

Sql to order results by sum

DB used - Oracle
create table customer_exercise(
customer_id number,
exercise_id number,
cnt number,
exercise_date date)
Table data
1000 10 3 17-DEC-15
1001 20 6 19-DEC-15
1000 20 2 20-DEC-15
1003 20 9 20-DEC-15
1000 20 6 22-DEC-15
1000 30 10 23-DEC-15
1001 10 25 10-DEC-15
Is it possible to get results using sql such that sum(cnt) for exercise_id 20 appears first in the resultset?
select customer_id , exercise_id, sum(cnt) from customer_exercise
where customer_id in (1000, 1001, 1003) and exercise_id in (20)
group by customer_id, exercise_id order by sum(cnt)
1001 20 6
1000 20 8
1003 20 9
select customer_id , exercise_id, sum(cnt) from customer_exercise
where customer_id in (1000, 1001, 1003) and exercise_id not in (20)
group by customer_id, exercise_id order by sum(cnt)
1000 10 3
1000 30 10
1001 10 25
What I am trying to do is merge the results of above two queries with one sql. Is it possible to write a single sql that will fetch the resultset like below?
1001 20 6
1000 20 8
1003 20 9
1000 10 3
1000 30 10
1001 10 25
I think that would be:
select customer_id , exercise_id, sum(cnt)
from customer_exercise
where customer_id in (1000, 1001, 1003)
group by customer_id, exercise_id
order by (case when exercise_id in (20) then 1 else 2 end), sum(cnt)

SQL query to calculate interval discount

I have trouble understanding how I can solve this problem with a T-SQL query.
I have a price column and a volume column. In another table a have discounts at different levels of volume. So my discount table could have values as
(StartLevel, DiscountFactor)
(0, 1);
(25, 0.95);
(50, 0.90);
(100, 0.75)
What I want is to calculate a total price. If Volume is 35, I want it to multiply
Price x ((35-25) x 0.95 + (25-0) x 1)
If the volume is 200, it should be
Price x ((200-100) x 0.75 + (100-50) x .9+(50-25) x .95+(25) x 1)
Can anybody help me with a query that solves this?
This can help:
DECLARE #products TABLE
(
id INT ,
price MONEY ,
volume INT
)
DECLARE #discounts TABLE
(
id INT ,
Level INT ,
Factor MONEY
)
INSERT INTO #products
VALUES ( 1, 10, 35 ),
( 2, 15, 200 )
INSERT INTO #discounts
VALUES ( 1, 0, 1 ),
( 2, 25, 0.95 ),
( 3, 50, 0.90 ),
( 4, 100, 0.75 )
SELECT p.id, p.price * SUM(ca.m)
FROM #products p
CROSS APPLY ( SELECT * ,
Factor * ( -Level + LEAD(Level) OVER ( PARTITION BY p.id ORDER BY Level, d ) ) AS m
FROM ( SELECT 1 AS d ,
Level ,
Factor
FROM #discounts
WHERE Level < p.volume
UNION ALL
SELECT 2 AS d ,
p.volume ,
0
) t
) ca
GROUP BY p.id, p.price
Without grouping it returns:
id price volume d Level Factor m
1 10.00 35 1 0 1.00 25.00
1 10.00 35 1 25 0.95 9.50
1 10.00 35 2 35 0.00 NULL
2 15.00 200 1 0 1.00 25.00
2 15.00 200 1 25 0.95 23.75
2 15.00 200 1 50 0.90 45.00
2 15.00 200 1 100 0.75 75.00
2 15.00 200 2 200 0.00 NULL
Then just group by product and sum of m results in:
id Total
1 345.00
2 2531.25
For a given Volume and Price you can get the discount based on interval using LEAD which is available in SQL Server 2012+ onwards.
Sample Data
DECLARE #PriceTable TABLE(Volume INT,Price DECIMAL(9,2) )
DECLARE #Discount TABLE(StartLevel int, DiscountFactor DECIMAL(9,2))
INSERT INTO #PriceTable
VALUES(75, 20.5),
(150, 20),
(250, 20.5),
(0, 15);
INSERT INTO #Discount
VALUES(0, 1),
(25, 0.95),
(50, 0.90),
(100, 0.75);
Query
SELECT Volume,Price,FinalPrice
FROM #PriceTable P
CROSS APPLY(
SELECT SUM(CASE WHEN (MaxLevel >=StartLevel) THEN (MaxLevel-StartLevel) ELSE 0 END *DiscountFactor)*P.Price as FinalPrice
FROM
(
SELECT CASE WHEN LEAD(StartLevel)OVER(ORDER BY StartLevel) < P.Volume THEN LEAD(StartLevel)OVER(ORDER BY StartLevel) ELSE P.Volume END MaxLevel,StartLevel, DiscountFactor
FROM #Discount
) IQ
)T
Output
Volume Price FinalPrice
75 20.50 1460.6250
150 20.00 2625.0000
250 20.50 4228.1250
0 15.00 0.0000