How to update several tables with a result query? - sql

I am working with SQL Server 2017, and I need to clean up duplicate rows and update all rows in other tables that contain my field.
I've got one table which contains my customers
USERID - Username
C79784F1-7254-4195-AF7F-66E651F3C995 | Robert
3C51AD27-21F1-4751-9931-7C66263B4708 | Robert
0D67A3E3-E7CF-4D95-935D-E077F4A6D315 | Bob
70A9552A-028B-4EA0-A309-4E93EEAB92E8 | William
1D8E9F5D-FEEB-43DA-9CDA-F22D610CDE78 | William
411BCC56-A4C9-4D9B-9D49-FA9255ECA968 | William
F0223C57-E3B2-4F94-9820-2D9A62A515D6 | Cathy
CREATE TABLE [dbo].[Users]
(
[UserID] [uniqueidentifier] NOT NULL,
[UserName] [nvarchar](260) NULL
);
INSERT INTO [dbo].[Users] (userid, username)
VALUES ('C79784F1-7254-4195-AF7F-66E651F3C995','Robert');
INSERT INTO [dbo].[Users] (userid, username)
VALUES ('3C51AD27-21F1-4751-9931-7C66263B4708','Robert');
INSERT INTO [dbo].[Users] (userid, username)
VALUES ('0D67A3E3-E7CF-4D95-935D-E077F4A6D315','Bob');
INSERT INTO [dbo].[Users] (userid, username)
VALUES ('70A9552A-028B-4EA0-A309-4E93EEAB92E8','William');
INSERT INTO [dbo].[Users] (userid, username)
VALUES ('1D8E9F5D-FEEB-43DA-9CDA-F22D610CDE78','William');
INSERT INTO [dbo].[Users] (userid, username)
VALUES ('411BCC56-A4C9-4D9B-9D49-FA9255ECA968','William');
INSERT INTO [dbo].[Users] (userid, username)
VALUES ('F0223C57-E3B2-4F94-9820-2D9A62A515D6','Cathy');
Then I have 7 tables that contains the userid column and 1 table with another name column
CreatedById - CreationDate - Folders
C79784F1-7254-4195-AF7F-66E651F3C995 | 2018-02-24 | Folder1
3C51AD27-21F1-4751-9931-7C66263B4708 | 2019-10-12 | PAD
0D67A3E3-E7CF-4D95-935D-E077F4A6D315 | 2021-05-12 | IEF
70A9552A-028B-4EA0-A309-4E93EEAB92E8 | 2021-01-27 | WIP
1D8E9F5D-FEEB-43DA-9CDA-F22D610CDE78 | 2021-06-29 | OLD_ONE
411BCC56-A4C9-4D9B-9D49-FA9255ECA968 | 2021-01-21 | ToTest
CREATE TABLE [dbo].[catalog]
(
[CreatedById] [uniqueidentifier] NOT NULL,
[CreationDate] DATE NOT NULL,
[Folders] [nvarchar](425)
);
INSERT INTO [dbo].[catalog] (CreatedById, CreationDate, Folders)
VALUES ('C79784F1-7254-4195-AF7F-66E651F3C995','2018-02-24','Folder1');
INSERT INTO [dbo].[catalog] (CreatedById, CreationDate, Folders)
VALUES ('3C51AD27-21F1-4751-9931-7C66263B4708','2019-10-12','PAD');
INSERT INTO [dbo].[catalog] (CreatedById, CreationDate, Folders)
VALUES ('0D67A3E3-E7CF-4D95-935D-E077F4A6D315','2021-05-12','IEF');
INSERT INTO [dbo].[catalog] (CreatedById, CreationDate, Folders)
VALUES ('70A9552A-028B-4EA0-A309-4E93EEAB92E8','2021-01-27','WIP');
INSERT INTO [dbo].[catalog] (CreatedById, CreationDate, Folders)
VALUES ('1D8E9F5D-FEEB-43DA-9CDA-F22D610CDE78','2021-06-29','OLD_ONE');
INSERT INTO [dbo].[catalog] (CreatedById, CreationDate, Folders)
VALUES ('411BCC56-A4C9-4D9B-9D49-FA9255ECA968','2021-01-21','ToTest');
My other tables:
CREATE TABLE table3 ([USERID] [uniqueidentifier] NOT NULL);
CREATE TABLE table4 ([USERID] [uniqueidentifier] NOT NULL);
CREATE TABLE table5 ([USERID] [uniqueidentifier] NOT NULL);
CREATE TABLE table6 ([USERID] [uniqueidentifier] NOT NULL);
INSERT INTO table3 (USERID) VALUES ('C79784F1-7254-4195-AF7F-66E651F3C995');
INSERT INTO table3 (USERID) VALUES ('3C51AD27-21F1-4751-9931-7C66263B4708');
INSERT INTO table3 (USERID) VALUES ('0D67A3E3-E7CF-4D95-935D-E077F4A6D315');
INSERT INTO table3 (USERID) VALUES ('70A9552A-028B-4EA0-A309-4E93EEAB92E8');
INSERT INTO table3 (USERID) VALUES ('1D8E9F5D-FEEB-43DA-9CDA-F22D610CDE78');
INSERT INTO table3 (USERID) VALUES ('411BCC56-A4C9-4D9B-9D49-FA9255ECA968');
INSERT INTO table4 (USERID) VALUES ('C79784F1-7254-4195-AF7F-66E651F3C995');
INSERT INTO table4 (USERID) VALUES ('3C51AD27-21F1-4751-9931-7C66263B4708');
INSERT INTO table4 (USERID) VALUES ('0D67A3E3-E7CF-4D95-935D-E077F4A6D315');
INSERT INTO table4 (USERID) VALUES ('70A9552A-028B-4EA0-A309-4E93EEAB92E8');
INSERT INTO table4 (USERID) VALUES ('1D8E9F5D-FEEB-43DA-9CDA-F22D610CDE78');
INSERT INTO table4 (USERID) VALUES ('411BCC56-A4C9-4D9B-9D49-FA9255ECA968');
INSERT INTO table5 (USERID) VALUES ('C79784F1-7254-4195-AF7F-66E651F3C995');
INSERT INTO table5 (USERID) VALUES ('3C51AD27-21F1-4751-9931-7C66263B4708');
INSERT INTO table5 (USERID) VALUES ('0D67A3E3-E7CF-4D95-935D-E077F4A6D315');
INSERT INTO table5 (USERID) VALUES ('70A9552A-028B-4EA0-A309-4E93EEAB92E8');
INSERT INTO table5 (USERID) VALUES ('1D8E9F5D-FEEB-43DA-9CDA-F22D610CDE78');
INSERT INTO table5 (USERID) VALUES ('411BCC56-A4C9-4D9B-9D49-FA9255ECA968');
INSERT INTO table6 (USERID) VALUES ('C79784F1-7254-4195-AF7F-66E651F3C995');
INSERT INTO table6 (USERID) VALUES ('3C51AD27-21F1-4751-9931-7C66263B4708');
INSERT INTO table6 (USERID) VALUES ('0D67A3E3-E7CF-4D95-935D-E077F4A6D315');
INSERT INTO table6 (USERID) VALUES ('70A9552A-028B-4EA0-A309-4E93EEAB92E8');
INSERT INTO table6 (USERID) VALUES ('1D8E9F5D-FEEB-43DA-9CDA-F22D610CDE78');
INSERT INTO table6 (USERID) VALUES ('411BCC56-A4C9-4D9B-9D49-FA9255ECA968');
I want to clean the duplicates and keep only one record in the database.
First, I created a query that gives me only the duplicate rows and keeps only one record.
With this record, I'll update table3, table4, table5, table6,
WITH singleUser AS
(
SELECT
a.UserName,
a.UserID
FROM
(SELECT
userid,
Username,
ROW_NUMBER() OVER (PARTITION BY username ORDER BY username ASC) AS rowNo,
COUNT(*) OVER (PARTITION BY username) AS c
FROM
dbo.users
WHERE
1 = 1
GROUP BY
userid, Username) a
WHERE
1 = 1
AND rowNo > 1
AND c = rowNo
)
Then I created a query that gives me all the tables that contain my 'Userid' column.
This query will return: table3, table4, table5, table6
WITH tableToUpdate AS
(
SELECT
TABLE_CATALOG AS 'Bdd',
TABLE_SCHEMA AS 'Schema',
TABLE_NAME AS 'TableName',
COLUMN_NAME AS 'ColumnName'
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
1 = 1
AND CASE
WHEN COLUMN_NAME = 'CreatedByID' THEN 1
WHEN COLUMN_NAME = 'UserID' THEN 1
ELSE 0
END = 1
)
And finally I created my merge query
MERGE INTO dbo.catalog c
USING (SELECT
u.UserID AS UserIDUsers,
su.UserID AS UserIDSingleUser
FROM
dbo.Users u
JOIN
singleUser su ON su.Username = u.username
WHERE
1 = 1) S ON c.CreatedByID = s.UserIDUsers
WHEN MATCHED THEN
UPDATE
SET c.CreatedByID =S.UserIDSingleUser
My merge result:
CreatedById - CreationDate - Folders
C79784F1-7254-4195-AF7F-66E651F3C995 | 2018-02-24 | Folder1
C79784F1-7254-4195-AF7F-66E651F3C995 | 2019-10-12 | PAD
0D67A3E3-E7CF-4D95-935D-E077F4A6D315 | 2021-05-12 | IEF
70A9552A-028B-4EA0-A309-4E93EEAB92E8 | 2021-01-27 | WIP
70A9552A-028B-4EA0-A309-4E93EEAB92E8 | 2021-06-29 | OLD_ONE
70A9552A-028B-4EA0-A309-4E93EEAB92E8 | 2021-01-21 | ToTest
It works very well, but is there a way to automatize it ?
Actually I've created 8 queries, but only the merge section change.
Also, how can I remove duplicate rows in my dbo.users table, after all fields have been updated?
Thank you for your help.

I came back to answer to my own question. After some days i finally did it.
beforehand I've created a table which comes from my CTE query (singleUser)
CREATE OR ALTER PROCEDURE dbo.mergeUserID
AS
DECLARE #tableName nvarchar(50)
DECLARE #sql nvarchar(max)
DECLARE #columnName nvarchar(50)
BEGIN
DECLARE cursor_db CURSOR FOR
SELECT
TABLE_NAME AS 'TableName'
,COLUMN_NAME AS 'ColumnName'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE 1=1
AND CASE
WHEN COLUMN_NAME = 'CreatedByID' then 1
WHEN COLUMN_NAME = 'ModifiedByID' then 1
WHEN COLUMN_NAME = 'OwnerID'then 1
WHEN COLUMN_NAME = 'UserID' then 1
ELSE 0
END = 1
OPEN cursor_db
FETCH NEXT FROM cursor_db INTO #tableName, #columnName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql ='MERGE INTO '
+ #tablename+ ' t USING (
SELECT
u.UserID as UserIDUsers
,su.UserID as UserIDSingleUser
FROM dbo.Users u
JOIN dbo.singleUser su on su.UserName = u.username
WHERE 1=1
)S ON t.'+#columnName+' = s.UserIDUsers
WHEN MATCHED THEN
UPDATE
SET t.'+#columnName+' = S.UserIDSingleUser;'
exec sp_executesql #sql
PRINT #sql
FETCH NEXT FROM cursor_db INTO #tableName, #columnName
END
CLOSE cursor_db
DEALLOCATE cursor_db
END;
GO
------------------------------------------------
DECLARE #RC nvarchar(max)
-- TODO: Set parameter values here.
EXECUTE #RC = [dbo].[mergeUserID]
PRINT #RC
GO
I don't know if it's well coded because it's the first time I've done this.
For example I've seen on some forum they put the ; after FETCH / CLOSE / DEALLOCATE ; others not.
with semicolon
Microsoft without semicolon
so Who is right or wrong, dunno ?

Related

Concatenate values in sql server and group by another value | ms sql server

I have simple select
select distinct UserName, Company from Users inner join Companies on Users.UserName = Companies.UserFullName
The result of query looks like this:
User1 | Company1
User1 | Company2
User1 | Company3
User1 | Company4
User2 | Company3
User2 | Company6
User2 | Company1
User2 | Company5
I want to concatenate Company values and group it by User. Like this:
User1 | Company1 , Company2 , Company3 , Company4
User2 | Company3 , Company6 , Company1 , Company5
Is it possible thing to do in sql server?
If you are using SQL Server 2017, you can use the new function, STRING_AGG:
SELECT UserName,
STRING_AGG(Company,' , ') WITHIN GROUP (ORDER BY Company) AS Companies
FROM #T1
GROUP BY Username;
Note that you have no ordering in your table, thus the order of 'Company3, Company6, Company1, Company5' cannot be retained for 'User2' unless you have some other column to order by.
The below code snippet would work for you -
CREATE TABLE #t1 (UserName VARCHAR(100), Company VARCHAR(100));
INSERT #t1 values ('User1','Company1');
INSERT #t1 values ('User1','Company2');
INSERT #t1 values ('User1','Company3');
INSERT #t1 values ('User1','Company4');
INSERT #t1 values ('User2','Company3');
INSERT #t1 values ('User2','Company6');
INSERT #t1 values ('User2','Company1');
INSERT #t1 values ('User2','Company5');
GO
select
UserName,
stuff((
select ',' + t.[Company]
from #t1 t
where t.UserName = #t1.UserName
order by t.[Company]
for xml path('')
),1,1,'') as CompanyName
from #t1
group by UserName;
Another solution that doesn't require the clause FOR XML PATH
This solution is a loop based
SET NOCOUNT ON
IF OBJECT_ID ('tempdb..#t1') IS NOT NULL DROP TABLE #T1;
IF OBJECT_ID ('tempdb..#t2') IS NOT NULL DROP TABLE #T2;
CREATE TABLE #t1 (UserName VARCHAR(100), Company VARCHAR(100));
INSERT #t1 values ('User1','Company1');
INSERT #t1 values ('User1','Company2');
INSERT #t1 values ('User1','Company3');
INSERT #t1 values ('User1','Company4');
INSERT #t1 values ('User2','Company3');
INSERT #t1 values ('User2','Company6');
INSERT #t1 values ('User2','Company1');
INSERT #t1 values ('User2','Company5');
GO
DECLARE #Table TABLE (UserName VARCHAR(100), Combined VARCHAR(4000))
DECLARE #i INT = 1
SELECT DENSE_RANK () OVER (ORDER BY UserName) Seq, *
INTO #T2
FROM #t1
WHILE #i <= (SELECT MAX(Seq) FROM #T2)
BEGIN
DECLARE #ConcatedCompany VARCHAR(4000) = ''
SELECT #ConcatedCompany+= ',' + Company
FROM #T2
WHERE Seq = #i
INSERT INTO #Table (UserName , Combined)
SELECT UserName , STUFF(#ConcatedCompany,1,1,'')
FROM #T2
WHERE Seq = #i
GROUP BY UserName
SET #i +=1
END
SELECT *
FROM #Table
UPDATE!!
Larnu's comment regarding the performance is a good point, usually I'd avoid using WHILE loops and think in terms of set based operations
so, here is the solution without a loop and without "FOR XML PATH"
SET NOCOUNT ON
IF OBJECT_ID ('tempdb..#t1') IS NOT NULL DROP TABLE #T1
IF OBJECT_ID ('tempdb..##T2') IS NOT NULL DROP TABLE ##T2
IF OBJECT_ID ('tempdb..##Table') IS NOT NULL DROP TABLE ##Table
CREATE TABLE #t1 (UserName VARCHAR(100), Company VARCHAR(100));
INSERT #t1 values ('User1','Company1');
INSERT #t1 values ('User1','Company2');
INSERT #t1 values ('User1','Company3');
INSERT #t1 values ('User1','Company4');
INSERT #t1 values ('User2','Company3');
INSERT #t1 values ('User2','Company6');
INSERT #t1 values ('User2','Company1');
INSERT #t1 values ('User2','Company5');
GO
CREATE TABLE ##Table (UserName nvarchar(50), Combined nvarchar(4000))
SELECT DENSE_RANK () OVER (ORDER BY UserName) Seq, *
INTO ##T2
FROM #t1
DECLARE #cmd NVARCHAR(MAX) =''
;WITH T2 (Seq) AS
(
SELECT DISTINCT Seq
FROM ##T2
)
SELECT #cmd += 'DECLARE #ConcatedCompany'+CONVERT(VARCHAR(10),Seq)+' NVARCHAR(4000) = ''''
SELECT #ConcatedCompany'+CONVERT(VARCHAR(10),Seq)+' += '','' + Company FROM ##T2 WHERE Seq = '+CONVERT(VARCHAR(10),Seq)+ CHAR(10)+
' INSERT INTO ##Table (UserName, Combined)
SELECT UserName , STUFF(#ConcatedCompany'+CONVERT(VARCHAR(10),Seq)+',1,1,'''')
FROM ##T2 WHERE Seq = '+CONVERT(VARCHAR(10),Seq) + CHAR(10)+
' GROUP BY UserName '+CHAR(10)+
';'
+CHAR(10)
FROM T2
EXEC sp_executesql #Cmd
SELECT UserName , Combined
FROM ##Table
DROP TABLE ##Table
DROP TABLE ##T2

Fill automatic data in second table when insert data in one table

I have two table Table1 and Table2 and structure of table as
Table1
Primary Key | Name
Table2
Primary Key | Table1_Id_pk | Status - true/false value
When I insert data into Table2 I want to automatically transfer that data in table1 from Table2 which have Status false.
You can create AFTER INSERT trigger:
CREATE TRIGGER DataMigration
ON Table2
AFTER INSERT
AS
BEGIN
INSERT INTO Table1
SELECT *
FROM TAble2
WHERe Status = 'false'
END
GO
Check this example, use ##identity to get inserted row-id and use that id for child table.
declare #t1 table (pk int identity(1,1) not null , name varchar(50) )
declare #t2 table (pk int identity(1,1) not null , t1pk int, status bit )
declare #new_table1Id int
insert into #t1 values( 'ajay')
set #new_table1Id = ##IDENTITY -This gives you last inserted id of #t1
insert into #t2 values( #new_table1Id , 0)
select * from #t1
select * from #t2
##IDENTITY official documentation

copy a column from one table to another where table1.col = table2.col

Suppose there are two tables which have the data mentioned in the insert query. There is no foreign key references between the two table.
create table uref.slave (
SLAVE_ID SMALLINT NOT NULL PRIMARY KEY,
DESC VARCHAR(20)
);
INSERT INTO uref.SLAVE values (1, null)
INSERT INTO uref.SLAVE values (2, null)
create table uref.master (
MASTER_ID SMALLINT NOT NULL PRIMARY KEY,
SLAVE_ID SMALLINT,
DESC VARCHAR(20)
);
INSERT INTO uref.MASTER values (1,1,'value1')
INSERT INTO uref.MASTER values (2,2,'value2')
Now I need a query which will copy uref.master.DESC into uref.slave.DESC based on uref.master.SLAVE_ID = uref.slave.SLAVE_ID.
The simplest solution may be to use MERGE.
MERGE INTO uref.SLAVE s
USING uref.MASTER m
ON (s.SLAVE_ID = m.SLAVE_ID)
WHEN MATCHED
THEN UPDATE SET Desc = m.Desc
It could be refined to update only when there is a change to be made
MERGE INTO uref.SLAVE s
USING uref.MASTER m
ON (s.SLAVE_ID = m.SLAVE_ID)
WHEN MATCHED
and ( s.Desc <> m.Desc
or (s.Desc is null and m.Desc is not null)
)
THEN UPDATE SET Desc = m.Desc
UPDATE uref.SLAVE t1
SET Desc =
(
SELECT t2.Desc
FROM uref.MASTER t2
WHERE t1.SLAVE_ID = t2.SLAVE_ID
)
WHERE EXISTS
(
SELECT *
FROM uref.MASTER t2
WHERE t1.SLAVE_ID = t2.SLAVE_ID
AND NOT t1.Desc=t2.Desc
)
AND t1.Desc IS NULL
if sql server, Try below sql: (recheck the table name and fields)
declare #urefSlave table (
SLAVE_ID SMALLINT ,
[DESC] VARCHAR(20)
);
INSERT INTO #urefSlave values (1, null)
INSERT INTO #urefSlave values (2, null)
Declare #urefMaster table (
MASTER_ID SMALLINT,
SLAVE_ID SMALLINT,
[DESC] VARCHAR(20)
);
INSERT INTO #urefMaster values (1,1,'value1')
INSERT INTO #urefMaster values (2,2,'value2')
select * from #urefMaster
select * from #urefSlave
update #urefSlave
set [DESC] = b.[DESC]
from #urefSlave a inner join #urefMaster b on a.SLAVE_ID = b.SLAVE_ID
select * from #urefSlave
REsult:
MASTER_ID SLAVE_ID DESC
--------- -------- --------------------
1 1 value1
2 2 value2
SLAVE_ID DESC
-------- --------------------
1 value1
2 value2
Updated
cannot help much in db2, because i don't have the tools to run the syntax
but from this link db2 update help
you can modify an example in there to meet your requirement:
UPDATE EMPLOYEE EU
SET (EU.SALARY, EU.COMM)
=
(SELECT AVG(ES.SALARY), AVG(ES.COMM)
FROM EMPLOYEE ES
WHERE ES.WORKDEPT = EU.WORKDEPT)
WHERE EU.EMPNO = '000120'
Hope this help.

Inserting the Identity of another Insert from Select?

Is This Possible?
Here is something I'm looking for, executed together:
First, it would execute the INSERT based on how many rows in the SELECT
INSERT INTO TABLE2 (xID, NAME)
SELECT xID, NAME FROM TABLE
Then getting the ##IDENTITY of each INSERTED ROW, it would create a new Insert including the same data of the first SELECT statement:
INSERT INTO TABLE3 (xID, NAME, ID)
SELECT xID, NAME, ID as Scope_IdentitY()
If not, what the best way without using cursor or while?
You have, at least, two options:
1) The OUTPUT...INTO target_table clause (SQL2005+)
2) Or you could write composable DML(SQL2008+).
Example:
DECLARE #Table2 TABLE(
ID INT IDENTITY PRIMARY KEY, --IDENTITY
xID INT NOT NULL,
NAME VARCHAR(25) NOT NULL
);
DECLARE #Table3 TABLE(
ID INT PRIMARY KEY, --No IDENTITY
xID INT NOT NULL,
NAME VARCHAR(25) NOT NULL
);
--First solution: OUTPUT ... INTO
INSERT INTO #Table2 (xID, NAME)
OUTPUT inserted.xID, inserted.NAME, inserted.ID INTO #Table3(xID, NAME, ID)
SELECT t.Col1, t.Col2
FROM (SELECT 11,'A' UNION ALL SELECT 22,'B' UNION ALL SELECT 33,'C') AS t(Col1,Col2);
--Second solution: composable DML
INSERT INTO #Table3(xID, NAME, ID)
SELECT src.xID, src.NAME, src.ID
FROM
(
INSERT INTO #Table2 (xID, NAME)
OUTPUT inserted.xID, inserted.NAME, inserted.ID
SELECT t.Col1, t.Col2
FROM (VALUES(44,'D'),(55,'E'),(66,'F')) AS t(Col1,Col2)
) src
SELECT * FROM #Table2
SELECT * FROM #Table3
INSERT INTO TABLE2 (xID, NAME)
OUTPUT
INSERTED.xID, INSERTED.NAME, INSERTED.ID
INTO TABLE3 (xID, NAME, ID)
SELECT xID, NAME FROM [TABLE]
You can declare a table variable and store the output of the rows inserted into dbo.Table2 in this variable and use the table variable as the input for table dbo.Table3.
CREATE TABLE dbo.Table1
(
xid int NOT NULL
, name varchar(30) NOT NULL
);
CREATE TABLE dbo.Table2
(
id int NOT NULL IDENTITY
, xid int NOT NULL
, name varchar(30) NOT NULL
);
CREATE TABLE dbo.Table3
(
id int NOT NULL
, xid int NOT NULL
, name varchar(30) NOT NULL
);
INSERT INTO dbo.Table1 (xid, name) VALUES
(195, 'abc'),
(242, 'def'),
(332, 'ghi');
GO
DECLARE #tempTable table
( id int
, xid int
, name varchar(30)
);
INSERT dbo.Table2
OUTPUT INSERTED.id, INSERTED.xid, INSERTED.name
INTO #tempTable
SELECT xid, name FROM dbo.Table1;
INSERT dbo.Table3 (id, xid, name)
SELECT id, xid, name FROM #tempTable;
SELECT id, xid, name FROM dbo.Table2;
SELECT id, xid, name FROM dbo.Table3;
GO
OK, based on your comments below, try this:
INSERT INTO TABLE2 (xID, NAME)
SELECT xID, NAME FROM TABLE;
INSERT INTO TABLE3 (xID, NAME, ID)
SELECT xID, NAME, ##identity
FROM TABLE2;
Assuming these table structures:
TABLE_A
-----------
X_ID
NAME
TABLE_B
----------------
TABLE_B_ID [PK]
X_ID
NAME
TABLE_C
----------------
TABLE_C_ID [PK]
X_ID
NAME
TABLE_B_ID [FK]
Then wouldn't this work (best in a transaction)?:
-- Grab data from TABLE_A and INSERT INTO TABLE_B
INSERT INTO TABLE_B (
X_ID,
NAME
)
SELECT
X_ID,
NAME
FROM
TABLE_A
-- Grab data from TABLE_B that matches the data imported from TABLE_A
-- and INSERT that data into TABLE_C (incl. the PK from TABLE_B)
INSERT INTO TABLE_C (
X_ID,
NAME,
TABLE_B_ID
)
SELECT
b.X_ID,
b.NAME,
b.TABLE_B_ID
FROM
TABLE_B b
INNER JOIN
TABLE_A a ON a.X_ID = b.X_ID

Using SQL to search for a set in a one-to-many relationship

Given the tables:
role: roleid, name
permission: permissionid, name
role_permission: roleid, permissionid
I have a set of permissions, and I want to see if there is an existing role that has these permissions, or if I need to make a new one. Note that I already know the permissionid, so really the permission table can be ignored - I just included it here for clarity.
Is this possible to do in a SQL query? I imagine it would have to be a dynamically-generated query.
If not, is there any better way than the brute force method of simply iterating over every role, and seeing if it has the exact permissions?
Note, I'm looking for a role that has the exact set of permissions - no more, no less.
You can select all roles that have the subset of permissions you are looking for. Count the number of permissions and see if it's exactly equal to the number of permissions you need:
select r.roleid
from role r
where not exists (select * from role_permissions rp where rp.roleid = r.roleid and rp.permissionid not in (1,2,3,4)) -- id of permissions
and (select count(*) from role_permissions rp where rp.roleid = r.roleid) = 4 -- number of permissions
Having made a hash of my first answer to this question, here is a slightly left field alternative which works but does involve adding data to the database.
The trick is to add a column to the permission table that holds a unique value for each row.
This is a fairly common pattern and will give precise results. The downside is you have to code your way around hiding the numerical equivalents.
id int(10)
name varchar(45)
value int(10)
Then the contents will become:
Permission: Role Role_Permission
id name value id name roleid permissionid
-- ---- ----- -- ---- ------ ------------
1 Read 8 1 Admin 1 1
2 Write 16 2 DataAdmin 1 2
3 Update 32 3 User 1 3
4 Delete 64 1 4
2 1
2 3
2 4
Then each combination of roles gives a unique value:
SELECT x.roleid, sum(value) FROM role_permission x
inner join permission p
on x.permissionid = p.id
Group by x.roleid
Giving:
roleid sum(value)
------ ----------
1 120 (the sum of permissions 1+2+3+4 = 120)
2 104 (the sum of permissions 1+3+4 = 104)
Now where did I leave that corkscrew...
This is an old sql trick (works in Oracle, at least):
SELECT roleid FROM role_permission t1
WHERE NOT EXISTS (
(SELECT permissionid FROM role_permission t2 WHERE t2.roleid = t1.roleid
MINUS
SELECT permissionid FROM role_permission WHERE roleid = 'Admin')
UNION
(SELECT permissionid FROM role_permission t2 WHERE roleid = 'Admin'
MINUS
SELECT permissionid FROM role_permsission t2 WHERE t2.roleid = t1.roleid)
)
Also not validated. Red wine always sounds good.
You basically need to check if there is a role that has the exact number of distinct permissions as you are checking for.
I have checked this stored procedure on SQL Server 2005 and it returns only those role ids that have an exact match of permission ids to those in the passed in list of comma separated permission ids -
CREATE PROC get_roles_for_permissions (#list nvarchar(max)) -- #list is a comma separated list of your permission ids
AS
SET NOCOUNT ON
BEGIN
DECLARE #index INT, #start_index INT, #id INT
DECLARE #permission_ids TABLE (id INT)
SELECT #index = 1
SELECT #start_index = 1
WHILE #index <= DATALENGTH(#list)
BEGIN
IF SUBSTRING(#list,#index,1) = ','
BEGIN
SELECT #id = CAST(SUBSTRING(#list, #start_index, #index - #start_index ) AS INT)
INSERT INTO #permission_ids ([id]) VALUES (#id)
SELECT #start_index = #index + 1
END
SELECT #index = #index + 1
END
SELECT #id = CAST(SUBSTRING(#list, #start_index, #index - #start_index ) AS INT)
INSERT INTO #permission_ids ([id]) VALUES (#id)
SELECT
r.roleid
FROM
role r
INNER JOIN
role_permission rp
ON r.roleid = rp.roleid
INNER JOIN
#permission_ids ids
ON
rp.permissionid = ids.id
GROUP BY r.roleid
HAVING(SELECT COUNT(*)
FROM role_permission
WHERE roleid = r.roleid) = (SELECT COUNT(*) FROM #permission_ids)
END
Example Data
CREATE TABLE [dbo].[role](
[roleid] [int] IDENTITY(1,1) NOT NULL,
[name] [nvarchar](50)
)
CREATE TABLE [dbo].[permission](
[permissionid] [int] IDENTITY(1,1) NOT NULL,
[name] [nvarchar](50)
)
CREATE TABLE [dbo].[role_permission](
[roleid] [int],
[permissionid] [int]
)
INSERT INTO role(name) VALUES ('Role1')
INSERT INTO role(name) VALUES ('Role2')
INSERT INTO role(name) VALUES ('Role3')
INSERT INTO role(name) VALUES ('Role4')
INSERT INTO permission(name) VALUES ('Permission1')
INSERT INTO permission(name) VALUES ('Permission2')
INSERT INTO permission(name) VALUES ('Permission3')
INSERT INTO permission(name) VALUES ('Permission4')
INSERT INTO role_permission(roleid, permissionid) VALUES (1, 1)
INSERT INTO role_permission(roleid, permissionid) VALUES (1, 2)
INSERT INTO role_permission(roleid, permissionid) VALUES (1, 3)
INSERT INTO role_permission(roleid, permissionid) VALUES (1, 4)
INSERT INTO role_permission(roleid, permissionid) VALUES (2, 2)
INSERT INTO role_permission(roleid, permissionid) VALUES (2, 3)
INSERT INTO role_permission(roleid, permissionid) VALUES (2, 4)
INSERT INTO role_permission(roleid, permissionid) VALUES (3, 3)
INSERT INTO role_permission(roleid, permissionid) VALUES (3, 4)
INSERT INTO role_permission(roleid, permissionid) VALUES (4, 4)
EXEC get_roles_for_permissions '3,4' -- RETURNS roleid 3
Maybe use a subquery along the lines of ...
SELECT * FROM role r
WHERE r.rolid = (SELECT x.roledid
FROM role_permission
WHERE x.permissionid in (1,2,3,4);
Sorry, haven't validated this, but having just spent an hour debugging PHP code for another question I feel the need for a glass of red wine.