SQL Server: Insert Into 2 Tables in one query - sql-server

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.

Related

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

T-SQL: Two Level Aggregation in Same Query

I have a query that joins a master and a detail table. Master table records are duplicated in results as expected. I get aggregation on detail table an it works fine. But I also need another aggregation on master table at the same time. But as master table is duplicated, aggregation results are duplicated too.
I want to demonstrate this situation as below;
If Object_Id('tempdb..#data') Is Not Null Drop Table #data
Create Table #data (Id int, GroupId int, Value int)
If Object_Id('tempdb..#groups') Is Not Null Drop Table #groups
Create Table #groups (Id int, Value int)
/* insert groups */
Insert #groups (Id, Value)
Values (1,100), (2,200), (3, 200)
/* insert data */
Insert #data (Id, GroupId, Value)
Values (1,1,10),
(2,1,20),
(3,2,50),
(4,2,60),
(5,2,70),
(6,3,90)
My select query is
Select Sum(data.Value) As Data_Value,
Sum(groups.Value) As Group_Value
From #data data
Inner Join #groups groups On groups.Id = data.GroupId
The result is;
Data_Value Group_Value
300 1000
Expected result is;
Data_Value Group_Value
300 500
Please note that, derived table or sub-query is not an option. Also Sum(Distinct groups.Value) is not suitable for my case.
If I am not wrong, you just want to sum value column of both table and show it in a single row. in that case you don't need to join those just select the sum as a column like :
SELECT (SELECT SUM(VALUE) AS Data_Value FROM #DATA),
(SELECT SUM(VALUE) AS Group_Value FROM #groups)
SELECT
(
Select Sum(d.Value) From #data d
WHERE EXISTS (SELECT 1 FROM #groups WHERE Id = d.GroupId )
) AS Data_Value
,(
SELECT Sum( g.Value) FROM #groups g
WHERE EXISTS (SELECT 1 FROM #data WHERE GroupId = g.Id)
) AS Group_Value
I'm not sure what you are looking for. But it seems like you want the value from one group and the collected value that represents a group in the data table.
In that case I would suggest something like this.
select Sum(t.Data_Value) as Data_Value, Sum(t.Group_Value) as Group_Value
from
(select Sum(data.Value) As Data_Value, groups.Value As Group_Value
from data
inner join groups on groups.Id = data.GroupId
group by groups.Id, groups.Value)
as t
The edit should do the trick for you.

Get first record from duplicate records and use its identity in other tables

I have two temp tables in which there are duplicates. Two tables contains records as below.
DECLARE #TempCompany TABLE (TempCompanyCode VARCHAR(100), TempCompanyName VARCHAR(100))
INSERT INTO #TempCompany VALUES ('00516','Company1')
INSERT INTO #TempCompany VALUES ('00135','Company1')
INSERT INTO #TempCompany VALUES ('00324','Company2')
INSERT INTO #TempCompany VALUES ('00566','Company2')
SELECT * FROM #TempCompany
DECLARE #TempProduct TABLE (TempProductCode VARCHAR(100), TempProductName VARCHAR(100), TempCompanyCode VARCHAR(100))
INSERT INTO #TempProduct VALUES ('001000279','Product1','00516')
INSERT INTO #TempProduct VALUES ('001000279','Product1','00135')
INSERT INTO #TempProduct VALUES ('001000300','Product2','00135')
INSERT INTO #TempProduct VALUES ('001000278','Product3','00566')
INSERT INTO #TempProduct VALUES ('001000278','Product3','00324')
INSERT INTO #TempProduct VALUES ('001000304','Product4','00566')
SELECT * FROM #TempProduct
Company is master table and product is child table. Product contains reference of company. Now these are my temp tables and I need to insert proper values from these tables to my main table.
I want to insert records as following
DECLARE #Company TABLE(CompanyID INT IDENTITY(1,1), CompanyCode VARCHAR(100), CompanyName VARCHAR(100))
INSERT INTO #Company VALUES ('00516','Company1')
DECLARE #IDOf00516 INT = ##IDENTITY
INSERT INTO #Company VALUES ('00324','Company2')
DECLARE #IDOf00324 INT = ##IDENTITY
SELECT * FROM #Company
DECLARE #Product TABLE(ProductID INT IDENTITY(1,1), ProductCode VARCHAR(100), ProductName VARCHAR(100), CompanyID INT)
INSERT INTO #Product VALUES ('001000279','Product1',#IDOf00516)
INSERT INTO #Product VALUES ('001000300','Product2',#IDOf00516)
INSERT INTO #Product VALUES ('001000278','Product3',#IDOf00324)
INSERT INTO #Product VALUES ('001000300','Product4',#IDOf00324)
SELECT * FROM #Product
I have attached the following image for how it looks when running the queries.
Can anybody help?
I would probably first insert unique records from tempCompany to Company table, then do an insert from TempProduct with a lookup to the inserted Company tables.
I.e. first insert companies:
INSERT INTO #Company (CompanyCode, CompanyName)
SELECT temp.TempCompanyCode, temp.TempCompanyName
FROM #TempCompany AS temp
Then insert products using a join to find the companyid:
INSERT INTO #Product (ProductCode, ProductName, CompanyId)
SELECT temp.TempProductCode, temp.TempProductName, c.CompanyID
FROM #TempProduct AS temp
JOIN #Company AS c ON temp.TempCompanyCode = c.CompanyCode -- Lookup to find CompanyID
However this does not take into account duplicates and the possibility that the main tables already have the records inserted.
Adding duplicate and check for already existing records the end result could look like:
--1. insert records not already in company table:
INSERT INTO #Company (CompanyCode, CompanyName)
SELECT DISTINCT temp.TempCompanyCode, temp.TempCompanyName
FROM #TempCompany AS temp
LEFT JOIN #Company AS c ON temp.TempCompanyCode = c.CompanyCode -- Will match Companies that already exists in #Companies
WHERE c.CompanyID IS NULL -- Company does not already exist
ORDER BY temp.TempCompanyCode
--2. insert product records:
INSERT INTO #Product (ProductCode, ProductName, CompanyId)
SELECT DISTINCT temp.TempProductCode, temp.TempProductName, c.CompanyID
FROM #TempProduct AS temp
JOIN #Company AS c ON temp.TempCompanyCode = c.CompanyCode -- Lookup to find CompanyID
LEFT JOIN #Product AS p ON temp.TempProductCode = p.ProductCode AND temp.TempCompanyCode = c.CompanyCode -- Will match products that already exists in #Products
WHERE p.ProductID IS NULL -- Product does not already exists
ORDER BY c.CompanyID, temp.TempProductCode
--3. Check result
SELECT * FROM #Company
SELECT * FROM #Product
Note that i make use of LEFT JOIN and add a WHERE condition to check whether the record already exists in your main tables.
This could very well be achieved using the MERGE statement - but I'm accustomed to the slighty less readable LEFT JOIN/WHERE method :)
EDIT
Only TempCompanyName is to be used for determining whether a row in TempCompany is unique. I.e. Company1 with CopmpanyCode 00135 should not be inserted.
To achieve this, I would make use of ROW_NUMBER in helping finding the company rows to insert. I've added an identity column to #TempCompany to make sure the first row inserted will be the row used (i.e. to make sure 00516 is used for Company1 and not 00135).
New #TempCompany definition:
DECLARE #TempCompany TABLE (TempCompanyId INT IDENTITY(1,1), TempCompanyCode VARCHAR(100), TempCompanyName VARCHAR(100))
And updated script with row_number added and an extra join from Product via TempCompany (on name) to Company. The extra join is to enable both Product1 with CompanyCode 00516 and CompanyCode 00135 to be handled correctly:
--1. insert records not already in company table:
;WITH OrderedTempCompanyRows AS (SELECT ROW_NUMBER() OVER (PARTITION BY TempCompanyName ORDER BY TempCompanyId) AS RowNo, TempCompanyCode, TempCompanyName FROM #TempCompany)
INSERT INTO #Company (CompanyCode, CompanyName)
SELECT DISTINCT temp.TempCompanyCode, temp.TempCompanyName
FROM OrderedTempCompanyRows temp
LEFT JOIN #Company AS c ON temp.TempCompanyName = c.CompanyName -- Will match Companies that already exists in #Companies
WHERE temp.RowNo = 1 -- Only first company according to row_number
AND c.CompanyID IS NULL -- Company does not already exist
ORDER BY temp.TempCompanyName
--2. insert product records:
INSERT INTO #Product (ProductCode, ProductName, CompanyId)
SELECT DISTINCT temp.TempProductCode, temp.TempProductName, c.CompanyID
FROM #TempProduct AS temp
JOIN #TempCompany tc ON temp.TempCompanyCode = tc.TempCompanyCode -- Find Companyname in #Tempcompany table
JOIN #Company AS c ON tc.TempCompanyName = c.CompanyName -- Join to #Company on Companyname to find CompanyID
LEFT JOIN #Product AS p ON temp.TempProductCode = p.ProductCode AND temp.TempCompanyCode = c.CompanyCode -- Will match products that already exists in #Products
WHERE p.ProductID IS NULL -- Product does not already exists
ORDER BY c.CompanyID, temp.TempProductName
--3. Check result
SELECT * FROM #Company
SELECT * FROM #Product

SQL Server pivot specified columns

I'm struggling with a query output issue. Please see sample data :
CREATE TABLE #Subject (ID INT PRIMARY KEY IDENTITY, Name NVARCHAR(50))
CREATE TABLE #Student (ID INT PRIMARY KEY IDENTITY, Name NVARCHAR(50))
CREATE TABLE #Grade (ID INT PRIMARY KEY IDENTITY,
StudentID INT REFERENCES #Student(ID),
SubjectID INT REFERENCES #Subject(ID),
Grade NVARCHAR(50), GradeText NVARCHAR(50))
INSERT INTO #Subject ( Name ) VALUES
(N'Maths'),
(N'Physics'),
(N'English')
INSERT INTO #Student ( Name ) VALUES
(N'Joe'),
(N'Tom'),
(N'Sally'),
(N'Fred'),
(N'Kim')
INSERT INTO #Grade
( StudentID, SubjectID, Grade, GradeText ) VALUES
(1,1,'Current','A'),
(2,3,'Expected','C'),
(3,2,'Mid','F'),
(4,1,'Final','B'),
(5,2,'Pre','C'),
(2,3,'Start','A'),
(3,1,'Current','A'),
( 1,2,'Expected','B'),
( 4,1,'Final','D'),
( 5,3,'Mid','E')
SELECT * FROM #Student
SELECT * FROM #Subject
SELECT * FROM #Grade
For the grade output I want to set some of the important grade types in the grade column to be their OWN columns. i.e. Current, Final I would like to be created as their own columns with associated grades, but the others can just be listed as they're not as important. This is a very simple example, the data I'm working with is much more complicated.
Is their a way to specify important columns to be created as their own columns and other data to just be listed as per normal? Also, all the pivot examples I've seen are querying from one table. What happens when you're query has many joins?
Are you aiming at something like this:
;with x as (
select *
from (
select StudentID, SubjectID, Grade, GradeText, Grade as grade_1, GradeText as GradeText_1
from (
select *
from #Grade
) as x
) as source
pivot (
max(GradeText_1)
for Grade_1 in ([Current], [Final])
) as pvt
)
select sub.Name as Subject, st.Name as Student, Grade, GradeText, [Current], Final
from x
inner join #Subject sub on x.SubjectID = sub.ID
inner join #Student st on x.StudentID = st.ID

Removing duplicate rows while also updating relations

My data is set up as follows:
CREATE TABLE TableA
(
id int IDENTITY,
name varchar(256),
description varchar(256)
)
CREATE TABLE TableB
(
id int IDENTITY,
name varchar(256),
description varchar(256)
) --unique constraint on name, description
CREATE TABLE TableA_TableB
(
idA int,
idB int
) --composite key referencing TableA and TableB
The situation is that I have many duplicate records in TableB that violate the unique constraint, and those duplicate records are referenced in TableA_TableB. So I'm trying to remove those records, which is simple enough (using the following CTE), but what would be the best way to update the records in TableA_TableB to reflect this change, i.e, have the TableA_TableB records reference the same ID in TableB as opposed to different IDs for each of the duplicates?
;WITH cte
AS (SELECT ROW_NUMBER() OVER (PARTITION BY [Name], [Description]
ORDER BY ( SELECT 0)) RN
FROM TableB)
DELETE FROM cte
WHERE RN = 1
Note: changed b.RowNum=1 to b.RowNum>1
First, you should try with ROLLBACK and then, if it's OK, uncomment COMMIT (this script wasn't tested):
DECLARE #UpdatedRows TABLE(ID INT PRIMARY KEY);
BEGIN TRANSACTION;
;WITH Base
AS(
SELECT ROW_NUMBER() OVER (PARTITION BY [Name], [Description] ORDER BY ( SELECT 0)) RowNum,
MIN(id) OVER(PARTITION BY [Name], [Description]) AS NewID,
ID -- Old ID
FROM TableB
),TableB_RowsForUpdate
AS(
SELECT *
FROM Base b
WHERE b.RowNum>1
)
UPDATE target
SET IDB=b.NewID
OUTPUT deleted.IDB INTO #UpdatedRows
FROM TableA_TableB target
INNER JOIN TableB_RowsForUpdate b ON target.IDB=b.ID;
DELETE b
FROM TableB b INNER JOIN #UpdatedRows upd ON b.ID=upd.ID;
ROLLBACK;
-- COMMIT;

Resources