I have an existing table, A, defined as follows:
CREATE TABLE dbo.A(
id int IDENTITY(1, 1) NOT NULL PRIMARY KEY CLUSTERED,
name nvarchar(255) NOT NULL UNIQUE NONCLUSTERED
);
This table has 1,000s of existing records.
I now require a new table, B, defined as follows:
CREATE TABLE dbo.B(
id int IDENTITY(1, 1) NOT NULL PRIMARY KEY CLUSTERED,
some_value int NOT NULL
);
I need to create a record in B for each record in A and add a reference from the A record to the corresponding B record. Firstly, I need to add a new field to table A that references table B (which necessarily must be nullable with a default value of NULL, at this stage):
ALTER TABLE dbo.A
ADD b_id int NULL references dbo.B;
How can I create the necessary B records and update the A.b_id field accordingly? I'm looking for something like this:
UPDATE dbo.A
SET b_id = (INSERT INTO dbo.B(some_value) VALUES(5));
such that the value of b_id is the id field of the newly inserted B. (Clearly, this query isn't valid.)
The only solution I can come up with is to write a complex stored procedure using multiple, separate queries. Is there a way to do this with a single query?
IF I am understanding correctly, there's no need for a CURSOR here. What you could do is a MERGE that allows your to OUTPUT both columns from the source data and the target:
CREATE TABLE dbo.A(
id int IDENTITY(1, 1) NOT NULL PRIMARY KEY CLUSTERED,
name nvarchar(255) NOT NULL UNIQUE NONCLUSTERED
);
GO
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N))
INSERT INTO dbo.A
SELECT NEWID()
FROM N N1, N N2, N N3;
GO
CREATE TABLE dbo.B(
id int IDENTITY(1, 1) NOT NULL PRIMARY KEY CLUSTERED,
some_value int NOT NULL
);
GO
ALTER TABLE dbo.A
ADD b_id int NULL references dbo.B;
GO
DECLARE #IDs table (a_id int, b_id int)
MERGE dbo.B B
USING dbo.A A ON B.id = A.b_id
WHEN NOT MATCHED BY TARGET THEN
INSERT (some_value)
VALUES(RAND() * 1000)
OUTPUT A.id, inserted.id
INTO #IDs;
UPDATE A
SET b_id = I.b_id
FROM dbo.A A
JOIN #Ids I ON A.id = I.a_id;
GO
SELECT *
FROM dbo.A;
GO
DROP TABLE dbo.A;
DROP TABLE dbo.B;
db<>fiddle
If you are willing (and able) to make a slight adjustment to TableB as well, you can use OUTPUT when inserting. I added a_id to TableB for my example. Updating b_id back to TableA is a bit redundant as you would now have TableA's ID in TableB for joining.
Here's an example.
-- Mock tables.
DECLARE #TableA table ( id int IDENTITY (100,1) PRIMARY KEY, [name] nvarchar(255), b_id int );
DECLARE #TableB table ( id int IDENTITY (1,1) PRIMARY KEY, a_id int, some_value int );
-- Mock data.
INSERT INTO #TableA ( [name] ) VALUES
( 'TableA_1' ), ( 'TableA_2' ), ( 'TableA_3' );
-- Create an OUTPUT table to capture the values inserted into TableB.
DECLARE #out table ( a_id int, b_id int );
INSERT INTO #TableB ( a_id, some_value )
OUTPUT inserted.a_id, inserted.id INTO #out
SELECT
id AS a_id, 999 AS some_value
FROM #TableA;
-- Update TableA's b_id column.
UPDATE #TableA
SET
b_id = o.b_id
FROM #TableA AS a
INNER JOIN #out AS o
ON a.id = o.a_id;
-- Show updated results.
SELECT * FROM #TableA ORDER BY id;
Returns
+-----+----------+------+
| id | name | b_id |
+-----+----------+------+
| 100 | TableA_1 | 1 |
| 101 | TableA_2 | 2 |
| 102 | TableA_3 | 3 |
+-----+----------+------+
Related
I am trying to figure out a method of creating a shortcut between three tables .
I have a master table, A, a table that references the first table, B, and a table that references B, C. I want to create a shortcut between A and C so I don't have to use table B.
In the below example, I want to ensure that the foreign keys FK_A_ID1 and FK_A_ID2 are always equal to each other, causing it to fail when the last insert statement is executed.
CREATE TABLE A (
ID int unique identity,
num int)
CREATE TABLE B (
ID int unique identity,
A_ID int NOT NULL,
CONSTRAINT FK_A_ID1 FOREIGN KEY (A_ID) REFERENCES A (ID))
CREATE TABLE C (
ID int unique identity,
A_ID int NOT NULL,
B_ID int NOT NULL,
CONSTRAINT FK_A_ID2 FOREIGN KEY (A_ID) REFERENCES A (ID),
CONSTRAINT FK_B_ID FOREIGN KEY (B_ID) REFERENCES B (ID))
INSERT INTO A VALUES (0);
DECLARE #A1 int = SCOPE_IDENTITY();
INSERT INTO A VALUES (1);
DECLARE #A2 int = SCOPE_IDENTITY();
INSERT INTO B Values (#A1);
DECLARE #B1 int = SCOPE_IDENTITY();
INSERT INTO C Values (#A2, #B1);
Is this possible by use of foreign keys or is there another built-in function that I don't know of?
The goal of this is to have a reliable 'shortcut' between tables A and C
One way to do this is with triggers. A trigger can do the joins necessary to ensure C.A = C.B->B.A. We use this method to verify parent keys match up between parent, child, and grandchild tables.
For example:
-- untested code
create trigger C_IU_Verify_A on C
for insert, update as
if exists
(
select 1
from inserted
inner join b on b.id = inserted.b_id
where b.a_id <> inserted.a_id
)
begin
raiserror('Parent table keys do not match.', 16, 1)
rollback
end
Another way to do this is with compound primary keys. Define the primary key of B as (a_id, id). Setup a foreign key from B(a_id) to A(id). Setup a second foreign key from C(a_id, b_id) to B(a_id, id). At this point, you have referential integrity between C.a_id and B.a_id.
For example:
create table a (id int primary key clustered)
create table b(a_id int, id int, primary key (a_id, id), unique(id))
create table c(a_id int, b_id int, id int, primary key (a_id, b_id, id), unique(id))
alter table B add constraint fk_b_aid foreign key (a_id) REFERENCES A(id)
alter table C add constraint fk_c_aid_bid foreign key (a_id, b_id) REFERENCES B(a_id, id)
insert into a (id) select 1
insert into a (id) select 2
insert into b (a_id, id) select 1, 1
insert into b (a_id, id) select 1, 2
--insert into b (a_id, id) select 2, 1 -- error: duplicate b.id
insert into b (a_id, id) select 2, 3
--insert into b (a_id, id) select 3, 1 -- error: there is no A with id = 3
insert into c (a_id, b_id, id) select 1, 1, 1
insert into c (a_id, b_id, id) select 1, 1, 2
insert into c (a_id, b_id, id) select 1, 2, 3
insert into c (a_id, b_id, id) select 2, 3, 4
--insert into c (a_id, b_id, id) select 1, 3, 5 -- error: there is no B with B.a_id = 1 and B.id = 3
drop table c;
drop table b;
drop table a;
I'm reasonably sure the following does what you want. Using psuedo code:
-- Has a primary key
CREATE TABLE A
(
A_id PrimaryKey
)
-- Has both a primary key and a compound unique constraint, as well as a foreign key to A
CREATE TABLE B
(
B_id PrimaryKey Unique_1of2
,A_id Unique_2of2 ForeignKey_to_A
)
-- Has a primary key and a compound foreign key to B
CREATE TABLE C
(
C_id PrimaryKey
,B_id ForeignKey_to_B_1of2
,A_id ForeignKey_to_B_2of2
)
Done this way:
It is not possible to put a value for A_id in B that is not also in A
it is not possible to put a values for A_id and B_id in table C that are not also a pair in B
NULL values may or may not mess with this--if you have to deal with them as well, you have bigger problems.
I have temp table each time store 100 values based on a specific condition.
I need Slno as 1,2,3,4 ...100 each time query executes .
If I use below syntax's, the 'Slno' is taking some other numbers
create table #temptable
(Slno INT IDENTITY(1,1) NOT NULL ,
Name varchar(50)
)
create table #temptable
(Slno int IDENTITY(1,1) PRIMARY KEY ,
Name varchar(50)
)
Please help if there is a way out without using Rank()?
You need to create an IDENTITY column as follows:
Syntax:
CREATE TABLE (
ID_column INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
...
);
It should be
Identity(seed,increment)
Here you go:
CREATE TABLE #temptable
(Slno INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
Name varchar(50)
)
Example:
INSERT INTO #temptable (Name) Values ('ABC')
INSERT INTO #temptable (Name) Values ('ABhshC')
INSERT INTO #temptable (Name) Values ('ABQRAC')
INSERT INTO #temptable (Name) Values ('ABhsAERAYRHAERhC')
SELECT * FROM #temptable
Results:
Slno Name
1 ABC
2 ABhshC
3 ABQRAC
4 ABhsAERAYRHAERhC
For example, I have 2 tables
Create table Authorized
(D_ID char(5), MNUM char(5), primary key(D_ID,MNUM),
foreign key(D_ID) REFERENCES Distributor(D_ID),
foreign key(MNUM) REFERENCES Model(MNUM));
Create table Orders
(ORDNO char(8), D_ID char(5), MNUM char(5),
Qty int, Date date, primary key (ORDNO,D_ID,MNUM),
foreign key(MNUM) REFERENCES Model(MNUM),
foreign key(D_ID) REFERENCES Distributor(D_ID));
INSERT INTO Authorized VALUES ('D0003', 'M0001');
INSERT INTO Authorized VALUES ('D0003', 'M0003');
How to I make sure that insert statement to the order table is restricted to D_ID that is authorized? Like if the statement is
INSERT INTO Orders VALUES
('20161232', 'D0003','M0002',2, '2016-12-22');
How do you prevent this insert statement from going through? AS M0002 is not authorized
You can create a Scalar function and use it in your table as:
CREATE FUNCTION IsAuthorized
(
#Value CHAR(5) --I think you mean "MNUM" column here
)
RETURNS BIT
AS
BEGIN
DECLARE #Result BIT = 0;
IF EXISTS (SELECT 1 FROM Authorized WHERE D_ID = #Value)
SET #Result = 1;
RETURN(#Result);
END;
GO
Here is how to use it in the table:
Create table Orders(
ORDNO char(8),
D_ID char(5) CONSTRAINT CHK_IsAuthorized CHECK(IsAuthorized(D_ID) = 1),
MNUM char(5),
...
...
How to declare one element table so it can be used in future queries?
DECLARE #Emp TABLE
(
ID BIGINT NULL,
CompanyID BIGINT NULL
)
INSERT INTO #EMP
SELECT ID,CompanyID FROM Emp WHERE PIN = 123
SELECT COMPANYID FROM COMPANY WHERE ID = #Emp.CompanyID
You can not. A table per definition contains a (possibly) unlimited number of elements. However, you can always do something like this:
DECLARE #CompanyID BIGINT
SET #CompanyID = (SELECT TOP 1 CompanyID FROM #Emp WHERE ...)
By the way, the following line is not correct, as the WHERE clause is incomplete.
SELECT COMPANYID FROM COMPANY WHERE #Emp.CompanyID
If you intention is to create a table variable that will only store a maximum of one row, you can do it like this:
DECLARE #Emp TABLE
(
ID BIGINT NULL,
CompanyID BIGINT NULL,
Lock char(1) not null default 'X' primary key check(Lock='X')
)
INSERT INTO #EMP (ID,CompanyID)
SELECT ID,CompanyID FROM Emp WHERE PIN = 123
Because the table's primary key is constrained to only one possible value, logically there can't be more than one row.
I have a table with 10 columns but only care about 3 columns for this. Imagine my table looks like this:
CREATE TABLE MyTable ( RowID int IDENTITY(1,1), UserID int, NodeID int, RoleID int )
What I need is a constraint that enforces the following: UserID and RoleID need to be unique for each NodeID (i.e. a user cannot have the same role in multiple nodes). In other words I want to allow
INSERT MyTable (UserID, NodeID, RoleID) SELECT 1, 1, 1
but not allow
INSERT MyTable (UserID, NodeID, RoleID) SELECT 1, 2, 1
if the first insert has occurred because that would result in a user having a role in multiple nodes.
Hopefully this is simple and I'm just making it more complex than it needs to be in my brain.
Since your constraint depends on data in other rows, this rules out a filtered index. IMO a viable option could be a trigger. Such a trigger could look like something like this:
CREATE TRIGGER dbo.MyTrigger ON dbo.Q1
AFTER INSERT, UPDATE
AS
DECLARE #userId INT, #Id INT, #roleId INT, #exists INT;
SELECT TOP 1
#userId = userID
,#roleId = roleID
,#Id = Id
FROM inserted;
SELECT TOP 1
#exists = Id
FROM Q1
WHERE userId = #userId
AND roleID = #roleID AND Id<> #Id;
IF ISNULL(#exists, 0) > 0
BEGIN
-- you would want to either undo the action here when you use an 'after' trigger
-- because as the name implies ... the after means the record is allready inserted/updated
RAISERROR ('No way we would allow this.', 16, 1);
END
-- else
-- begin
-- another alternative would be to use a instead of trigger, which means the record
-- has not been inserted or updated and since that type of trigger runs the trigger 'instead of'
-- updating or inserting the record you would need to do that yourself. Pick your poison ...
-- end
GO
An unique index should enforce your requirements
CREATE UNIQUE NONCLUSTERED INDEX [idx_Unique] ON [dbo].[MyTable]
(
[UserID] ASC,
[NodeID] ASC,
[RoleID] ASC
)
From the comments I suppose you will need two unique indices
CREATE UNIQUE NONCLUSTERED INDEX [idx_User_Node] ON [dbo].[MyTable]
(
[UserID] ASC,
[NodeID] ASC
)
GO
CREATE UNIQUE NONCLUSTERED INDEX [idx_User_Role] ON [dbo].[MyTable]
(
[UserID] ASC,
[RoleID] ASC
)