T-SQL Divide value across rows - sql-server

I have the following columns and data in table:
PeriodID Days
1 NULL
2 NULL
3 NULL
4 NULL
5 NULL
Then I have days that should divide across the rows as follows:
If Days < 5 (for example 2) I will have:
PeriodID Days
1 NULL
2 NULL
3 NULL
4 1
5 1
If days >= 5 and days%5=0 (for example 5) I will have:
PeriodID Days
1 1
2 1
3 1
4 1
5 1
If days > 5 and days%5!=0 (for example 12) I will have:
PeriodID Days
1 3
2 3
3 2
4 2
5 2
I am able doing this with loops, and I hope for better solution using some smart technique or T-SQL function. Thanks in advance.

This should do it for you:
DECLARE #numDays int
SET #numDays = 12
UPDATE someTable
SET Days = CASE WHEN #numDays < 5
THEN CASE WHEN #numDays >= 6 - PeriodId THEN 1 ELSE NULL END
ELSE FLOOR((#numDays + 5 - PeriodId) / 5)
END

Related

LAG function behaviour on first row and group of repeated partition

I am trying to use the LAG() function in MSSQL and I am getting a weird behavior.
The table looks like this:
ID TotalReadings Month Device
0 1 4 January M
1 1 4 January D
2 1 4 January T
2 1 4 January L
2 1 2 February M
2 1 2 February D
2 1 2 February L
0 1 2 February T
1 1 6 March M
2 1 6 March D
2 1 6 March L
2 1 6 March T
2 1 6 April M
2 1 6 April D
2 1 6 April T
2 1 6 April L
What I did was:
Select *,
CASE
WHEN
ISNULL(LAG(TotalReadings) OVER (PARTITION BY ID ORDER BY Month ), 0) < TotalReadings THEN 'Increase'
WHEN
ISNULL(LAG(TotalReadings) OVER (PARTITION BY ID ORDER BY Month), 0) = TotalReadings THEN 'Neutral'
WHEN
ISNULL(LAG(TotalReadings) OVER (PARTITION BY ID ORDER BY Month), 0) > TotalReadings THEN 'Decrease'
END As Trend
from table
and got:
ID TotalReadings Month Device Trend
0 1 4 January M Increase
1 1 4 January D Neutral
2 1 4 January T Neutral
2 1 4 January L Neutral
2 1 2 February M Decrease
2 1 2 February D Neutral
2 1 2 February L Neutral
0 1 2 February T Neutral
1 1 6 March M Increase
2 1 6 March D Neutral
2 1 6 March L Neutral
2 1 6 March T Neutral
2 1 6 April M Neutral
2 1 6 April D Neutral
2 1 6 April T Neutral
2 1 6 April L Neutral
But what I really want is to have first grouping by Month with trend named "Start" since there is no previous value to compare with, and remaining should take into account the similar TOtalReadings, which on monthly basis is the same, so trend should not be correct just for first row at beginning of new month row but for all: like this:
ID TotalReadings Month Device Trend
0 1 4 January M Start
1 1 4 January D Start
2 1 4 January T Start
2 1 4 January L Start
2 1 2 February M Decrease
2 1 2 February D Decrease
2 1 2 February L Decrease
0 1 2 February T Decrease
1 1 6 March M Increase
2 1 6 March D Increase
2 1 6 March L Increase
2 1 6 March T Increase
2 1 6 April M Neutral
2 1 6 April D Neutral
2 1 6 April T Neutral
2 1 6 April L Neutral
any clue?
Here you go:
create table #t
(id int, totalreadings int, month int, device char(1))
insert into #t
values
(1,4,1,'M'),
(1,4,1,'D'),
(1,4,1,'T'),
(1,4,1,'L'),
(1,2,2,'M'),
(1,2,2,'D'),
(1,2,2,'L'),
(1,2,2,'T'),
(1,6,3,'M'),
(1,6,3,'D'),
(1,6,3,'L'),
(1,6,3,'T'),
(1,6,4,'M'),
(1,6,4,'D'),
(1,6,4,'L'),
(1,6,4,'T')
Select *,
CASE
WHEN
LAG(TotalReadings) OVER (PARTITION BY ID,device ORDER BY Month ) < TotalReadings THEN 'Increase'
WHEN
LAG(TotalReadings) OVER (PARTITION BY ID,device ORDER BY Month) = TotalReadings THEN 'Neutral'
WHEN
LAG(TotalReadings) OVER (PARTITION BY ID,device ORDER BY Month) > TotalReadings THEN 'Decrease'
ELSE 'Start'
END As Trend
from #t
order by month
id totalreadings month device Trend
1 4 1 D Start
1 4 1 L Start
1 4 1 M Start
1 4 1 T Start
1 2 2 T Decrease
1 2 2 M Decrease
1 2 2 L Decrease
1 2 2 D Decrease
1 6 3 D Increase
1 6 3 L Increase
1 6 3 M Increase
1 6 3 T Increase
1 6 4 T Neutral
1 6 4 M Neutral
1 6 4 L Neutral
1 6 4 D Neutral
If you add a identity column, then you can use this code
create table #order ( i int identity(1,1), ID int, TotalReadings int, Month varchar(20), Device varchar(1))
insert #order values
( 1 , 4 ,'January' ,'M' )
,( 1 , 4 ,'January' ,'D' )
,( 1 , 4 ,'January' ,'T' )
,( 1 , 4 ,'January' ,'L' )
,( 1 , 2 ,'February' ,'M' )
,( 1 , 2 ,'February' ,'D' )
,( 1 , 2 ,'February' ,'L' )
,( 1 , 2 ,'February' ,'T' )
,( 1 , 6 ,'March' ,'M' )
,( 1 , 6 ,'March' ,'D' )
,( 1 , 6 ,'March' ,'L' )
,( 1 , 6 ,'March' ,'T' )
,( 1 , 6 ,'April' ,'M' )
,( 1 , 6 ,'April' ,'D' )
,( 1 , 6 ,'April' ,'T' )
,( 1 , 6 ,'April' ,'L' )
Select *
,CASE
WHEN
lag(TotalReadings, 4) OVER (PARTITION BY ID ORDER BY i,id, Month) < TotalReadings THEN 'Increase'
WHEN
lag(TotalReadings, 4) OVER (PARTITION BY ID ORDER BY i,id, Month) = TotalReadings THEN 'Neutral'
WHEN
lag(TotalReadings, 4) OVER (PARTITION BY ID ORDER BY i,id, Month) > TotalReadings THEN 'Decrease'
WHEN
lag(TotalReadings, 4) OVER (PARTITION BY ID ORDER BY i,id, Month) is null THEN 'start'
END As Trend
from #order
order by i
Edit 1: NO NEED OF IDENTITY COLUMN
Select *
,CASE
WHEN
lag(TotalReadings, 4) OVER (PARTITION BY ID ORDER BY id, MONTH(MONTH + ' 1 2014') ) < TotalReadings THEN 'Increase'
WHEN
lag(TotalReadings, 4) OVER (PARTITION BY ID ORDER BY id, MONTH(Month + ' 1 2014') ) = TotalReadings THEN 'Neutral'
WHEN
lag(TotalReadings, 4) OVER (PARTITION BY ID ORDER BY id, MONTH(Month + ' 1 2014') ) > TotalReadings THEN 'Decrease'
WHEN
lag(TotalReadings, 4) OVER (PARTITION BY ID ORDER BY id, MONTH(Month + ' 1 2014') ) is null THEN 'start'
END As Trend
from #order
order by MONTH(Month + ' 1 2014')

TSQL Aggregating values based on other column

I have a table that looks as follows
Amount Factor Month Customer
1 1 2 A
3 1 2 A
4 -1 2 A
2 1 2 B
2 1 2 B
3 -1 2 B
4 1 3 A
5 1 3 A
6 -1 3 A
I want to aggregate (sum) the column Amount per Month and Customer. The Amounts should be multiplied with the value in the column Factor.
Hence, the result should look as follows (could be an UPDATE to the same table or a new table):
Amount Factor Month Customer
0 1 2 A
1 1 2 B
3 1 3 A
Try below
SELECT SUM(Amount * Factor) as Amount,Month,Customer
FROM tableName
GROUP BY Month,Customer
I think this is what you want:
select month, customer, sum(amount * factor) as sum_amount
from t
group by month, customer;
I'm not sure why you would want factor in the result set.

How to Group Items in a Count statement

I'm trying to create a query that will return Total Claims reported in 0-3 days, 4-7 days, 8-14 days, and 15+
Select DATEDiff(DD,LossDate,DateReported) As TimeToReport,Count(ClaimId) As Num from LossRun
where PolicyNum='1234567890'
And PolTerm='201403'
Group By DATEDiff(DD,LossDate,DateReported)
order by DATEDiff(DD,LossDate,DateReported);
This is what i get
TimeToReport NumofClaims
0 5
1 3
2 1
3 4
4 3
5 2
6 2
7 2
8 1
12 1
13 1
14 2
15 2
48 1
52 1
107 1
121 1
147 1
533 1
Basically i want to see the total for 0-3, 4-7,8-14,and the rest,,,, timeToReport
You can try to use SUM with CASW WHEN
select
SUM(CASW WHEN TimeToReport <= 3 THEN NumofClaims ELSE 0 END) '0~3 day',
SUM(CASW WHEN TimeToReport >= 4 AND TimeToReport <=7 THEN NumofClaims END) '4-7 days',
SUM(CASW WHEN TimeToReport >= 8 AND TimeToReport <=14 THEN NumofClaims ELSE 0 END) '8-14 days',
SUM(CASW WHEN TimeToReport >= 15 THEN NumofClaims ELSE 0 END) '15+ day'
from (
Select DATEDiff(DD,LossDate,DateReported) As TimeToReport,Count(ClaimId) As Num
from LossRun
where PolicyNum='1234567890'
And PolTerm='201403'
Group By DATEDiff(DD,LossDate,DateReported)
) t
The most simple way is going to be by creating your own temp table which includes the min and max for each bucket and then joining to it.
declare #t table (OrderedID int, EmpID int, EffDate date, Salary money)
insert into #t
values
(1,1234,'20150101',1)
,(2,1234,'20160101',2)
,(3,1234,'20170101',8)
,(4,1234,'20180101',15)
,(1,2351,'20150101',17)
,(5,1234,'20190101',4)
,(5,1234,'20190101',2)
,(5,1234,'20190101',9)
declare #Bin table (MinVal int, MaxVal int)
insert into #Bin
values
(1,3)
,(4,6)
,(7,9)
,(10,15)
,(15,20)
,(20,25)
Select
B.MinVal,count(T.EmpID) as EmpsInBin
From #t T
inner join #Bin B on T.Salary between B.MinVal and B.MaxVal
group by B.MinVal
Output
MinVal EmpsInBin
1 3
4 1
7 2
10 1
15 2

How to perform stable sort over multiple columns?

Imagine i have a data set that contains:
Date Id
-------------- ----
11/1/2017 null
11/4/2017 3
11/5/2017 null
11/12/2017 10
null 1
null 2
null 7
null 8
null 9
I want the rows ordered so that both columns are increasing.
Using a naïve ORDER BY Date, ID does not do it:
There is an ordering
There is an ordering that satisfies the results of my desired sort order:
the date column is always increasing
the id column value is always increasing
Or course, that's not a unique ordering:
Date Id
-------------- ---------------
null 1
11/1/2017 null
null 2
11/4/2017 3
null 7
null 8
null 9
11/5/2017 null
11/12/2017 10
A programming language could do it
I know i can accomplish this on the client side. In a functional functional programming language: use a stable sorting algorithm:
A stable sort is one which preserves the original order of the input
set, where the comparison algorithm does not distinguish between two
or more items.
Consider a sorting algorithm that sorts cards by rank, but not by
suit. The stable sort will guarantee that the original order of cards
having the same rank is preserved; the unstable sort will not.
Unfortunately i have
9.1 million rows
1.8 GB
of monotonically increasing rows to put in best possible chronological sort order. Obviously i'd prefer to do this on the server - which is well suited to handling large amounts of data.
How can i perform a stable-sort in SQL Server?
Example Data
Example SQL Fiddle
CREATE TABLE #SortDemo (Date datetime NULL, Id int NULL)
INSERT INTO #SortDemo (Date, Id)
VALUES
('20171101', null),
('20171104', 3),
('20171105', null),
('20171112', 10),
(null, 1),
(null, 2),
(null, 7),
(null, 8),
(null, 9)
SELECT * FROM #SortDemo
ORDER BY Date, Id
It is a solvable problem. You don't have to throw your hands up and say computers cannot be used to solve problems.
Start with the data, and add a new surrogate "Rank" column.
Date Id Rank
-------------- ---- ----
null 7 null
null 1 null
null 9 null
null 2 null
null 8 null
11/1/2017 null null
11/4/2017 3 null
11/5/2017 null null
11/12/2017 10 null
11/13/2017 null null
Then seed Rank to the Id:
UPDATE SortDemo SET Rank = Id
WHERE Id IS NOT NULL
Date Id Rank
-------------- ---- ----
null 7 7
null 1 1
null 9 9
null 2 2
null 8 8
11/1/2017 null null
11/4/2017 3 3
11/5/2017 null null
11/12/2017 10 10
11/13/2017 null null
Then for the remaining items items with a Date, we need to assign it to the "next" rank:
Date Id Rank
-------------- ---- ----
null 7 7
null 1 1
null 9 9
null 2 2
null 8 8
11/1/2017 null null <-- 3
11/4/2017 3 3
11/5/2017 null null <-- 10
11/12/2017 10 10
11/13/2017 null null <-- ?
with
UPDATE SortDemo SET Rank = (
SELECT MIN(Rank) FROM SortDemo s2
WHERE s2.Date >= SortDemo.Date)
WHERE SortDemo.Date IS NOT NULL
Date Id Rank
-------------- ---- ----
null 7 7
null 1 1
null 9 9
null 2 2
null 8 8
11/1/2017 null 3 <--
11/4/2017 3 3
11/5/2017 null 10 <--
11/12/2017 10 10
11/13/2017 null null <-- ?
There's also the edge case where there are no items "after" us; which we can fix by going backwards to one the highest previous rank:
UPDATE SortDemo SET Rank = (
SELECT MAX(Rank) FROM SortDemo s2
WHERE s2.Date <= SortDemo.Date)
WHERE SortDemo.Date IS NOT NULL
Date Id Rank
-------------- ---- ----
null 7 7
null 1 1
null 9 9
null 2 2
null 8 8
11/1/2017 null 3
11/4/2017 3 3
11/5/2017 null 10
11/12/2017 10 10
11/13/2017 null 10 <--10
And we're done
We can now sort by surrogate Rank, and break ties by sorting by Date:
SELECT * FROM SortDemo
ORDER BY Rank, Date
Date Id Rank
-------------- ---- ----
null 1 1
null 2 2
11/1/2017 null 3
11/4/2017 3 3
null 7 7
null 8 8
null 9 9
11/5/2017 null 10
11/12/2017 10 10
11/13/2017 null 10
Solution complete. Sql Fiddle
The answer is in escrow until Monday, so i can give people the opportunity to earn reputation for solving a unique problem.

Cursor to update values in certain fashion

In SQL Server 2008 R2, I have a table like this:
ID Dates Count
1 03-02-2014 2
2 04-02-2014 1
3 05-02-2014 NULL
4 06-02-2014 1
5 07-02-2014 3
6 08-02-2014 NULL
7 09-02-2014 2
8 10-02-2014 NULL
9 11-02-2014 1
10 12-02-2014 3
11 13-02-2014 NULL
12 14-02-2014 1
I have an INT variable having some value such as #XCount = 15.
My requirement is to update the count column with (#XCount - Count) such as the result of previous record will be subtracted by the Count value in the next record.
Result:
ID Dates Count
1 03-02-2014 13 (15-2)
2 04-02-2014 12 (13-1)
3 05-02-2014 12 (12-0)
4 06-02-2014 11 (12-1)
5 07-02-2014 8 (11-3)
6 08-02-2014 8 (8-0)
7 09-02-2014 6 (8-2)
8 10-02-2014 6 (6-0)
9 11-02-2014 5 (6-1)
10 12-02-2014 2 (5-3)
11 13-02-2014 2 (2-0)
12 14-02-2014 1 (2-1)
I'm reluctant to use cursors as a solution. Can somebody help me?
How about something like
DECLARE #XCount INT = 15
;WITH Vals AS(
SELECT ID, Dates, [Count] OriginalCount, #XCount - ISNULL([COUNT],0) NewCount
FROM Table1
WHERE ID = 1
UNION ALL
SELECT t.ID, t.Dates, t.[Count], v.NewCount - ISNULL(t.[Count],0)
FROM Table1 t INNER JOIN Vals v ON t.ID = v.ID + 1
)
SELECT *
FROM Vals
SQL Fiddle DEMO
Do note thought that this is a recursive query, and that sometimes (until the tech allows for it, such as SQL SERVER 2012 LAG or Running totals) old does work.

Resources