two digit year as primary key in sql server - sql-server

I have a table with these columns:
ID int,
d date
Now what I need is to define the primary key in such a way that ID would be unique for each year; meaning that there can not be two same IDs in 2004, but it is possible to have two same IDs in two different years.
Like:
insert into myTable values(1, '1-1-2004'), (1, '1-1-2005')
but not like:
insert into myTable values(1, '3-1-2005'), (1, '1-1-2005')
I tried this:
primary key(ID, datepart(YY, d))
but I get syntax error.

One way of doing this, if you can alter the table structure, is to add a persisted computed column for the year part, and then add a primary key for (id, computer_col), like this:
CREATE TABLE myTable (
id INT NOT NULL,
d DATE NOT NULL,
y AS DATEPART(YEAR,d) PERSISTED NOT NULL,
PRIMARY KEY(id,y)
)
I'm not saying this is a good solution in any way, but it should work. Using a trigger on insert or a check constraint might be better.
Using your test data this will allow the first insert statement, but disallow the second as it violates the primary key constraint.

Related

How do I avoid duplicates on an SQL primary key?

Complete beginner here. I'm trying to create this simple joint table on SSMS but I'm getting this duplicate error regarding the primary key:
Msg 2627, Level 14, State 1, Line 23
Violation of PRIMARY KEY constraint 'PK__FactOffl__B14003C24ECE0589'. Cannot insert duplicate key in object 'dbo.FactOfflineSales'. The duplicate key value is (43659).
What am I doing wrong?
CREATE TABLE FactOfflineSales
(
SalesOrderID int NOT NULL PRIMARY KEY,
SalesOrderNumber nvarchar(20) NOT NULL,
SalesPersonID int NULL,
CustomerID int NULL,
SpecialOfferID int NOT NULL,
TerritoryID int NOT NULL,
ProductID int NOT NULL,
CurrencyRateID int NULL,
OrderQuantity smallint NULL,
UnitPrice money NULL,
SubTotal money NULL,
TaxAmount money NULL,
Freight money NULL,
LineTotal money NULL,
UnitPriceDiscount float NULL,
OrderDate datetime NULL,
ShipDate datetime NULL,
DueDate datetime NULL,
OnlineOrderFlag int NULL
);
INSERT INTO FactOfflineSales (
SalesOrderID
,SalesOrderNumber
,SalesPersonID
,CustomerID
,SpecialOfferID
,TerritoryID
,ProductID
,CurrencyRateID
,OrderQuantity
,UnitPrice
,SubTotal
,TaxAmount
,Freight
,LineTotal
,UnitPriceDiscount
,OrderDate
,ShipDate
,DueDate
,OnlineOrderFlag
)
SELECT
SalesOrderHeader.SalesOrderID
,SalesOrderHeader.SalesOrderNumber
,SalesOrderHeader.SalesPersonID
,SalesOrderHeader.CustomerID
,SalesOrderDetail.SpecialOfferID
,SalesOrderHeader.TerritoryID
,SalesOrderDetail.ProductID
,SalesOrderHeader.CurrencyRateID
,SalesOrderDetail.OrderQty
,SalesOrderDetail.UnitPrice
,SalesOrderHeader.SubTotal
,SalesOrderHeader.TaxAmt
,SalesOrderHeader.Freight
,SalesOrderDetail.LineTotal
,SalesOrderDetail.UnitPriceDiscount
,SalesOrderHeader.OrderDate
,SalesOrderHeader.ShipDate
,SalesOrderHeader.DueDate
,SalesOrderHeader.OnlineOrderFlag
FROM
AdventureWorks2019.Sales.SalesOrderHeader SalesOrderHeader
LEFT JOIN
AdventureWorks2019.Sales.SalesOrderDetail SalesOrderDetail ON SalesOrderHeader.SalesOrderID = SalesOrderDetail.SalesOrderID;
You can not insert the duplicate value in primary key column.
You table FactOfflineSales has primary key on column SalesOrderID which will hold not null and unique values. But looking at your query it seems like there is one-to-many relationship between salesorderheader and salesorderdetails table. so your select query is giving multiple records for single salesorderid, means multiple records with same salesorderid.
It is not allowed to insert duplicate values in FactOfflineSales.salesorderid. Hence, it is throwing an error.
Best practice is to use auto generated sequence for primary key column. You should add one more column in table and declare it as primary key.(something like FactOfflineSales_id)
Your LEFT JOIN on SalesOrderDetail is mutiplying out rows from SalesOrderHeader, from which the primary key comes.
You need to add SalesOrderDetailID to the table, with that (or that and SalesOrderID together) as the primary key
There is a lot going on here. Let me unpack.
A join between tables helps when there is many to many. What does this mean? It means a header has many detail records and a detail record can belong to many headers. If this is not your case, you don't need a join table.
If you really need a join table, you normally do one of two things. The first would be create a composite key on the IDs from both tables. This would only be unique more than once if you load the table with a query that has dupes (solved with DISTINCT). The second (other option) is create a derived key for the primary key (like IDENTITY) and then have the two fields.
As you are learning, neither apply, but you can play with the queries to learn.
See if adding a distinct in the SELECT eliminates dupes. I don't think this will work, but DISTINCT is good to learn.
When the above fails, add a derived key on the table and then rerun your query. You do this by creating this join table with an Id field that is IDENTITY(1,1) for the PRIMARY KEY
As already mentioned by others, your issue is you have dupes on the primary key, which is sales order Id. Why dupes? Because each header has multiple detail records. On record 2, it will fail, as you had the header's id as your primary key.

SQL Server : identity and autoincrement for varchar

This is my first question on this platform. I am working on a database project. I want to use autoincrement for my primary key for id, but also want to add an alphabet before it. Are there other ways to do it apart from using 2 columns declaring one as identity and casting the other? I have worked with stored procedures and triggers.
Thank you
PS: I want to do it using one column if possible
You won't be able to do this with just one column.
The best solution is to use
an ID INT IDENTITY(1,1) column to get SQL Server to handle the automatic increment of your numeric value
a computed, persisted column to convert that numeric value to the value you need
So try this:
CREATE TABLE dbo.tblCompany
(
ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
CompanyID AS 'CMP-' + RIGHT('00000' + CAST(ID AS VARCHAR(5)), 5) PERSISTED,
.... your other columns here....
)
Now, every time you insert a row into tblCompany without specifying values for ID or CompanyID:
INSERT INTO dbo.tblCompany(Col1, Col2, ..., ColN)
VALUES (Val1, Val2, ....., ValN)
then SQL Server will increase your ID value, and CompanyID will contain values like CMP-00001, CMP-00002,...... and so on - automatically. The CompanyID column will be fill automatically, by SQL Server, upon inserting a new row - so there's no need for triggers or stored procedures or anything else - just this declaration in your table definition.
UPDATE: if you're using SQL Server 2012 or newer, you can do it with just one column - if you also create a SEQUENCE - like this:
CREATE SEQUENCE SEQ_CompanyID
AS INT
START WITH 1000
INCREMENT BY 1;
CREATE TABLE dbo.Company
(
CompanyID VARCHAR(20) NOT NULL
CONSTRAINT DF_CompanyID
DEFAULT('CMP-' + CAST(NEXT VALUE FOR dbo.SEQ_CompanyID AS VARCHAR(10))),
CompanyName VARCHAR(100) NOT NULL,
----- other columns here
)
Now if you make sure to insert with omitting the CompanyID column in the insert statement, like this:
INSERT INTO dbo.Company (CompanyName)
VALUES ('Company #1'), ('Company ABC'), ('Company Three');
then you get CMP-1001', 'CMP-1002 etc. as your CompanyID, again, automatically handled by SQL Server upon inserting a new row.

SQL FOREIGN KEY constraint in Insert

I've got a problem to insert some values into table.
Microsoft SQL server management shows that:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK__Players__Menager__55D59338". The conflict occurred in database
"TransferyProjekt", table "dbo.Menagers", column 'idMenager'.
My Create table script.
CREATE TABLE Menagers (
idMenager INT IDENTITY(1,1) NOT NULL,
[name] VARCHAR(30) NOT NULL CHECK (name LIKE '[A-Z]%'),
surname VARCHAR(30) NOT NULL CHECK (surname LIKE '[A-Z]%'),
phoneNumber VARCHAR(18) NOT NULL CHECK (phoneNumber LIKE '+[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]'),
PRIMARY KEY (idMenager)
);
CREATE TABLE Players (
idPlayer INT IDENTITY(1,1) NOT NULL,
[name] VARCHAR(30) NOT NULL,
surname VARCHAR(30) NOT NULL,
age DATE NOT NULL check (DATEDIFF(year,age,GETDATE()) > 18),
club INT NOT NULL,
Menager INT NOT NULL,
PRIMARY KEY (idPlayer),
FOREIGN KEY (club) REFERENCES Clubs(idClub),
FOREIGN KEY (Menager) REFERENCES Menagers(idMenager)
);
My insert look like.
INSERT INTO Menagers VALUES
('Adil','Green','+232247832'),
('Wicky','Dock','+301494064'),
('Alead','King','+447499384'),
('Darmian','Dagoly','+445587849'),
('Kamila','Dobra','+958789278'),
('Mateusz','Jankowiak','+849383098'),
('Lendy','Day','+448902920'),
('Martin','Lloyd','+501044468'),
('Adam','Dosh','+045033739'),
('Cristian','Cosy','+307748735'),
('Andrew','Lloyd','+635875452'),
('Matias','Banega','+520091224'),
('Carl','Rossi','+196935415'),
('Michał','Rolnik','+156541588'),
('Denny','Nowsky','+231785387'),
('Micky','Elly','+125774609'),
('George','Taylor','+094371433'),
('Barack','Obama','+916764868'),
('Jin','Chan','+906765545'),
('Lee','Konsu','+608935829'),
('Adam','Kenzo','+417708081'),
('Bryan','Along','+939454178'),
('Robert','Leey','+183354912'),
('Tom','Vardy','+576176145'),
('Kevin','Betword','+721582207');
INSERT INTO Players VALUES
('Lionel','Messi','1986-07-13','23','4'),
('Cristiano','Ronaldo','1986-04-11','23','5'),
('Sergio','Ramos','1986-09-07','23','12'),
('Łukasz','Piszczek','1986-11-20','23','14'),
('Robert','Lewandowski','1986-12-01','2','13'),
('Michał','Pazdan','1986-06-01','3','23'),
('Łukasz','Trałka','1986-05-02','7','20'),
('Łukasz','Teodorczyk','1986-04-14','6','18'),
('Mariusz','Miley','1985-03-06','3','26');
You should define column names if your source table and destination table has different column counts or different order
INSERT INTO Menagers([name],surname, phoneNumber )
VALUES
('Adil','Green','+232247832'),
('Wicky','Dock','+301494064'),
....
INSERT INTO Players([name], surname, age, club, Menager )
VALUES
('Lionel','Messi','1986-07-13','23','4'),
('Cristiano','Ronaldo','1986-04-11','23','5'),
...
Your foreign key and primary key columns are INT type. But, you are having following SQL
INSERT INTO Players VALUES
('Lionel','Messi','1986-07-13','23','4')
where last column belongs to foreign key column and your value is string.
A foreign key is a reference to a unique value in a different table, and SQL will ensure "referential integrity" - which means it won't let you end you with "orphaned" key references. When you insert a value to a foreign key column it must be a null or a existing reference to a row in the other table, and when you delete, you must delete the row containing the foreign key first, then the row it references.
If you don't, you will get an error such as you describe.
So enter the row into the "main" table first, then enter the "dependant" table information second.
In FK column I added unfortunately referece to row who is not exists in table Managers...
('Mariusz','Miley','1985-03-06','3','26');
but I don't have 26'th row in table Managers (Blame me, now) :(

Autoincrement of primary key column with varchar datatype in it

CREATE TABLE Persons
(
P_Id varchar(6) NOT NULL,
LastName varchar(255) NOT NULL,
FirstName varchar(255),
PRIMARY KEY (P_Id)
)
in this table P_Id is the primary key. We want to generate autoincrement of P_Id with default value (PN00) in the start while inserting only LastName and FirstName .eg :-PN001 for first entry ,PN002 for second,PN003 for third and so on .
The only viable solution is to use
an ID INT IDENTITY(1,1) column to get SQL Server to handle the automatic increment of your numeric value
a computed, persisted column to convert that numeric value to the value you need
So try this:
CREATE TABLE dbo.Persons
(ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
P_ID AS 'PN' + RIGHT('00000' + CAST(ID AS VARCHAR(5)), 5) PERSISTED,
.... your other columns here....
)
Now, every time you insert a row into Persons without specifying values for ID or P_ID:
INSERT INTO dbo.Persons(Col1, Col2, ..., ColN)
VALUES (Val1, Val2, ....., ValN)
then SQL Server will automatically and safely increase your ID value, and P_Id will contain values like PN00001, PN00002,...... and so on - automatically, safely, reliably, no duplicates.
There are different ways to address your issue.
You could use a Trigger.Triggers are activated on some events. You could create a trigger for 'Instead of Insert On Persons' event. When the event is triggered, then generate a new P_Id. Insert this new P_Id alongwith all the values as the new record for your table.
This approach wont have coupling with the table's schema.
Refer this link for more information on Triggers :
https://msdn.microsoft.com/en-IN/library/ms189799.aspx
Refer this link to emulate 'before insert trigger' in SQL Server:
How to emulate a BEFORE INSERT trigger in T-SQL / SQL Server for super/subtype (Inheritence) entities?
You could also use a Procedure like :
create procedure Persons_insert(#lastname varchar(255), #firstname varchar(255))
as
begin
--write code to generate the ID as you like
insert into Persons(p_id,lastname,firstname)values(generated_id,lastname,firstname);
end

Ensuring relationship integrity in a database modelling sets and subsets

I have a model that consists in these 3 tables (among others):
Item
PK id_item
Set
PK id_set
Subset
PK id_subset
Each Item MUST belong to one and just one Set (1..N)
You can define zero or more Subsets for each Set (0..N)
Each Item belongs to zero or one subset (0..N)
Ive modelled the database adding the following FK:
Item
PK id_item
FK id_set
FK id_subset
Set
PK id_set
Subset
PK id_subset
FK id_set
I cannot find a way to forbid the database to accept Items belonging to one Set (A) and to a Subset (B2) that belongs to a different Set (B).
Is there anyway to do so? Or is this just a bad design/modelling?
This is a SQL Server 2008 database
First, if an Item can belong to a subset, you must add a foreign key between the Item table and the subset table.
Second, add a check constraint on the Item table that will make sure that if the subset_id does not belong in the set_id, will raise an exception.
To do that, first you create a user defied function to test the values:
CREATE FUNCTION udf_CheckSubSet
(
#id_set int,
#id_subset int
)
RETURNS int
AS
BEGIN
IF #id_subset IS NULL OR EXISTS (
SELECT 1
FROM Subset
WHERE id_subset = #id_subset
AND id_set = #id_set
)
BEGIN
RETURN 1
END
-- logical else
RETURN 0
END
then you create the check constraint:
ALTER TABLE Item
ADD CONSTRAINT cc_Item_subset CHECK (dbo.udf_CheckSubSet(id_set, id_subset) = 1);
However, I also suggest to create a stored procedure to insert the item, and test inside the stored procedure before inserting the item.
The reason for this is that it's much more expensive (performance-wise) to handle exceptions then to simply test the input before inserting it to the table.
you might be wondering why do you even need the check constraint, if you already handle the problem with the stored procedure. The answer to this question is that the check constraint will not allow inserting updating the data in the table even if someone tries to do it directly from SSMS, or just write an insert or update statement.
Disclaimer: while it is possible to implement this kind of constraint using database schema alone, I strongly advise you against using the approach explained below in any real life project.
Academically speaking, in order to do what you want you have to migrate the identifying key from Set via both Set and Subset foreign keys. The schema will look like this:
use master;
go
if db_id('SampleDB') is not null
set noexec on;
go
create database SampleDB;
go
use SampleDB;
go
/*==============================================================*/
/* Table: Sets */
/*==============================================================*/
create table dbo.[Sets] (
[Id] int not null,
[Name] varchar(50) not null,
constraint [PK_Sets] primary key (Id)
)
go
/*==============================================================*/
/* Table: SubSets */
/*==============================================================*/
create table dbo.[SubSets] (
[SetId] int not null,
[SubsetId] int not null,
[Name] varchar(50) not null,
constraint [PK_SubSets] primary key (SetId, SubsetId)
)
go
alter table dbo.SubSets
add constraint FK_SubSets_Sets_SetId foreign key (SetId)
references dbo.Sets (Id)
go
/*==============================================================*/
/* Table: Items */
/*==============================================================*/
create table dbo.[Items] (
[Id] int not null,
[SetId] int not null,
[SubsetId] int null,
[Name] varchar(50) not null,
constraint [PK_Items] primary key (Id)
)
go
alter table dbo.Items
add constraint FK_Items_Sets_SetId foreign key (SetId)
references dbo.Sets (Id)
go
alter table dbo.Items
add constraint FK_Items_SubSets_SetIdSubsetId foreign key (SetId, SubsetId)
references dbo.SubSets (SetId, SubsetId)
go
set noexec off;
go
use master;
go
As you can see, the PK on the dbo.Subset table is somewhat lame. It serves its purpose, of course, but it could have been made simpler. Another unusual thing is that SubsetId column in dbo.Items table participates in 2 foreign keys that point to different tables.
You can insert some data into this schema, and it will be perfectly fine:
insert into dbo.Sets (Id, Name)
values
(1, 'Set 1'),
(2, 'Set 2');
go
insert into dbo.SubSets (SetId, SubsetId, Name)
values
(1, 1, 'Subset 1-1'),
(1, 2, 'Subset 1-2');
go
insert into dbo.Items (Id, SetId, SubsetId, Name)
values
(1, 1, 1, 'Banana'),
(2, 1, 1, 'Plate'),
(3, 1, 2, 'Charger'),
(4, 1, null, 'Toothpick'),
(5, 2, null, 'Cup');
And you will be hit with FK constraint violation when you try to add contradictory data, such as this:
insert into dbo.Items (Id, SetId, SubsetId, Name)
values
(6, 2, 1, 'Fake t-shirt');
The subset 1 does not belong to the set 2, so the command above will not succeed.
Now - why you should never use this design approach, unless being forced to do so at the gunpoint:
Not every business constraint can and should be implemented on the
schema level. Actually, writing it down in the stored procedure will
be easier to understand, maintain and work with, in most cases;
It contains rarely used tricks which are very confusing and
unexpected for most people, even seasoned database professionals. All
of this add up to the cost of maintenance;
Last but not least - queries that will work correctly with this kind
of schema will be, how shall I put this, awkward and difficult to write. Also, you will most probably encounter a lot of problems it you will try to combine this schema with any kind of ORM. Or maybe not; or maybe they will only manifest themselves once being put in production, etc.

Resources