SQL Server 2008: LAG and runningTotals - sql-server-2008

CREATE TABLE #NetAmounts
(
priority int,
NetAmount decimal(10, 2),
DedicatedAmt decimal(10, 2)
)
INSERT INTO #NetAmounts (priority, NetAmount, DedicatedAmt)
VALUES (1, 6000, 2500),
(2, 6000, 2500),
(3, 6000, 2500),
(4, 6000, 2500)
Net Amount is always same throughout.
I want to update Dedicated Amount so that the sum of dedicated amount is always equals to the netamount by priority order
Expected Result:
Priority NetAmount DedicatedAmt
---------------------------------
1 6000 2500
2 6000 2500
3 6000 1000
4 6000 0
My query (not working)
;WITH CTE AS
(
SELECT
*,
RunningTotal = SUM(DedicatedAmt) OVER(ORDER BY 1)
FROM #NetAmounts
)
SELECT
Priority,
Netamount,
DedicatedAmt = CASE
WHEN DedicatedAmt > RunningTotal
THEN CASE
WHEN LAG(Netamount, 1, Netamount) OVER(ORDER BY Priority) >= DedicatedAmt
AND RunningTotal <= Netamount
THEN ABS(LAG(Netamount, 1, Netamount) OVER(ORDER BY Priority) - DedicatedAmt)
ELSE 0
END
ELSE DedicatedAmt
END
FROM CTE
Thanks

Related

sql server 2008 running totals between 2 dates

I need to get running totals between 2 dates in my sql server table and update the records simultaneoulsy. My data is as below and ordered by date,voucher_no
DATE VOUCHER_NO OPEN_BAL DEBITS CREDITS CLOS_BAL
-------------------------------------------------------------------
10/10/2017 1 100 10 110
12/10/2017 2 110 5 105
13/10/2017 3 105 20 125
Now if i insert a record with voucher_no 4 on 12/10/2017 the output should be like
DATE VOUCHER_NO OPEN_BAL DEBITS CREDITS CLOS_BAL
------------------------------------------------------------------
10/10/2017 1 100 10 110
12/10/2017 2 110 5 105
12/10/2017 4 105 4 109
13/10/2017 3 109 20 129
I have seen several examples which find running totals upto a certain date but not between 2 dates or from a particular date to end of file
You should consider changing your database structure. I think it will be better to keep DATE, VOUCHER_NO, DEBITS, CREDITS in one table. And create view to calculate balances. In that case you will not have to update table after each insert. In this case your table will look like
create table myTable (
DATE date
, VOUCHER_NO int
, DEBITS int
, CREDITS int
)
insert into myTable values
('20171010', 1, 10, null),( '20171012', 2, null, 5)
, ('20171013', 3, 20, null), ('20171012', 4, 4, null)
And view will be
;with cte as (
select
DATE, VOUCHER_NO, DEBITS, CREDITS, bal = isnull(DEBITS, CREDITS) * case when DEBITS is null then -1 else 1 end
, rn = row_number() over (order by DATE, VOUCHER_NO)
from
myTable
)
select
a.DATE, a.VOUCHER_NO, a.DEBITS, a.CREDITS
, OPEN_BAL = sum(b.bal + case when b.rn = 1 then 100 else 0 end) - a.bal
, CLOS_BAL = sum(b.bal + case when b.rn = 1 then 100 else 0 end)
from
cte a
join cte b on a.rn >= b.rn
group by a.DATE, a.VOUCHER_NO, a.rn, a.bal, a.DEBITS, a.CREDITS
Here's another solution if you can not change your db structure. In this case you must run update statement each time after inserts. In both cases I assume that initial balance is 100 while recalculation
create table myTable (
DATE date
, VOUCHER_NO int
, OPEN_BAL int
, DEBITS int
, CREDITS int
, CLOS_BAL int
)
insert into myTable values
('20171010', 1, 100, 10, null, 110)
,( '20171012', 2, 110, null, 5, 105)
, ('20171013', 3, 105, 20, null, 125)
, ('20171012', 4, null, 4, null, null)
;with cte as (
select
DATE, VOUCHER_NO, DEBITS, CREDITS, bal = isnull(DEBITS, CREDITS) * case when DEBITS is null then -1 else 1 end
, rn = row_number() over (order by DATE, VOUCHER_NO)
from
myTable
)
, cte2 as (
select
a.DATE, a.VOUCHER_NO
, OPEN_BAL = sum(b.bal + case when b.rn = 1 then 100 else 0 end) - a.bal
, CLOS_BAL = sum(b.bal + case when b.rn = 1 then 100 else 0 end)
from
cte a
join cte b on a.rn >= b.rn
group by a.DATE, a.VOUCHER_NO, a.rn, a.bal
)
update a
set a.OPEN_BAL = b.OPEN_BAL, a.CLOS_BAL = b.CLOS_BAL
from
myTable a
join cte2 b on a.DATE = b.DATE and a.VOUCHER_NO = b.VOUCHER_NO

Mysql return 0 not null in sum

Help needed please I have a table with taskId, Materials, Labour and a table with expenses in. The issue i have is that some tasks do not have and expense column in the taskenpense table so the column returns null. I need null to be 0.
` CREATE TABLE emptasks ( empTaskId INT, taskMaterials NUMERIC(8,2),taskLabour NUMERIC(8,2));
INSERT INTO emptasks VALUES
(1, 50, 50),
(2, 450.26, 50),
(3, 2505.10, 50),
(4, 2505.10, 50),
(5, 500, 500),
(6, 1000, 50);
CREATE TABLE taskexpenses (
feeID INT,
empTaskId INT,
expense NUMERIC(8,2));
INSERT INTO taskexpenses VALUES
(1, 1, 50.00),
(1, 2, 50.00),
(2, 2, 126.00),
(3, 3, 50.00),
(4, 4, 50.00),
(2, 2, 1206.00);
SELECT
p.empTaskId,
p.Labour,
p.Materials,
f.Expenses,
p.Labour + p.Materials - f.Expenses AS Total,
ROUND( (f.Expenses + p.Materials) / p.Labour * 100, 2) AS Percentage
FROM (
SELECT
empTaskId,
SUM(taskMaterials) AS Labour,
SUM(taskLabour) AS Materials
FROM emptasks
GROUP BY empTaskId
) p
LEFT JOIN (
SELECT taskexpenses.empTaskId,
SUM(expense) AS Expenses
FROM emptasks
INNER JOIN taskexpenses ON emptasks.empTaskId = taskexpenses.empTaskId
GROUP BY empTaskId
) f ON p.empTaskId = f.empTaskId
the result is
empTaskId Labour Materials Expenses Total Percentage
1 50 50 50 50 200
2 450.26 50 1382 -881.74 318.04
3 2505.1 50 50 2505.1 3.99
4 2505.1 50 50 2505.1 3.99
5 500 500 (null) (null) (null)
6 1000 50 (null) (null) (null)
I need the null value to return 0 so the sum can be worked out
FIDDLE LINK
THanks
Jon
Slightly simpler than in the answer from #Bob Jarvis is to use the IFNULL() function.
SELECT
p.empTaskId,
p.Labour,
p.Materials,
IFNULL(f.Expenses, '0') AS Expenses,
IFNULL(p.Labour + p.Materials - f.Expenses, '0') AS Total,
IFNULL(ROUND( (f.Expenses + p.Materials) / p.Labour * 100, 2), '0') AS Percentage
FROM ...
See fiddle
Use the COALESCE function:
SELECT p.empTaskId,
p.Labour,
p.Materials,
COALESCE(f.Expenses, 0) AS Expenses,
COALESCE(p.Labour, 0) + COALESCE(p.Materials, 0) - COALESCE(f.Expenses, 0) AS Total,
ROUND( (COALESCE(f.Expenses, 0) + COALESCE(p.Materials, 0)) / p.Labour * 100, 2) AS Percentage
FROM (SELECT empTaskId,
SUM(COALESCE(taskMaterials, 0)) AS Labour,
SUM(COALESCE(taskLabour, 0)) AS Materials
FROM emptasks
GROUP BY empTaskId) p
LEFT JOIN (SELECT taskexpenses.empTaskId,
SUM(COALESCE(expense, 0)) AS Expenses
FROM emptasks
INNER JOIN taskexpenses
ON emptasks.empTaskId = taskexpenses.empTaskId
GROUP BY empTaskId) f
ON p.empTaskId = f.empTaskId
Note that here I've put COALESCE on just about everything which might possibly be NULL. If you only want to put it on the Expenses column change it to be what you want.
Best of luck.

Count consecutive rows with a particular status

I need to count whether there are three consecutive failed login attempts of the user in last one hour.
For example
id userid status logindate
1 1 0 2014-08-28 10:00:00
2 1 1 2014-08-28 10:10:35
3 1 0 2014-08-28 10:30:00
4 1 0 2014-08-28 10:40:00
In the above example, status 0 means failed attempt and 1 means successful attempt.
I need a query that will count three consecutive records of a user with status 0 occurred in last one hour.
I tried below query
SELECT COUNT( * ) AS total, Temp.status
FROM (
SELECT a.status, MAX( a.id ) AS idlimit
FROM loginAttempts a
GROUP BY a.status
ORDER BY MAX( a.id ) DESC
) AS Temp
JOIN loginAttempts t ON Temp.idlimit < t.id
HAVING total >1
Result:
total status
2 1
I don't know why it display status as 1. I also need to add a where condition on logindate and status field but don't know how would it work
For consecutive count you can use user defined variables to note the series values ,like in below query i have use #g and #r variable, in inner query i am storing the current status value that could be 1/0 and in case expression i am comparing the value stored in #g with the status column if they both are equal like #g is holding previous row value and previous row's status is equal to the current row's status then do not change the value stored in #r,if these values don't match like #g <> a.status then increment #r with 1, one thing to note i am using order by with id column and assuming it is set to auto_increment so for consecutive 1s #r value will be same like #r was 3 for first status 1 and the again status is 1 so #r will 3 until the status changes to 0 same for status 0 vice versa
SELECT t.userid,t.consecutive,t.status,COUNT(1) consecutive_count
FROM (
SELECT a.* ,
#r:= CASE WHEN #g = a.status THEN #r ELSE #r + 1 END consecutive,
#g:= a.status g
FROM attempts a
CROSS JOIN (SELECT #g:=2, #r:=0) t1
WHERE a.`logindate` BETWEEN '2014-08-28 10:00:00' AND '2014-08-28 11:00:00'
ORDER BY id
) t
GROUP BY t.userid,t.consecutive,t.status
HAVING consecutive_count >= 3 AND t.status = 0
Now in parent query i am grouping results by userid the resultant value of case expression i have name is it as consecutive and status to get the count for each user's consecutive status
One thing to note for above query that its necessary to provide the
hour range like i have used between without this it will be more
difficult to find exactly 3 consecutive statuses with in an hour
Sample data
INSERT INTO attempts
(`id`, `userid`, `status`, `logindate`)
VALUES
(1, 1, 0, '2014-08-28 10:00:00'),
(2, 1, 1, '2014-08-28 10:10:35'),
(3, 1, 0, '2014-08-28 10:30:00'),
(4, 1, 0, '2014-08-28 10:40:00'),
(5, 1, 0, '2014-08-28 10:50:00'),
(6, 2, 0, '2014-08-28 10:00:00'),
(7, 2, 0, '2014-08-28 10:10:35'),
(8, 2, 0, '2014-08-28 10:30:00'),
(9, 2, 1, '2014-08-28 10:40:00'),
(10, 2, 1, '2014-08-28 10:50:00')
;
As you can see from id 3 to 5 you can see consecutive 0s for userid 1 and similarly id 6 to 8 userid 2 has consecutive 0s and they are in an hour range using above query you can have results as below
userid consecutive status consecutive_count
------ ----------- ------ -------------------
1 2 0 3
2 2 0 3
Fiddle Demo
M Khalid Junaid's answer is great, but his Fiddle Demo didn't work for me when I clicked it.
Here is a Fiddle Demo which works as of this writing.
In case it doesn't work later, I used the following in the schema:
CREATE TABLE attempts
(`id` int, `userid` int, `status` int, `logindate` datetime);
INSERT INTO attempts
(`id`, `userid`, `status`, `logindate`)
VALUES
(1, 1, 0, '2014-08-28 10:00:00'),
(2, 1, 1, '2014-08-28 10:10:35'),
(3, 1, 0, '2014-08-28 10:30:00'),
(4, 1, 0, '2014-08-28 10:40:00'),
(5, 1, 0, '2014-08-28 10:50:00'),
(6, 2, 0, '2014-08-28 10:00:00'),
(7, 2, 0, '2014-08-28 10:10:35'),
(8, 2, 0, '2014-08-28 10:30:00'),
(9, 2, 1, '2014-08-28 10:40:00'),
(10, 2, 1, '2014-08-28 10:50:00')
;
And this as the query:
SELECT t.userid,t.consecutive,t.status,COUNT(1) consecutive_count
FROM (
SELECT a.* ,
#r:= CASE WHEN #g = a.status THEN #r ELSE #r + 1 END consecutive,
#g:= a.status g
FROM attempts a
CROSS JOIN (SELECT #g:=2, #r:=0) t1
WHERE a.`logindate` BETWEEN '2014-08-28 10:00:00' AND '2014-08-28 11:00:00'
ORDER BY id
) t
GROUP BY t.userid,t.consecutive,t.status
HAVING consecutive_count >= 3 AND t.status = 0;

Group by, with rank and sum - not getting correct output

I'm trying to sum a column with rank function and group by month, my code is
select dbo.UpCase( REPLACE( p.Agent_name,'.',' '))as Agent_name, SUM(convert ( float ,
p.Amount))as amount,
RANK() over( order by SUM(convert ( float ,Amount )) desc ) as arank
from dbo.T_Client_Pc_Reg p
group by p.Agent_name ,p.Sale_status ,MONTH(Reg_date)
having [p].Sale_status='Activated'
Currently I'm getting all total value of that column not month wise
Name amount rank
a 100 1
b 80 2
c 50 3
for a amount 100 is total amount till now but , i want get current month total amount not last months..
Maybe you just need to add a WHERE clause? Here is a minor re-write that I think works generally better. Some setup in tempdb:
USE tempdb;
GO
CREATE TABLE dbo.T_Client_Pc_Reg
(
Agent_name VARCHAR(32),
Amount INT,
Sale_Status VARCHAR(32),
Reg_date DATETIME
);
INSERT dbo.T_Client_Pc_Reg
SELECT 'a', 50, 'Activated', GETDATE()
UNION ALL SELECT 'a', 50, 'Activated', GETDATE()
UNION ALL SELECT 'b', 20, 'Activated', GETDATE()
UNION ALL SELECT 'b', 20, 'Activated', GETDATE()
UNION ALL SELECT 'b', 20, 'Activated', GETDATE()
UNION ALL SELECT 'b', 20, 'Activated', GETDATE()
UNION ALL SELECT 'b', 20, 'NotActivated', GETDATE()
UNION ALL SELECT 'c', 25, 'Activated', GETDATE()
UNION ALL SELECT 'c', 25, 'Activated', GETDATE()
UNION ALL SELECT 'c', 25, 'Activated', GETDATE()-40;
Then the query:
SELECT
Agent_name = UPPER(REPLACE(Agent_name, '.', '')),
Amount = SUM(CONVERT(FLOAT, Amount)),
arank = RANK() OVER (ORDER BY SUM(CONVERT(FLOAT, Amount)) DESC)
FROM dbo.T_Client_Pc_Reg
WHERE Reg_date >= DATEADD(MONTH, DATEDIFF(MONTH, 0, CURRENT_TIMESTAMP), 0)
AND Reg_date < DATEADD(MONTH, DATEDIFF(MONTH, 0, CURRENT_TIMESTAMP) + 1, 0)
AND Sale_status = 'Activated'
GROUP BY UPPER(REPLACE(Agent_name, '.', ''))
ORDER BY arank;
Now cleanup:
USE tempdb;
GO
DROP TABLE dbo.T_Client_Pc_Reg;

How to declare an array inside MS SQL Server Stored Procedure?

I need to declare 12 decimal variables, corresponding to each month's year, with a cursor I sum values to this variables, then later I Update some sales information.
I don't know if sql server has this syntax
Declare MonthsSale(1 to 12) as decimal(18,2)
This code works Ok. !
CREATE PROCEDURE [dbo].[proc_test]
AS
BEGIN
--SET NOCOUNT ON;
DECLARE #monthsales TABLE ( monthnr int, amount decimal(18,2) )
-- PUT YOUR OWN CODE HERE
-- THIS IS TEST CODE
-- 1 REPRESENTS JANUARY, ...
INSERT #monthsales (monthnr, amount) VALUES (1, 100)
INSERT #monthsales (monthnr, amount) VALUES (1, 100)
INSERT #monthsales (monthnr, amount) VALUES (2, 200)
INSERT #monthsales (monthnr, amount) VALUES (3, 300)
INSERT #monthsales (monthnr, amount) VALUES (4, 400)
INSERT #monthsales (monthnr, amount) VALUES (5, 500)
INSERT #monthsales (monthnr, amount) VALUES (6, 600)
INSERT #monthsales (monthnr, amount) VALUES (7, 700)
INSERT #monthsales (monthnr, amount) VALUES (8, 800)
INSERT #monthsales (monthnr, amount) VALUES (9, 900)
INSERT #monthsales (monthnr, amount) VALUES (10, 1000)
INSERT #monthsales (monthnr, amount) VALUES (11, 1100)
INSERT #monthsales (monthnr, amount) VALUES (12, 1200)
SELECT monthnr, SUM(amount) AS SUM_MONTH_1 FROM #monthsales WHERE monthnr = 1 GROUP BY monthnr
SELECT monthnr, SUM(amount) AS SUM_MONTH_2 FROM #monthsales WHERE monthnr = 2 GROUP BY monthnr
SELECT monthnr, SUM(amount) AS SUM_MONTH_3 FROM #monthsales WHERE monthnr = 3 GROUP BY monthnr
SELECT monthnr, SUM(amount) AS SUM_MONTH_4 FROM #monthsales WHERE monthnr = 4 GROUP BY monthnr
SELECT monthnr, SUM(amount) AS SUM_MONTH_5 FROM #monthsales WHERE monthnr = 5 GROUP BY monthnr
SELECT monthnr, SUM(amount) AS SUM_MONTH_6 FROM #monthsales WHERE monthnr = 6 GROUP BY monthnr
SELECT monthnr, SUM(amount) AS SUM_MONTH_7 FROM #monthsales WHERE monthnr = 7 GROUP BY monthnr
SELECT monthnr, SUM(amount) AS SUM_MONTH_8 FROM #monthsales WHERE monthnr = 8 GROUP BY monthnr
SELECT monthnr, SUM(amount) AS SUM_MONTH_9 FROM #monthsales WHERE monthnr = 9 GROUP BY monthnr
SELECT monthnr, SUM(amount) AS SUM_MONTH_10 FROM #monthsales WHERE monthnr = 10 GROUP BY monthnr
SELECT monthnr, SUM(amount) AS SUM_MONTH_11 FROM #monthsales WHERE monthnr = 11 GROUP BY monthnr
SELECT monthnr, SUM(amount) AS SUM_MONTH_12 FROM #monthsales WHERE monthnr = 12 GROUP BY monthnr
-- END TEST CODE
END
You could declare a table variable (Declaring a variable of type table):
declare #MonthsSale table(monthnr int)
insert into #MonthsSale (monthnr) values (1)
insert into #MonthsSale (monthnr) values (2)
....
You can add extra columns as you like:
declare #MonthsSale table(monthnr int, totalsales tinyint)
You can update the table variable like any other table:
update m
set m.TotalSales = sum(s.SalesValue)
from #MonthsSale m
left join Sales s on month(s.SalesDt) = m.MonthNr
Is there a reason why you aren't using a table variable and the aggregate SUM operator, instead of a cursor? SQL excels at set-oriented operations. 99.87% of the time that you find yourself using a cursor, there's a set-oriented alternative that's more efficient:
declare #MonthsSale table
(
MonthNumber int,
MonthName varchar(9),
MonthSale decimal(18,2)
)
insert into #MonthsSale
select
1, 'January', 100.00
union select
2, 'February', 200.00
union select
3, 'March', 300.00
union select
4, 'April', 400.00
union select
5, 'May', 500.00
union select
6, 'June', 600.00
union select
7, 'July', 700.00
union select
8, 'August', 800.00
union select
9, 'September', 900.00
union select
10, 'October', 1000.00
union select
11, 'November', 1100.00
union select
12, 'December', 1200.00
select * from #MonthsSale
select SUM(MonthSale) as [TotalSales] from #MonthsSale
T-SQL doesn't support arrays that I'm aware of.
What's your table structure? You could probably design a query that does this instead:
select
month,
sum(sales)
from sales_table
group by month
order by month
Great question and great idea, but in SQL you'll need to do this:
For data type datetime, something like this-
declare #BeginDate datetime = '1/1/2016',
#EndDate datetime = '12/1/2016'
create table #months (dates datetime)
declare #var datetime = #BeginDate
while #var < dateadd(MONTH, +1, #EndDate)
Begin
insert into #months Values(#var)
set #var = Dateadd(MONTH, +1, #var)
end
If all you really want is numbers, do this-
create table #numbas (digit int)
declare #var int = 1 --your starting digit
while #var <= 12 --your ending digit
begin
insert into #numbas Values(#var)
set #var = #var +1
end
You can declare an array using the VALUES keyword. Your example can be expressed succintly in the following form:
SELECT * FROM (VALUES
(1,100),
(2,200),
(3,300),
(4,400),
(5,500),
(6,600),
(7,700),
(8,800),
(9,900),
(10,1000),
(11,1100),
(12,1200)
) MonthSale (monthnr, Amount)
Feel free to INSERT it into a table or place a WHERE or GROUP condition for your purposes.