Azure SQL - Calculate stock level based on previous date - sql-server

I have transactions table that looks like below. I need to create a stored procedure that will calculate the StockLevel column.
The dates are consecutive (always 1 day gap) and the formula is :
StockLevel = yesterdayStockLevel + AIn - AOut
Sample data:
Date | ArticleId | AOut | AIn | StockLevel
Aug 1 1 1 10 10 - 1 = 9
Aug 2 1 2 0 9 - 2 = 7
Aug 3 1 1 0 7 - 1 = 6
Aug 4 1 2 0 6 - 2 = 4
Aug 5 1 3 5 4 - 3 + 5 = 6
Aug 6 1 0 0 6 - 0 = 6
I prefer to run it on SQL (if possible) and not use cursor (if possible).
THANKS!!

Try this :
Select Date, ArticleID, AOut, AIn, AInSum-AOutSum as StockLevel
From (
Select *,
sum(AOut)over (Partition by ArticleId Order by Date) AOutSum,
sum(AIn)over (Partition by ArticleId Order by Date) AInSum
From yourTabble
) X

Related

Convert Month Days Into Weekdays Per Week In SQL

I have a calendar table and I want to show each month in a grid, and when I select a month from the table, I have some row with monthday and weekday columns, but I must convert it to multiple rows per week to show the month in the grid.
my select command to get month info is something like this
SELECT Monthday, Weekday FROM Calendar Where Month = 5
that Results to this :
Monthday | Weekday
--------- --------
1 4
2 5
3 6
4 7
5 1
6 2
7 3
8 4
. .
. .
. .
and I Want to Convert it To Something like this
1 | 2 | 3 | 4 | 5 | 6 | 7
-- --- --- --- --- --- --
1 2 3 4
5 6 7 8 . . .
just like a calendar grid.
I think the answer is by Pivot, but I don't know how, Do you know a solution how to convert my select command?
Let's suppose you have a calendar table with the structure mentioned below, which is populated with a query like the following:
CREATE TABLE Calendar (
TheDate DATE PRIMARY KEY,
YearNumber SMALLINT,
MonthNumber SMALLINT,
DayNumber SMALLINT,
WeekdayNumber SMALLINT
)
INSERT INTO dbo.Calendar (TheDate, YearNumber, MonthNumber, DayNumber, WeekdayNumber)
SELECT x.TheDate,
YEAR(x.TheDate) AS YearNumber, MONTH(x.TheDate) AS MonthNumber, DAY(x.TheDate) AS DayNumber,
(DATEPART(WEEKDAY,x.TheDate)+##DATEFIRST-2)%7+1 AS WeekdayNumber
FROM (
SELECT TOP 365 DATEADD(DAY,N-1,'20210101') AS TheDate
FROM (SELECT ROW_NUMBER() OVER (ORDER BY low) AS N FROM master..spt_values) t
ORDER BY N
) x
The formula for WeekdayNumber is written this way to ignore the SET DATEFIRST setting and always consider Monday as the first day of week. If you prefer another day to be the first in the week, adjust -2 to another value.
To display something like a calendar for a particular month, you can use a query like this:
SELECT * FROM (
SELECT DayNumber, WeekdayNumber,
DENSE_RANK() OVER (ORDER BY DayNumber-WeekdayNumber) AS WeekNumber
FROM dbo.Calendar WHERE YearNumber=2021 AND MonthNumber=5
) t
PIVOT (MAX(DayNumber) FOR WeekdayNumber IN ([1],[2],[3],[4],[5],[6],[7])) p
This produces the following result:
WeekNumber 1 2 3 4 5 6 7
-------------------- ------ ------ ------ ------ ------ ------ ------
1 NULL NULL NULL NULL NULL 1 2
2 3 4 5 6 7 8 9
3 10 11 12 13 14 15 16
4 17 18 19 20 21 22 23
5 24 25 26 27 28 29 30
6 31 NULL NULL NULL NULL NULL NULL
I am using just the DayNumber and WeekdayNumber columns to compute a week number and then I am using PIVOT to arrange the values for DayNumber in the desired format.

SQL Server add a column and populate multiple rows based on conditions across multiple columns

I have a table that looks like this:
Timestamp CPID Con Context Type Value
2018-01-01 03:11 1 2 6 8 0
2018-01-01 03:11 1 2 3 8 0
2018-01-01 03:11 1 2 3 3 100
2018-01-01 03:15 2 1 6 8 16
2018-01-01 03:15 2 1 3 8 15
2018-01-01 03:15 2 1 3 3 200
I want to add a column called new_column, and populate it with 1s when Value=0 when Context=6. I want to consider Timestamp, CPID and Con as a group, so that when for a given group has Context=6, the other rows in that group are also assigned 1 in new_column. The result would look like this:
Timestamp CPID Con Context Type Value new_column
2018-01-01 03:11 1 2 6 8 0 1
2018-01-01 03:11 1 2 3 8 0 1
2018-01-01 03:11 1 2 3 3 100 1
2018-01-01 03:15 2 1 6 8 16 0
2018-01-01 03:15 2 1 3 8 15 0
2018-01-01 03:15 2 1 3 3 200 0
Notes: the row orders are not always the same, so I can't just fill down 2 rows every time; I also cannot directly ALTER Table because it is read only.
I'm still new to SQL so struggling with this one.
You can create a view by using exists :
select t.*,
(case when exists (select 1
from table t1
where t1.Timestamp = t.Timestamp and t1.CPID = t.CPID and
t1.Con = t.Con and (t1.Context = 6 or t1.Value = 0)
)
then 1 else 0
end) as new_column
from table t;

Get average and max in 1 minute intervals T-SQL

I have a query that extracts the number of records in 1 minute intervals. I want to compare the average vs last value.
I only get he total which is the count(1) and its valid. The average and the maximum is always returning as 1 which is not accurate.
Is there something that I am missing in this SQL?
SELECT
FORMAT(timestamp, 'hh:mm') AS tm,
AVG(1) AS avgOccurances,
MAX(1) AS maxocc,
COUNT(1) AS total
FROM
[history]
WHERE
timestamp BETWEEN '2018-04-16 14:00:00.707' AND '2018-04-18 15:00:00.707'
AND result = 'F'
GROUP BY
FORMAT(timestamp, 'hh:mm')
ORDER BY
tm ASC
Result
01:00 1 1 13
01:01 1 1 10
01:02 1 1 11
01:03 1 1 7
01:04 1 1 13
01:05 1 1 7
01:06 1 1 14
01:07 1 1 11
01:08 1 1 12
01:09 1 1 10
01:10 1 1 5
01:11 1 1 6
01:12 1 1 8
01:13 1 1 13
01:14 1 1 9
01:15 1 1 8
01:16 1 1 2
01:17 1 1 10
01:18 1 1 9
01:19 1 1 13
01:20 1 1 9
01:21 1 1 8
01:22 1 1 14
01:23 1 1 10
The query below assumes each row should have the following:
Count per minute
Count per minute for the previous minute
Count for the minute with the most occurrences
If that's incorrect just let me know. Here's the query:
WITH countbyminute AS (
SELECT
FORMAT(timestamp, 'hh:mm') AS tm,
COUNT(*) AS occurences
FROM history
GROUP BY FORMAT(timestamp, 'hh:mm')
)
SELECT
tm,
occurrences,
LAG(occurrences) OVER (ORDER BY TIMESTAMP) AS priorocc,
MAX(occurrences) OVER () AS maxocc
FROM countbyminute
ORDER BY tm;
And I'd suggest using HH:mm as the format string, which will rerurn the hours using a 24-hour clock (1:00PM as 13:00).

How to count number of months in T-SQL

I've got a problem in SQL Server.
"Whate'er is well conceived is clearly said, And the words to say it flow with ease", Nicolas Boileau-Despreaux
Well, I don't think I'll be able to make it clear but I'll try ! And I'd like to apologize for my bad english !
I've got this table :
id ind lvl result date
1 1 a 3 2017-01-31
2 1 a 3 2017-02-28
3 1 a 1 2017-03-31
4 1 a 1 2017-04-30
5 1 a 1 2017-05-31
6 1 b 1 2017-01-31
7 1 b 3 2017-02-28
8 1 b 3 2017-03-31
9 1 b 1 2017-04-30
10 1 b 1 2017-05-31
11 2 a 3 2017-01-31
12 2 a 1 2017-02-28
13 2 a 3 2017-03-31
14 2 a 1 2017-04-30
15 2 a 3 2017-05-31
I'd like to count the number of month the combo {ind, lvl} remain in the result 1 before re-initializing the number of month to 0 if the result is not 1.
Clearly, I need to get something like that :
id ind lvl result date BadResultRemainsFor%Months
1 1 a 3 2017-01-31 0
2 1 a 3 2017-02-28 0
3 1 a 1 2017-03-31 1
4 1 a 1 2017-04-30 2
5 1 a 1 2017-05-31 3
6 1 b 1 2017-01-31 1
7 1 b 3 2017-02-28 0
8 1 b 3 2017-03-31 0
9 1 b 1 2017-04-30 1
10 1 b 1 2017-05-31 2
11 2 a 3 2017-01-31 0
12 2 a 1 2017-02-28 1
13 2 a 3 2017-03-31 0
14 2 a 1 2017-04-30 1
15 2 a 3 2017-05-31 0
So that if I was looking for the number of months the result was 1 for the date 2017-05-31 with the id 1 and the lvl a, I know it's been 3 months.
Assume all the date the the end day of month:
;WITH tb(id,ind,lvl,result,date) AS(
select 1,1,'a',3,'2017-01-31' UNION
select 2,1,'a',3,'2017-02-28' UNION
select 3,1,'a',1,'2017-03-31' UNION
select 4,1,'a',1,'2017-04-30' UNION
select 5,1,'a',1,'2017-05-31' UNION
select 6,1,'b',1,'2017-01-31' UNION
select 7,1,'b',3,'2017-02-28' UNION
select 8,1,'b',3,'2017-03-31' UNION
select 9,1,'b',1,'2017-04-30' UNION
select 10,1,'b',1,'2017-05-31' UNION
select 11,2,'a',3,'2017-01-31' UNION
select 12,2,'a',1,'2017-02-28' UNION
select 13,2,'a',3,'2017-03-31' UNION
select 14,2,'a',1,'2017-04-30' UNION
select 15,2,'a',3,'2017-05-31'
)
SELECT t.id,t.ind,t.lvl,t.result,t.date
,CASE WHEN t.isMatched=1 THEN ROW_NUMBER()OVER(PARTITION BY t.ind,t.lvl,t.id-t.rn ORDER BY t.id) ELSE 0 END
FROM (
SELECT t1.*,c.MonthDiff,CASE WHEN c.MonthDiff=t1.result THEN 1 ELSE 0 END AS isMatched
,CASE WHEN c.MonthDiff=t1.result THEN ROW_NUMBER()OVER(PARTITION BY t1.ind,t1.lvl,CASE WHEN c.MonthDiff=t1.result THEN 1 ELSE 0 END ORDER BY t1.id) ELSE null END AS rn
FROM tb AS t1
LEFT JOIN tb AS t2 ON t1.ind=t2.ind AND t1.lvl=t2.lvl AND t2.id=t1.id-1
CROSS APPLY(VALUES(ISNULL(DATEDIFF(MONTH,t2.date,t1.date),1))) c(MonthDiff)
) AS t
ORDER BY t.id
id ind lvl result date
----------- ----------- ---- ----------- ---------- --------------------
1 1 a 3 2017-01-31 0
2 1 a 3 2017-02-28 0
3 1 a 1 2017-03-31 1
4 1 a 1 2017-04-30 2
5 1 a 1 2017-05-31 3
6 1 b 1 2017-01-31 1
7 1 b 3 2017-02-28 0
8 1 b 3 2017-03-31 0
9 1 b 1 2017-04-30 1
10 1 b 1 2017-05-31 2
11 2 a 3 2017-01-31 0
12 2 a 1 2017-02-28 1
13 2 a 3 2017-03-31 0
14 2 a 1 2017-04-30 1
15 2 a 3 2017-05-31 0
By slightly tweaking your input data and slightly tweaking how we define the requirement, it becomes quite simple to produce the expected results.
First, we tweak your date values so that the only thing that varies is the month and year - the days are all the same. I've chosen to do that my adding 1 day to each value1. The fact that this produces results which are one month advanced doesn't matter here, since all values are similarly transformed, and so the monthly relationships stay the same.
Then, we introduce a numbers table - here, I've assumed a small fixed table is adequate. If it doesn't fit your needs, you can easily locate examples online for creating a large fixed numbers table that you can use for this query.
And, finally, we recast the problem statement. Instead of trying to count months, we instead ask "what's the smallest number of months, greater of equal to zero, that I need to go back from the current row, to locate a row with a non-1 result?". And so, we produce this query:
declare #t table (id int not null,ind int not null,lvl varchar(13) not null,
result int not null,date date not null)
insert into #t(id,ind,lvl,result,date) values
(1 ,1,'a',3,'20170131'), (2 ,1,'a',3,'20170228'), (3 ,1,'a',1,'20170331'),
(4 ,1,'a',1,'20170430'), (5 ,1,'a',1,'20170531'), (6 ,1,'b',1,'20170131'),
(7 ,1,'b',3,'20170228'), (8 ,1,'b',3,'20170331'), (9 ,1,'b',1,'20170430'),
(10,1,'b',1,'20170531'), (11,2,'a',3,'20170131'), (12,2,'a',1,'20170228'),
(13,2,'a',3,'20170331'), (14,2,'a',1,'20170430'), (15,2,'a',3,'20170531')
;With Tweaked as (
select
*,
DATEADD(day,1,date) as dp1d
from
#t
), Numbers(n) as (
select 0 union all select 1 union all select 2 union all select 3 union all select 4
union all
select 5 union all select 6 union all select 7 union all select 8 union all select 9
)
select
id, ind, lvl, result, date,
COALESCE(
(select MIN(n) from Numbers n1
inner join Tweaked t2
on
t2.ind = t1.ind and
t2.lvl = t1.lvl and
t2.dp1d = DATEADD(month,-n,t1.dp1d)
where
t2.result != 1
),
1) as [BadResultRemainsFor%Months]
from
Tweaked t1
The COALESCE is just there to deal with the edge case, such as for your 1,b data, where there is no previous row with a non-1 result.
Results:
id ind lvl result date BadResultRemainsFor%Months
----------- ----------- ------------- ----------- ---------- --------------------------
1 1 a 3 2017-01-31 0
2 1 a 3 2017-02-28 0
3 1 a 1 2017-03-31 1
4 1 a 1 2017-04-30 2
5 1 a 1 2017-05-31 3
6 1 b 1 2017-01-31 1
7 1 b 3 2017-02-28 0
8 1 b 3 2017-03-31 0
9 1 b 1 2017-04-30 1
10 1 b 1 2017-05-31 2
11 2 a 3 2017-01-31 0
12 2 a 1 2017-02-28 1
13 2 a 3 2017-03-31 0
14 2 a 1 2017-04-30 1
15 2 a 3 2017-05-31 0
1An alternative way to perform the adjustment is to use a DATEADD/DATEDIFF pair to perform a "floor" operation against the dates:
DATEADD(month,DATEDIFF(month,0,date),0) as dp1d
Which resets all of the date values to be the first of their own month rather than the following month. This may fell more "natural" to you, or you may already have such values available in your original data.
Assuming the dates are continously increasing in month, you can use window function like so:
select
t.id, ind, lvl, result, dat,
case when result = 1 then row_number() over (partition by grp order by id) else 0 end x
from (
select t.*,
dense_rank() over (order by e, result) grp
from (
select
t.*,
row_number() over (order by id) - row_number() over (partition by ind, lvl, result order by id) e
from your_table t
order by id) t ) t;

Merge two tables while dragging one column from one table to the other

With Sql Server 2014:
I have two tables - Events and Locations, that share a time column and I need to merge them into one table order by time. In the Events table there is an Event column that I need to place in all the Locations row following that event (time wise), here is an example:
Events:
time event
------------
09:00 2
09:10 3
10:15 1
10:17 2
10:30 3
Locations:
time X Y
-------------
09:01 1 3
09:02 2 3
09:05 4 1
09:09 6 4
09:10 7 8
09:11 8 8
09:12 9 7
10:17 1 2
10:19 5 4
10:20 4 3
10:25 5 4
10:28 3 5
Merged Table:
time X Y event
--------------------
09:00 0 0 2
09:01 1 3 2 <
09:02 2 3 2 <
09:05 4 1 2 <
09:09 6 4 2 <
09:10 0 0 3
09:10 7 8 3 <
09:11 8 8 3 <
09:12 9 7 3 <
10:15 0 0 1
10:17 0 0 2
10:17 1 2 2 <
10:19 5 4 2 <
10:20 4 3 2 <
10:25 5 4 2 <
10:28 3 5 2 <
10:30 0 0 3
The elements that mark with '<' are the inserted Events.
Any ideas and help on how to perform this task is welcome.
You can use UNION ALL and APPLY:
SQL Fiddle
SELECT
[Time], X = 0, Y = 0, [Event]
FROM [Events]
UNION ALL
SELECT l.*, x.Event
FROM Locations l
CROSS APPLY(
SELECT TOP 1 *
FROM [Events]
WHERE [Time] <= l.[Time]
ORDER BY [Time] DESC
)x
ORDER BY [Time]

Resources