What can I use instead of "Set Identity Insert On"? - sql

I have stored procedure in SQL Server. In this procedure, I delete duplicate records and insert one unique records to table. When I insert new unique record I am using below script.
SET IDENTITY_INSERT tbl_personnel_info ON
INSERT INTO tbl_personnel_info (pk_id, first_name, last_name, department, age, phone_number)
SELECT pk_id, first_name, last_name, department, age, phone_number
FROM #Unique
SET IDENTITY_INSERT tbl_personnel_info Off
Everthing is okey with this script but in the production SET IDENTITY_INSERT command needs to ALTER permission. Giving this permission should be dangerous so I can't give this permission. Also I must insert old pk_id instead of new. How can I do this without SET IDENTITY_INSERT command.
For example I have those records.
first_name
last_name
department
age
phone_number
John
Doe
IT
21
XXX
John
Doe
Finance
22
YYY
John
Doe
HR
23
ZZZ
And the record i want is
first_name
last_name
department
age
phone_number
John
Doe
IT
23
YYY
I also have my wanted record in the #Unique table. I want to delete 3 records and add record which is in the unique table.

I still believe that you have a bit of an xy problem here, and you would be better off preventing the duplicates at source rather than having a clean up procedure that needs to be regularly run by people other than the sa, but to actually answer your question one option would be not not delete the records you want to retain.
If you generate your #Unique table before you do the delete, then you can simply use something like:
SET XACT_ABORT ON;
BEGIN TRANSACTION;
UPDATE p WITH (UPDLOCK, SERIALIZABLE)
SET first_name = u.first_name,
last_name = u.last_name,
department = u.department,
age = u.age,
phone_number = u.phone_number
FROM tbl_personnel_info AS p
INNER JOIN #Unique AS u
ON u.pk_id = p.pk_id
WHERE NOT EXISTS
( SELECT u.first_name, u.last_name, u.department, u.age. u.phone_number
INTERSECT
SELECT p.first_name, p.last_name, p.department, p.age. p.phone_number
);
DELETE p
FROM tbl_personnel_info AS p
WHERE NOT EXISTS (SELECT 1 FROM #Unique AS u WHERE u.pk_id = p.pk_id);
COMMIT TRANSACTION;
This will update the records you want to retain and were originally planning to re-insert(but only if there is a value that needs to be updated), then only delete any records that don't exist in your temp table.
One big issue you may face here is foreign keys, you would presumably also need to tidy up any records related to the records you are deleting? This is another reason why you would be much better off preventing the duplicates at source and doing one single clear up (therefore stored procedure not required).
For a bit of an analogy, you have a hole in your boat and your current approach is to grab a bucket and keep scooping water over board, which you'll be doing forever and the hole will only get bigger. The hole is as small as it will ever be right now - so now is the best time to plug it.

Related

Informix SQL merging/joining and updating a table

I have an Informix database.
EMPLOYEE
LAST_NAME FIRST_NAME SSN
----------------------------------------------
SMITH JOHN 123456789
DOE JANE 987654321
SMITH JOHN 5555555
SCHEDULE
SSN START END
---------------------------
123456789 8:00AM 4:00PM
987654321 8:00AM 4:00PM
I need to take the profile John Smith with ssn 5555555 and replace it with the other John Smith with ssn 123456789. Also with this query I need to update the Schedule table to update to with the new ssn 5555555.
The most important thing is that the profile with ssn 123456789 is now attached to the schedule table with the ssn 5555555. Then I need to be able to delete the old employee with ssn 123456789.
An original version of the question seemed to discuss truncating a 9-digit SSN into a 7-digit not-quite-SSN. The revised mapping of 123456789 ⟶ 5555555 is not one that can be done algorithmically. The original question also discussed dealing with multiple mappings, though the example data only shows and describes one.
I'm assuming that the Employee table does not yet have the 7-digit SSN for John Smith, even though the sample data shows that.
There are multiple ways to address the requirements. This is one way to do it.
To address the generalized mapping, we can define an appropriate temporary table and populate it with the relevant data:
CREATE TEMP TABLE SSN_map
(
SSN_9 INTEGER NOT NULL UNIQUE,
SSN_7 INTEGER NOT NULL UNIQUE
);
INSERT INTO SSN_map(SSN_9, SSN_7) VALUES(123456789, 5555555);
-- …
This table might need to be a 'regular' table if it will take time to get its content correct. This would allow multiple sessions to get the mapping done correctly.
If there is an algorithmic mapping between the 9-digit and 7-digit SSNs, you could still create the SSN_map table applying the algorithm to the SSN_9 (SSN) value to create the mapping. You might also be able to apply the algorithm 'on the fly' and avoid the mapping table, but it makes the UPDATE statement harder to get right.
Assuming the database supports transactions (Informix databases can be logged, meaning 'with transactions', or unlogged, meaning 'without transactions'), then:
BEGIN WORK;
-- Create 7-digit SSN entry corresponding to 9-digit SSN
INSERT INTO Employee(SSN, Last_Name, First_Name)
SELECT m.SSN_7, e.Last_Name, e.First_Name -- , other employee columns
FROM Employee AS e
JOIN SSN_map AS m ON e.SSN = m.SSN_9;
Some (older) versions of Informix won't let you SELECT from the table you are modifying as shown. If that's a problem, then use:
SELECT m.SSN_7, e.Last_Name, e.First_Name -- , other employee columns
FROM Employee AS e
JOIN SSN_map AS m ON e.SSN = m.SSN_9
INTO TEMP mapped_emp WITH NO LOG;
INSERT INTO Employee SELECT * FROM mapped_emp;
DROP TABLE mapped_emp;
Continuing: the Employee table now contains two entries for each mapped employee, one with the old 9-digit SSN, one with the new 7-digit not-quite-SSN.
UPDATE Schedule
SET SSN = (SELECT SSN_7 FROM SSN_map WHERE SSN_9 = SSN)
WHERE EXISTS(SELECT * FROM SSN_map WHERE SSN_9 = SSN);
This updates the Schedule with the new not-quite-SSN value. The WHERE EXISTS clause is there to ensure that only rows with an entry in the SSN mapping table are changed. If it was not present, any unmatched row would have the SSN set to NULL, which won't be good.
DELETE FROM Employee
WHERE SSN IN (SELECT SSN_9 FROM SSN_map);
COMMIT WORK;
This deletes the old data with 9-digit SSNs from the Employee table. You could drop the SSN_map table at this point too.
Complete test script
-- Outside the test, the Employee and Schedule tables would exist
-- and be fully loaded with data before running this script
BEGIN WORK;
CREATE TABLE EMPLOYEE
(
LAST_NAME CHAR(15) NOT NULL,
FIRST_NAME CHAR(15) NOT NULL,
SSN INTEGER NOT NULL PRIMARY KEY
);
INSERT INTO Employee(Last_Name, First_Name, SSN) VALUES('SMITH', 'JOHN', 123456789);
INSERT INTO Employee(Last_name, First_Name, SSN) VALUES('DOE', 'JANE', 987654321);
CREATE TABLE SCHEDULE
(
SSN INTEGER NOT NULL REFERENCES Employee,
START DATETIME HOUR TO MINUTE NOT NULL,
END DATETIME HOUR TO MINUTE NOT NULL,
PRIMARY KEY(SSN, START)
);
INSERT INTO Schedule(SSN, Start, End) VALUES(123456789, '08:00', '16:00');
INSERT INTO Schedule(SSN, Start, End) VALUES(987654321, '08:00', '16:00');
SELECT * FROM Employee;
SELECT * FROM Schedule;
-- Start the work for mapping SSN to not-quite-SSN
CREATE TEMP TABLE SSN_map
(
SSN_9 INTEGER NOT NULL UNIQUE,
SSN_7 INTEGER NOT NULL UNIQUE
);
INSERT INTO SSN_map(SSN_9, SSN_7) VALUES(123456789, 5555555);
-- In the production environment, this is where you'd start the transaction
--BEGIN WORK;
-- Create 7-digit SSN entry corresponding to 9-digit SSN
INSERT INTO Employee(SSN, Last_Name, First_Name)
SELECT m.SSN_7, e.Last_Name, e.First_Name -- , other employee columns
FROM Employee AS e
JOIN SSN_map AS m ON e.SSN = m.SSN_9;
UPDATE Schedule
SET SSN = (SELECT SSN_7 FROM SSN_map WHERE SSN_9 = SSN)
WHERE EXISTS(SELECT * FROM SSN_map WHERE SSN_9 = SSN);
DELETE FROM Employee
WHERE SSN IN (SELECT SSN_9 FROM SSN_map);
SELECT * FROM Employee;
SELECT * FROM Schedule;
-- When satisfied, you'd use COMMIT WORK instead of ROLLBACK WORK
ROLLBACK WORK;
--COMMIT WORK;
Sample output
The first four lines are the 'before' data; the last four are the 'after' data.
SMITH JOHN 123456789
DOE JANE 987654321
123456789 08:00 16:00
987654321 08:00 16:00
DOE JANE 987654321
SMITH JOHN 5555555
5555555 08:00 16:00
987654321 08:00 16:00
As you can see, the material for John Smith was updated correctly, but the material for Jane Doe was unchanged. This is correct given that there was no mapping for Jane.
For people not used to Informix: yes, you really can include DDL statements like CREATE TABLE inside a transaction, and yes, the created table really is rolled back if you roll back the transaction. Not all DBMS are as generous as that.

Database upgrade issues with SSDT

I've been using SSDT for my database design for the past 9 months on a project and I'm about to abandon it for DbUp. Hopefully, there's a simpler solution...
Here's the problem. I have a database with the following table:
Persons
-----------
Id (PK)
Name
Email
I would like to upgrade my database to allow a person to have multiple email addresses:
Persons EmailAddresses
----------- ----------------
ID (PK) ID (PK)
Name PersonId (FK)
Email
To do this all within SSDT without dataloss I would need to do some fancy Pre & Post deployment scripting:
-- PreDeployment.sql
IF EXISTS (SELECT 1 FROM sys.Tables where [name] = 'Persons')
BEGIN
CREATE Table TMP_Persons (ID, Name);
CREATE Table TMP_EmailAddresses (ID, PersonId, Email);
SELECT INTO TMP_Persons FROM Persons
SELECT INTO TMP_EmailAddresses FROM Persons
DELETE FROM Persons
END
-- PostDeployment.sql
IF EXISTS (SELECT 1 FROM sys.Tables where [name] = 'TMP_Persons')
BEGIN
SELECT INTO Persons FROM TMP_Persons
SELECT INTO EmailAddresses FROM TMP_EmailAddresses
DROP TABLE TMP_Persons;
DROP TABLE TMP_EmailAddresses;
END
This (although tricky) is do-able and I've been doing this for majority of my changes. However, the problem comes where you have multiple versions of your database. For example, I have the following scenarios:
New Developers - No prior database
Dev Machine - Database is very current
Production - Database is a week or more old
In the event that Production is more out-of-date than the dev machine (possibly from not deploying for a while or from needing to rollback) the above script may fail. This means that the Dev would need to know and take into account prior versions of the database.
For example, say that the Persons table was previously named Users. I would have to account for this possibility in my Pre-Deployment script.
-- PreDeployment.sql
IF EXISTS (SELECT 1 FROM sys.Tables where [name] = 'Users')
BEGIN
CREATE Table TMP_Persons (ID, Name);
CREATE Table TMP_EmailAddresses (ID, PersonId, Email);
SELECT INTO TMP_Persons FROM Users
SELECT INTO TMP_EmailAddresses FROM Users
DELETE FROM Persons
END
IF EXISTS (SELECT 1 FROM sys.Tables where [name] = 'Persons')
BEGIN
CREATE Table TMP_Persons (ID, Name);
CREATE Table TMP_EmailAddresses (ID, PersonId, Email);
SELECT INTO TMP_Persons FROM Persons
SELECT INTO TMP_EmailAddresses FROM Persons
DELETE FROM Persons
END
As time goes on and more variations occur the PreDeployment script is going to get very chaotic and error-prone. This just seems unmanageable to me. Aside from switching to DbUp or something else, is there a better way to do this within SSDT?

Find the original record and change only that not the inserted one

I have tried it but not successful so far. Since my knowledge in query is limited, I thought I will better post it here.
I have students table with the following structure
create table students(
id int not null primary key identity,
sname varchar(25),
status varchar(25),
renew varchar(15),
enrollment datetime,
)
I have a number of students who has an ID, studentName(sname),status('active' or 'not-active'), renew('no' for new student, yes' for renewed student) and enrollment date.
insert into students values('jay','active','no','2010-01-01')
insert into students values('Phil','active','no','2010-01-01')
insert into students values('Cru','active','no','2010-01-01')
insert into students values('slow','active','no','2010-01-01')
insert into students values('true','active','no','2010-01-01')
insert into students values('false','active','no','2010-01-01')
Now I have an INSERT Trigger which is suppose to deactive an old student when a student is renewed. So if I insert the following which has renewal set to 'yes', it should make the already existing record 'inactive'.
insert into students values('false','active','yes','2011-01-01')
I wrote this INSERT Trigger and it works but it in-actives the old and the new inserted record both. I want only the original record to be inactivated. Also not that only enrollment date and nenew fields are different, the rest are the same between original and insert records. How to fix this? Here is my trigger
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[tr_renew_student]
ON [dbo].students
-- WITH ENCRYPTION
FOR INSERT
-- WITH APPEND
-- NOT FOR REPLICATION
AS
-- insert sql here
if exists(select * from inserted where Renew = 'yes')
BEGIN
UPDATE students
SET status = 'Inactive'
FROM Inserted i
INNER JOIN students T2
ON i.sname = T2.sname
END
Note that this is close approximation to my problem. Thank you
Change your update to this:
UPDATE students
SET status = 'Inactive'
FROM Inserted i
INNER JOIN students T2
ON i.sname = T2.sname
AND i.id <> t2.id
This checks that the row you are updating is NOT the newly inserted row.
Have you looked at ##identity? Years back I dealt with something similar and used ##identity to get the last created identity value; basically getting the latest identity value then setting all the records matching the criteria except the one with the ID returned via ##identity.
Read about the identity value getters here:
http://blog.sqlauthority.com/2007/03/25/sql-server-identity-vs-scope_identity-vs-ident_current-retrieve-last-inserted-identity-of-record/
Added: You're right about the inserted table. If you didn't want to / can't use the inserted table, your trigger could look something like this.
SELECT ##identity // <- this gets the last identity value inserted.
UPDATE students
SET status = 'Inactive'
WHERE students.name = (SELECT name FROM students WHERE id = ##identity)
AND id <> ##identity
Note: written from memory and not tested.

UPDATE query that fixes orphaned records

I have an Access database that has two tables that are related by PK/FK. Unfortunately, the database tables have allowed for duplicate/redundant records and has made the database a bit screwy. I am trying to figure out a SQL statement that will fix the problem.
To better explain the problem and goal, I have created example tables to use as reference:
alt text http://img38.imageshack.us/img38/9243/514201074110am.png
You'll notice there are two tables, a Student table and a TestScore table where StudentID is the PK/FK.
The Student table contains duplicate records for students John, Sally, Tommy, and Suzy. In other words the John's with StudentID's 1 and 5 are the same person, Sally 2 and 6 are the same person, and so on.
The TestScore table relates test scores with a student.
Ignoring how/why the Student table allowed duplicates, etc - The goal I'm trying to accomplish is to update the TestScore table so that it replaces the StudentID's that have been disabled with the corresponding enabled StudentID. So, all StudentID's = 1 (John) will be updated to 5; all StudentID's = 2 (Sally) will be updated to 6, and so on. Here's the resultant TestScore table that I'm shooting for (Notice there is no longer any reference to the disabled StudentID's 1-4):
alt text http://img163.imageshack.us/img163/1954/514201091121am.png
Can you think of a query (compatible with MS Access's JET Engine) that can accomplish this goal? Or, maybe, you can offer some tips/perspectives that will point me in the right direction.
Thanks.
The only way to do this is through a series of queries and temporary tables.
First, I would create the following Make Table query that you would use to create a mapping of the bad StudentID to correct StudentID.
Select S1.StudentId As NewStudentId, S2.StudentId As OldStudentId
Into zzStudentMap
From Student As S1
Inner Join Student As S2
On S2.Name = S1.Name
Where S1.Disabled = False
And S2.StudentId <> S1.StudentId
And S2.Disabled = True
Next, you would use that temporary table to update the TestScore table with the correct StudentID.
Update TestScore
Inner Join zzStudentMap
On zzStudentMap.OldStudentId = TestScore.StudentId
Set StudentId = zzStudentMap.NewStudentId
The most common technique to identify duplicates in a table is to group by the fields that represent duplicate records:
ID FIRST_NAME LAST_NAME
1 Brian Smith
3 George Smith
25 Brian Smith
In this case we want to remove one of the Brian Smith Records, or in your case, update the ID field so they both have the value of 25 or 1 (completely arbitrary which one to use).
SELECT min(id)
FROM example
GROUP BY first_name, last_name
Using min on ID will return:
ID FIRST_NAME LAST_NAME
1 Brian Smith
3 George Smith
If you use max you would get
ID FIRST_NAME LAST_NAME
25 Brian Smith
3 George Smith
I usually use this technique to delete the duplicates, not update them:
DELETE FROM example
WHERE ID NOT IN (SELECT MAX (ID)
FROM example
GROUP BY first_name, last_name)

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