Update a SQL table with values from another nested query - sql

I am currently using a SQL Server Agent job to create a master user table for my in-house web applications, pulling data from 3 other databases; Sharepoint, Practice Management System and Our HR Database.
Currently it goes...
truncate table my_tools.dbo.tb_staff
go
insert into my_tools.dbo.tb_staff
(username
,firstname
,surname
,chargeoutrate)
select right(wss.nt_user_name,
,hr.firstname
,hr.surname
,pms.chargeoutrate
from sqlserver.pms.dbo.staff as pms
inner join sqlserver.wss_content.dbo.vw_staffwss as wss
on pms.nt_user_name = wss.nt_user_name
inner join sqlserver.hrdb.dbo.vw_staffdetails as hr
on wss.fullname = hr.knownas
go
The problem is that the entire table is cleared as the first step so my auto increment primary key/identified on tb_staff is certain to change. Also if someone is removed from sharepoint or the PMS they will not be recreated on this table and this will cause inconsistencies throughout the database.
I want to preserve entries in this table, even after they are removed from one of the other systems.
I suppose what I want to do is:
1) Mark all exiting entries in tb_staff as inactive (using a column called active and set it to false)
2) Run the query on the three joined tables and update every found record, also marking them as active.
I can't see how I can nest a select statement within an Update statement like I have here with the Insert statement.
How can I achieve this please?
*please note I have edited my SQL down to 4 columns and simplified it so small errors are probably due to rushed editing. The real query is far bigger.

WITH source AS(
SELECT RIGHT(wss.nt_user_name, 10) nt_user_name, /*Or whatever - this is invalid in the original SQL*/
hr.firstname,
hr.surname,
pms.chargeoutrate
FROM staff AS pms
INNER JOIN vw_staffwss AS wss
ON pms.nt_user_name = wss.nt_user_name
INNER JOIN vw_staffdetails AS hr
ON wss.fullname = hr.knownas
)
MERGE
INTO tb_staff
USING source
ON source.nt_user_name= tb_staff.username /*Or whatever you are using as the key */
WHEN MATCHED
THEN UPDATE SET active=1 /*Can synchronise other columns here if needed*/
WHEN NOT MATCHED BY TARGET
THEN INSERT (username, firstname, surname, chargeoutrate, active) VALUES (nt_user_name,firstname, surname, chargeoutrate, 1)
WHEN NOT MATCHED BY source
THEN UPDATE SET active=0;

Related

Oracle sql: insert into by copying from two different tables

Although it's much more complex than I'm about to explain, I'll try to only stick to the relevant bits of what I want to accomplish. Our data model is quite complex, and the terms are also a bit confusing. We basically have a Request, and this request can have an active Request_Status (which has an Enum_Value to indicate it's current status), as well as previous Request_Statuses that aren't relevant anymore (to preserve history). A Person is linked to this Request, but the Values that are entered are linked to the current Request_Status.
So here are those tables, and the relevant columns:
Persons:
person_id
unique_code
Some other values
Requests:
request_id
fk_person_id
year
Some other values
Enum_Values:
enum_value_id
value
Some other values
Request_Statuses:
request_status_id
fk_request_id
fk_enum_value_id
created_date
Some other values
Values:
value_id
fk_request_status_id
Some other values
I have: A list of Person.unique_codes.
I want to achieve two things:
For each Person.unique_code I want to get the Request of the year 2017, and then create a new Request_Status with fk_enum_value_id set to 4, linked to this existing Request.
Create copies of the Values that were linked to the previously active Request_Status, and set their fk_request_status_id to the currently active Request_Status (the records I've created in step 1).
I've been able to do step 1 myself with a monstrous query (but it works..)
Here is the monstrous query for step 1:
Some things to note:
- There will only be a single Request of a given year.
- There can be more than one Request_Statuses for a given Request, so finding the active is the one with the highest created_date.
- p.unique_code IN ('12345','67890') is privatized and reduced code. In reality I have about 500 person.unique_codes.
- SELECT rs1.fk_request_id, 4 /*, some other irrelevant values */ FROM Request_Statuses rs1 LEFT JOIN Request_Statuses rs2 ON (rs1.fk_request_id = rs2.fk_request_id AND rs1.created_date < rs2.created_date) WHERE rs2.created_date IS NULL is copied from this SO answer for the question "Retrieving the last record in each group". I've used the windowing function at the top before, but it wasn't really suitable for sub-queries in combination with Oracle SQL, so I've used the (probably slightly slower) original method that was posted in 2009, which does work as intended.
INSERT_INTO Request_Statuses (fk_request_id, fk_enum_value_id /*, some other irrelevant values */)
(SELECT rs1.fk_request_id, 4 /*, some other irrelevant values */ FROM Request_Statuses rs1
LEFT JOIN Request_Statuses rs2 ON (rs1.fk_request_id = rs2.fk_request_id AND rs1.created_date < rs2.created_date)
WHERE rs2.created_date IS NULL AND rs1.fk_request_id IN (SELECT r.request_id FROM Requests r
WHERE r.fk_person_id IN (SELECT p.person_id FROM Persons p
WHERE p.unique_code IN ('12345','67890')) AND r.year = 2017));
And I'm currently working on step 2.
I currently have this:
INSERT INTO Values (fk_request_status_id /* some other irrelevant values */)
(SELECT /*TODO: Get request_status_id created in step 1*/, /* some other irrelevant values */
FROM Values v1 WHERE v1.fk_request_status_id IN (SELECT rs.status_id FROM Request_Statuses rs
WHERE rs.fk_request_id IN (SELECT r.request_id FROM Requests r
WHERE r.fk_person_id IN (SELECT p.person_id FROM Persons p
WHERE p.bsn IN ('12345','67890')) AND r.year = 2017) AND (SELECT COUNT(*) FROM Values v2
WHERE v2.fk_request_status_id = rs.status_id) > 0));
All I need is to get the request_status_id of the Request_Statuses I've created in step 1, based on the same person.unique_code, and insert it at the TODO..
I've also been thinking about using a default value for now, and then update just the fk_request_status_id with a third (monstrous) query. Unfortunately, the fk_request_status_id in combination with a second column in the Values table form an unique constraint, and fk_request_status_id cannot be empty, so I can't just insert any value here to update later.. Maybe I should remove the constraints temporarily, and add them later again after the query..
PS: Performance isn't that important. I've only got around 500-750 person.unique_codes for which I have to create one new Request_Status each (and zero to about 50 Values that are potentially linked to the previous active Request_Status). It should work in under 4 hours, though. ;)

SQL Server : trigger firing every time

For my school project I need to add a trigger to my SQL Server database. I decided a 'no double usernames' trigger on my Users table would be relevant.
The problem is, that this trigger is firing every time I execute an INSERT query. I can't figure out why this is happening every time. I even tried different ways of writing my trigger.
The trigger I have now:
CREATE TRIGGER [Trigger_NoDuplicates]
ON [dbo].[Users]
FOR INSERT
AS
BEGIN
SET NOCOUNT ON
IF(EXISTS(SELECT Username FROM Users
WHERE Username = (SELECT Username FROM inserted)))
BEGIN;
RAISERROR('This username already exists!',15, 0)
ROLLBACK
END
END
Thanks in advance!
A trigger always fires every time, do you mean "raises an error every time"?
You currently have the following (expanded to multiple lines to make it clearer)...
IF (
EXISTS (
SELECT Username
FROM users
WHERE Username = (SELECT Username FROM inserted)
)
)
The key point here is the name of the table inserted. Past tense. It's already happened.
Anything in the inserted table has already been inserted into the target table.
So, what you need to check is that the username is in the target table more than once already.
However, it is possible to insert more than one record in to a table at once. This means that Username = (SELECT Username FROM inserted) will cause its own error. (You can't compare a single value to a set of values, and inserted can contain more than one row => more than one username...)
This is how I would approach your trigger...
IF EXISTS (
SELECT
users.Username
FROM
users
INNER JOIN
inserted
ON inserted.Username = users.Username
GROUP BY
users.Username
HAVING
COUNT(*) > 1
)
This takes the (already inserted in to) users table, and picks out all the records that mach username with any record in the inserted table.
Then it GROUPs them by they username field.
Then it filters the results to only include groups with more than 1 record.
These groups (usernames), have duplicate entries and should cause your trigger to raise an error.
An alternative is a bit more similar to your approach, but many people won't recognise it, so I generally wouldn't recommend it...
IF EXISTS (
SELECT
users.Username
FROM
users
WHERE
users.Username = ANY (SELECT username FROM inserted)
GROUP BY
users.Username
HAVING
COUNT(*) > 1
)
The ANY keyword gets very rarely used, but does what it sounds like. It allows a single value to be compared to a set of values.
Finally, if your table has an IDENTITY column, you can avoid the GROUP BY by explicitly stating you don't want to compare a row to itself...
IF EXISTS (
SELECT
users.Username
FROM
users
INNER JOIN
inserted
ON inserted.Username = users.Username
AND inserted.id <> users.id
)

Insert Into Temp Table from another Temp Table throws error

I've seen a few of the questions and answers for this, but they all seem different than my problem. I am trying to insert into a temp table with a where clause from a real table to the id on a different temp table. Let me explain
Here is my first insert. It creates a temp table based on the parameters
Insert Into #programs (programs_id, state_program_ID, org_no, bldg_no)
Select programs_ID, state_program_ID, org_no, bldg_no
From programs as p
Where p.org_no = #org_no
And p.bldg_no = #bldg_no
And p.school_yr = #school_year
This returns a table that has a flat list of programs. Programs are offered at the school level and are slightly modified from the related state_program.
Then I need a list of all students that have taken the program from the program_student table.
Insert Into #programStudent (programs_id , ss_id, status_id)
Select ps.programs_id, ps.ss_id, ps.status_id
From program_student as ps
Where ps.programs_id = #programs.program_id
--'#programs.program_id' throws error
This would meet my need having all students that have taken any of the programs offered by the school at that school year.
The full error is
The multi-part identifier '#programs.program_id' could not be bound.
You are not addressing the #programs table in your second query. that last line will have to change to something like this:
WHERE EXISTS (SELECT TOP 1 1 FROM #Programs WHERE #Programs.programs_id = ps.program_id)
This is how you must address temp tables - they do not become variables in your current script - they are actual tables which get cleaned up after you disconnect. As such, they need to be introduced as tables - in a from clause, for each query that needs to reference them.
You had a typo when defining the #programs table - you called the column programs_id and not program_id. Just fix it, and you should be fine:
Insert Into #programs (program_id, state_program_ID, org_no, bldg_no)
-- "s" removed Here ---------^
Select programs_ID, state_program_ID, org_no, bldg_no
From programs as p
Where p.org_no = #org_no
And p.bldg_no = #bldg_no
And p.school_yr = #school_year

Recommended way to deal with updating m2m table postgres

I have the below tables
A project table
project_id,project_name
A skill table
skill_id,skill_name
A project_skill table (many to many relationship)
project_skill_id,project_id,skill_id
The browser will have a form which asks the user to enter a project name and and SO style autocomplete for tags. I'm sending the below json format back to sql for insertion
{"project_name":"foo","skills":["bar","baz"]}
My question relates to a situation where the user gets to edit an existing project.Assuming the user removes "baz" from skills and includes "zed". How do i properly deal with updating the many to many table
{"project_name":"foo","skills":["bar","zed","biz"]}
Do i remove all records from the m2m table and do a fresh insert with the new skills?
remove all records based on project_id
insert new records of bar,zed,biz
Do i check in the server what was removed/added and remove only what was actually removed
remove baz from table
add biz
This also pertains to modifying project_name etc. Do i check what was modified and update the necessary or perform a complete delete and insert
I'd use a CTE with a MERGE (note this is SQL Server but Postgres should be similar):
;WITH src AS
(
SELECT p.project_id, s.skill_id
FROM
dbo.project AS p
INNER JOIN #input AS i ON p.project_name = i.project_name
INNER JOIN dbo.skill AS s ON i.skill_name = s.skill_name
)
MERGE INTO dbo.project_skill AS tgt
USING src
ON tgt.project_id = src.project_id AND tgt.skill_id = src.skill_id
WHEN NOT MATCHED BY TARGET THEN
INSERT (project_id, skill_id) VALUES (src.project_id, src.skill_id)
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
where #input contains the new values:
DECLARE #input TABLE
(
project_name VARCHAR(100),
skill_name VARCHAR(100)
);

SQL Server 2008 - Help writing simple INSERT Trigger

This is with Microsoft SQL Server 2008.
I've got 2 tables, Employee and EmployeeResult and I'm trying to write a simple INSERT trigger on EmployeeResult that does this - each time an INSERT is done into EmployeeResult such as:
(Jack, 200, Sales)
(Jane, 300, Marketing)
(John, 400, Engineering)
It should look up for the Name, Department entry pairs, such as
(Jack, Sales),
(Jane, Marketing),
(John, Engineering)
within the Employee table, and if such an employee does not exist, should insert that into the Employee table.
What I have is this with unknowns on how to fix the "???"s:
CREATE TRIGGER trig_Update_Employee
ON [EmployeeResult]
FOR INSERT
AS
IF EXISTS (SELECT COUNT(*) FROM Employee WHERE ???)
BEGIN
INSERT INTO [Employee] (Name, Department) VALUES (???, ???)
END
Schema:
Employee
--------
Name, varchar(50)
Department, varchar (50)
EmployeeResult
--------------
Name, varchar(50)
Salary, int
Department, varchar (50)
You want to take advantage of the inserted logical table that is available in the context of a trigger. It matches the schema for the table that is being inserted to and includes the row(s) that will be inserted (in an update trigger you have access to the inserted and deleted logical tables which represent the the new and original data respectively.)
So to insert Employee / Department pairs that do not currently exist you might try something like the following.
CREATE TRIGGER trig_Update_Employee
ON [EmployeeResult]
FOR INSERT
AS
Begin
Insert into Employee (Name, Department)
Select Distinct i.Name, i.Department
from Inserted i
Left Join Employee e
on i.Name = e.Name and i.Department = e.Department
where e.Name is null
End
cmsjr had the right solution. I just wanted to point out a couple of things for your future trigger development. If you are using the values statement in an insert in a trigger, there is a stong possibility that you are doing the wrong thing. Triggers fire once for each batch of records inserted, deleted, or updated. So if ten records were inserted in one batch, then the trigger fires once. If you are refering to the data in the inserted or deleted and using variables and the values clause then you are only going to get the data for one of those records. This causes data integrity problems. You can fix this by using a set-based insert as cmsjr shows above or by using a cursor. Don't ever choose the cursor path. A cursor in a trigger is a problem waiting to happen as they are slow and may well lock up your table for hours. I removed a cursor from a trigger once and improved an import process from 40 minutes to 45 seconds.
You may think nobody is ever going to add multiple records, but it happens more frequently than most non-database people realize. Don't write a trigger that will not work under all the possible insert, update, delete conditions. Nobody is going to use the one record at a time method when they have to import 1,000,000 sales target records from a new customer or update all the prices by 10% or delete all the records from a vendor whose products you don't sell anymore.
check this code:
CREATE TRIGGER trig_Update_Employee ON [EmployeeResult] FOR INSERT AS Begin
Insert into Employee (Name, Department)
Select Distinct i.Name, i.Department
from Inserted i
Left Join Employee e on i.Name = e.Name and i.Department = e.Department
where e.Name is null
End