Adding new NOT NULL column to existing database - sql

Lets say I have a system that has a person table:
CREATE TABLE Person
(
FirstName NVARCHAR(50) NOT NULL,
LastName NVARCHAR(50) NOT NULL,
DOB DATE NOT NULL
)
If we want to update the system after it has been in place for a number of years and we now want to also capture the person's address (by means of a new NVARCHAR column called Address which is NOT NULL)
Therefore we want all new records to be NOT NULL but we don't have any data for the old records so they would have to be NULL
What is the best way to do this? We cannot add a NOT NULL column because it would be NULL for all the existing records.
Is the best way to add the column and allow NULLS, add some placeholder value (EG '.') to the existing records, then alter the column to be NULL?
Or is there some other way to do it?

You cannot add a new NOT NULL column without assigning non-NULL values to existing rows (via a default constraint). However, you can add a NULL column and ensure only NOT NULL values going forward by specifying a check constraint that prohibits NULL values along with the NOCKECK option so that existing data are not validated:
CREATE TABLE dbo.Person
(
FirstName NVARCHAR(50) NOT NULL,
LastName NVARCHAR(50) NOT NULL,
DOB DATE NOT NULL
)
INSERT INTO dbo.Person (FirstName, LastName , DOB)
VALUES(N'foo', N'bar', '20010101');
GO
--add new column and CHECK constraint with NOCHECK
ALTER TABLE dbo.Person WITH NOCHECK
ADD Address nvarchar(50) NULL
CONSTRAINT CK_Person_Address CHECK (Address IS NOT NULL);
GO
--this fails due to check constraint violation
INSERT INTO dbo.Person (FirstName, LastName , DOB)
VALUES(N'foo2', N'bar2', '20020101');
GO
--this succeeds
INSERT INTO dbo.Person (FirstName, LastName , DOB, Address)
VALUES(N'foo2', N'bar2', '20020101', N'non-null address');
GO
This method provides the proper NULL semantics for existing persons that have no addresses yet guarantee the desired data integrity for new persons.

Related

How to make the default selection of a value in sql out of multiple choices?

I have a column in my SQL table i.e. Gender and there can be one of two possible values for it, 'M' and 'F'.
For those values, I am able to pass two values by using a check constraint as option when creating the table:
Gender varchar(6) CHECK (Gender IN ('M', 'F'))
Also, one of those value is defined as the default:
Gender varchar(6) DEFAULT 'M'
But here, if I am trying to merge those two queries while table creation, I am not getting the output. I want to pass two choices for column value and default as 'M'.
Both can be used as part of the create table syntax:
create table t(gender char(1) default('M') check(gender in ('M','F')));
Demo Fiddle
You can easily use both in the CREATE TABLE statement - and preferably, define explicit names for your constraints!
CREATE TABLE Person
(
FirstName VARCHAR(50) NOT NULL,
LastName VARCHAR(50) NOT NULL,
Gender CHAR(1) NOT NULL
CONSTRAINT CHK_Person_Gender CHECK (Gender IN ('M', 'F'))
CONSTRAINT DF_Person_Gender DEFAULT ('M')
)

SQL auto-validating data when adding it to row

My SQL code is as follows:
CREATE TABLE personsdb
(personID int IDENTITY(1,1) NOT NULL,
personName varchar(50) NOT NULL,
associatedWith varchar(50) NOT NULL,
CONSTRAINT pk_persondb PRIMARY KEY (personID),
CONSTRAINT uq_persondb UNIQUE (associatedWith))
INSERT INTO personsdb
(personID, personName, associatedWith)
VALUES
('John', 'Mary'),
('Jack', 'Maggie'),
('Jeff', 'Marie')
I have a UNIQUE Constraint on the 'associatedWith' column, as I want to make sure that a person in personName can only be associated with one person in associatedWith, i.e. Mary could not be associated with Jeff because Mary is already associated with John.
My query relates to inserting the next row of the table. I want to insert 'Mary' into the personName column, but need a rule that autopopulates or only allows 'John' to be populated in the corresponding 'associatedWith' field, as a person can only be associated with one other person, and as John is already associated with Mary, when Mary is entered into the table, she should automatically be associated with John.
I'm relatively new to SQL but I'd like to figure it out as much as possible myself - if you could hint at a way to do this (in layman's terms) I'd be grateful so that I can go and research and learn how to do this.
Thanks very much in advance for your help.
David
This is a unary relationship which you cannot implement using foreign keys. You'll have to use a trigger. See below for full implementation.
CREATE TABLE dbo.Person(PersonId int not null primary key identity(1, 1), PersonName varchar(20) not null, AssociatedWith varchar(20));
GO
create trigger dbo.AssociationConstraint
ON dbo.Person
FOR INSERT, UPDATE
AS
IF (EXISTS(SELECT TOP(1) 1 FROM inserted i INNER JOIN dbo.Person p on i.PersonName = p.AssociatedWith)
OR EXISTS(SELECT TOP(1) 1 FROM inserted i INNER JOIN dbo.Person p on i.AssociatedWith = p.PersonName))
BEGIN
RAISERROR('Person is already part of a relationship', 16, 1);
ROLLBACK TRANSACTION;
END
GO
insert into dbo.Person(PersonName, AssociatedWith) values('John', 'Mary'), ('Jack', 'Maggie'), ('Jeff', 'Marie');
--this will error
insert into dbo.Person(PersonName, AssociatedWith) values('Mary', 'Jack');

Inserting null values when using bulk insert

I have one table:
CREATE TABLE cust (
cust_id NOT NULL,
cust_name NOT NULL,
address NULL
);
I have to insert these rows into another table:
CREATE TABLE 1cust_det (
cust_id NOT NULL,
cust_name NOT NULL,
address NOT NULL
);
Unfortunately the cust.address column contains NULL values. I want to insert these rows from cust to 1cust_det using a cursor. What do I have to do?
INSERT INTO
cust_det
SELECT
cust_id,
cust_name,
COALESCE(address, 'UNKNOWN')
FROM
cust
If you have access to change the destination table, just add a default to the column.
CREATE TABLE 1cust_det (
cust_id NOT NULL,
cust_name NOT NULL,
address NOT NULL DEFAULT 'DEFAULT_VALUE');
or if you can edit the existing destination table and it doesnt get drooped
ALTER TABLE 1cust_det
ALTER address SET DEFAULT 'DEFAULT_VALUE'
The easiest way if you don't have control of the destination table to add a default value to the address column is to use a case statement in the insert itself. In the example below you can also use a ISNULL evaluation, but you might want to search for empty strings as well. Please try to find a better way to insert instead of using a cursor.
INSERT dbo.1cust_det
(cust_id,cust_name,[address])
SELECT cust_id,cust_name,
CASE
WHEN [address] IS NULL THEN 'some default value'
ELSE [address]
END AS [address]
FROM cust
Above answers are correct. You may have another table that may have address for cust_id. Join that table to get missing address. I have seen that in almost all databases, address is stored for every customer. You must get address where address is NULL in the table cust.

SQL constraint “at least one of two attributes”

I need to create a table User with telephone_number and e_mail_adress columns. Each row must have at least one of those columns set. It could have both or just one, but it must have at least one of them.
How can I express that constraint in SQL?
create table Users (
/* Whatever */
TelephoneNumber varchar(2000) null,
EmailAddress varchar(5) null,
constraint CK_AtLeastOneContact CHECK (
TelephoneNumber is not null or
EmailAddress is not null
)
)
You may want to adjust the data types :-)

How can I insert a set of child records while updating the parent?

I'm using SQL Server 2005 and wish to create a number address records, updating the contact records with the new Id's:
Take the following tables
create table contact(id int primary key identity, home_address_id int, work_address_id int)
create table address(id int primary key identity, street varchar(25), number int)
And foreign keys:
ALTER TABLE dbo.contact ADD CONSTRAINT FK_contact_address1 FOREIGN KEY (home_address_id) REFERENCES dbo.address(id)
ALTER TABLE dbo.contact ADD CONSTRAINT FK_contact_address2 FOREIGN KEY (work_address_id) REFERENCES dbo.address(id)
some dummy data
insert into contact default values
insert into contact default values
insert into contact default values
How can I insert a default empty address record for all contacts who have no home address, and update the home_address_id in one go?
The first part is simple:
insert into address(street) select null from contact where home_address_id is null
I can even get the newly create address id's:
declare #addressTable table(id int)
insert into address(street)
OUTPUT INSERTED.Id INTO #addressTable
select null from contact where home_address_id is null
Here's the new id's
select * from #addressTable
But how to update the contact table with these new Id's?
If possible, I would suggest normalizing your database by adding a Contact_Addresses table:
CREATE TABLE Contact_Addresses
(
contact_id INT NOT NULL,
address_id INT NOT NULL,
address_type VARCHAR(10) NOT NULL,
CONSTRAINT PK_Contact_Addresses PRIMARY KEY CLUSTERED (contact_id, address_id, address_type),
CONSTRAINT FK_ContactAddresses_Contacts (contact_id) REFERENCES Contacts (id),
CONSTRAINT FK_ContactAddresses_Addresses (address_id) REFERENCES Addresses (id),
CONSTRAINT CK_ContactAddresses_address_type CHECK address_type IN ('HOME', 'WORK')
)
Next, I would suggest not putting "dummy" records in your database. It's going to end up causing headaches down the road. The database should contain an accurate record of the data in your system. If you want to display some value by default when no address exists in the system for a contact then handle that in your UI.
If you really must though, then the following code should do the trick:
;WITH C_CTE AS
(
SELECT
id,
home_address_id,
ROW_NUMBER() OVER(ORDER BY id) AS seq
FROM
Contacts
),
(
SELECT
id,
ROW_NUMBER() OVER(ORDER BY id) AS seq
FROM
Addresses
)
UPDATE
C_CTE
SET
home_address_id = A.id
FROM
C_CTE C
INNER JOIN A_CTE A ON A.seq = C.seq
I would do it from the moment you get a new contact, thusly:
[receive contact information]
//prior to inserting contact
declare #homeAddress int, #workAddress int
[insert home address here (real or default based on input)]
set #homeAddress = ##Identity
[insert work address here (real or default)]
set #workAddress = ##Identity
[insert contact here referencing #homeAddress & #workAddress]
For the stuff already in your table, you're going to have to associate all of your null value ids to a contact id. Or, you could clear out your null value addresses, and modify the above statement to an update somehow (brain's not working at the moment, so all I'm coming up with is a cursor, and cursors are evil).