I have a unique index today that allows all null values for column ssn, but if it has a value it has to be unique in combination with server.
Now I want to change it so that it both allow null values and a special defined value.
The old index was created like this:
CREATE UNIQUE INDEX UQ_SSN ON person (
CASE WHEN ssn IS NULL THEN NULL ELSE server END,
ssn);
Now I would like to change it into something like this:
CREATE UNIQUE INDEX UQ_SSN ON person (
CASE WHEN (ssn IS NULL OR ssn = 'SPECIAL_VALUE') THEN NULL ELSE server END,
ssn);
That doesn't work though. With that index I'm still allowed to add null-values. But I am only allowed to add one row where ssn = 'SPECIAL_VALUE', on the second one I get the error:
ORA-00001: unique constraint (APP_DB.UQ_SSN) violated
You are getting the issue because when ssn = 'SPECIAL_VALUE' it creates the index on null, 'SPECIAL_VALUE' and while you insert new record then also it creates the index on null, 'SPECIAL_VALUE' which is not allowed.
You must use some other column like PK_COLUMN when there is ssn = 'SPECIAL_VALUE' so that null, pk_col will be different for each row.
Try case..when in the second column of the index.
CREATE UNIQUE INDEX UQ_SSN ON person (
server,
CASE WHEN ssn is null or ssn = 'SPECIAL_VALUE' THEN to_char(id) ELSE ssn END);
Note: You can use CASE .. WHEN without else. so I have slightly modified it.
Related
I am having a simple scores table for HTML5 game with columns: name, email and score. Email value should be a unique value, but when the same users play the game again to better their scores, the score should be updated for that user. Now it returns an error because of the unique value. How should I create a table that will update the data?
The table I have created so far:
CREATE TABLE `scores` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR( 20 ) NOT NULL,
`email` VARCHAR( 320 ) NOT NULL UNIQUE,
`score` INT NOT NULL,
PRIMARY KEY ( `id` )
) ENGINE = InnoDB;
You could use an insert statement with an on duplicate key clause:
INSERT INTO scores (name, email, score)
VALUES ('some_name', 'some_email', 123) -- Values from your application
ON DUPLICATE KEY UPDATE
score = CASE VALUES(score) > score THEN VALUES(score) ELSE score END
It is not about your create table query, it is about your insert query.
Use a try catch method to update the unique value
try
{
// inserting the line
}
catch (exception)
{
// Drop the row with that unique value
// inserting the same line which you have added in "try" section
}
I have this function:
CREATE FUNCTION CheckAkvNames (#Name VARCHAR(20))
RETURNS INT
AS
BEGIN
DECLARE #NoTexist int = 1
SELECT
#NoTexist = CASE WHEN COUNT(*) > 0 THEN 0 ELSE 1 END
FROM
[dbo].[Names]
WHERE
[Name] = #Name
RETURN #NoTexist
END
GO
ALTER TABLE [dbo].[Names]
ADD CONSTRAINT chkNames CHECK(dbo.CheckAkvNames([Name]) = 1);
GO
The problem is, when I run this on empty table I can't insert ...
So this change works:
CASE WHEN (COUNT(*) - 1) > 0 THEN 0 ELSE 1 END
WHY? Any ideas?
Edit:
Aim is to insert only names that are not in the table. I know it would be better to use key, point of the question is not to find better solution but why this solution does not work.
The constraint you added to the table actually means that you can't insert any name in the table, because for any value inserted in the table the function should return 1.This is impossible because if the name was inserted then the constraint would be violated.
This is why count(*) - 1 works: if there is already a name inserted and you tried to insert the same name then the constraint would be violated.
If you want unique names in a table, do not use a check constraint, use a unique constraint (or equivalently a unique index):
ALTER TABLE [dbo].[Names]
ADD CONSTRAINT unq_names_name UNIQUE (Name);
Basically let's say I have a "Business" that owns postal codes that it services. Let's also suppose I have another relational table that sets up fees.
CREATE TABLE [dbo].[BusinessPostalCodes]
(
[BusinessPostalCodeId] INT IDENTITY (1, 1) NOT NULL,
[BusinessId] INT NOT NULL,
[PostalCode] VARCHAR (10) NOT NULL
)
CREATE TABLE [dbo].[BusinessPostalCodeFees]
(
[BusinessId] INT NOT NULL,
[BusinessProfileFeeTypeId] INT NOT NULL,
[BusinessPostalCodeId] INT NOT NULL,
[Fee] SMALLMONEY NULL
)
I want to know if it's possible to set up a foreign key (or something) on BusinessPostalCodeFees that ensures that the related BusinessId of BusinessPostalCodes is the same as the BusinessId of BusinessPostalCodeFees.
I realize that I can remove BusinessId entirely, but I would much rather keep this column and have a way of guaranteeing they will be the same. Is there anything I can do?
It sounds like (and correct me if I'm wrong) that you're trying to make sure that any entry into BusinessPostalCodeFees' BusinessId and BusinessPostalCodeId columns match an entry in the BusinessPostalCodes table. If that's the case, then yes, you can definitely have a foreign key that references a compound primary key.
However, if you need to keep the BusinessId, I'd recommend normalizing your tables a step further than you have. You'll end up with duplicate data as-is.
On a side note, I would recommend you don't use the money data types in SQL: See here.
In the end, Jeffrey's solution didn't quite work for my particular situation. Both columns in the relation have to be unique (like a composite key). Turns out the answer here (for me) is a Checked Constraint.
Create a function that you want to have the constraint pass or fail:
CREATE FUNCTION [dbo].[MatchingBusinessIdPostalCodeAndProfileFeeType]
(
#BusinessId int,
#BusinessPostalCodeId int,
#BusinessProfileFeeTypeId int
)
RETURNS BIT
AS
BEGIN
-- This works because BusinessPostalCodeId is a unique Id.
-- If businessId doesn't match, its filtered out.
DECLARE #pcCount AS INT
SET #pcCount = (SELECT COUNT(*)
FROM BusinessPostalCodes
WHERE BusinessPostalCodeId = #BusinessPostalCodeId AND
BusinessId = #BusinessId)
-- This works because BusinessProfileFeeTypeId is a unique Id.
-- If businessId doesn't match, its filtered out.
DECLARE #ftCount AS INT
SET #ftCount = (SELECT COUNT(*)
FROM BusinessProfileFeeTypes
WHERE BusinessProfileFeeTypeId = #BusinessProfileFeeTypeId AND
BusinessId = #BusinessId)
-- Both should have only one record
BEGIN IF (#pcCount = 1 AND #ftCount = 1)
RETURN 1
END
RETURN 0
END
Then just add it to your table:
CONSTRAINT [CK_BusinessPostalCodeFees_MatchingBusinessIdPostalCodeAndProfileFeeType]
CHECK (dbo.MatchingBusinessIdPostalCodeAndProfileFeeType(
BusinessId,
BusinessPostalCodeId,
BusinessProfileFeeTypeId) = 1)
I have a table like this :
create table ReceptionR1
(
numOrdre char(20) not null,
dateDepot datetime null,
...
)
I want to increment my id field (numOrdre) like '225/2015','226/2015',...,'1/2016' etc. What should I have to do for that?
2015 means the actual year.
Please let me know any possible way.
You really, and I mean Really don't want to do such a thing, especially as your primary key. You better use a simple int identity column for you primary key and add a non nullable create date column of type datetime2 with a default value of sysDateTime().
Create the increment number by year either as a calculated column or by using an instead of insert trigger (if you don't want it to be re-calculated each time). This can be done fairly easy with the use of row_number function.
As everyone else has said - don't use this as your primary key! But you could do the following, if you're on SQL Server 2012 or newer:
-- step 1 - create a sequence
CREATE SEQUENCE dbo.SeqOrderNo AS INT
START WITH 1001 -- start with whatever value you need
INCREMENT BY 1
NO CYCLE
NO CACHE;
-- create your table - use INT IDENTITY as your primary key
CREATE TABLE dbo.ReceptionR1
(
ID INT IDENTITY
CONSTRAINT PK_ReceptionR1 PRIMARY KEY CLUSTERED,
dateDepot DATE NOT NULL,
...
-- add a colum called "SeqNumber" that gets filled from the sequence
SeqNumber INT,
-- you can add a *computed* column here
OrderNo = CAST(YEAR(dateDepot) AS VARCHAR(4)) + '/' + CAST(SeqNumber AS VARCHAR(4))
)
So now, when you insert a row, it has a proper and well defined primary key (ID), and when you fill the SeqNumber with
INSERT INTO dbo.ReceptionR1 (dateDepot, SeqNumber)
VALUES (SYSDATETIME(), NEXT VALUE FOR dbo.SeqOrderNo)
then the SeqNumber column gets the next value for the sequence, and the OrderNo computed column gets filled with 2015/1001, 2015/1002 and so forth.
Now when 2016 comes around, you just reset the sequence back to a starting value:
ALTER SEQUENCE dbo.SeqOrderNo RESTART WITH 1000;
and you're done - the rest of your solution works as before.
If you want to make sure you never accidentally insert a duplicate value, you can even put a unique index on your OrderNo column in your table.
Once more, you cannot use the combo field as your primary key. This solution sort or works on earlier versions of SQL and calculates the new annual YearlySeq counter automatically - but you had better have an index on dateDepot and you might still have issues if there are many, many (100's of thousands) of rows per year.
In short: fight the requirement.
Given
create table dbo.ReceptionR1
(
ReceptionR1ID INT IDENTITY PRIMARY KEY,
YearlySeq INT ,
dateDepot datetime DEFAULT (GETDATE()) ,
somethingElse varchar(99) null,
numOrdre as LTRIM(STR(YearlySeq)) + '/' + CONVERT(CHAR(4),dateDepot,111)
)
GO
CREATE TRIGGER R1Insert on dbo.ReceptionR1 for INSERT
as
UPDATE tt SET YearlySeq = ISNULL(ii.ReceptionR1ID - (SELECT MIN(ReceptionR1ID) FROM dbo.ReceptionR1 xr WHERE DATEPART(year,xr.dateDepot) = DATEPART(year,ii.dateDepot) and xr.ReceptionR1ID <> ii.ReceptionR1ID ),0) + 1
FROM dbo.ReceptionR1 tt
JOIN inserted ii on ii.ReceptionR1ID = tt.ReceptionR1ID
GO
insert into ReceptionR1 (somethingElse) values ('dumb')
insert into ReceptionR1 (somethingElse) values ('requirements')
insert into ReceptionR1 (somethingElse) values ('lead')
insert into ReceptionR1 (somethingElse) values ('to')
insert into ReceptionR1 (somethingElse) values ('big')
insert into ReceptionR1 (somethingElse) values ('problems')
insert into ReceptionR1 (somethingElse) values ('later')
select * from ReceptionR1
What am i missing?
create table Diver(
diver_number int primary key check(diver_number>0) not null,
first_name char(30) not null,
last_name char(30) not null,
fullname AS first_name+' '+last_name,
bithdate date not null,
email nchar(100) not null,
diver_password char(8) not null check(Len(diver_password) = 8
AND diver_password not like('%[^a-z0-9]%')),
diver_signature nchar(200) not null,
signature_date date not null,
old_diving_diaries nchar(200))
insert into Diver VALUES('1111','Dana','shwartz','1966/04/11','danas#gmail.com','dana1234','http://www.google.co.il','')
I'm getting this error:
Column name or number of supplied values does not match table definition.
Why?
Yup, the error pretty much speaks for itself. You're trying to insert 8 values into a table with 10 columns.
Consider listing the column names you wish to insert into explicitly
insert into Diver (column names here)
VALUES('1111','Dana','shwartz','1966/04/11','danas#gmail.com','dana1234','http://www.google.co.il','')
You miss old_diving_diaries.
You need to chage the values to
VALUES('1111','Dana','shwartz','1966/04/11','danas#gmail.com','dana1234','http://www.google.co.il','',
'') <-- this
You need only 9 data because of the computed column.
The number of values and data_types must be the same.
VALUES('1111','Dana','shwartz','1966/04/11','danas#gmail.com','dana1234','http://www.google.co.il','ds2016','sdd1','odd1')
10 values needed to pass. Now it must run.