update 1 table from another table by next earliest date - sql-server

I have 2 tables: budget and budget_rate:
Budget Table
resource period hours dollars
-------- ------ ----- -------
ADMIN03 01/31/16 160 8000
ADMIN03 02/28/16 150 7500
Rate Table
resource rate eff_date
-------- ---- --------
ADMIN03 50.00 01/01/16
ADMIN03 52.50 01/01/17
When the rates change in the rate table, I need to update the budget based on the rate that matches the resource name and is the first rate record earlier than the budget record.
Can this be accomplished with a single UPDATE?
Something like:
update b
set b.dollars = b.hours*r.rate
from
budget b join rate r on
b.resource = r.resource and
b.period >= r.eff_date

I assume rate table is realtive small, so I would recalculate it to have range columns.
with oRates as (
select resource,
rate,
eff_date,
ROW_NUMBER() over(partition by resource order by eff_date desc) rn
from Rates
),
pRates as (
select r1.resource,
r1.rate,
r1.eff_date from_date,
isnull(r2.eff_date,'2070-01-01') to_date
from oRates r1
left join oRates r2 on (r1.rn = r2.rn+1)
)
update b
set dollars = hours * r.rate
from Budget b
join pRates r on (b.resource = r.resource
and b.period >= from_date
and b.period < to_date)

One possible solution is using a computed column instead of some sort of manual update.
An example of how this could be done can be seen here: formula for computed column based on different table's column
For a working example with your data, you'd create a function like this:
CREATE FUNCTION dbo.ufn_BudgetDollars (#resource NVARCHAR(255), #date DATE, #hours INT)
RETURNS DECIMAL(10, 2)
AS BEGIN
DECLARE #out DECIMAL(10, 2);
SELECT #out = #hours * rate
FROM (
SELECT rate, ROW_NUMBER() OVER (ORDER BY eff_date DESC) rn
FROM tblRate
WHERE eff_date <= #date
AND resource = #resource) T
WHERE RN = 1;
RETURN #out;
END
GO
When you've created your function, you would want to drop and recreate the Dollars column on the budget table...
ALTER TABLE tblBudget DROP COLUMN Dollars;
ALTER TABLE tblBudget ADD Dollars AS dbo.ufn_BudgetDollars(resource, Period, Hours);
GO

Related

Need to generate rows with missing data in a large dataset - SQL

We are comparing values between months over multiple years. As time moves on the number of years and months in the dataset increases. We are only interested in months where there were values for every year, i.e. a full set.
Consider the following example for 1 month (1) over 3 years (1,2,3) and two activities (101, 102)
Dataset:
Activity Month year Count
------- ---- ------ ------
101 1 1 2
101 1 2 3
101 1 3 1
102 1 1 1
102 1 2 1
In the example above only activity 101 will come into consideration as it satisfies the condition that there must be a count for the activity for month 1 IN year 1, 2 and 3.
Activity 102 doesn't qualify for further analysis as it has no record for year 3.
I would like to generate a record with which I can then evaluate this. The record will effectively generate the new record with the missing row (in this case 102, 1, 3 , 0) to complete the dataset
Activity Month year Count
------- ---- ------ ------
102 1 3 0
We find the problem difficult as the data keeps in growing, the number of activities keep expanding and it is a combination of activity, year and month that need to be evaluated.
An elegant solution will be appreciated.
As I mention in my comment, presumably you have both an Activity table and some kind of Calendar table with details of your activities and the years in your system. As such you can therefore do a CROSS JOIN between these 2 objects and then LEFT JOIN to your table to get the data set you want:
--Create sample objects/data
CREATE TABLE dbo.Activity (Activity int); --Obviously your table has more columns
INSERT INTO dbo.Activity (Activity)
VALUES (101),(102);
GO
CREATE TABLE dbo.Calendar (Year int,
Month int);--Likely your table has more columns
INSERT INTO dbo.Calendar (Year, Month)
VALUES(1,1),
(2,1),
(3,1);
GO
CREATE TABLE dbo.YourTable (Activity int,
Year int,
Month int,
[Count] int);
INSERT INTO dbo.YourTable (Activity,Month, Year, [Count])
VALUES(101,1,1,2),
(101,1,2,3),
(101,1,3,1),
(102,1,1,1),
(102,1,2,1);
GO
--Solution
SELECT A.Activity,
C.Month,
C.Year,
ISNULL(YT.[Count],0) AS [Count]
FROM dbo.Activity A
CROSS JOIN dbo.Calendar C
LEFT JOIN dbo.YourTable YT ON A.Activity = YT.Activity
AND C.[Year] = YT.[Year]
AND C.[Month] = YT.[Month]
WHERE C.Month = 1; --not sure if this is needed
If you don't have an Activity and Calendar table (I suggest, however, you should), then you can use subqueries with a DISTINCT, but note this will be far from performant with large data sets:
SELECT A.Activity,
C.Month,
C.Year,
ISNULL(YT.[Count],0) AS [Count]
FROM (SELECT DISTINCT Activity FROM dbo.YourTable) A
CROSS JOIN (SELECT DISTINCT Year, Month FROM dbo.YourTable) C
LEFT JOIN dbo.YourTable YT ON A.Activity = YT.Activity
AND C.[Year] = YT.[Year]
AND C.[Month] = YT.[Month]
WHERE C.Month = 1; --not sure if this is needed

SQL weighted revenue query

6I have 3 tables. Examples below.
Weight
Channel WeightFirst WeightMiddle WeightLast
Dir 40 45 50
NatS 0 0 0
PC 20 25 30
UnRef 40 45 50
Sales
saleID revenue
32150 1600.00
32153 516.00
Visits
visitID saleID visitDate channel visitRevenue
4479433 32153 2014-12-09 15:00:41.000 NatS NULL
4479434 32153 2014-12-09 14:55:21.000 PC NULL
4479435 32153 2014-12-09 15:09:01.000 UnRef NULL
4755575 32150 2014-12-07 16:41:24.000 NatS NULL
4756323 32150 2014-12-07 16:52:56.000 PC NULL
4756324 32150 2014-12-06 20:49:41.000 Dir NULL
I need to calculate visitRevenus in the Visits table based on the WeightFirst, WeightMiddle, WeightLast in the Weight table.
First visitDate in the Visits table gets WeightFirst, last visitDate gets WeightLast, and everything in between those dates gets WeightMiddle.
For example saleID 32153 has the first visitDate as visitID 4479434, so PC gets a WeightFirst of 20, then visitID 4479433 gets 0 for NatS, and visitID 4479435 gets 50 for UnRef. Total weight is 70. With saleID revenue in Sales being 516.00
I need to divide 516.00 by 70, then multiply the result by each weight and update visitRevenue in the Visits table with that result.
So PC would get 147.4285714285714 and UnRef would get 368.5714285714286. Add them together and it's 516.
The table I have hold more than just 2 saleIDs, many channels, and large amounts of visitDates. I need some SQL to update visitRevenue
with these calculated figures but am having trouble getting started. Any help would be most welcome! And if anymore info is required please ask.
Thanks
This, I think, does the trick. I don't know your system so I did not know if you could alter the underlying tables so this does everything with table variables.
BTW, it really helps to have the tables set up first :)
DECLARE #weight TABLE (
Channel varchar(max),
WeightFirst int,
WeightMiddle int,
WeightLast int)
INSERT INTO #weight VALUES
('Dir', 40,45,50),
('NatS', 0, 0, 0),
('PC', 20,25,30),
('UnRef',40,45,50)
DECLARE #sales TABLE (
salesID int,
revenue float)
INSERT INTO #sales VALUES
(32150,1600.00),
(32153,516.00)
DECLARE #visits TABLE (
visitID int,
salesID int,
visitDate datetime,
channel varchar(max),
VisitRevenue float)
INSERT INTO #visits VALUES
(4479433, 32153, '2014-12-09 15:00:41.000','NatS', NULL),
(4479434, 32153, '2014-12-09 14:55:21.000','PC', NULL),
(4479435, 32153, '2014-12-09 15:09:01.000','UnRef',NULL),
(4755575, 32150, '2014-12-07 16:41:24.000','NatS', NULL),
(4756323, 32150, '2014-12-07 16:52:56.000','PC', NULL),
(4756324, 32150, '2014-12-06 20:49:41.000','Dir', NULL)
DECLARE #visitWeight TABLE (
visitID int,
salesID int,
visitDate datetime,
channel varchar(max),
VisitRevenue float,
visitNumber int,
visitWeight int,
totalWeight float,
revenue float)
INSERT INTO #visitWeight
SELECT visitID, v.salesID, visitDate,channel,visitRevenue,
ROW_NUMBER() OVER (PARTITION BY v.salesID ORDER BY visitDate ASC) AS visitNumber ,NULL,NULL, revenue
FROM #visits v JOIN #sales s ON v.salesID=s.salesID
UPDATE #visitWeight -- this sets the first weight, also sets everything else to middle
SET visitWEIGHT =
CASE WHEN visitNumber=1 THEN WeightFirst ELSE weightMiddle END
FROM #visitWeight vw JOIN #weight w on vw.channel=w.channel
UPDATE #visitWeight -- this sets the last weight
SET visitWEIGHT = WeightLast
FROM
(SELECT salesID, max(visitNumber) AS maxVisit FROM #visitWeight GROUP BY salesID) AS t
JOIN #visitWeight vw ON t.maxVisit=vw.visitNumber JOIN
#weight w on vw.channel=w.channel
UPDATE #visitWeight
SET totalWeight = s.sumWeight,
VisitRevenue = revenue/s.sumWeight*visitWeight
FROM (SELECT salesID, SUM(visitWeight) AS sumWeight FROM #visitWeight GROUP BY salesID) AS s
SELECT * FROM #visitWeight order by salesID, visitDate

Checking next row in table is incremented by 1 minute in datetime column

I need to check alot of data in a Table to make sure my feed has not skipped anything.
Basically the table has the following columns
ID Datetime Price
The data in DateTime column is incremented by 1 minute in each successive row. I need to check the next row of the current one to see if is 1 minute above the one being queries in that specific context.
The query will probably need some sort of loop, then grab a copy of the next row and compare it to the datetime row of the current to make sure it is incremented by 1 minute.
I created a test-table to match your description, and inserted 100 rows with 1 minute between each row like this:
CREATE TABLE [Test] ([Id] int IDENTITY(1,1), [Date] datetime, [Price] int);
WITH [Tally] AS (
SELECT GETDATE() AS [Date]
UNION ALL
SELECT DATEADD(minute, -1, [Date]) FROM [Tally] WHERE [Date] > DATEADD(minute, -99, GETDATE())
)
INSERT INTO [Test] ([Date], [Price])
SELECT [Date], 123 AS [Price]
FROM [Tally]
Then i deleted a record in the middle to simulate a missing minute:
DELETE FROM [Test]
WHERE Id = 50
Now we can use this query to find missing records:
SELECT
a.*
,CASE WHEN b.[Id] IS NULL THEN 'Next record is Missing!' ELSE CAST(b.[Id] as varchar) END AS NextId
FROM
[Test] AS a
LEFT JOIN [Test] AS b ON a.[Date] = DATEADD(minute,1,b.[Date])
WHERE
b.[Id] IS NULL
The resullt will look like this:
Id Date Price NextId
----------- ----------------------- ----------- ------------------------------
49 2013-05-11 22:42:56.440 123 Next record is Missing!
100 2013-05-11 21:51:56.440 123 Next record is Missing!
(2 row(s) affected)
The key solution to the problem is to join the table with itself, but use datediff to find the record that is supposed to be found on the next minute. The last record of the table will of course report that the next row is missing, since it hasn't been inserted yet.
Borrowing TheQ's sample data you can use
WITH T
AS (SELECT *,
DATEDIFF(MINUTE, '20000101', [Date]) -
DENSE_RANK() OVER (ORDER BY [Date]) AS G
FROM Test)
SELECT MIN([Date]) AS StartIsland,
MAX([Date]) AS EndIsland
FROM T
GROUP BY G

How to add and update data using the date range period (date from and to)?

I have a table like below
Stage 1
Table Name : Product
Date_From Date_To Available
01/03/2011 05/03/2011 5
06/03/2011 15/03/2011 6
Stage 2
If I update above table with below data
Date_From Date_To Available
04/03/2011 08/03/2011 4
10/03/2011 18/03/2011 2
Stage 3
I need output of Product table (Stage 1) like this, when update with stage 2 data at same time
Date_From Date_To Available
01/03/2011 03/03/2011 5
04/03/2011 08/03/2011 4
06/03/2011 15/03/2011 6
10/03/2011 18/03/2011 2
Hoping for your help
EDIT:
create table t (dt_from datetime,dt_to datetime, Available int)
insert into t values ('20110301','20110305',5)
insert into t values ('20110306','20110315',6)
run after trigger is created
--insert into t values ('20110304','20110308',4)
---insert into t values ('20110310','20110318',2)
SELECT *,ROW_NUMBER() OVER (ORDER BY dt_from) rn FROM t
CREATE TRIGGER my_tr ON t FOR INSERT
AS
UPDATE t SET dt_to=(SELECT TOP 1 DATEADD(d,-1,dt_from) FROM inserted t1 WHERE dt_from
BETWEEN t.dt_from AND t.dt_to AND t.Available<>t1.Available)
WHERE EXISTS (SELECT * FROM inserted t1 WHERE dt_from
BETWEEN t.dt_from AND t.dt_to AND t.Available<>t1.Available)
UPDATE t SET dt_from=(SELECT TOP 1 DATEADD(d,1,dt_to) FROM inserted t1 WHERE dt_to
BETWEEN t.dt_from AND t.dt_to AND t.Available<>t1.Available)
WHERE EXISTS (SELECT * FROM inserted t1 WHERE dt_to
BETWEEN t.dt_from AND t.dt_to AND t.Available<>t1.Available)
the above code is working fine , i get the below result
Date_From Date_To Available
01/03/2011 03/03/2011 5
04/03/2011 08/03/2011 4
09/03/2011 09/03/2011 6
10/03/2011 18/03/2011 2
But only one problem i facing is
insert into t values ('20110301','20110318',5)
it not get the correct result
i need result like below
Date_From Date_To Available
01/03/2011 18/03/2011 5
Hoping your help
You should probably have a stored procedure that takes your values, and then decides whether an INSERT or UPDATE is needed, based on the dates...
CREATE PROCEDURE dbo.InsertOrUpdateData
#FromDate DATE, #ToDate DATE, #NewAvail INT
AS
IF EXISTS(SELECT * FROM dbo.YourTable
WHERE Date_From = #FromDate AND Date_To = #ToDate)
UPDATE dbo.YourTable
SET Available = Available + #NewAvail
WHERE Date_From = #FromDate AND Date_To = #ToDate
ELSE
INSERT INTO dbo.YourTable(Date_From, Date_To, Availability)
VALUES(#FromDate, #ToDate, #NewAvail)
With this stored procedure, you can call
EXEC dbo.InsertOrUpdateData
#FromDate = '20110301', #ToDate = '20110305', #NewAvail = 42
If those dates already exists, that row will be updated, if those dates don't exist, a new row will be inserted. No trigger voodoo or anything like that needed....
Is that what you're looking for??
When you get a new row you need to do the following:
Using the new ValidFrom and ValidTo dates - Checking existing data for anything that either:
Has a ValidFrom < new Validfrom and ValidTo >= new ValidFrom - if found, ValidTo should be set to 1 day before new ValidFrom.
Has a ValidFrom >= new ValidFrom and ValidTo <= new ValidTo - if found, You might need to do check again to see if moving the dates would overlap previous entries
Has a ValidFrom >= new ValidFrom and ValidFrom <= new ValidTo and ValidTo >= new ValidTo. This record might need to be split in two.
etc - any other concievable combination of Date overlaps ...
I would rather recommend using a new table with day - available, giving one row per product per day, with the available measure. This way, any new entries with validfrom / validto can just write the available value for the relevant days for the relevant products.
DayID ProductId Available

Query to get sum of an employees commission

Query to get total commissions for an employee, and update their totalCommission column in the employee table.
This query is run every few days (batch).
The rules:
1. an employee can only get a maximum of $100/day of commision, if they get more than $100 it just gets set to $100.
Tables:
Employee
(employeeID INT PK, totalCommissions INT, username, ...)
Sale
(saleID INT PK, employeeID INT FK, saleTotal, commission, created DATETIME)
Using SQL Server 2005.
So this query will have to group by day I presume, and use a case statement to set the daily commision to $100 if the sum is > 100 for that day, and then set the total SUM for all days to the Employee.TotalCommission column.
assuming you are limiting the dates somewhere using value of "somedate-goes-here":
update employee set totalcommissions = totalc
from
(
-------------------------------------
-- sum capped commissions by employee
-------------------------------------
select employeeID, sum(sum_commissions) as totalc from
(
---------------------------------------
-- make sure sum is capped if necessary
---------------------------------------
select employeeID
, case when sum_of_c > 100 then 100 else sum_of_c as sum_commisions
from
(
-----------------------------------------------
-- get sum of commissions per day per employee
-----------------------------------------------
select employeeID, sum(commission) as sum_of_c from sale
where created > "somedate-goes-here"
group by employeeID, day(created)
) as x
) as c
group by employeeID
) y
inner join employee on employee.employeeID = y.employeeID

Resources