MS SQL insert into two tables - sql-server

I have four tables.
Person(
GUID uniqueidentifier
LastName varchar(20)
FirstName varchar(20)
SSN varchar(20)
ResidenceAddressGUID uniqueidentifier
)
Adress(
GUID uniqueidentifier
Street varchar(50)
Zip varchar(10)
)
Code(
CodeNumber
PersonGUID
Street
Zip
)
Tmp(
FirstName
LastName
Street
Zip
CodeNumber
)
And I want to move the data from Tmp to Person and Adress. What is the simplest way to do it?
EDIT:
I actually found out that TMP has a field "Code" that should be copied to the Code table with the street-adress and zip, no relationship to the address table.

No, you can't insert into multiple tables in one MySQL command. You can however use transactions.
BEGIN
INSERT INTO PERSON(LastName, FirstName)
SELECT
FirstName,
LastName
FROM TMP
INSERT INTO ADDRESS(Street)
SELECT
Street
FROM TMP;
COMMIT;

You can use a INSERT INTO statement combined with a SELECT statement to map the columns from TMP you want to insert into ADDRESS.
INSERT INTO Address(Street, Zip)
SELECT
Street,
Zip
FROM TMP
Also, you do the same thing for PERSON table.
INSERT INTO Person(LastName, FirstName, ResidenceAddressGUID)
SELECT
T.Street,
T.Zip,
A.GUID
FROM TMP T INNER JOIN ADDRESS A
ON T.Street = A.Street
AND T.Zip = A.Zip
Based on your later comments, I modified my queries and I hope now you understand the pattern and can do the queries for the CODE table yourself.

If (Address&Zip) combination is unique, you could use this:
INSERT INTO Address
SELECT DISTINCT
T.Street,
T.Zip
FROM
TMP T LEFT JOIN
Address A ON A.Zip = T.Zip AND Z.Street = T.Street
WHERE
A.GUID IS NULL
INSERT INTO Person (
FirstName,
LastName,
ResidenceAddressGUID)
SELECT
T.FirstName,
T.LastName,
A.GUID
FROM
TMP T INNER JOIN
Address A ON A.Street = T.Street AND A.ZIP = T.ZIP
If they are not unique, you should have a look at mssql INSERT OUTPUT option

Select statements can be used in combination with an insert statement to copy data from one table to another. One caveat, you must include a primary key for the tmp data your moving. In my example, I have included a sequence, which would work with an Oracle database. Depending on how the keys are assigned you may be able to omit the sequence, such as when a MySql autonumber exists.
insert into Person(GUID, FirstName, LastName)
select PERSON_SEQ.nextval, FirstName, LastName from Tmp;
insert into Adress(GUID, Street, Zip)
select ADRESS_SEQ.nextval, Street,Zip from Tmp;

Related

How can insert a subquery into the table using Declare

I am trying to code and run a full table so it can run sucessfully without error.
I am also making the sql table, by using Address and Person Table.
Here is the error
Msg 102, Level 15, State 1, Line 159
Incorrect syntax near '='.
/*
Foundation Lab
Creating Objects in SQL
Purpose: Create objects in SQL and set all the properties to achieve a robust well performing database: tables, views, sequences
Each comment describes a task
Write SQL statement(s) for the task under the comment
The script must be able to run repeatedly (which is why you will need to drop tables so that you can recreate them)
The SQL script meu be able to run completely and correctly on another database.
*/
/*
Tidy up any previous runs by dropping (deleting) any objects that this script creates.
Use the IF EXISTS clause for brevity.
*/
/*
Drop an existing table "Address"
Drop an existing View "AddressView"
Drop an existing table "Person"
Drop an existing view "Person"
*/
DROP TABLE IF EXISTS [dbo].[Person]
DROP TABLE IF EXISTS [dbo].[Address]
DROP VIEW IF EXISTS[dbo].[PersonView]
DROP VIEW IF EXISTS [dbo].[AddressView]
GO
/*
Create an Address table with columns:
AddressKey INT
HouseNumber INT
StreetName VARCHAR(100)
PostCode VARCHAR(8)
Set the AddressKey column as the primary key
Set the AddressKey column as an identity column
All columns must be not NULL
*/
/*
Add three rows to the table for the addresses
with the following values for HouseNumber, StreetName and PostCode
*/
CREATE TABLE Address
(
AddressKey INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
HouseNumber INT NOT NULL,
StreetName VARCHAR(100) NOT NULL,
PostCode VARCHAR(8) NOT NULL
);
/*
Add three rows to the table for the addresses
with the following values for HouseNumber, StreetName and PostCode
32, 'Acacia Avenue', 'SL1 1AA'
52, 'Cambray Road', 'SW12 9ES'
10, 'Downing Street', 'SW1A 2AA'
Use a single statement, not three statements
*/
INSERT INTO Address
(
HouseNumber,
StreetName,
PostCode
)
VALUES
(10, 'Downing Street', 'SW1A 2AA'),
(52, 'Cambray Road', 'SW12 9ES'),
(32, 'Acacia Avenue', 'SL1 1AA');
-- Check that the data in the Address tables is as expected
SELECT *
FROM Address;
/*
Create a view, named AddressView, that has HouseNumber and PostCode columns only
*/
GO
CREATE VIEW AddressView
AS
SELECT HouseNumber,
PostCode
FROM Address;
GO
-- Check the view works as expected.
SELECT *
FROM AddressView;
/*
Create a Person table with columns:
PersonKey INT
AddressKey INT
FirstName VARCHAR(100),
LastName VARCHAR(100)
DateOfBirth DATE
Set the PersonKey column as the primary key.
Set the PersonKey column as an identity column
All columns must be NOT NULL.
*/
CREATE TABLE Person
(
Personkey INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
AddressKey INT NOT NULL,
FirstName VARCHAR(100) NOT NULL,
LastName VARCHAR(100) NOT NULL,
DateofBirth DATE NOT NULL
);
-- Check that the rows are now in the Person table
SELECT *
FROM Person;
/*
Create a foreign key relationship
so that AddressKey in the Person table references the AddressKey column in the Address table.
*/
--ALTER TABLE Person ADD FOREIGN KEY (AddressKey) REFERENCES Address (AddressKey);
ALTER TABLE Person
ADD
FOREIGN KEY (AddressKey) REFERENCES Address (AddressKey);
/*
Add two sample rows to the table
* Boris Johnson in Downing Street (use variables)
* Theresa May in Cambray Road (use a SQL sub query)
*/
--'1959-01-19')
DECLARE #AddressKeyPM INT;
SELECT #AddressKeyPM = a.AddressKey FROM Address a WHERE a.PostCode = 'SW1A 2AA' AND a.HouseNumber = 10;
SELECT #AddressKeyPM
INSERT INTO Person
(
AddressKey,
FirstName,
LastName,
DateofBirth
)
VALUES
(#AddressKeyPM, 'Boris', 'Johnson', '1964-06-19');
INSERT INTO Person
(
AddressKey,
FirstName,
LastName,
DateofBirth
)
VALUES
(#AddressKeyPM = a.AddressKey FROM AddressKey a WHERE a.Postcode = 'SL1 1AA' AND a.HouseNumber = 32, 'Theresa', 'May', '1956-10-01');
-- Check that the Person table now has these two rows of data.
SELECT *
FROM Person;
/*
Show that the foreign key constraint is active
Try insert a row with a value for Addresskey that is not in the Address table
e.g. Kier Starmer, born on '1963-01-19', with AddressKey 12345
Note the error message
*/
/*INSERT INTO Person
(AddressKey, FirstName, LastName, DateofBirth)
VALUES
('12345', 'Kier', 'Starmer', '1963-01-19');
*/
-- It won't work because it needed updating with the main table with is the address table. --
-- Create a PersonView view that the FirstName and LastName (but not the DateOfBirth)
GO
CREATE VIEW PersonView
AS
SELECT FirstName,
LastName
FROM Person;
GO
/*
Opens the person view before altering
*/
SELECT *
FROM PersonView;
GO
-- Extend the view to include the House Number and PostCode from the Address table
SELECT * FROM Address a INNER JOIN Person p on p.AddressKey = a.AddressKey
GO
-- Alter the view to show only Boris and Theresa First, Last, HouseNumber and Postcode
Alter VIEW dbo.PersonView
AS
SELECT p.FirstName, p.LastName, a.HouseNumber, a.PostCode
FROM Person p INNER JOIN Address a on p.AddressKey = a.AddressKey
-- Check that the view is working correctly
GO
SELECT *
FROM PersonView;
So If you would like to teach me how to correct and fix the coding, then that would be great!
If you want to write a sub query then you can try this
INSERT INTO Person
(
AddressKey,
FirstName,
LastName,
DateofBirth
)
SELECT a.AddressKey, 'Theresa', 'May', '1956-10-01' FROM Address a WHERE a.PostCode = 'SL1 1AA' AND a.HouseNumber = 32;
Try this:
INSERT INTO Person
SELECT
AddressKey = (SELECT AddressKey FROM Address WHERE Postcode = 'SW1A 2AA' AND HouseNumber = 10)
, FirstName = 'Boris'
, LastName = 'Johnson'
, DateofBirth = '1964-06-19'
;
INSERT INTO Person
SELECT
AddressKey = (SELECT AddressKey FROM Address WHERE Postcode = 'SL1 1AA' AND HouseNumber = 32)
, FirstName = 'Theresa'
, LastName = 'May'
, DateofBirth = '1956-10-01'
;

SQL to join nvarchar(max) column with int column

I need some expert help to do left join on nvarchar(max) column with an int column. I have a Company table with EmpID as nvarchar(max) and this column holds multiple employee ID's separated with commas:
1221,2331,3441
I wanted to join this column with Employee table where EmpID is int.
I did something like below, But this doesn't work when I have 3 empId's or just 1 empID.
SELECT
A.*, B.empName AS empName1, D.empName AS empName2
FROM
[dbo].[Company] AS A
LEFT JOIN
[dbo].[Employee] AS B ON LEFT(A.empID, 4) = B.empID
LEFT JOIN
[dbo].[Employee] AS D ON RIGHT(A.empID, 4) = D.empID
My requirement is to get all empNames if there are multiple empID's in separate columns. Would highly appreciate any valuable input.
You should, if possible, normalize your database.
Read Is storing a delimited list in a database column really that bad?, where you will see a lot of reasons why the answer to this question is Absolutly yes!.
If, however, you can't change the database structure, you can use LIKE:
SELECT A.*, B.empName AS empName1, D.empName AS empName2
FROM [dbo].[Company] AS A
LEFT JOIN [dbo].[Employee] AS B ON ',' + A.empID + ',' LIKE '%,'+ B.empID + ',%'
You can give STRING_SPLIT a shot.
SQL Server (starting with 2016)
https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql
CREATE TABLE #Test
(
RowID INT IDENTITY(1,1),
EID INT,
Names VARCHAR(50)
)
INSERT INTO #Test VALUES (1,'John')
INSERT INTO #Test VALUES (2,'James')
INSERT INTO #Test VALUES (3,'Justin')
INSERT INTO #Test VALUES (4,'Jose')
GO
CREATE TABLE #Test1
(
RowID INT IDENTITY(1,1),
ID VARCHAR(MAX)
)
INSERT INTO #Test1 VALUES ('1,2,3,4')
GO
SELECT Value,T.* FROM #Test1
CROSS APPLY STRING_SPLIT ( ID , ',' )
INNER JOIN #Test T ON value = EID
It sounds like you need a table to link employees to companies in a formal way. If you had that, this would be trivial. As it is, this is cumbersome and super slow. The below script creates that linkage for you. If you truly want to keep your current structure (bad idea), then the part you want is under the "insert into..." block.
--clean up the results of any prior runs of this test script
if object_id('STACKOVERFLOWTEST_CompanyEmployeeLink') is not null
drop table STACKOVERFLOWTEST_CompanyEmployeeLink;
if object_id('STACKOVERFLOWTEST_Employee') is not null
drop table STACKOVERFLOWTEST_Employee;
if object_id('STACKOVERFLOWTEST_Company') is not null
drop table STACKOVERFLOWTEST_Company;
go
--create two example tables
create table STACKOVERFLOWTEST_Company
(
ID int
,Name nvarchar(max)
,EmployeeIDs nvarchar(max)
,primary key(id)
)
create table STACKOVERFLOWTEST_Employee
(
ID int
,FirstName nvarchar(max)
,primary key(id)
)
--drop in some test data
insert into STACKOVERFLOWTEST_Company values(1,'ABC Corp','1,2,3,4,50')
insert into STACKOVERFLOWTEST_Company values(2,'XYZ Corp','4,5,6,7,8')--note that annie(#4) works for both places
insert into STACKOVERFLOWTEST_Employee values(1,'Bob') --bob works for abc corp
insert into STACKOVERFLOWTEST_Employee values(2,'Sue') --sue works for abc corp
insert into STACKOVERFLOWTEST_Employee values(3,'Bill') --bill works for abc corp
insert into STACKOVERFLOWTEST_Employee values(4,'Annie') --annie works for abc corp
insert into STACKOVERFLOWTEST_Employee values(5,'Matthew') --Matthew works for xyz corp
insert into STACKOVERFLOWTEST_Employee values(6,'Mark') --Mark works for xyz corp
insert into STACKOVERFLOWTEST_Employee values(7,'Luke') --Luke works for xyz corp
insert into STACKOVERFLOWTEST_Employee values(8,'John') --John works for xyz corp
insert into STACKOVERFLOWTEST_Employee values(50,'Pat') --Pat works for XYZ corp
--create a new table which is going to serve as a link between employees and their employer(s)
create table STACKOVERFLOWTEST_CompanyEmployeeLink
(
CompanyID int foreign key references STACKOVERFLOWTEST_Company(ID)
,EmployeeID INT foreign key references STACKOVERFLOWTEST_Employee(ID)
)
--this join looks for a match in the csv column.
--it is horrible and slow and unreliable and yucky, but it answers your original question.
--drop these messy matches into a clean temp table
--this is now a formal link between employees and their employer(s)
insert into STACKOVERFLOWTEST_CompanyEmployeeLink
select c.id,e.id
from
STACKOVERFLOWTEST_Company c
--find a match based on an employee id followed by a comma or preceded by a comma
--the comma is necessary so we don't accidentally match employee "5" on "50" or similar
inner join STACKOVERFLOWTEST_Employee e on
0 < charindex( convert(nvarchar(max),e.id) + ',',c.employeeids)
or 0 < charindex(',' + convert(nvarchar(max),e.id) ,c.employeeids)
order by
c.id, e.id
--show final results using the official linking table
select
co.Name as Employer
,emp.FirstName as Employee
from
STACKOVERFLOWTEST_Company co
inner join STACKOVERFLOWTEST_CompanyEmployeeLink link on link.CompanyID = co.id
inner join STACKOVERFLOWTEST_Employee emp on emp.id = link.EmployeeID

SQL Server: Insert Into 2 Tables in one query

I have seen a few questions similar to this but none gave me the answer I was looking for.
So here is the example
[Table A]:
ID pk/auto-increment
Name
Age
...
[Table B]:
ID pk/auto-increment
FK_A_ID fk
Comment
I have an import of data that contains over 700 rows (and growing)
[Table Import]
Name / Age / ... / Comment
Is it possible to use a query similar to:
INSERT INTO [TABLE A] (Name, Age, ...), [Table B] (FK_A_ID, Comments)
SELECT
Name, Age, ..., ##IDENTITY, Comment
FROM
[TABLE Import]
Or a shorter question, is it possible to insert into 2 tables in the same query referencing the first insert? - when I right it out like that it seems unlikely.
Thanks
You can't. But you can use transaction, like this:
START TRANSACTION;
INSERT INTO tableA
SELECT Name, Age, ... FROM tableImport;
INSERT INTO tableB
SELECT A.ID, I.Comment
FROM tableA A INNER JOIN tableImport I
ON A.Name = I.Name AND A.Age = I.Age AND ...;-- (if columns not unique)
COMMIT;
I think you can do it with some temporary tables, and the row_number feature, then perform separate inserts in to TABLE A and TABLE B from the temporary table
UNTESTED
create table source
(
Name varchar(50),
age int,
comment varchar(100)
)
go
insert into source
(name, age, comment)
values
('adam',12,'something'),
('steve',12,'everything'),
('paul',12,'nothing'),
('john',12,'maybe')
create table a
(
id int identity(1,1) not null,
name varchar(50),
age int,
rowid int
)
go
create table b
(
id int identity(1,1) not null,
comment varchar(50),
fkid int not null
)
go
declare #tempa table
(
RowID int,
Name varchar(50),
age int,
comment varchar(100)
)
go
insert into #tempa
(rowid, name, age, comment)
SELECT ROW_NUMBER() OVER(ORDER BY name DESC) AS RowId,
name, age, comment
FROM source
go
insert into a
(name, age, rowid)
select name, age, rowid
from #tempa
insert into b
(comment, fkid)
select t.comment,
a.id as fkid
from #tempa t inner join a a
on t.rowid = a.rowid
In my honest opinion, the best way to do this is create a stored procedure and rollback in case of failure. If you do so, you don't need a transaction because until you supply the "COMMIT" command nothing will be inserted.

How to query with unknown combination of optional parameters in SQL Server without cursors?

I have a search that has three input fields (for arguments sake, let's say LastName, Last4Ssn, and DateOfBirth). These three input fields are in a dynamic grid where the user can choose to search for one or more combinations of these three fields. For example, a user might search based on the representation below:
LastName Last4Ssn DateOfBirth
-------- -------- -----------
Smith NULL 1/1/1970
Smithers 1234 NULL
NULL 5678 2/2/1980
In the example, the first row represents a search by LastName and DateOfBirth. The second, by LastName and Last4Ssn. And, the third, by Last4Ssn and DateOfBirth. This example is a bit contrived as the real-world scenario has four fields. At least two of the fields must be filled (don't worry about how to validate) with search data and it is possible that all fields are filled out.
Without using cursors, how does one use that data to join to existing tables using the given values in each row as the filter? Currently, I have a cursor that goes through each row of the above table, performs the join based on the columns that have values, and inserts the found data into a temp table. Something like this:
CREATE TABLE #results (
Id INT,
LastName VARCHAR (26),
Last4Ssn VARCHAR (4),
DateOfBirth DATETIME
)
DECLARE #lastName VARCHAR (26)
DECLARE #last4Ssn VARCHAR (4)
DECLARE #dateOfBirth DATETIME
DECLARE search CURSOR FOR
SELECT LastName, Last4Ssn, DateOfBirth
FROM #searchData
OPEN search
FETCH NEXT FROM search
INTO #lastName, #last4Ssn, #dateOfBirth
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #results
SELECT s.Id, s.LastName, s.Last4Ssn, s.DateOfBirth
FROM SomeTable s
WHERE Last4Ssn = ISNULL(#last4Ssn, Last4Ssn)
AND DateOfBirth = ISNULL(#dateOfBirth, DateOfBirth)
AND (
LastName = ISNULL(#lastName, LastName)
OR LastName LIKE #lastName + '%'
)
FETCH NEXT FROM search
INTO #lastName, #last4Ssn, #dateOfBirth
END
CLOSE search
DEALLOCATE search
I was hoping there was some way to avoid the cursor to make the code a bit more readable. Performance is not an issue as the table used to search will never have more than 5-10 records in it, but I would think that for more than a few, it'd be more efficient to query the data all at once rather than one row at a time. The SomeData table in my example can be very large.
I don't see why you can't just join the two tables together:
CREATE TABLE #results (
Id INT,
LastName VARCHAR (26),
Last4Ssn VARCHAR (4),
DateOfBirth DATETIME
)
INSERT INTO #results
select s.id, s.lastname, s.last4ssn, s.dateofbirth
from SomeTable s
join #searchData d
ON s.last4ssn = isnull(d.last4ssn, s.last4ssn)
AND s.dateofbirth = isnull(d.dateofbirth, s.dateofbirth)
AND (s.lastname = isnull(d.lastname, s.lastname) OR
OR s.lastname like d.lastname + '%')
EDIT:
Since the data is large, we'll need some good indices. One index isn't good enough since you effectively have 3 clauses OR'd together. So the first step is to create those indices:
CREATE TABLE SomeData (
Id INT identity(1,1),
LastName VARCHAR (26),
Last4Ssn VARCHAR (4),
DateOfBirth DATETIME
)
create nonclustered index ssnlookup on somedata (last4ssn)
create nonclustered index lastnamelookup on somedata (lastname)
create nonclustered index doblookup on somedata (dateofbirth)
The next step involves crafting the query to use those indices. I'm not sure what the best way here is, but I think it's to have 4 queries union'd together:
with searchbyssn as (
select somedata.* from somedata join #searchData
on somedata.last4ssn = #searchData.last4ssn
), searchbyexactlastname as (
select somedata.* from somedata join #searchData
on somedata.lastname = #searchData.lastname
), searchbystartlastname as (
select somedata.* from somedata join #searchData
on somedata.lastname like #searchdata.lastname + '%'
), searchbydob as (
select somedata.* from somedata join #searchData
on somedata.dateofbirth = #searchData.dateofbirth
), s as (
select * from searchbyssn
union select * from searchbyexactlastname
union select * from searchbystartlastname
union select * from searchbydob
)
select s.id, s.lastname, s.last4ssn, s.dateofbirth
from s
join #searchData d
ON (d.last4ssn is null or s.last4ssn = d.last4ssn)
AND s.dateofbirth = isnull(d.dateofbirth, s.dateofbirth)
AND (s.lastname = isnull(d.lastname, s.lastname)
OR s.lastname like d.lastname + '%')
Here's a fiddle showing the 4 index seeks: http://sqlfiddle.com/#!6/3741d/3
It shows significant resource usage for the union, but I think that would be negligible compared to the index scans for large tables. It wouldn't let me generate more than a few hundred rows of sample data. Since the number of result rows is small, it is not expensive to join to #searchData at the end and filter all the results again.

Best way to get multiple newly created key values from table inserts using SQL Server?

The function Scope_Identity() will provide the last generated primary key value from a table insert. Is there any generally accepted way to get multiple keys from an insertion of a set (an insert resulting from a select query)?
In SQL Server 2005 onwards, you can use the OUTPUT clause to get a returned set of values. From the linked article:
The following example creates the
EmployeeSales table and then inserts
several rows into it using an INSERT
statement with a SELECT statement to
retrieve data from source tables. The
EmployeeSales table contains an
identity column (EmployeeID) and a
computed column (ProjectedSales).
Because these values are generated by
the SQL Server Database Engine during
the insert operation, neither of these
columns can be defined in #MyTableVar.
USE AdventureWorks ;
GO
IF OBJECT_ID ('dbo.EmployeeSales', 'U') IS NOT NULL
DROP TABLE dbo.EmployeeSales;
GO
CREATE TABLE dbo.EmployeeSales
( EmployeeID int IDENTITY (1,5)NOT NULL,
LastName nvarchar(20) NOT NULL,
FirstName nvarchar(20) NOT NULL,
CurrentSales money NOT NULL,
ProjectedSales AS CurrentSales * 1.10
);
GO
DECLARE #MyTableVar table(
LastName nvarchar(20) NOT NULL,
FirstName nvarchar(20) NOT NULL,
CurrentSales money NOT NULL
);
INSERT INTO dbo.EmployeeSales (LastName, FirstName, CurrentSales)
OUTPUT INSERTED.LastName,
INSERTED.FirstName,
INSERTED.CurrentSales
INTO #MyTableVar
SELECT c.LastName, c.FirstName, sp.SalesYTD
FROM HumanResources.Employee AS e
INNER JOIN Sales.SalesPerson AS sp
ON e.EmployeeID = sp.SalesPersonID
INNER JOIN Person.Contact AS c
ON e.ContactID = c.ContactID
WHERE e.EmployeeID LIKE '2%'
ORDER BY c.LastName, c.FirstName;
SELECT LastName, FirstName, CurrentSales
FROM #MyTableVar;
GO
SELECT EmployeeID, LastName, FirstName, CurrentSales, ProjectedSales
FROM dbo.EmployeeSales;
GO
Use the row count and last identity value....
DECLARE #LastID int
DECLARE #Rows int
--your insert from a select here
SELECT #LastID=##IDENTITY, #Rows=##ROWCOUNT
--set of rows you want...
SELECT * FROM YourTable Where TableID>#LastID-#Rows AND TableID<=#LastID

Resources