SQL Merge with Partial Matching tables - sql-server

I have investigated lots of posts relating to SQL merging of two tables, and am very familiar with merging in SQL, but this one has me stumped. Here is the scenario: I have a table of values which are stored by Grade and Currency, i.e.
GradeID = 3, Rate = 175, CurrencyID = 5.
There are five grades, a different value for each and these are the default values for each grade in GBP (CurrencyID = 5).
I have another table with an extra column of Fiscal Period, which is a 1st of the month date to which the rate applies, i.e.
GradeID = 3, Rate = 150, FiscalPeriodID = 95, CurrencyID = 5.
Not all values for each grade, fiscal period and currency will be different from the default, so users will only enter differences where necessary. I have a Windows form for the user to view and update these values, but am struggling to show the results of both tables together.
The ideal solution for a case where there is no stored data, i.e. only the default values apply, is that the screen will show the default values at the current fiscal period i.e. 95 = 01/11/2017, but where there are values which have been entered, the table will show the changed rows by grade but grouped by each fiscal period which has a non-default value.
The first part of setting the current fiscal period for the default values is relatively easy:
SELECT [RateID], [GradeID], [ChargeRate],
(SELECT [FiscalPeriodID] FROM [dbo].[FiscalPeriod] WHERE MONTH([FiscalPeriod]) =
MONTH(GETDATE()) AND YEAR([FiscalPeriod]) = YEAR(GETDATE())) AS [FiscalPeriodID]
,[CurrencyID]
FROM [dbo].[DefaultChargeRateByLevel]
But what I'd like to do is perform one query that does either this simple selection (when no results returned for any fiscal period) or a merge of the default and updated values to show groups of 5 values per fiscal period.
My initial thought was to populate two temporary tables with the results from each query then perform a merge, but I can't figure out how to populate the fiscal period for each 'group of values. This is what i have so far, but it fails with the following error: Cannot insert the value NULL into column 'FiscalPeriodID', table 'tempdb.dbo.#TempTarget
IF OBJECT_ID('tempdb..#TempTarget') IS NOT NULL
DROP TABLE #TempTarget
IF OBJECT_ID('tempdb..#TempSource') IS NOT NULL
DROP TABLE #TempSource
SELECT
[GradeID]
,[ChargeRate]
,[FiscalPeriodID]
,[CurrencyID]
INTO #TempTarget
FROM (SELECT
[GradeID]
,[ChargeRate]
,[FiscalPeriodID]
,[CurrencyID]
FROM
[dbo].[ChargeRateByProjectAndLevel]) AS A
SELECT * FROM #TempTarget
SELECT
[GradeID]
,[ChargeRate]
,[FiscalPeriodID]
,[CurrencyID]
INTO #TempSource
FROM (SELECT
D.[GradeID]
,D.[ChargeRate]
,M.[FiscalPeriodID]
,D.[CurrencyID]
FROM
[dbo].[DefaultChargeRateByLevel] AS D
LEFT JOIN #TempTarget AS M ON M.[GradeID] = D.[GradeID]
AND M.[CurrencyID] = D.[CurrencyID]
) AS B
SELECT * FROM #TempSource
/* Now merge rates data from temp table to FxRates table */
MERGE #TempTarget AS [Target]
USING #TempSource AS [Source]
ON [Source].[GradeID] = [Target].[GradeID]
AND [Source].[CurrencyID] = [Target].[CurrencyID]
WHEN NOT MATCHED BY TARGET THEN
INSERT (
[GradeID]
,[ChargeRate]
,[FiscalPeriodID]
,[CurrencyID]
)
VALUES (
[Source].[GradeID]
,[Source].[ChargeRate]
,[Source].[FiscalPeriodID]
,[Source].[CurrencyID]
);
SELECT * FROM #TempTarget

Related

SQL query to insert stats of students to a table

I would like some help on how to write an sql server query in order to insert the monthly stats of students into a table.
My monthly stats table is something like this:
| StudentID | Year | Month | Grade1 | Grade2| Absences
Now I have another table with the Students Details like StudentID, name, etc. Also multiple other tables with grades, presence etc.
My goal is to select all studentsIDs from StudentDetails and insert them to the Monthly Stats table while I calculate Grade1, Grade2, and Absences from other multiple tables.
What is the best way to write such a query?
Do I first insert the StudentsIds, Year column and Month column with a select into query and after that, I iterate somehow through every studentid that were inserted and run update queries (for calculating rest of columns) for every studentID for the specified month and year?
I just need an example or some logic on how to achieve this.
For the the first part of inserting studentids I have this:
declare #maindate date = '20230101';
insert into Monthly_Stats (StudentID, Year, Month)
(select StudentID, AllocatedYear, AllocatedMonth
from Students_Allocation
where AllocatedMonth = DATEPART(MONTH, #maindate)
and AllocatedYear = DATEPART(YEAR, #maindate)
and Active = 1)
After insertion I would like somehow to update every other column (Grade1, Grade2,Absences...) from multiple other tables for each StudentID for the aforementioned Month and Year.
Any ideas?
This is what I usually perform batch update
UPDATE Monthly_Stats
SET
Monthly_Stats.GRADE1 = T1.Somedata + T2.Somedata + T3.Somedata
FROM
Monthly_Stats MS INNER JOIN TABLE_1 as T1
left join TABLE_2 as T2 on T1.StudentID = T2.StudentID and T1.Year = T2.Year and T1.Month = T2.Month
left join TABLE_3 as T3 on T1.StudentID = T3.StudentID and T1.Year = T3.Year and T1.Month = T3.Month
ON
MS.StudentID = T1.StudentID and MS.Year = T1.Year and MS.Month = T1.Month;
Be careful with the two left join. Depending on your database normalization, you may need more conditions in the ON clause to ensure the join output is as expected.
Hope it helps

WHERE clause on individual rows

I'm trying to write a query that will select rows from a table but I'm struggling a little with there WHERE clause.
I have a list of C# objects that has an ID and a Date. I want to find rows in my database where the ID is equal to one of the ids in my list of objects, but at the same time, the Date that comes with the specific ID has to be between a ValidFrom and a ValidTo field in the Database.
I already a query that will work but it is not a very pretty solution:
SELECT *
FROM [dbo].[Employees] AS emp
WHERE emp.IsDeleted = 0
AND (emp.EmployeeId = 1
AND (emp.ValidFrom <= '2017-05-01')
AND (emp.ValidTo > '2017-05-01'))
OR (emp.EmployeeId = 2
AND (emp.ValidFrom <= '2018-05-01')
AND (emp.ValidTo > '2018-05-01'))
And then I'd proceed to add the 'OR' statement from there.
Is there a more optimal way for me to accomplish this?
You can populate the filtering criteria in a separate table:
CREATE TABLE #RecordsTobeFiltered
(
EmployeeId INT
,ValidFrom DATE
,ValidTo DATE
);
INSERT INTO #RecordsTobeFiltered (EmployeeId, ValidFrom, ValidTo)
VALUES (1, '2017-05-01', '2017-05-01')
,(2, '2018-05-01', '2018-05-01')
--- and many records as you need
SELECT *
FROM [dbo].[Employees] AS emp
INNER JOIN #RecordsTobeFiltered F
ON emp.EmployeeId = f.EmployeeId
and emp.ValidFrom <= f.ValidFrom
and emp.validTo > f.ValidTo
WHERE emp.IsDeleted = 0;

SQL Server : update every 5 records with Past Months

I want to update 15 records in that first 5 records date should be June 2019,next 5 records with July 2019,last 5 records with Aug 2019 based on employee id,Can any one tell me how to write this type of query in SQL Server Management Studio V 17.7,I've tried with below query but unable to do for next 5 rows..
Like below query
Update TOP(5) emp.employee(nolock) set statusDate=GETDATE()-31 where EMPLOYEEID='XCXXXXXX';
To update only a certain number of rows of a table you will need to include a FROM clause and join a sub-query which limits the number of rows. I would suggest using OFFSET AND FETCH instead of top so that you can skip X number of rows
You will also want to use the DATEADD function instead of directly subtracting a number from the DateTime function GETDATE(). I'm not certain but I think your query will subtract milliseconds. If you intend to go back a month I would suggest subtracting a month rather than 31 days. Alternatively it might be easier to specify an exact date like '2019-06-01'
For example:
TableA
- TableAID INT PK
- EmployeeID INT FK
- statusDate DATETIME
UPDATE TableA
SET statusDate = '2019-06-01'
FROM TableA
INNER JOIN
(
SELECT TableAID
FROM TableA
WHERE EmployeeID = ''
ORDER BY TableAID
OFFSET 0 ROWS
FETCH NEXT 5 ROWS ONLY
) T1 ON TableA.TableAID = T1.TableAID
Right now it looks like your original query is updating the table employee rather than a purchases table. You will want to replace my TableA with whichever table it is you're updating and replace TableAID with the PK field of it.
You can use a ROW_NUMBER to get a ranking by employee, then just update the first 15 rows.
;WITH EmployeeRowsWithRowNumbers AS
(
SELECT
T.*,
RowNumberByEmployee = ROW_NUMBER() OVER (
PARTITION BY
T.EmployeeID -- Generate a ranking by each different EmployeeID
ORDER BY
(SELECT NULL)) -- ... in no particular order (you should supply one if you have an ordering column)
FROM
emp.employee AS T
)
UPDATE E SET
statusDate = CASE
WHEN E.RowNumberByEmployee <= 5 THEN '2019-06-01'
WHEN E.RowNumberByEmployee BETWEEN 6 AND 10 THEN '2019-07-01'
ELSE '2019-08-01' END
FROM
EmployeeRowsWithRowNumbers AS E
WHERE
E.RowNumberByEmployee <= 15

How to add column A (date column) to Column B ( number of business days) in teradata to get the new date?

Here's my data;
table A.pickup_date is a date column
table A.biz_days is the business days I want to add up to A.pickup_date
table B.date
table B.is_weekend (Y or N)
table B. is_holiday (Y or N)
Basically from table B, I know for each date, if any date is a business day or not. Now I want to have a third column in table A for the exact date after I add A.business_days to A.pickup_date.
Can anyone provide me with either a case when statement or procedure statement for this? Unfortunately we are not allowed to write our own functions in Teradata.
This is pretty darned ugly, but I think it should get you started.
First I created a volatile table to represent your table a:
CREATE VOLATILE TABLE vt_pickup AS
(SELECT CURRENT_DATE AS pickup_date,
8 AS Biz_Days) WITH DATA PRIMARY INDEX(pickup_date)
ON COMMIT PRESERVE ROWS;
INSERT INTO vt_pickup VALUES ('2015-02-24',5);
Then I joined that with sys_calendar.calendar to get the days of the week:
CREATE VOLATILE TABLE VT_Days AS
(
SELECT
p.pickup_date,
day_of_week
FROM
vt_pickup p
INNER JOIN sys_calendar.CALENDAR c
ON c.calendar_date >= p.pickup_date
AND c.calendar_date < (p.pickup_date + Biz_Days)
) WITH DATA
PRIMARY INDEX(pickup_date)
ON COMMIT PRESERVE ROWS
Then I can use all that to generate the actual delivery date:
SELECT
p.pickup_date,
p.biz_days,
biz_days + COUNT(sundays.day_of_week) + COUNT (saturdays.day_of_week) AS TotalDays,
COUNT (sundays.day_of_week) AS Suns,
COUNT (saturdays.day_of_week) AS Sats,
p.pickup_date + totaldays AS Delivery_Date,
FROM
vt_pickup p
LEFT JOIN vt_days AS Sundays ON
p.pickup_date = sundays.pickup_date
AND sundays.day_of_week = 1
LEFT JOIN vt_days AS saturdays ON
p.pickup_date = saturdays.pickup_date
AND saturdays.day_of_week = 7
GROUP BY 1,2
You should be able to use the logic with another alias for your holidays.
The easiest way to do this is calculating a sequential number of business days (add it as a new column to your calendar table if it's a recurring operation, otherwise using WITH):
SUM(CASE WHEN is_weekend = 'Y' OR is_holiday = 'Y' THEN 0 ELSE 1 END)
OVER (ORDER BY calendar_date
ROWS UNBOUNDED PRECEDING) AS biz_day#
Then you need two joins:
SELECT ..., c2.calendar_date
FROM tableA AS a
JOIN tableB AS c1
ON a.pickup_date = c1.calendar_date
JOIN tableB AS c2
ON c2.biz_day# = c1.biz_day# + a.biz_days
AND is_weekend = 'N'
AND is_holiday = 'N'

Subquery returned more than 1 value. this is not permitted when the subquery follows = or when the subquery is used as an expression?

I want to update multiple tables and values after inserting values in one table so I created a trigger. It works fine for inserts of one row, but as soon I insert more rows, SQL Server gives me following error:
subquery returned more than 1 value. this is not permitted when the subquery follows = or when the subquery is used as an expression?
Here is my trigger:
CREATE TRIGGER [dbo].[tbl_Sales_ForInsert]
ON [dbo].[SALES]
FOR INSERT
AS
BEGIN
DECLARE #ITEMMODEL varchar(100)
SELECT #ITEMMODEL = ITEM_MODEL FROM inserted
UPDATE SALES
SET PROFIT = TOTAL_PRICE - (SELECT QUANTITY FROM SALES WHERE ITEM_MODEL = #ITEMMODEL) * (SELECT RATE FROM ITEM_DETAILS WHERE ITEM_MODEL = #ITEMMODEL)
WHERE ITEM_MODEL = #ITEMMODEL
UPDATE ITEM_DETAILS
SET QUANTITY = QUANTITY - (SELECT QUANTITY FROM SALES WHERE ITEM_MODEL = #ITEMMODEL)
WHERE ITEM_MODEL = #ITEMMODEL
--UPDATE ITEM_DETAILS SET AMOUNT = AMOUNT - (SELECT RATE FROM ITEM_DETAILS WHERE ITEM_MODEL=#ITEMMODEL) * (SELECT QUANTITY FROM SALES WHERE ITEM_MODEL=#ITEMMODEL) where ITEM_MODEL=#ITEMMODEL
END
As I insert data in SALES table for 1st time the update got successful but for 2nd time it gives me above error remember ITEM_MODEL is foreign key constraint in SALES table.
I have been suffering with this error can anyone help me please?
Your fundamental flaw is that you seem to expect the trigger to be fired once per row - this is NOT the case in SQL Server. Instead, the trigger fires once per statement, and the pseudo table Inserted might contain multiple rows.
Given that that table might contain multiple rows - which one do you expect will be selected here??
SELECT #ITEMMODEL = ITEM_MODEL FROM inserted
It's undefined - you might get the values from arbitrary rows in Inserted.
You need to rewrite your entire trigger with the knowledge the Inserted WILL contain multiple rows! You need to work with set-based operations - don't expect just a single row in Inserted!
So in your case, your trigger code should look something like this:
CREATE TRIGGER [dbo].[tbl_Sales_ForInsert]
ON [dbo].[SALES]
FOR INSERT
AS
BEGIN
-- update the dbo.Sales table, set "PROFIT" to the difference of
-- TOTAL_PRICE and (QUANTITY * RATE) from the "Inserted" pseudo table
UPDATE s
SET s.PROFIT = i.TOTAL_PRICE - (i.QUANTITY * i.RATE)
FROM dbo.Sales s
INNER JOIN Inserted i ON i.ITEM_MODEL = s.ITEM_MODEL
-- update the dbo.ITEM_DETAILS table
UPDATE id
SET id.QUANTITY = id.QUANTITY - i.Quantity
FROM dbo.ITEM_DETAILS id
INNER JOIN Inserted i ON id.ITEM_MODEL = i.ITEM_MODEL
END
Marc_s is right about expecting the inserted pseudo table containing more than one row. There are instances that a query might work if a subquery was just limited to one row with a TOP(1).
UPDATE SALES
SET PROFIT = TOTAL_PRICE - (SELECT TOP(1) QUANTITY FROM SALES WHERE ITEM_MODEL = #ITEMMODEL)
* (SELECT TOP(1) RATE FROM ITEM_DETAILS WHERE ITEM_MODEL = #ITEMMODEL)
WHERE ITEM_MODEL = #ITEMMODEL

Resources