Constraint column with specific value in table scope - sql-server

Based on Constraint for only one record marked as default would the same approach of a view and unique clustered index apply if I wanted to achieve the same result at a table scope?
I have a table called Accounts. There can be only one System account, however there can be many Partner and Client accounts. Each type of account does not vary in terms of the columns but instead with just the Type column.
ID | Type | Name
1 System Contoso
2 Partner Wingtip
3 Partner Northwind
4 Client Adventure Works
5 Client Fabrikam
In the above I want to prevent adding another System account, but allow many partner and client accounts. It feels like a concern that belongs in the database as opposed to the domain (maybe I'm wrong)?

If you know that the system account will always have ID number 1, you can implement this with a CHECK constraint. Something along these lines . . .
create table accounts (
id integer primary key,
type varchar(15) not null,
name varchar(15) not null,
unique (type, name),
check (
(id = 1 and type = 'System') or
(id <> 1 and type <> 'System')
)
);
In fact, if this is your database, the system account can have any id number. Just change the CHECK() constraint to match.
If you're building for deployment to a client site, you can add the system account before deployment. (And you probably should, regardless of how you handle the constraints.)
Think about about what to do when a user tries to delete rows from this table. (Especially that system account row.) Then think about what to do when a database admin tries to delete rows from that table.
You can probably use a foreign key constraint (no cascade) or a trigger to prevent a database admin from accidentally deleting the system account. The admin can probably get around those restrictions, but you'd hope she knows what she's doing if she's willing to go that far to delete a row.

Related

Why does SQL Server generate a new one identity value even if the insert fails?

The query to create the table with its respective field set as IDENTITY is the following:
CREATE TABLE user (
email varchar(100) primary key
name varchar(30),
pwd varchar(10)
)
Alter table to add IDENTITY field:
ALTER TABLE user ADD id int /*NOT NULL*/ IDENTITY;
The email field to be PRIMARY KEY INDEX will fail if a NULL or DUPLICATED value was set, for example supposed that myemail#domain.com already exists, OK the query fails, but I change the email to anotheremail#domain.com SQL Server generate a new one value for the IDENTITY field based on the query(s) that failed before. My question is why does this happen? (Is this ONLY on SQL Server or other database providers also)
Well, this is clearly documented in "CREATE TABLE (Transact-SQL) IDENTITY (Property)":
Reuse of values - For a given identity property with specific seed/increment, the identity values are not reused by the engine. If a particular insert statement fails or if the insert statement is rolled back then the consumed identity values are lost and will not be generated again. This can result in gaps when the subsequent identity values are generated.
Further along the documentation also answers why and suggests what to do if this is not acceptable:
These restrictions are part of the design in order to improve performance, and because they are acceptable in many common situations. If you cannot use identity values because of these restrictions, create a separate table holding a current value and manage access to the table and number assignment with your application.

Friends within an application that can be both application users and non-users - how to design the data model?

I am writing an application in which application users can add friends with whom they can later share things inside the app. The thing is that they can add friends who are already using the application (easy case) but they can also add people who are not yet using the application but might start doing it one day (the tricky part). In the latter case they can simply add a record in the app and then share it via email but the record still needs to be stored in a database. An important thing to mention is that whenever a user adds a record for a friend a record is added for him/her as well (because they are sharing stuff I need to add information about user's part as well).
If friends could only be application users, then I would simply have a User table and a junction table defining the many-to-many relationship between users. Then, the record table could look somewhat like this:
CREATE TABLE IF NOT EXISTS Record (
record_id INT NOT NULL, -- primary key
record_col VARCHAR(45) NULL, -- some column describing the record
added_by_id VARCHAR(45) NOT NULL, -- references user_id from the User table
added_for_id VARCHAR(45) NOT NULL -- references user_id from the User table
);
However, since one can add friends who are not application users and can then add records for them I think I need an additional table to store information about friends:
CREATE TABLE IF NOT EXISTS Friend (
friend_id INT NOT NULL, -- primary key
friend_col VARCHAR(45) NULL, -- some column describing the friend
friend_of_user_id VARCHAR(45) NOT NULL, -- id of the user who added this friend
is_user BIT(1) NOT NULL, -- boolean indicating if this friend has already created an account
friend_user_id INT NULL -- if above is true, user_id of this friend would go here
);
But then how do I handle this in the Record table? I could have some records added for application users and some for non-users so for some of them the foreign key would reference user_id and for the rest friend_id. This doesn't seem right to me...
I could also try putting everyone (both application users and non-users) in a single table but then for the same physical person (let it be John Smith) I would have potentially multiple records added by different people who are friends with John and a record representing the actual John Smith being application user. This seems nasty.
What am I missing here? This doesn't seem like an unusual thing to do and yet I cannot figure out a proper solution.

SET IDENTITY_INSERT ON/OFF needed on application server, but ALTER permission seems dangerous. Suggestion?

We are building a multi-user web app where they need an unique postId for post they create. Each post has (userId, postId) as the compound primary key.
Right now, postId is an identity value, but because of the need to support some operations that require postId to be inserted as is (no re-numbering), we decide to use SET IDENTITY_INSERT ON/OFF.
However, our DBA told us that such operation is not meant be used by the application server because the ALTER permission requirement:
Permissions
User must own the table or have ALTER permission on the table.
https://msdn.microsoft.com/en-ca/library/ms188059.aspx
If the application server got hacked, with ALTER permission it seems rather risky. Our DBA suggests us to not use identity value at all, and locally generate an unique postId per user.
Can SET IDENTITY_INSERT ON be left on globally?
If it can't be left on globally, does avoiding identity value and use local generation of postId (per user) with max(postId)+1 per user make sense? We much prefer to use identity value if possible because we are worried about possible deadlocks and performance issues associated with custom postId generation.
Starting with SQL Server 2012 you can use sequences like in Oracle. You may be better off with those. First, create the sequence:
CREATE SEQUENCE mySeq AS LONG START WITH 1 INCREMENT BY 1;
GO
Then have the table's primary key default to the next sequence value (instead of being an IDENTITY value):
CREATE TABLE myTable (
myPK LONG PRIMARY KEY DEFAULT (NEXT VALUE FOR mySeq),
myWhatever...
);
If you don't specify a PK value with an INSERT you'll get a unique, generated sequence value. It's basically the same behavior as an IDENTITY. But if you want to specify a PK value you can, as long as you don't violate the primary key's uniqueness - but again, that's the same behavior as an IDENTITY with SET IDENTITY INSERT ON.
It sounds like you need to evaluate your database design if this is possible. A post should be a fixed entity and an identity column as a single primary key should be sufficient. In your comment you mentioned that you might want to copy posts from one user to another user. If you want to split the post so that user1 and user2 can independently control their own versions of the post, then it's just a matter of copying all the post attributes into a new record (which creates a new identity key) and then updating the new records user attribute from User1 to User2. But if you want the users to share the same post... then you should do that with a relationship from user to post to avoid the need to maintain duplicate data in your post table. In other words, if you want to assign user1 and user2 to an identical version of the post, then create a relationship table with two fields (Post ID, User ID). This way you can simply add a user to the post by inserting a new record into the relationship table.
Example: Post 1 is owned by user 1. Post 2 is owned by user 1 and 2.
Post Table - Key (Post ID)
(Post ID=1, PostText="This post is important!")
(Post ID=2, PostText="This post is also important!")
Users - Key (User ID)
(User ID=1, Name="Bob")
(User ID=2, Name="George")
Post Users - Key (Post ID, User ID)
(Post ID=1, User ID=1)
(Post ID=2, User ID=1)
(Post ID=2, User ID=2)
This concerns me a little:
"...the need to support some operations that require postId to be inserted as is (no re-numbering)..."
I assume this sort of operation is the exception, and not the norm? I can also only assume that you're inserting the same post with the same Post ID into the same table without deleting the original? It's still not clear WHY you want to do this.
I really don't see why you'd need to worry about changing the Post ID if you're assigning posts to another user. Nothing else changes except the values in the User ID column by the sounds of it. Unless you mean you can have two or more posts with the same Post ID and the same User ID. If so, why?
To answer your questions:
No, IDENTITY_INSERT cannot be set globally. It is a per object, per session setting.
Using MAX(PostID) + 1 doesn't make sense. Is there a reason why IDENTITY(1, 1) doesn't work for the PostID column? You can re-seed if necessary.
Don't use application-generated UUIDs as key values. They make queries so much slower. At worst, use NEWSEQUENTIALID() in SQL Server if you absolutely want to use a UUID. UUIDs unnecessarily bloat tables and indexes, and with the exception of NEWSEQUENTIALID, are query performance killers.
What you could do is have a Primary key column simply called ID, and then a surrogate key called Post ID if that needs to be non-unique. That way, when you copy a post, the copy of the original gets a new ID, but still retains the original Post ID, but with no need to worry about doing anything unnecessary to the PK.
However, without any changes to the application or the DB, what I would suggest is using a stored procedure executed as the owner of the stored proc (which also owns the Posts table) to set IDENTITY_INSERT to ON only when absolutely necessary. Here is an example I've just written:
create procedure dbo.sp_CopyPost
(
#PostID INT
,#UserID INT
)
with execute as owner
as
begin
set nocount on;
set identity_insert dbo.Posts on;
begin tran
insert into dbo.Posts
(
PostID
,UserID
--Whatever other columns
)
values
(
#PostID
,#UserID
--Whatever other values
)
commit tran
set identity_insert dbo.Posts off;
select ##IDENTITY
end
That will do what you want, based on the current wording of your question, and the comments you've made.
If you need to essentially "plug the gaps" in your identity column, you will find Microsoft-recommended queries to do so in section B here:
https://msdn.microsoft.com/en-us/library/ms186775.aspx

SQL Server Auto Incrementing Identity Per Customer(Tenant) With No Gaps

We have a multi-tenant database which holds multiple customers with each customer having a collection of users like so (Simplified example omitting foreign key specification from users to customers):
CREATE TABLE dbo.Customers
(
CustomerId INT NOT NULL IDENTITY(1, 1),
Name NVARCHAR(256) NOT NULL
)
CREATE TABLE dbo.Users
(
User INT NOT NULL IDENTITY(1, 1),
CustomerId INT NOT NULL,
)
As part of this design the users are required to have a membership number, when we designed this we decided to use the UserId as the membership number however as with all things this requirement has grown and this is no longer an option for two reasons:
After we upgraded to 2012 on each server restart the column is jumping by 1000 values, we have used the workaround shown here: http://www.codeproject.com/Tips/668042/SQL-Server-2012-Auto-Identity-Column-Value-Jump-Is (-t272) to stop that happening but has made us realise that IDENTITY(1, 1) isn't good enough.
What we really want now is to ensure that the number is incremented per customer but it has to be permanent and cannot change once assigned.
Obviously a sequence will not work as again it needs to be per customer we also need to enforce a unique constraint on this per customer/user and ensure that the value is never changed once assigned and does not change if a user is deleted (although this shouldn't happen as we don't delete users but mark them as archived, however I want to guarantee this won't affect it).
Below is a sample of what I wrote which can generate the number, but what is the best way to use this or something similar which ensures a unique, sequential value per customer/user without a chance of any issues as users could be created at the same time from different sessions.
ROW_NUMBER() OVER (ORDER BY i.UserId) + ISNULL((SELECT MAX(users.MembershipNumber)
FROM [User].Users users
WHERE users.Customers_CustomerId = i.Customers_CustomerId), 0)
EDIT: Clarification
I apologise I just re-read my question and I did not make this clear enough, we are not looking to replace UserId, we are happy with the gaps and unique per database identifier that is used on all foreign keys, what we are looking to add is a MembershipNumber that will be displayed to the User which is why it needs to be sequential per customer with no gaps as this membership number will be used on cards that are given to the user so needs to be unique.
Since you already found the problem with Identity columns and how to fix it, I wouldn't say it's not good enough.
However, it doesn't seem to suit your needs since you want the user number to increment per customer.
I would suggest keeping the User column as an Identity column and the primary key of the table, and add another column to specify the User number by customer. this column will also be an integer number with a default value of the result of a UDF that will calculate the next number per customer (see example in this post).
You can protect that value from ever changing by using an instead of update trigger on the users table.
This way to keep a single column primary key, any you have a unique, sequential user number per customer.
Update
Apparently, it is impossible to send column values to a default constraint.
But you can still use an instead of insert trigger to accomplish your goal.
It's because of the default caching sqlserver implements for the sequence objects. See this former thread
Identity increment is jumping in SQL Server database
If the gaps are an issue, sql-server2012 has introduced the Sequence object. These you can declare with NOCACHE, so restarting the Server doesn't create gaps.
I want to share my thoughts on it. Please see below.
Create seperate table which will holds CustomerID and Count columns like below.
CREATE TABLE dbo.CustomerSequence
(
#CustomerID int,
#Count int
);
Write some kind of stored proc like below.
CREATE PROC dbo.usp_GetNextValueByCustomerID
#CustomerID int,
#Count int OUTPUT
AS
BEGIN
UPDATE dbo.CustomerSequence
SET #Count = Count += Count
WHERE CustomerID = #CustomerID;
END
Just call the above stored proc by passing CustomerID and get the next Sequence value from it.
If you have several users adding new registers simultaneously, I think the best idea is to create a compound Primary key, where the user is a tiny byte (if you have less than 255 users) and the incremental number is an integer. Then, when adding a new register you create a string Primary Key, like 'NN.xxxxxx' . Assuming [Number] is your incremental number and [Code] is the user's code (or local machine assigned number), you assign the new UserId using the DMax function , as follows:
NextNumber = Nz(DMax("Number", "clients", "Code=" & Me!code, 0) + 1
UserId= code & "." & NextNumber
where
NN is the user's code
"." is used to separate both fields, and
XXXX is the new Number

How to prevent duplicate entries in database

I am trying to implement a friend request feature and using oracle 11 xe database.
My user1 sends a request to user2 so a record in database is created as
requestId: 12,
fromUser: user1,
toUser: user2,
status : 0 (indicating pending),
creationTime: timestamp
I want to prevent the same entry i.e user1 can send friend request only once not twice. request Id is primary key and user1 and user2 are both foreign keys.
what kind of oracle database constraint or sql command i should add to my table .
Please help
If you want to have a unique combination of requestId, fromUser and toUser and such that none of the columns could never be NULL. Then make the combination as Primary Key.
Alternatively, if you want to allow NULL values for and only want fromUser and toUser to always be UNIQUE, then create a unique index on them and create a unique constraint. The unique constraint will use the unique index.
For example,
CREATE UNIQUE INDEX indx_usr_uk
ON table_name (fromUser, toUser);
ALTER TABLE table_name
ADD CONSTRAINT user_unique UNIQUE (fromUser, toUser);
If you don't explicitly create an unique index, then Oracle will use any existing index.
You might have combination of 'fromUser','toUser' columns as primary key. As one user can send the friend request to another user, only once.

Resources