SQL Server - resetting a running total - sql-server

I am trying to derive a paging logic.
I have fields like:
RecordNo Lines
1 20
2 130
3 50
4 60
5 350
6 100
Say my pagesize is 170 lines.
The result I want to get is:
RecordNo Lines CumSum PageNo
1 20 20 1
2 130 150 1
3 50 50 2 (as cumulative sum 200 exceeds 170, reset to 0)
4 60 110 2
5 350 350 3 ((as cumulative sum 460 exceeds 170, reset to 0)
6 100 100 4 ((as cumulative sum 460 exceeds 170, reset to 0)
I can do it using cursor, but is there a way to achieve it by SQL(s) only?
Here is the ddl and sample data as posted by the OP:
CREATE TABLE PAGING (RECORDNO INT, LINES INT );
INSERT INTO PAGING VALUES(1,20);
INSERT INTO PAGING VALUES(2,130);
INSERT INTO PAGING VALUES(3,50);
INSERT INTO PAGING VALUES(4,60);
INSERT INTO PAGING VALUES(5,350);
INSERT INTO PAGING VALUES(6,100);
Update:
Zohar, Thanks for looking into this. The query worked perfectly with the data I gave but when I extended with more data, it does not give correct result as pagebase does not move with sum exceeding 170.
Here is the data I tried the SQL with:
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (1, 20);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (2, 130);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (3, 50);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (4, 60);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (5, 350);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (6, 100);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (7, 20);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (8, 10);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (9, 20);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (10, 30);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (11, 5);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (12, 5);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (13, 5);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (14, 10);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (15, 205);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (16, 156);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (17, 5);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (18, 2);
INSERT [dbo].[PAGING] ([RECORDNO], [LINES]) VALUES (19, 7);

Update:
Here is the complete solution and it's fiddle link:
WITH cte AS
(
SELECT RECORDNO,
LINES,
SUM(LINES) OVER (ORDER BY RECORDNO) CumSum,
SUM(LINES) OVER (ORDER BY RECORDNO) / 170 AS PageNumberBase
FROM PAGING
)
SELECT RECORDNO,
LINES,
SUM(LINES) OVER (PARTITION BY PageNumberBase ORDER BY RECORDNO) As CumSum,
DENSE_RANK() OVER(ORDER BY PageNumberBase) As PageNumber
FROM cte
ORDER BY RECORDNO
First version:
Using dense_rank window function and a cte I was able to produce something very close, just the cumSum is not perfect.
Unfortunatly I don't have the time to play with it more so I'll leave it here with a link to sqlFiddle and hope the OP or someone else will be able to complete the solution:
WITH cte AS
(
SELECT RECORDNO,
LINES,
SUM(LINES) OVER (ORDER BY RECORDNO) CumSum,
SUM(LINES) OVER (ORDER BY RECORDNO) / 170 AS PageNumber
FROM PAGING
)
SELECT RECORDNO,
LINES,
PageNumber,
CumSum - (170 * PageNumber) As CumSum,
DENSE_RANK() OVER(ORDER BY PageNumber)
FROM cte
Here is the link to the fiddle

Related

How to calculate time difference on column value change?

I am trying to write a query to get the accumulated time difference with respect to a value change in a column in SQL Server 2012.
I am trying to gather some analytics on how much time a task was pending on another user, in this task there are 2 participants Role = 0 is the implementer and Role = 1 is the reviewer of the task. Through the duration of the task the implementer and the the reviewer can perform activities on the task multiple times. The aim is to to get the total time it was pending for the reviewer and the implementer.
To re create a snapshot of the data please see the example below
CREATE TABLE ActivityTable
([Id] [int] IDENTITY(1,1) NOT NULL,
[RoleId] [int] NULL,
[ActivityDate] [DATETIME] NULL)
INSERT INTO [ActivityTable] VALUES (1, '2018-10-19 13:00:19.840')
INSERT INTO [ActivityTable] VALUES (1, '2018-10-19 13:00:18.073')
INSERT INTO [ActivityTable] VALUES (1, '2018-10-19 12:59:48.417')
INSERT INTO [ActivityTable] VALUES (0, '2018-10-15 13:48:00.557')
INSERT INTO [ActivityTable] VALUES (0, '2018-10-15 12:56:25.567')
INSERT INTO [ActivityTable] VALUES (0, '2018-10-15 12:56:09.967')
INSERT INTO [ActivityTable] VALUES (0, '2018-10-15 12:55:26.500')
INSERT INTO [ActivityTable] VALUES (0, '2018-10-15 12:53:17.997')
INSERT INTO [ActivityTable] VALUES (1, '2018-10-15 12:36:17.967')
INSERT INTO [ActivityTable] VALUES (1, '2018-10-15 12:35:38.497')
INSERT INTO [ActivityTable] VALUES (1, '2018-10-15 12:33:05.860')
INSERT INTO [ActivityTable] VALUES (1, '2018-10-15 12:32:07.793')
INSERT INTO [ActivityTable] VALUES (1, '2018-10-15 12:32:00.010')
INSERT INTO [ActivityTable] VALUES (0, '2018-10-15 12:18:18.417')
INSERT INTO [ActivityTable] VALUES (0, '2018-10-15 12:17:16.370')
INSERT INTO [ActivityTable] VALUES (0, '2018-10-15 12:11:48.590')
INSERT INTO [ActivityTable] VALUES (0, '2018-10-15 11:58:38.557')
INSERT INTO [ActivityTable] VALUES (0, '2018-10-15 11:56:23.820')`
So the total time for each transition would look like
RoleInfo Start Time End Time Duration Minutes
0 2018-10-15 11:56:23.820 2018-10-15 12:32:00.010 37
1 2018-10-15 12:32:00.010 2018-10-15 12:53:17.997 22
0 2018-10-15 12:53:17.997 2018-10-19 12:59:48.417 5767
1 2018-10-19 12:59:48.417 2018-10-19 13:00:19.840 1
and the final result expected is aggregation of pending times
RoleInfo Duration in Minutes
0 5804
1 23
refer to comments within the query.
execute each inner query by itself to see the result for better understanding
select RoleId, Duration = sum(datediff(minute, StartTime, EndTime))
from
(
-- perform a group by RoleId + grp
-- min() and max() on ActivityDate will gives you based on RoleId
-- however you wanted the StartTime of next RoleId. For this LEAD() OVER() is used
select RoleId,
StartTime = min(ActivityDate),
EndTime = coalesce(lead(min(ActivityDate)) over (order by min(ActivityDate)),
max(ActivityDate))
from
(
-- identify the group. each group is continuous same RoleId value
select *, grp = Id - dense_rank() over (partition by RoleId
order by ActivityDate desc)
from ActivityTable
) a
group by RoleId, grp
) b
group by RoleId
by the way, i think the expected result that you posted is wrong
/* RESULT
RoleId Duration
0 5802
1 22
*/

How to write a case when statement when there are overlaps in T-SQL

I have a table like this
How can I group it to this
Small is the sum of the count when Count <25; Large is the sum of the count when Count>=25; Total is the sum of all counts.
Try it like this...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
ID INT NOT NULL PRIMARY KEY,
nCount int NOT NULL
);
INSERT #TestData (ID, nCount) VALUES
(1, 10), (2, 15), (3, 22), (4, 23),
(5, 25), (6, 27), (7, 30);
--=====================================
WITH
cte_Totals AS (
SELECT
Total = SUM(td.nCount),
Small = SUM(CASE WHEN td.nCount < 25 THEN td.nCount ELSE 0 END),
Large = SUM(CASE WHEN td.nCount >= 25 THEN td.nCount ELSE 0 END)
FROM
#TestData td
)
SELECT
x.[Group],
x.[Count]
FROM
cte_Totals t
CROSS APPLY (VALUES (1, 'Total', t.Total), (2, 'Small', t.Small), (3, 'Large', t.Large) ) x (SortBy, [Group],[Count])
ORDER BY
x.SortBy;
Results...
Group Count
----- -----------
Total 152
Small 70
Large 82
HTH,
Jason
The simplest way is to use CASE:
SELECT
SUM(Count) as Total,
SUM(CASE WHEN Count < 25 THEN Count ELSE 0 END) as Small,
SUM(CASE WHEN Count >= 25 THEN Count ELSE 0 END) as Large
FROM table
Late answer (keep the accepted as is), but I did want to introduce a concept which may be more helpful down the line.
I maintain a generic Tier Table. The following is a simplified example, but you can take the aggregation tiers out of the code, and put it in a table... things change, and you can serve multiple masters.
Sample Data
Declare #YourTable table (ID int,[Count] int)
Insert Into #YourTable values
(1, 10), (2, 15), (3, 22), (4, 23), (5, 25), (6, 27), (7, 30)
Declare #Tier table (Tier varchar(50),Seq int,Title varchar(50),R1 int,R2 int)
Insert Into #Tier values
('MyGroup',1,'Total',0,99999)
,('MyGroup',2,'Small',0,25)
,('MyGroup',3,'Large',25,99999)
The Actual Query
Select T.Title
,[Count] = sum(D.[Count])
From #Tier T
Join #YourTable D on (T.Tier='MyGroup' and D.Count >= T.R1 and D.Count<T.R2)
Group By T.Title,T.Seq
Order By T.Seq
Returns
Title Count
Total 152
Small 70
Large 82
EDIT - There are many ways you can construct this
Example
Declare #YourTable table (ID varchar(50),[Count] int)
Insert Into #YourTable values
('Tywin', 10), ('Tywin', 15), ('Tyrion', 22), ('Bran', 23), ('Ned', 25), ('John', 27), ('Robb', 30)
Declare #Tier table (Tier varchar(50),Seq int,Title varchar(50),R1 int,R2 int,C1 varchar(50),C2 varchar(50))
Insert Into #Tier values
('MyGroup',1,'Total' ,null,null,'a','z')
,('MyGroup',2,'Group 1',null,null,'Tywin,Tyrion',null)
,('MyGroup',3,'Group 2',null,null,'Bran,Ned,John,Robb',null)
Select T.Title
,[Count] = sum(D.[Count])
From #Tier T
Join #YourTable D on T.Tier='MyGroup' and (D.ID between C1 and C2 or patindex('%,'+D.ID+',%',','+C1+',')>0)
Group By T.Title,T.Seq
Order By T.Seq
Returns
Title Count
Total 152
Group 1 47
Group 2 105

How do you validate that range doesn't overlap in a list of data?

I have a list of data :
Id StartAge EndAge Amount
1 0 2 50
2 2 5 100
3 5 10 150
4 6 9 160
I have to set Amount for various age group.
The age group >0 and <=2 need to pay 50
The age group >2 and <=5 need to pay 100
The age group >5 and <=10 need to pay 150
But
The age group >6 and <=9 need to pay 160 is an invalid input because >6 and <=9 already exist on 150 amount range.
I have to validate such kind of invalid input before inserting my data as a bulk.Once 5-10 range gets inserted anything that is within this range should not be accepted by system. For example: In above list, user should be allowed to insert 10-15 age group but any of the following should be checked as invalid.
6-9
6-11
3-5
5-7
If Invalid Input exists on my list I don't need to insert the list.
You could try to insert your data to the temporary table first.
DECLARE #TempData TABLE
(
[Id] TINYINT
,[StartAge] TINYINT
,[EndAge] TINYINT
,[Amount] TINYINT
);
INSERT INTO #TempData ([Id], [StartAge], [EndAge], [Amount])
VALUES (1, 0, 2, 50)
,(2, 2, 5, 100)
,(3, 5, 10, 150)
,(4, 6, 9, 160);
Then, this data will be transferred to your target table using INSERT INTO... SELECT... statement.
INSERT INTO <your target table>
SELECT * FROM #TempData s
WHERE
NOT EXISTS (
SELECT 1
FROM #TempData t
WHERE
t.[Id] < s.[Id]
AND s.[StartAge] < t.[EndAge]
AND s.[EndAge] > t.[StartAge]
);
I've created a demo here
We can use recursive CTE to find how records are chained by end age and start age pairs:
DECLARE #DataSource TABLE
(
[Id] TINYINT
,[StartAge] TINYINT
,[EndAge] TINYINT
,[Amount] TINYINT
);
INSERT INTO #DataSource ([Id], [StartAge], [EndAge], [Amount])
VALUES (1, 0, 2, 50)
,(2, 2, 5, 100)
,(3, 5, 10, 150)
,(4, 6, 9, 160)
,(5, 6, 11, 160)
,(6, 3, 5, 160)
,(7, 5, 7, 160)
,(9, 10, 15, 20)
,(8, 7, 15, 20);
WITH PreDataSource AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY [StartAge] ORDER BY [id]) as [pos]
FROM #DataSource
), DataSource AS
(
SELECT [Id], [StartAge], [EndAge], [Amount], [pos]
FROM PreDataSource
WHERE [id] = 1
UNION ALL
SELECT R.[Id], R.[StartAge], R.[EndAge], R.[Amount], R.[pos]
FROM DataSource A
INNER JOIN PreDataSource R
ON A.[Id] < R.[Id]
AND A.[EndAge] = R.[StartAge]
AND R.[pos] =1
)
SELECT [Id], [StartAge], [EndAge], [Amount]
FROM DataSource;
This is giving us, the following output:
Note, that before this, we are using the following statement to prepare the data:
SELECT *, ROW_NUMBER() OVER (PARTITION BY [StartAge] ORDER BY [id]) as [pos]
FROM #DataSource;
The idea is to find records with same start age and to calculated which one is inserted first. Then, in the CTE we are getting only the first.
Assuming you are bulk inserting the mentioned data into a temp table(#tmp) or table variable (#tmp).
If you are working on sql server 2012 try the below.
select *
from(select *,lag(endage,1,0)over(order by endage) as [col1]
from #tmp)tmp
where startage>=col1 and endage>col1
The result of this query should be inserted into your main table.

Select query using variable not running in mssql

Select query is not working when use variable in MSSQL2014
My Schema is :-
CREATE TABLE product
(idproduct int, name varchar(50), description varchar(50), tax decimal(18,0))
INSERT INTO product
(idproduct, name, description,tax)
VALUES
(1, 'abc', 'This is abc',10),
(2, 'xyz', 'This is xyz',20),
(3, 'pqr', 'This is pqr',15)
CREATE TABLE product_storage
(idstorage int,idproduct int,added datetime, quantity int, price decimal(18,0))
INSERT INTO product_storage
(idstorage,idproduct, added, quantity,price)
VALUES
(1, 1, 2010-01-01,0,10.0),
(2, 1, 2010-01-02,0,11.0),
(3, 1, 2010-01-03,10,12.0),
(4, 2, 2010-01-04,0,12.0),
(5, 2, 2010-01-05,10,11.0),
(6, 2, 2010-01-06,10,13.0),
(7, 3, 2010-01-07,10,14.0),
(8, 3, 2010-01-07,10,16.0),
(9, 3, 2010-01-09,10,13.0)
and i am executing below command:-
declare #price1 varchar(10)
SELECT p.idproduct, p.name, p.tax,
[#price1]=(SELECT top 1 s.price
FROM product_storage s
WHERE s.idproduct=p.idproduct AND s.quantity > 0
ORDER BY s.added ASC),
(#price1 * (1 + tax/100)) AS [price_with_tax]
FROM product p
;
This is not working in MSSQL, Please Help me out.
for detail check http://sqlfiddle.com/#!6/91ec2/296
And My query is working in MYSQL
Check for detail :- http://sqlfiddle.com/#!9/a71b8/1
Try this query
SELECT
p.idproduct
, p.name
, p.tax
, (t1.price * (1 + tax/100)) AS [price_with_tax]
FROM product p
inner join
(
SELECT ROW_NUMBER() over (PARTITION by s.idproduct order by s.added ASC) as linha, s.idproduct, s.price
FROM product_storage s
WHERE s.quantity > 0
) as t1
on t1.idproduct = p.idproduct and t1.linha = 1
Try it like this:
Explanantion: You cannot use a variable "on the fly", but you can do row-by-row calculation in an APPLY...
SELECT p.idproduct, p.name, p.tax,
Price.price1,
(price1 * (1 + tax/100)) AS [price_with_tax]
FROM product p
CROSS APPLY (SELECT top 1 s.price
FROM product_storage s
WHERE s.idproduct=p.idproduct AND s.quantity > 0
ORDER BY s.added ASC) AS Price(price1)
;
EDIT: Your Fiddle uses a bad literal date format, try this:
INSERT INTO product_storage
(idstorage,idproduct, added, quantity,price)
VALUES
(1, 1, '20100101',0,10.0),
(2, 1, '20100102',0,11.0),
(3, 1, '20100103',10,12.0),
(4, 2, '20100104',0,12.0),
(5, 2, '20100105',10,11.0),
(6, 2, '20100106',10,13.0),
(7, 3, '20100107',10,14.0),
(8, 3, '20100108',10,16.0),
(9, 3, '20100109',10,13.0)
Here is the correct schema for SQL Server and query runs perfect as Shnugo Replied.
VALUES
(1, 1, convert(datetime,'2010-01-01'),0,10.0),
(2, 1, convert(datetime,'2010-01-02'),0,11.0),
(3, 1, convert(datetime,'2010-01-03'),10,12.0),
(4, 2, convert(datetime,'2010-01-04'),0,12.0),
(5, 2, convert(datetime,'2010-01-05'),10,11.0),
(6, 2, convert(datetime,'2010-01-06'),10,13.0),
(7, 3, convert(datetime,'2010-01-07'),10,14.0),
(8, 3, convert(datetime,'2010-01-07'),10,16.0),
(9, 3, convert(datetime,'2010-01-09'),10,13.0)

SSAS Measure average related to range values

I have Sales data provided weekly and Lookup data provided quarterly.
In the SSAS data cube I have pre-calculated average of sales data for each period of time and what I need to do is to get related record from LookupTable for next calculations, where: LookupTable.Min < Sales Average < LookupTable.Max
Example:
Sales = 297 + 33 + 311 = 641
SalesAverage = 213.66
LookupRecordShrinkageIndicator = Min < SalesAverage < Max = 0 < 213.66 < 9000 = 0.007
CREATE TABLE dbo.SalesData
(
Id int,
Sales decimal(18, 2) )
CREATE TABLE dbo.LookupTable
(
Id int,
Min int,
Max int,
Shrinkage decimal(10, 5),
Wages decimal(10, 5),
Waste decimal(10, 5)
)
INSERT [dbo].[SalesData] ([Id], [Sales]) VALUES (1, 297)
INSERT [dbo].[SalesData] ([Id], [Sales]) VALUES (2, 33)
INSERT [dbo].[SalesData] ([Id], [Sales]) VALUES (3, 311)
INSERT [dbo].[LookupTable] ([Id], [Min], [Max], [Shrinkage], [Wages], [Waste]) VALUES (1, 0, 9000, 0.00700, 0.12700, 0.00300)
INSERT [dbo].[LookupTable] ([Id], [Min], [Max], [Shrinkage], [Wages], [Waste]) VALUES (2, 9000, 9250, 0.00700, 0.12700, 0.00300)
INSERT [dbo].[LookupTable] ([Id], [Min], [Max], [Shrinkage], [Wages], [Waste]) VALUES (3, 9250, 9500, 0.00700, 0.12300, 0.00300)
I need to create calculated member based on sales average which contains indicators from lookup table for next calculations.
To solve this issue I had to use my LookupTable as dimension and as measures, let's see how I did this.
Create dimension based on LookupTable:
Add Lookup measures do the cube and add Lookup dimension to the cube as well.
Create Fact relationship between Lookup dimension and Lookup measures group
That's all:
Let's see mdx example:
SELECT
{
FILTER([Lookup Table].[Id].AllMembers , [Measures].[Min] <= 213 AND [Measures].[Max] > 213 )
}
ON COLUMNS,
{
[Measures].[Shrinkage - Lookup Table], [Measures].[Wages - Lookup Table], [Measures].[Waste - Lookup Table]
} ON ROWS
FROM
[MyCube]
And result:
I hope this example will be useful

Resources