How to update foreign keys in single one statement - sql

I want to update foreign keys into a table named User where foreign keys refer to a Project table, a Group table.
The user belongs to a project, within a group, the group are defined to the project level. The detailed user information are also stored into a table ToolUser
The create statement for the User table is as following:
CREATE TABLE [dbo].[User](
[ID] [int] IDENTITY(1,1) NOT NULL,
[IDproject] [int] NOT NULL,
[IDtoolUser] [int] NOT NULL,
[IDgroup] [int] NOT NULL,
[datemodified] [datetime] NOT NULL DEFAULT (getdate())
IDproject is FK for ID in Project table
IDtoolUser is FK for ID in ToolUser table
IDgroup is FK for ID in Group table
The update I want to perform consist in changing the group the user is assigned to which requires to resolve the project ID, the group ID before updating the groupID in the User table
I came up with a solution where the group id is calculed with a function, [dbo].[IDofGroup], and used into an UPDATE statement as following
UPDATE [dbo].[User]
SET IDgroup = [dbo].[IDofGroup]('TESTPROJ','none')
FROM [dbo].[User] as u
INNER JOIN [dbo].[Group] g
ON g.[ID]= u.[IDgroup]
INNER JOIN [dbo].[Project] p
ON p.[ID]= u.[IDproject]
AND p.[name] ='TESTPROJ'
WHERE g.[name] = 'TESTGROUP'
GO
The function IDofGroup itself performs couple SELECT to resolve project ID and group ID as following
ALTER FUNCTION [dbo].[IDofGroup]
(
-- Add the parameters for the function here
#ProjectName as varchar(max),
#GroupName as varchar(max)
)
RETURNS int
AS
BEGIN
-- Declare the return variable here
DECLARE #ProjectID int, #ResultID int
SET #ResultID=0
-- get project ID for #ProjectName
SELECT #ProjectID= ID
FROM [dbo].[Project]
WHERE [name]=#ProjectName
-- find id fro group which name is #GroupName and project ID #ProjectID
SELECT #ResultID= ID
FROM [dbo].[Group]
WHERE [IDproject]=#ProjectID
AND [name]=#GroupName
-- Return the result of the function
RETURN #ResultID
END
I m wondering if there any way to perform the UPDATE with some kind of JOIN in a single UPDATE statement w/o the need of using of function IDofGroup or other SELECT statements before the UPDATE statement

Try this one:
UPDATE u
SET IDgroup = g1.[ID]
FROM [dbo].[User] as u
INNER JOIN [dbo].[Project] p
ON p.[ID]= u.[IDproject]
INNER JOIN [dbo].[Group] g
ON g.[ID]= u.[IDgroup]
INNER JOIN [dbo].[Group] g1
ON g.[IDproject] = g1.[IDproject]
WHERE p.[name] ='TESTPROJ'
AND g.[name] = 'TESTGROUP'
AND g1.[name] = 'none'

Related

Stored procedure for login not working as intended

I have 3 tables, users, Roles, and User_Roles.
CREATE TABLE [Web].[Users]
(
[Employee_ID] [Nvarchar] (10) NOT NULL PRIMARY KEY,
[Username] [nvarchar] (25) NOT NULL,
)
GO
CREATE TABLE [Web].[Roles]
(
[Role_ID] [Int] NOT NULL IDENTITY PRIMARY KEY,
[Roles] [nvarchar] (25) NOT NULL,
)
GO
CREATE TABLE [Web].[User_Roles]
(
[Employee_ID] [Nvarchar](10) NOT NULL,
[Role_ID] [int] NOT NULL,
CONSTRAINT FK_Employee_ID
FOREIGN KEY (Employee_ID) REFERENCES [Web].[Users] (Employee_ID)
ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT FK_Role_ID
FOREIGN KEY (Role_ID) REFERENCES [Web].[Roles] (Role_ID),
CONSTRAINT pk_User_Roles PRIMARY KEY (Employee_ID, Role_ID)
);
The way it works is, when you login, it checks your Username against the Employee_ID in Users table, then goes to the User_Roles table and matches the Employee_ID it got from the Users table to the User_Roles table. Then it gets the Role_ID from the User_Roles table and matches the Role_ID to the roles in the Roles table.
I'm trying to write a stored procedure that does the checks and so far I've gotten close, but I feel I'm missing something. Hoping I can get help with it. This is what I have so far.
CREATE PROCEDURE [Web].[Get_User_Roles]
#Username NVARCHAR(25)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Employee_ID Nvarchar, #Role_ID INT
SELECT #Employee_ID = #Employee_ID, #Role_ID = #Role_ID
FROM [Web].[Users]
WHERE #Employee_ID = #Employee_ID AND #Role_ID = #Role_ID
IF #Employee_ID IS NOT NULL
BEGIN
IF NOT EXISTS (SELECT #Employee_ID FROM [Web].Users
WHERE #Username = Username)
BEGIN
SELECT
#Employee_ID [Employee_ID],
(SELECT #Role_ID FROM User_Roles
WHERE #Role_ID = #Role_ID) [Roles]-- User Valid
END
ELSE
BEGIN
SELECT -2 [#Username], '' [Roles]--User not activated
END
END
ELSE
BEGIN
SELECT -1 [#Username], '' [Roles]-- User invalid
END
END
Any help to improve this would be appreciated. I put the tables code above so that it's available. My final outcome I shall ask again in this section as well as the middle. The way it works is, when you login, it checks your Username against the Employee_ID in Users Table, then goes to the User_Roles table and matches the Employee_ID it got from the Users table to the User_Roles table. Then it gets the Role_ID from the User_Roles table and matches the Role_ID to the Roles in the Roles table. I'm trying to make a stored procedure that does the checks and so far I've gotten close, but I feel I'm missing something. Hoping I can get help with it. This is what I have so far.
First of all, this part is not going to work at all:
select #Employee_ID = #Employee_ID, #Role_ID = #Role_ID
from [Web].[Users] WHERE #Employee_ID = #Employee_ID and #Role_ID = #Role_ID
You are comparing variables with themselves in the WHERE Statement, you are assigning variables to each other, and you have no Role_ID in the [Web].[Users] table. Try the following
SELECT Employee_ID, Roles
FROM [Web].[Users] A
INNER JOIN [Web].[User_Roles] B
ON A.[Employee_ID] = B.[Employee_ID]
INNER JOIN [Web].[Roles] C
ON b.[Role_ID] = C.[Role_ID]
WHERE [Username] = #Username
This code will return the Employee_id and Roles if you have a user with that username, or an empty data set otherwise.
Still not very clear what you want as output but I think it would be something like this. This will return -2 in the UserStatus column if there isn't a role for the user. Otherwise it will return 1 in that column and the Role. You should just check if there are rows returned when you call this. If it returns no rows then the user doesn't exist.
select UserStatus = case when ur.Role_ID is null then -2 else 1 end
, r.Roles
from Web.Users u
left join Web.User_Roles ur on ur.Employee_ID = u.Employee_ID
left join Web.Roles r on r.Role_ID = ur.Role_ID
where u.Username = #Username

How to save auto generated primary key Id in foreign key column in same table

Following is the table structure:
CREATE TABLE [User] (
[Id] bigint identity(1,1) not null,
[FirstName] nvarchar(100) not null,
[LastName] nvarchar(100) not null,
[Title] nvarchar(5) null,
[UserName] nvarchar(100) not null,
[Password] nvarchar(100) not null,
[Inactive] bit null,
[Created] Datetime not null,
[Creator] bigint not null,
[Modified] DateTime null,
[Modifier] bigint null
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
(
[Id] Asc
)
);
IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[FK_User_Creator]') AND parent_object_id = OBJECT_ID(N'[User]'))
ALTER TABLE [User] ADD CONSTRAINT [FK_User_Creator] FOREIGN KEY([Creator]) REFERENCES [User]([Id])
GO
INSERT INTO [User] (Creator) Values ([Id] ?)
This is a case when table is empty and first user is going to add in table. Otherwise I don't have issue.
How can I insert Id in creator column with insert statement at the same time?
One way could be using Sequence instead of identity column. The below script might serve the same purpose:
CREATE SEQUENCE dbo.useridsequence
AS int
START WITH 1
INCREMENT BY 1 ;
GO
CREATE TABLE [User] (
[Id] bigint DEFAULT (NEXT VALUE FOR dbo.useridsequence) ,
[FirstName] nvarchar(100) not null,
[LastName] nvarchar(100) not null,
[Title] nvarchar(5) null,
[UserName] nvarchar(100) not null,
[Password] nvarchar(100) not null,
[Inactive] bit null,
[Created] Datetime not null,
[Creator] bigint DEFAULT NEXT VALUE FOR dbo.useridsequence ,
[Modified] DateTime null,
[Modifier] bigint null
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
(
[Id] Asc
)
);
IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[FK_User_Creator]') AND parent_object_id = OBJECT_ID(N'[User]'))
ALTER TABLE [User] ADD CONSTRAINT [FK_User_Creator] FOREIGN KEY([Creator]) REFERENCES [User]([Id])
GO
INSERT INTO [User]
(
-- Id -- this column value is auto-generated
FirstName,
LastName,
Title,
UserName,
[Password],
Inactive,
Created,
Creator,
Modified,
Modifier
)
VALUES
(
'Foo',
'Bar',
'Title',
'UserName ',
'Password',
0,
GETDATE(),
DEFAULT,
GETDATE(),
1
)
SELECT * FROM [User] AS u
Result :
The short answer is that you can't do this. And I suggest your model is logically flawed in the first place. Do you intend to define all actual database users (e.g., create user ... for login ...) as rows in [Users]? You need to think about that - but the typical answer is no. If the answer is yes, then you don't need the creator column at all because it is redundant. All you need is the created date - for which you probably should have defined a default.
But if you want to do this, you will need to do it in two steps (and you will need to make the column nullable). You insert a row (or rows) with values for the "real" data columns. Then update those same rows with the identity values generated for id. An example showing different ways to do this
use tempdb;
set nocount on;
CREATE TABLE dbo.[user] (
[user_id] smallint identity(3,10) not null primary key,
[name] nvarchar(20) not null,
[active] bit not null default (1),
[created] Datetime not null default (current_timestamp),
[creator] smallint null
);
ALTER TABLE dbo.[user] ADD CONSTRAINT [fk_user] FOREIGN KEY(creator) REFERENCES dbo.[user](user_id);
GO
-- add first row
insert dbo.[user] (name) values ('test');
update dbo.[user] set creator = SCOPE_IDENTITY() where user_id = SCOPE_IDENTITY()
-- add two more rows
declare #ids table (user_id smallint not null);
insert dbo.[user] (name) output inserted.user_id into #ids
values ('nerk'), ('pom');
update t1 set creator = t1.user_id
from #ids as newrows inner join dbo.[user] as t1 on newrows.user_id = t1.user_id;
select * from dbo.[user] order by user_id;
-- mess things up a bit
delete dbo.[user] where name = 'pom';
-- create an error, consume an identity value
insert dbo.[user](name) values (null);
-- add 2 morerows
delete #ids;
insert dbo.[user] (name) output inserted.user_id into #ids
values ('nerk'), ('pom');
update t1 set creator = t1.user_id
from #ids as newrows inner join dbo.[user] as t1 on newrows.user_id = t1.user_id;
select * from dbo.[user] order by user_id;
drop table dbo.[user];
And I changed the identity specification to demonstrate something few developers realize. It isn't always defined as (1,1) and the next inserted value can jump for many reasons - errors and caching/restarts for example. Lastly, I think you will regret naming a table with a reserved word since references to it will require the use of delimiters. Reduce the pain.

Manually delete cascade on SQL Server

I have a set of tables (SQL Server 2014) and I need to perform a delete cascade but manually. I have these three tables:
CREATE TABLE dbo.A
(
IdKey int identity primary Key,
Name nvarchar(50)
)
CREATE TABLE dbo.B
(
Id int identity primary Key,
ChildIdKey int,
Name nvarchar(50)
)
CREATE TABLE dbo.C
(
Id int identity primary Key,
OtherIdKey int,
Name nvarchar(50)
)
If I need to remove IdKey from table A, I need first to identify the foreign keys pointed to IdKey on table A. I have a FK over ChildIdKey on table B and another on FK over OtherIdKey on table C. Column names are different so now I need to get all tables where I have a FK pointed to IdKey and perform a delete cascade manually and in order to avoid FK errors.
How can I get the list of tables in the correct order for performing the cascade delete manually?
Try this to find out all table depending on a particular column of some-table
DECLARE #ColumnName sysname,#TableName sysname
SET #ColumnName='userId'
SET #TableName='user'
SELECT Fkn.name FKName ,
cname.name FKColumnName,
FCT.name FKContainingTable ,
CCT.name ColumnContainingTable
FROM sys.foreign_key_columns Fk
INNER JOIN sys.foreign_keys fkn ON fkn.object_id=fk.constraint_object_id
INNER JOIN sys.tables FCT ON fCt.object_id = fk.parent_object_id
INNER JOIN sys.tables CCT ON fk.referenced_object_id = CCT.object_id
INNER JOIN sys.columns CLM ON CCT.object_id = clm.object_id
INNER JOIN sys.columns CName ON CName.object_id=fk.parent_object_id AND CName.column_id=fk.parent_column_id
AND CLM.name = #ColumnName
AND cct.name = #TableName

INSERT Conflict on Virtual Table SQL

I am having a problem on the INSERT because of a FK reference. The process goes like this:
I create the table Cuentas, and Cuentas_Con_RowNumber
I select from a huge table with over 3 million records. Because some are repeated and I need to store only 1 "cuenta", I made the tempDB. I have to do this, because on the huge db there are many records with the same Cuenta_Nro with different transactions, and I just need one.
I select from the tempDB all the columns but the RowNumber and then insert it into the Cuentas table.
The problem is that the tempDB Pais (country) column is not a FK which references to the Paises (countries) table, and on the original table (Cuentas) it does, therefore, it crashes.
Code:
CREATE TABLE Paises
(
Pais_Id numeric(18,0) PRIMARY KEY NOT NULL,
Pais_Nombre varchar(255) NOT NULL
)
CREATE TABLE Cuentas
(
Cuenta_Nro numeric(18,0) PRIMARY KEY NOT NULL,
Cuenta_Estado varchar(255),
Cuenta_Moneda varchar(255) DEFAULT 'Dolar',
Cuenta_Tipo numeric(18,0)
FOREIGN KEY REFERENCES Tipo_De_Cuentas(Tipo_De_Cuenta_Id),
Cuenta_PaisOrigen numeric(18, 0)
FOREIGN KEY REFERENCES Paises(Pais_Id),
Cuenta_PaisAsignado numeric(18, 0)
FOREIGN KEY REFERENCES Paises(Pais_Id),
Cuenta_Fec_Cre datetime,
Cuenta_Fec_Cierre datetime,
Cuenta_Tarjeta numeric(18, 0)
FOREIGN KEY REFERENCES Tarjetas(Tarjeta_Nro),
Cuenta_Cliente numeric(18, 0)
FOREIGN KEY REFERENCES Clientes(Cliente_Id)
)
CREATE TABLE #Cuentas_Con_RowNumer
(
Cuenta_Nro numeric(18,0) PRIMARY KEY NOT NULL,
Cuenta_Estado varchar(255),
Cuenta_PaisOrigen numeric(18,0)),
Cuenta_Fec_Cre datetime,
Cuenta_Fec_Cierre datetime,
Cuenta_Cliente numeric(18,0),
Cuenta_Tarjeta numeric(18,0),
RowNumber int
)
INSERT INTO #Cuentas_Con_RowNumer
SELECT *
FROM (SELECT
Maestro.Cuenta_Numero, Maestro.Cuenta_Estado, Maestro.Cuenta_Pais_Codigo,
Maestro.Cuenta_Fecha_Creacion, Maestro.Cuenta_Fecha_Cierre, Clientes.Cliente_Id, Maestro.Tarjeta_Numero,
ROW_NUMBER() OVER (PARTITION BY Maestro.Cuenta_Numero ORDER BY Maestro.Cuenta_Numero) AS RowNumber
FROM gd_esquema.Maestra Maestro, dbo.Clientes
WHERE
Clientes.Cliente_Apellido = Maestro.Cli_Apellido AND
Clientes.Cliente_Nombre = Maestro.Cli_Nombre) AS a
WHERE a.RowNumber = '1'
INSERT INTO Cuentas
(
Cuenta_Nro, Cuenta_Estado, Cuenta_PaisOrigen, Cuenta_Fec_Cre,
Cuenta_Fec_Cierre, Cuenta_Cliente, Cuenta_Tarjeta
)
SELECT
Cuenta_Nro, Cuenta_Estado, Cuenta_PaisOrigen, Cuenta_Fec_Cre,
Cuenta_Fec_Cierre, Cuenta_Cliente, Cuenta_Tarjeta
FROM #Cuentas_Con_RowNumer
The error message is:
Instrucción INSERT en conflicto con la restricción FOREIGN KEY "FK__Cuentas__Cuenta___24B338F0". El conflicto ha aparecido en la base de datos "GD1C2015", tabla "dbo.Paises", column 'Pais_Id'.
The issue is because Maestro.Cuenta_Pais_Codigo column is being pulled from gd_esquema.Maestra table which in turn is going as Cuenta_PaisOrigen in target table and has a foreign key defined.
There will be some records which are being selected for insert Cuentas table that doesn't have a matching Pais_Id record in dbo.Paises table.
You can add a inner join as below and check the results as :
INSERT INTO #Cuentas_Con_RowNumer
SELECT *
FROM (SELECT
...
FROM gd_esquema.Maestra Maestro
inner join dbo.Clientes on
Clientes.Cliente_Apellido = Maestro.Cli_Apellido AND
Clientes.Cliente_Nombre = Maestro.Cli_Nombre
inner join Paises P on Maestro.Cuenta_Pais_Codigo = P.Pais_Id
) AS a
WHERE a.RowNumber = '1'

What's the query needed to copy data from one table to another two?

I recently changed my database structure and now I want to copy from my old table Transfers to the new ones I just created (Orders and Orders_Transfer):
--old table to copy from
-- table 'Transfers'
CREATE TABLE [dbo].[Transfers] (
[Id] int IDENTITY(1,1) NOT NULL,
[Date] datetime NOT NULL,
[Memo] nvarchar(max) NULL,
[Employee_Id] int NULL,
[InventoryFrom_Id] int NOT NULL,
[InventoryTo_Id] int NOT NULL,
);
-- new tables to copy to
-- table 'Orders'
CREATE TABLE [dbo].[Orders] (
[Id] int IDENTITY(1,1) NOT NULL,
[Date] datetime NOT NULL,
[Memo] nvarchar(max) NULL,
[Employee_Id] int NULL
);
-- Creating table 'Orders_Transfer'
CREATE TABLE [dbo].[Orders_Transfer] (
[Id] int NOT NULL,--foreign key on Orders.Id
[InventoryFrom_Id] int NOT NULL,
[InventoryTo_Id] int NOT NULL
);
I want to iterate through the old Transfers table and copy some part of it to Orders (Date, Memo, Employee) and the rest to Orders_Transfer (InventoryFrom_Id, InventoryTo_Id). The Id column in Orders_Transfer is also a FK on Orders.Id so I want to copy the auto-generated value as well.
I read about the scope_identity() function and the OUTPUT clause, but I’m a beginner to SQL and can’t put it all together.
I’m using SQL Server 2008
What is the query I need to do this? Any help would be appreciated, thanks!
I would create an OldId column on the Orders table to store the old primary key:
ALTER TABLE [dbo].[Orders] ADD [OldId] INT;
Then copy in the old data:
INSERT INTO [dbo].[Orders]
(
[OldId],[Date],[Memo],[EmployeeID]
)
SELECT [Id] AS [OldId],[Date],[Memo],[EmployeeID]
FROM [dbo].[Transfers];
Copy the remaining data using the OldId:
INSERT INTO [dbo].[Orders_Transfer]
(
[Id],
[InventoryFrom_Id],
[InventoryTo_Id]
)
SELECT
O.Id, T.[InventoryFrom_Id], T.[InventoryTo_Id]
FROM [dbo].[Orders] O
INNER JOIN [dbo].[Transfers] T
ON O.[OldId] = T.[Id];
And drop the OldId column:
ALTER TABLE [dbo].[Orders] DROP COLUMN [OldId];
To keep the Id values in sync you are going to need to use IDENTITY_INSERT.
SET IDENTITY_INSERT dbo.Orders ON;
/* Insert data into the Orders table */
INSERT INTO [dbo].[Orders]
([Id]
,[Date]
,[Memo]
,[Employee_Id])
SELECT Id
, Date
, Memo
, Employee_Id
FROM Transfers;
SET IDENTITY_INSERT dbo.Orders OFF;
/* Insert data into the Orders_Transfer table */
INSERT INTO [dbo].[Ordres_Transfer]
([Id]
,[InventoryFrom_ID]
,[InventoryTo_ID]
SELECT Id
, InventoryFrom_ID
, InentoryTo_ID
FROM Transfers;
Insert into newtable select * from oldtable //if same schema
insert into newtable(col1,col2,col3) select col1,col2,col3 from old table // for different table schema