I have a table like this one:
id fk_id
1 1
2 1
3 2
4 3
5 3
The field fk_id references another table, I want to create a constraint to permit max two insertion with each fk_id.
I want to prevent this:
id fk_id
1 1
2 1
3 1 <-- FAIL
4 3
5 3
This is a relationship of "one to many (but max 2)" or "one to one (or two)" - I donĀ“t know how I can name it.
Can I do this with MS SQL Server? Maybe a CHECK CONSTRAINT ?
SOLUTION:
-- function to check if there are more then two rows
CREATE FUNCTION [dbo].[CheckMaxTwoForeignKeys](#check_id int)
RETURNS bit
AS
BEGIN
DECLARE #result bit
DECLARE #count int
SELECT #count = COUNT(*) FROM mytable WHERE fk_id = #check_id
IF #count <= 2
SET #result = 1
ELSE
SET #result = 0
RETURN #result
END
-- create the constraint
ALTER TABLE mytable
ADD CONSTRAINT CK_MaxTwoFK CHECK ( ([dbo].[CheckMaxTwoForeignKeys]([fk_id])=1) )
You should create a check constraint that calls a function; the function returns 1 if there are 2 or less values for the current value (current value that is being checked).
The check constraint should be something like check(dbo.FunctionCheckValidityOfValue = 1)
Related
I am trying to write a stored procedure that would increment one of the column in the table by 1, everytime either of the country code (AXX,XPP,CGG) is given as user input, depending on the already existing country code value
ID CountryCode
1 AXX
2 AXX
1 XPP
3 AXX
4 AXX
2 XPP
My code below fetches a value that reads the ID like
ID
1
2
3
4
...
...
Create procedure [dbo].[sp_Incremental]
#code varchar(50)
as
begin
declare #IDbigint
set #ID= 1 ;
begin try
Select #ID=count(ID)+1 from [Incremental_tbl]
Begin
Insert into [Incremental_tbl] values (#ID,#code)
End
end
go
You can use this SP :
CREATE PROCEDURE p_Incremental #code VARCHAR(50)
AS
SET NOCOUNT ON;
INSERT INTO Incremental_tbl (ID, code)
SELECT COALESCE(MAX(ID), 0) + 1, #code
FROM Incremental_tbl
GROUP BY #code;
GO
But, do not forget that, in concurrency acesses, same values can occur for the ID !
I have two tables, Price List and Price Code:
CREATE TABLE PriceLists
(
PriceListID INT IDENTITY(1,1) NOT NULL,
Reference NVARCHAR(50) NOT NULL,
Description NVARCHAR(255) NOT NULL
)
Each Price List had lots of price codes:
CREATE TABLE PriceCodes
(
PriceCodeID INT IDENTITY(1,1) NOT NULL,
PriceListID INT NOT NULL
PriceCodeName NVARCHAR(50) NOT NULL
)
Basically what I need is a stored procedure; when a new price list is entered, that stored procedure is going to add same number of price codes to the PriceCodes table with new PriceListID.
For example:
If a new row is inserted into the PriceList table with PriceListID = 1 and Reference = 2017, then the stored procedure should insert these rows into the PriceCode table:
PriceCodeID PriceListID PriceCodeName
------------------------------------------
1 1 CodeA
2 1 CodeB
3 1 CodeC
And if we have this data in the PriceList table :
PriceListID Reference
------------------------
1 2017
1 2018
then the PriceCodse table should be like this:
PriceCodeID PriceListID PriceCodeName
------------------------------------------
1 1 CodeA
2 1 CodeB
3 1 CodeC
4 2 CodeA
5 2 CodeB
6 2 CodeC
You could easily package this up into an AFTER INSERT trigger, and even get rid of that ugly cursor - try this:
CREATE TRIGGER trg_InsertPriceCodes
ON dbo.PriceLists
AFTER INSERT
AS
BEGIN
INSERT INTO dbo.PriceCodes (PriceListID, PriceCodeName)
SELECT i.PriceListID, VAL.Name
FROM Inserted i
CROSS APPLY (VALUES ('CodeA'), ('CodeB'), ('CodeC')) AS VAL(Name)
END
So now, any time you insert a row into PriceLists, the trigger will take care of adding three rows with values CodeA, CodeB, and CodeC for PriceCodeName into the PriceCodes table. No stored procedure and no RBAR (row-by-agonizing-row) trigger necessary.....
I wrote this code and it seems working, just need few adjustments.
SET NOCOUNT ON;
DECLARE #priceCodeName VARCHAR(50), #PriceListID INT, #PriceGroup VARCHAR(20);
DECLARE priceCode_cursor CURSOR FOR
SELECT PriceCodeName ,PriceListID ,PriceGroup
FROM PriceCodes2
ORDER BY PriceCodeName;
OPEN priceCode_cursor
FETCH NEXT FROM priceCode_cursor
INTO #priceCodeName, #PriceListID, #PriceGroup
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO [dbo].[PriceCodes2]
([PriceCodeName]
,[PriceListID]
,[PriceGroup])
VALUES
(#priceCodeName,#PriceListID,#PriceGroup)
FETCH NEXT FROM priceCode_cursor
INTO #priceCodeName, #PriceListID, #PriceGroup
END
CLOSE priceCode_cursor;
DEALLOCATE priceCode_cursor;
I'm creating a temp table to store data from a csv and then altering the table after to create a new column to identify each row by a unique number in ascending order.
This gets created fine, however I can't query the table using these row numbers. Seems as if it doesn't get set. On SSMS when I use the newly created column it red lines it with the error Invalid Column Name 'columnName' but I can still query the database.
declare #loopNum INT
set #loopNum = 0
CREATE TABLE #A
(
column1 BIGINT NOT NULL,
column2 BIGINT NOT NULL,
)
DECLARE #command NVARCHAR(150)
SET #command = just reads from file into temp table A. this works fine
EXEC SP_EXECUTESQL #command
ALTER TABLE #A
ADD RowNumbers INT IDENTITY(1,1)
--if i run a select * from #a, all 4 columns show perfectly
while #loopNum <= 5
begin
select * from #a where loopNum = RowNumbers -- doesn't return anything yet loop is going up one as 6 blank results are returned
set #loopNum = #loopNum + 1
end
The select statement doesn't recognise "RowNumbers" so I'm not sure if there's a problem with how I've done the alter command.
This is what I get so far.
Column 1 | Column 2 | RowNumbers
A | B | 1
C | D | 2
It just doesn't loop through it.
A couple of issues here.
One, you're going to get an empty row first always because you declared #loopNum = 0 and your WHILE loop starts at 1.
Two, you are using "loopNum" instead of your variable #loopNum in your SELECT.
Rextester: http://rextester.com/XJNML62761
I have a table 'TEST' with some columns. One of them is called 'VALIDATED'. This column is a bit.
TEST Table:
Id1 Id2 VALIDATED
1 1 0
1 2 0
1 3 0
2 4 0
2 5 0
2 6 0
Id1 and Id2 are INT values and primary keys.
I have created below trigger for this table and for that column 'VALIDATED' only when it is updated:
CREATE TRIGGER [dbo].[MY_TRIGGER]
ON [dbo].[TEST]
FOR UPDATE
AS
IF UPDATE([VALIDATED])
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for trigger here
DECLARE #VALIDATED_INSERT BIT;
SELECT #VALIDATED_INSERT = [VALIDATED]
FROM INSERTED;
PRINT 'VALIDATED INSERT:'
PRINT #VALIDATED_INSERT
DECLARE #VALIDATED_TABLE BIT;
SELECT #VALIDATED_TABLE = [VALIDATED]
FROM [dbo].[TEST]
PRINT 'VALIDATED TABLE:'
PRINT #VALIDATED_TABLE
END
Imagine we update row which has Id1=1 and Id2=1 using below update statement and field 'VALIDATED' is set to 0 initially as above table TEST shows:
Update TEST
SET VALIDATED=1
Where Id1= 1 and Id2 = 1
1) If I update this column 'VALIDATED' to 1 it prints:
VALIDATED INSERT:
1
VALIDATED TABLE:
1
2) Now, VALIDATED is set to 1 in the table so If I update it again for the same row (Id1 = 1 and Id2 = 1):
Update TEST
SET VALIDATED=0
Where Id1= 1 and Id2 = 1
It prints:
VALIDATED INSERT:
0
VALIDATED TABLE:
1
I don't understand it:
Why in case 1) VALIDATED TABLE is printed as 1 instead of 0?
I am incrementing the alphanumeric value by 1 for the productid using stored procedure. My procedure incrementing the values up to 10 records, once its reaching to 10th say for PRD0010...no more its incrementing... however, the problem is it is repeating
the same values PRD0010.. for each SP call.
What could be the cause of this?
create table tblProduct
(
id varchar(15)
)
insert into tblProduct(id)values('PRD00')
create procedure spInsertInProduct
AS
Begin
DECLARE #PId VARCHAR(15)
DECLARE #NId INT
DECLARE #COUNTER INT
SET #PId = 'PRD00'
SET #COUNTER = 0
SELECT #NId = cast(substring(MAX(id), 4, len(MAX(id))) as int)
FROM tblProduct group by left(id, 3) order by left(id, 3)
--here increse the vlaue to numeric id by 1
SET #NId = #NId + 1
--GENERATE ACTUAL APHANUMERIC ID HERE
SET #PId = #PId + cast(#NId AS VARCHAR)
INSERT INTO tblProduct(id)values (#PId)
END
Change
SELECT #NId = cast(substring(MAX(id), 4, len(MAX(id))) as int)
FROM tblProduct group by left(id, 3) order by left(id, 3)
To
SELECT TOP 1
#NId = cast(substring(id, 4, len(id)) as int)
FROM tblProduct order by LEN(id) DESC, ID DESC
You have to remember that
PRD009
is always greater than
PRD0010
or
PRD001
All in all, I think your approach is incorrect.
Your values will be
PRD00
PRD001
...
PRD009
PRD0010
PRD0011
...
PRD0099
PRD00100
This will make sorting a complete nightmare.
In addition to astander's analysis, you also have a concurrency issue.
The simple fix would be to add this at the beginning of your proc:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
And add a COMMIT at the end. Otherwise, two callers of this stored proc will get the same MAX/TOP 1 value from your table, and insert the same value.
Also, you can and should prevent these duplicates from existing by adding a key to your table, for this column. If you already have a PRIMARY KEY on this table, you can add an additional key using a UNIQUE constraint. This will prevent duplicates occurring in the future, no matter what programming errors occur. E.g.
ALTER TABLE tblProduct ADD CONSTRAINT UQ_Product_ID UNIQUE (ID)