Running total with maximum limit - sql

I have a simple question but its kinda difficult for me to solve it.
I would like to have sum up a specific column till it reached a limit and resets it self. (SQL 2012)
Lets say the limit is 50
- List item
- Value Total
- 10 10
- 20 30
- 30 60
- 40 50 (60-limit) + the current row value
- 2 2
- 3 5
- 10 15
- 25 40
- 15 55
- 5 10 (55-limit) + the current row value
Thank you very much.

This should do it if you have SQL Server 2012 or later:
DECLARE #Table TABLE (Id INT, ListItem INT);
INSERT INTO #Table VALUES (1, 10);
INSERT INTO #Table VALUES (2, 20);
INSERT INTO #Table VALUES (3, 30);
INSERT INTO #Table VALUES (4, 40);
INSERT INTO #Table VALUES (5, 2);
INSERT INTO #Table VALUES (6, 3);
INSERT INTO #Table VALUES (7, 10);
INSERT INTO #Table VALUES (8, 25);
INSERT INTO #Table VALUES (9, 15);
INSERT INTO #Table VALUES (10, 5);
WITH RunningTotal AS (
SELECT Id, ListItem, SUM(ListItem) OVER (ORDER BY Id) % 50 AS RT FROM #Table)
SELECT
rt.Id,
rt.ListItem,
CASE WHEN rt.RT < rt2.RT THEN rt.RT + 50 ELSE rt.RT END AS RunningTotal
FROM
RunningTotal rt
LEFT JOIN RunningTotal rt2 ON rt2.Id = rt.Id - 1
ORDER BY
rt.Id;
The tricky bit is allowing the numbers to overflow the 50 one time, otherwise this would be trivial.
Results are:
Id LI RunningTotal
1 10 10
2 20 30
3 30 60
4 40 50
5 2 2
6 3 5
7 10 15
8 25 40
9 15 55
10 5 10

create table running_totals
(
id int identity(1,1),
val int
)
insert into running_totals
select 1 union all
select 20 union all
select 10 union all
select 30 union all
select 50 union all
select 10 union all
select 11 union all
select 22 union all
select 40 union all
select 60 union all
select 20 union all
select 10 union all
select 15
declare cur_run_tot cursor for select id,val from running_totals order by id asc
declare #id int ,#val int,#runtot int
open cur_run_tot
create table #RunTot
(
id int,val int, runtot int
)
fetch next from cur_run_tot into #id,#val
while ##FETCH_STATUS = 0
begin
if #runtot is null or #runtot+#val > 50
set #runtot = #val
else
set #runtot = #runtot+ #val
insert into #RunTot
select #id,#val,#runtot
fetch next from cur_run_tot into #id,#val
end
select id as ID, val as Current_Value, runtot as Running_Total from #RunTot
drop table #RunTot
deallocate cur_run_tot

Related

What is the best way to sum information from two separate tables in SQL with join?

I have two tables with the following structure:
CREATE TABLE COST1 (
ID,
COUNTER,
COST
)
CREATE TABLE COST2 (
ID,
COUNTER,
COST
)
ID can be used for a JOIN; and while COUNTER and COST are have the same name in both tables they are not related to each other the way ID is. I would like to create a result set COST3 that has the form:
ID, sum(COST1.cost) + sum(COST2.cost).
Here is what I have come up with but I don't know if summing over the original tables with a GROUP BY that results from the JOIN would work as I intend?
SELECT
ID,
( sum(c1.COST) + sum(c2.COST) ) as COST_TOTAL
FROM
COST1 c1
JOIN COST2 c2 ON c1.ID = c2.ID
GROUP BY
ID;
With some data, here is what the result should look like:
COST1
ID
Counter
Cost
A
1
50
A
2
30
B
1
25
B
2
30
COST2:
ID
Counter
Cost
A
1
20
A
2
40
B
1
50
B
2
10
B
3
20
COST3:
ID
Cost
A,
140
B,
135
create table COST1(ID varchar(5), Counter int,Cost int);
insert into COST1 values('A', 1, 50 );
insert into COST1 values('A', 2, 30 );
insert into COST1 values('B', 1, 25 );
insert into COST1 values('B', 2, 30 );
insert into COST1 values('C', 2, 30 );
create table COST2(ID varchar(5), Counter int,Cost int);
insert into COST2 values('A', 1, 20 );
insert into COST2 values('A', 2, 40 );
insert into COST2 values('B', 1, 50 );
insert into COST2 values('B', 2, 10 );
insert into COST2 values('B', 3, 20 );
insert into COST2 values('D', 3, 20 );
Query#1 (To get sum for all the IDs which are available in both tables)
Select C1.ID, (C1.cost+C2.cost) as Cost
from
(select ID, sum(COST) cost from COST1
group by ID) C1
Inner join
(select ID, sum(COST) cost from COST2
group by ID) C2
on C1.ID=C2.ID
GO
Output:
ID
Cost
A
140
B
135
Query#2 (To get sum for all the IDs which are available in any of the tables)
select ID, sum(cost) Cost
from (
(select id, cost
from cost1
) union all
(select id, cost
from cost2
)
) Cost3
group by id;
GO
Output:
ID
Cost
A
140
B
135
C
30
D
20
db<fiddle here
Consider performing the aggregation of non-unique ID rows in inner-queries:
SELECT
COALESCE( c1.group_id, c2.group_id ) AS id,
( COALESCE( c1.group_cost, 0 ) + COALESCE( c2.group_cost, 0 ) ) AS total_cost
FROM
(
SELECT
cost1.id AS group_id,
SUM( cost1.cost ) AS group_cost
FROM
cost1
GROUP BY
cost1.id
) AS c1
FULL OUTER JOIN
(
SELECT
cost2.id AS group_id,
SUM( cost2.cost ) AS group_cost
FROM
cost2
GROUP BY
cost2.id
) AS c2
ON c1.group_id = c2.group_id
)
ORDER BY
id
Use a sum over a union of a sum:
select id, sum(s) COST_TOTAL
from (
select id, sum(cost) s
from cost1
group by id
union all
select id, sum(cost)
from cost2
group by id
) u
group by id
Note that you need the “all” version of union to preserve duplicate rows.
I would be inclined to use union all in a subquery and then aggregate:
select id, sum(cost)
from ((select id, cost
from cost1
) union all
(select id, cost
from cost2
)
) ic
group by id;
union all the two table then group by id the subquery. use a case when to partition between the two tables a specific column value.
create table #COST1(ID varchar(5), Counter int,Cost int);
insert into #COST1 values('A', 1, 50 );
insert into #COST1 values('A', 2, 30 );
insert into #COST1 values('B', 1, 25 );
insert into #COST1 values('B', 2, 30 );
insert into #COST1 values('C', 2, 30 );
create table #COST2(ID varchar(5), Counter int,Cost int);
insert into #COST2 values('A', 1, 20 );
insert into #COST2 values('A', 2, 40 );
insert into #COST2 values('B', 1, 50 );
insert into #COST2 values('B', 2, 10 );
insert into #COST2 values('B', 3, 20 );
insert into #COST2 values('D', 3, 20 );
SELECT ID,
Sum(case when Type='Cost1' then Cost else 0 end+case when Type='Cost2' then Cost else 0 end) Cost,
Sum(Counter) Counter
FROM
(
SELECT *,'Cost1' Type FROM #COST1
UNION ALL
SELECT *,'Cost2' Type FROM #COST2
)x
group by ID
drop table #COST1
drop table #COST2
output
ID Cost Counter
A 140 6
B 135 9
C 30 2
D 20 3

Get Records depend on their sum value

I have a SQL Server table which have records like this
ID | Value
1 | 100
2 | 150
3 | 250
4 | 600
5 | 1550
6 | 50
7 | 300
I need to select random records, but the only condition is that the total sum of this records value achieve a specific number or percentage i define.
let's say i need a total value of 300 or 10%, so here are the chances
1 | 100
2 | 150
6 | 50
or
3 | 250
6 | 50
or
7 | 300
can any one help me to do this.
Think this recursive CTE works, no idea what the performance will be like though once you get past a trivial amount of rows:
DECLARE #Test TABLE
(
ID INT NOT NULL,
VAL INT NOT NULL
);
INSERT INTO #Test
VALUES (1,100),
(2,150),
(3,250),
(4,600),
(5,1550),
(6,50),
(7,300);
DECLARE #SumValue INT = 300,
#Percentage INT = 10;
WITH GetSums
AS
(
SELECT T.ID,
T.Val,
CAST(T.ID AS VARCHAR(MAX)) AS IDs
FROM #Test AS T
UNION ALL
SELECT T1.ID,
T1.Val + GS.Val AS Val,
CAST(T1.ID AS VARCHAR(MAX)) + ',' + GS.IDs AS IDs
FROM #Test AS T1
INNER
JOIN GetSums AS GS
ON T1.ID > GS.ID
)
SELECT GS.IDs,
GS.Val
FROM GetSums AS GS
WHERE (GS.Val = #SumValue OR GS.VAL = (SELECT SUM(Val) FROM #Test AS T) / #Percentage)
OPTION (MAXRECURSION 50);
Similar found here:
find all combination where Total sum is around a number
Try this...we will get the correct answer if the 6th value is 250...
SELECT 1 ID, 100 Value
INTO #Temp_1
UNION ALL SELECT 2 , 150
UNION ALL SELECT 2 , 150
UNION ALL SELECT 3 , 250
UNION ALL SELECT 4 , 600
UNION ALL SELECT 5 , 1550
UNION ALL SELECT 6 , 250
UNION ALL SELECT 7 , 300
CREATE TABLE #Temp_IDs
(
ID Int,
Value Numeric(18,2)
)
DELETE
FROM #Temp_IDs
DECLARE #ID Int,
#Vale Numeric(18,2),
#ContinueYN Char(1)
SET #ContinueYN = 'Y'
IF EXISTS (SELECT TOP 1 1 FROM #Temp_1
WHERE Value <= 300
AND ID NOT IN (SELECT ID FROM #Temp_IDs )
AND Value <= (SELECT 300 - ISNULL( SUM(Value),0) FROM #Temp_IDs)
ORDER BY NEWID())
BEGIN
WHILE (#ContinueYN = 'Y')
BEGIN
SELECT #ID = ID,
#Vale = Value
FROM #Temp_1
WHERE Value <= 300
AND ID NOT IN (SELECT ID FROM #Temp_IDs )
AND Value <= (SELECT 300 - ISNULL( SUM(Value),0) FROM #Temp_IDs)
ORDER BY NEWID()
INSERT INTO #Temp_IDs
SELECT #ID,#Vale
IF (SELECT SUM(Value) FROM #Temp_IDs) = 300
BREAK
ELSE IF #ID IS NULL
BEGIN
DELETE FROM #Temp_IDs
END
SET #ID = NULL
SET #Vale = NULL
END
END
SELECT *
FROM #Temp_IDs
DROP TABLE #Temp_IDs
DROP TABLE #Temp_1

SQL Server - How to query the set of maximum numbers from a list of numbers from top to bottom

Best way to explain this would be through an example. Let's say I have this simple 2 column table:
Id | Score
1 | 10
2 | 5
3 | 20
4 | 15
5 | 20
6 | 25
7 | 30
8 | 30
9 | 10
10 | 40
The query should return the IDs of each item where the max score changed. So, from the top, 10 would be the top score since item 1 has 10 the first time through but then on item 3 it has a score of 20 so it just had a new max score and this continues until the bottom of the table. So eventually, the query will result to:
1, 3, 6, 7, 10
I tried doing a Cursor and loop through the table but I was wondering if there was a much simple way of doing this.
Thanks
Solution (SQL2012+):
SELECT v.MaxScore, MIN(v.Id) AS FirstId
FROM (
SELECT *, MAX(t.Score) OVER(ORDER BY t.Id ASC) AS MaxScore
FROM #Table AS t
) v
GROUP BY v.MaxScore
Demo
one more version,works for versions >= 2008,you can remove apply to make it work for 2005 as well
;with cte
(Id , Score)
as
(
select 1 , 10 union all
select 2 , 5 union all
select 3 , 20 union all
select 4 , 15 union all
select 5 , 20 union all
select 6 , 25 union all
select 7 , 30 union all
select 8 , 30 union all
select 9 , 10 union all
select 10 , 40
)
select min(id)
from
cte c2
cross apply
(select case when score -(select max(score) from cte c1 where c1.id<=c2.id )=0
then 1 else 0 end) b(val)
where val=1
group by Score
Output:
1
3
6
7
10
I think you can just do a MIN on the id with a GROUP BY Score. Like this:
SELECT MIN(Id) FROM table GROUP BY Score
Using LAG function, that returns prev value of score:
DECLARE #Table TABLE(Id int, Score int)
INSERT INTO #Table
VALUES
(1 , 10),
(2 , 10),
(3 , 20),
(4 , 20),
(5 , 20),
(6 , 25),
(7 , 30),
(8 , 30),
(9 , 30),
(10 , 40)
SELECT *
FROM
(
SELECT
*,
LAG(t.Score, 1, NULL) OVER (ORDER BY t.Id) AS PrevScore
FROM #Table AS t
) AS p
WHERE p.Score <> p.PrevScore OR p.PrevScore IS NULL
Try This
declare #scores varchar(max)
select #scores = isnull(#scores+',','')+convert(varchar,min(id))
from #temp group by score
select #scores

Add column with row number

I want to add a column to my select showing a set of number from say 1 to 4.
Example:
Select * gives me
Id Transaction
1 10
2 11
3 12
4 13
5 14
6 15
I want to add a column called "Flow". The result should be like this.
Id Transaction Flow
1 10 1
2 11 2
3 12 3
4 13 4
5 14 1
6 15 2
In this example the flow is from 1-4. Could be 1-n.
No particular relation between Id and Flow is needed.
If you're using SQL Server or other DBMS that allows ROW_NUMBER, you could do this:
CREATE TABLE #Tbl(Id INT, [Transaction] INT);
INSERT INTO #Tbl VALUES
(1, 10), (2, 11), (3, 12), (4, 13), (5, 14), (6, 15);
DECLARE #N INT = 4;
SELECT *,
Flow = 1 + ((ROW_NUMBER() OVER(ORDER BY Id) - 1) % #N)
FROM #Tbl
DROP TABLE #Tbl;
If you are using mySql.
Query
set #r := 0;
select Id, `Transaction`,
#r := (#r % 4) + 1 as Flow
from your_table_name
order by Id;
Demo
EDIT
Following sql query can be used irrespective of rdbms.
Query
select *, (
select ((count(*) - 1) % 4) + 1 as Flow
from your_table_name t2
where t1.Id >= t2.Id
) as Flow
from your_table_name t1;

Finding Missing Numbers When Data Is Grouped In SQL Server

I need to to write a query that will calculate the missing numbers in a sequence when the data is "grouped". The data in each group is in sequence, but each individual group would have its own sequence. The data would look something like this:
Id| Number|
-----------
1 | 250 |
1 | 270 | <260 Missing
1 | 280 | <290 Missing
1 | 300 |
1 | 310 |
2 | 110 |
2 | 130 | <120 Missing
2 | 140 |
3 | 260 |
3 | 270 |
3 | 290 | <280 Missing
3 | 300 |
3 | 340 | <310, 320 & 330 Missing
I have found a solution based on this post from CELKO here:
http://bytes.com/topic/sql-server/answers/511668-query-find-missing-number
In essence to set up a demo run the following:
CREATE TABLE Sequence
(seq INT NOT NULL
PRIMARY KEY (seq));
INSERT INTO Sequence VALUES (1);
INSERT INTO Sequence VALUES (2);
INSERT INTO Sequence VALUES (3);
INSERT INTO Sequence VALUES (4);
INSERT INTO Sequence VALUES (5);
INSERT INTO Sequence VALUES (6);
INSERT INTO Sequence VALUES (7);
INSERT INTO Sequence VALUES (8);
INSERT INTO Sequence VALUES (9);
INSERT INTO Sequence VALUES (10);
CREATE TABLE Tickets
(buyer CHAR(5) NOT NULL,
ticket_nbr INTEGER DEFAULT 1 NOT NULL
PRIMARY KEY (buyer, ticket_nbr));
INSERT INTO Tickets VALUES ('a', 2);
INSERT INTO Tickets VALUES ('a', 3);
INSERT INTO Tickets VALUES ('a', 4);
INSERT INTO Tickets VALUES ('b', 4);
INSERT INTO Tickets VALUES ('c', 1);
INSERT INTO Tickets VALUES ('c', 2);
INSERT INTO Tickets VALUES ('c', 3);
INSERT INTO Tickets VALUES ('c', 4);
INSERT INTO Tickets VALUES ('c', 5);
INSERT INTO Tickets VALUES ('d', 1);
INSERT INTO Tickets VALUES ('d', 6);
INSERT INTO Tickets VALUES ('d', 7);
INSERT INTO Tickets VALUES ('d', 9);
INSERT INTO Tickets VALUES ('e', 10);
SELECT DISTINCT T1.buyer, S1.seq
FROM Tickets AS T1, Sequence AS S1
WHERE seq <= (SELECT MAX(ticket_nbr) -- set the range
FROM Tickets AS T2
WHERE T1.buyer = T2.buyer)
AND seq NOT IN (SELECT ticket_nbr -- get missing numbers
FROM Tickets AS T3
WHERE T1.buyer = T3.buyer);
CELKO does mention that this is for a small number of tickets, in my example my numbers table is limited to 200 rows with a single column which is a primary key with each row an increment of 10 as that is what I am interested in. I modified CELKOs query as follows (added in min range):
SELECT DISTINCT T1.buyer, S1.seq
FROM Tickets AS T1, Sequence AS S1
WHERE seq <= (SELECT MIN(ticket_nbr) -- set the MIN range
FROM Tickets AS T2
WHERE T1.buyer = T2.buyer)
AND seq <= (SELECT MAX(ticket_nbr) -- set the MAX range
FROM Tickets AS T2
WHERE T1.buyer = T2.buyer)
AND seq NOT IN (SELECT ticket_nbr -- get missing numbers
FROM Tickets AS T3
WHERE T1.buyer = T3.buyer)
ORDER BY buyer, seq;
The output would be those numbers that are missing:
buyer seq
a 1
b 1
b 2
b 3
e 1
e 2
e 3
e 4
e 5
e 6
e 7
e 8
e 9
This works exactly as I want, however, on my data set it is very slow (11 second run time at the moment - it appears to be the DISTINCT which slows things down tremendously and presumably will gt worse as the base data set grows). I have tried all manner of things to make it more efficient but sadly my ambition exceeds my knowledge. Is it possible to make the query above more efficient/faster. My only constraint is that the dataset I am making needs to be a SQL View (as it feeds a report) and will execute on SQL Azure.
Cheers
David
If my understanding is correct, you want to fill in the missing data from the table. The table would consist of ID and a Number which is incremented by 10.
CREATE TABLE Test(
ID INT,
Number INT
)
INSERT INTO Test VALUES
(1, 250), (1, 270), (1, 280), (1, 300), (1, 310),
(2, 110), (2, 130), (2, 140), (3, 260), (3, 270),
(3, 290), (3, 300), (3, 340);
You could do this by using a Tally Table and doing a CROSS JOIN on the Test table:
;WITH E1(N) AS(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)
,E2(N) AS(SELECT 1 FROM E1 a, E1 b)
,E4(N) AS(SELECT 1 FROM E2 a, E2 b)
,Tally(N) AS(
SELECT TOP (SELECT MAX(Number)/10 FROM Test)
(ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) - 1) * 10
FROM E4
),
MinMax AS(
SELECT
ID,
Minimum = MIN(Number),
Maximum = MAX(Number)
FROM Test
GROUP BY ID
),
CrossJoined AS(
SELECT
m.ID,
Number = Minimum + t.N
FROM MinMax m
CROSS JOIN Tally t
WHERE
Minimum + t.N <= Maximum
)
SELECT * FROM CrossJoined c
ORDER BY c.ID, c.Number
RESULT
ID Seq
----------- --------------------
1 250
1 260
1 270
1 280
1 290
1 300
1 310
2 110
2 120
2 130
2 140
3 260
3 270
3 280
3 290
3 300
3 310
3 320
3 330
3 340
If you only want to find the missing Number from Test grouped by ID, just replace the final SELECT statement:
SELECT * FROM CrossJoined c
ORDER BY c.ID, c.Number
to:
SELECT c.ID, c.Number
FROM CrossJoined c
WHERE NOT EXISTS(
SELECT 1 FROM Test t
WHERE
t.ID = c.ID
AND t.Number = c.Number
)
ORDER BY c.ID, c.Number
RESULT
ID Number
----------- --------------------
1 260
1 290
2 120
3 280
3 310
3 320
3 330