SQL : Self referencing row - sql

I have a Users table:
Create table dbo.Users (
Id int IDENTITY (1, 1) PRIMARY KEY,
ManagerId int FOREIGN KEY references dbo.Users(Id)
);
Users have Managers.
Managers are themselves Users.
Is it possible to have ManagerId NOT NULL and insert a row telling SQL it references itself? i.e. ManagerID = Id?
PS: I already know I can do it in two steps with ManagerID NULL, I also know I could code my app so that NULL means the user is managing himself, but that's not the point of my question.

With an identity column, I don't think so. But we've had sequences for a long time. You could define your table like this:
create sequence [SQ_Users] as int start with 1;
create table dbo.Users (
Id int NOT NULL
constraint [PK_Users] PRIMARY KEY
constraint [DF_UserID] DEFAULT (next value for [dbo].[SQ_Users]),
ManagerId int not null
constraint [FK_User_Manager] FOREIGN KEY references dbo.Users(Id)
);
(Note: I named your constraints because I so loathe system-named ones; it's a service I provide). Now, if you want to have a user be their own manager, you do it like this:
declare #UserId int = next value for [dbo].[SQ_Users];
insert into dbo.Users (ID, ManagerID) values (#UserID, #UserID);
Note that this doesn't take away the common use case of the table itself auto-generating its own ID. That is, you can still do:
insert into dbo.Users (ManagerID) values (#ManagerID);
For what it is worth, it is a common idiom in hierarchical data (like what you have) that a NULL value for the parent ID means that the element is at the top or references itself. That is, your current setup shouldn't cause any sideways glances from "the next guy".

Related

How can I prevent a table not having references pointing to it?

I have these tables:
CREATE TABLE s_users (
user_id serial PRIMARY KEY,
username text
);
CREATE TABLE s_projects (
project_id serial PRIMARY KEY,
projectname text
);
CREATE TYPE s_MEMBERSHIP_TIER AS ENUM ('pending', 'member', 'admin');
CREATE TABLE s_memberships (
user_id INT NOT NULL,
project_id INT NOT NULL,
membership_tier MEMBERSHIP_TIER,
CONSTRAINT s_one_membership_type
UNIQUE(user_id, project_id)
);
I want to ensure that it will be impossible to get to a situation where:
(1) a membership refers to a project or user that doesn't exist, and
(2) a project is without members.
I think I can achieve (1) by changing the first rows in CREATE TABLE s_memberships ( to
user_id INT NOT NULL REFERENCES s_users ON DELETE CASCADE ON UPDATE CASCADE,
project_id INT NOT NULL REFERENCES s_projects ON DELETE CASCADE ON UPDATE CASCADE,
But how can I avoid a project without members?
I.e.
INSERT INTO s_users (username) values ('mickey');
INSERT INTO s_users (username) values ('donald');
INSERT INTO s_projects (projectname) values ('p1'); -- this should not succeed to create a project without any members.
I've tried something like:
ALTER TABLE s_projects ADD CONSTRAINT pid FOREIGN KEY(project_id) REFERENCES s_memberships(project_id);
But I'm getting an error:
ERROR: there is no unique constraint matching given keys for referenced table "s_memberships"
Is there some other constraint I can add? Or, alternatively, is there a better way to organise my tables?
There are two options I can think of:
You create a foreign key from s_projects to s_membership that identifies a "special member" (project leader?) that must always be there.
You have a column member_count in s_project that is maintained by a trigger on s_membership, so that it always contains the number of members in that project. Then you place a check constraint on s_project that forces that number to be greater than 0.
A project without members is quite tricky. You can't insert a membership unless there is a project. And you want to requite that a project has members.
One solution is to use a deferrable constraint so you can insert both rows at the same time. You can alter the table to defer the constraint check, insert the rows, and then undefer the constraint.
However, I prefer other solutions.
One is to include a member count in projects. Maintaining this requires triggers -- which are yucky -- but you can then get "active" projects using a where clause: where num_members > 0.
Or just create a view:
create view active_projects as
select p.*
from projects p
where exists (select 1 from memberships m where m.project_id = p.project_id);
In other words, these solutions allow "inactive" projects, but then just hide them when desired.

How to implement UNIX like ACL for table rows

I am new sql stuff so please forgive me for asking dumb question. I am creating my first real life application for college project.
At its core, It need to handle more than thousands of users which should not able to read or write to each others data unless given privileges. like Linux does with user and groups.
in below schema which I tried , a user can view(read) and edit(write) other users if they have read permissions.( r=2 w=1 r+w=3 ).
for example if cgroup_1 is admin and cgroup_2 is managers and unixperm is 32 then it means users in admin group can read+write(3) and users in managers group can only read(2)
create table cgroups
(
id int unsigned primary key auto_increment,
title varchar(100) not null unique,
cunixperm tinyint unsigned not null default 32 ,# r=2 w=1
cgroup_1 int unsigned not null default 1 references cgroups (id) on delete cascade on update cascade,
cgroup_2 int unsigned references cgroups (id) on delete cascade on update cascade
);
create table users
(
id int unsigned auto_increment primary key,
username varchar(255) not null unique,
cunixperm tinyint unsigned not null default 30, # r=2 w=1 3=r+w
cgroup_1 int unsigned default 1 not null references cgroups (id) on delete cascade on update cascade ,
cgroup_2 int unsigned references cgroups (id) on delete cascade on update cascade
);
create table many_users_in_many_cgroups
(
user_id int unsigned references users(id),
cgroup_id int unsigned references cgroups(id),
primary key (user_id,cgroup_id)
);
insert into cgroups(title)
values ('admins'),('managers'),('writers');
insert into users(username, cunixperm, cgroup_1, cgroup_2)
values ('user1',30,1,null),
('user2',30,1,2),
('user3',22,2,2),
('user4',02,3,3);
insert into many_users_in_many_cgroups
values (1,1),(2,2),(3,3),(4,4);
Now suppose user 2 has logged into my app, How can I only show the user rows where he has read (2) or read+write(3) permissions.
if above schema is not (probably) appropriate pls give me an example with appropriate scheme
I am currently using MariaDB but open for solutions for others too.
The short answer is: the control of access part is built into the application.
The mechanism must lie outside SQL, as SQL can (usually) only grant access on table level, not row or column.
As to how exactly to program it, one way would be this. Let's suppose you have a data table like this (I am using pseudo SQL, because I don't remember the exact Mysql syntax):
Create table data (
rowid unique auto-increment,
datafield text);
Create table data_acl(
rowid foreign key references data (rowid),
cgroup references cgroups(id),
permissions int);
This assumes all users are in some cgroup, so to give access to user you give access to cgroup. This makes it easier and follows the Unix idea of each user having his own group.
Your user table only needs to list user names.
Your cgroups table only needs to list user and group.
Create table cgroups (
Id autoincrement int,
name);
Create table cgroup_users (
user_id foreign key references users(id),
cgroup_id foreign key references cgroups(id)
);
Now to list all rows of data user has access to you just:
Select distinct datafield,acl.permissions from data d, data_acl acl, cgroups g
Where d.rowid=acl.rowid and acl.cgroup in (select distinct cgroup_id from cgroup_users where user_id=?)
Sorry for formatting, posting this from a mobile.

Is a single field contain multiple foreign key relationship from different table [SQL]

I have 3 table Student,Teacher,User.
Student:
CREATE TABLE Student( id INT NOT NULL PRIMARY KEY,name VARCHAR(50) NOT NULL);
INSERT INTO [dbo].[Student]([id],[name]) VALUES(4,'Ram'),(5,'Raman');
Teacher:
CREATE TABLE Teacher( id INT NOT NULL PRIMARY KEY,name VARCHAR(50) NOT NULL);
INSERT INTO [dbo].[Student]([id],[name]) VALUES(1,'Raj'),(2,'Rahul');
User:
CREATE TABLE [dbo].[User](
id INT NOT NULL PRIMARY KEY,
user_id INT NOT NULL,
user_type CHAR(1) NOT NULL,
user_name VARCHAR(10) NOT NULL,
user_password VARCHAR(255) NOT NULL,
CONSTRAINT FOREIGN KEY (user_id) REFERENCES Student (id),
CONSTRAINT FOREIGN KEY (user_id) REFERENCES Teacher (id) );
Now I try to INSERT in User table with below query
INSERT INTO [dbo].[User] ([id] ,[user_id] ,[user_type],[user_name] ,[user_password]) VALUES (1 ,1,'S','Raj_001','********')
It gives me error for violation of foreign key due to
value of user_id is available in Teacher and not in Student
So my question is: How can I achieve that a value of user_id is present in one of those table and data should be inserted in User table.
Your table structure is flawed. A foreign key tells the database that there is definitely one and only one row on one side of the relationship. You can't have a partial match, it's all or nothing. This is before considering how you would ensure that you don't end up with the same id in both the teacher and student table.
It would be better to have two columns in your user table, one for teacher id and one for student id. In fact going further given the only extra data in both student and teacher tables is their name why not just eliminate both and store the name in the user table?
Another option to consider is that your foreign key is pointed in the wrong direction. Perhaps a better approach is reversing it to ensure each student and teacher is a user rather than that a user is either a student or a teacher.
First of all get rid of those key words from table name like [User],user_id etc.
It really is problematic and irritating.
Secondly why 2 key in [User] table,id, user_id ? It is not require.
I will keep only id or user_id.
Thirdly, knowing the real table structure or even purpose of each table help in better data modeling.
From [User] table what it appear is that id and user_type are composite primary key.
It should be. If this is true then you can't define FK constraint, as user_type is not available in either Teacher table and Student Table.
And what is appear that ,for example first data is inserted in Student or Teacher then data is inserted in User table in same Transaction.
So in all above scenario, Instead of Trigger is ideal scenario in this condition.
My script is just demo,
Create Proc spStudentInsert
as
set nocount on
set xact_abort on
begin try
begin tran
--bulk insert or single insert ,no problem
insert into Student
insert into [User]
if (##Trancount>0)
commit
end try
begin catch
if (##Trancount>0)
rollback
end catch
CREATE TRIGGER INSTEADOF_TR_I_User ON [user]
INSTEAD OF INSERT
AS
BEGIN
DECLARE #Flag BIT = 1
IF NOT EXISTS (
SELECT 1
FROM Student S
INNER JOIN inserted i ON i.id = S.id
)
SET #Flag = 0
ELSE IF NOT EXISTS (
SELECT 1
FROM Teacher T
INNER JOIN inserted i ON i.id = T.id
)
AND #Flag = 1
SET #Flag = 0
IF (#Flag = 0)
BEGIN
RAISERROR (
N'Invalid user'
,16
,1
)
RETURN
END
END
In case I am wrong about id, user_type composite PK then you can do other way,
PK of User id is FK in Student table as well as Teacher table.
Also , id are PK in their respective table.
So first you insert in User table then you insert in Student or Teacher table.
So design in this case will be,
CREATE TABLE [dbo].[User](
id INT NOT NULL ,
user_type CHAR(1) NOT NULL,
user_name VARCHAR(10) NOT NULL,
user_password VARCHAR(255) NOT NULL,
CONSTRAINT [PK_user] PRIMARY KEY (id)
)
INSERT INTO [dbo].[User] ([id] ,[user_type],[user_name] ,[user_password])
VALUES (1 ,1,'S','Ram_001','********')
--drop table [User]
--alter table [user]
-- drop constraint PK_user
CREATE TABLE Student( id INT NOT NULL PRIMARY KEY,name VARCHAR(50) NOT NULL);
ALTER TABLE Student
add CONSTRAINT FK_StudentUser FOREIGN KEY (id) REFERENCES [User] (id);
INSERT INTO [dbo].[Student]([id],[name]) VALUES(1,'Ram'),(5,'Raman');
--select * from [Student]
CREATE TABLE Teacher( id INT NOT NULL PRIMARY KEY,name VARCHAR(50) NOT NULL);
ALTER TABLE Teacher
add CONSTRAINT FK_TeacherUser FOREIGN KEY (id) REFERENCES [User] (id);
INSERT INTO [dbo].Teacher([id],[name]) VALUES(1,'Raj'),(2,'Rahul');
So what it appear from your question, I will create Instead of Trigger and go with that model.
There are two ways to do this without re-doing your table schema
Create a 4th table that contains the union of ID from Student and Teacher. Presumably, you would insert to that table whenever you insert into Student and Teacher, and then have the constraint act against that table.
Create a custom function based constraint rather than a foreign key which looks up against a union of both the student and teacher tables.
Neither of these are great/clean solutions, and as others have noted, you probably are dealing with the fact that the schema isn't ideal.
Still, if you're just modifying an existing system (and I assume this is a simplified version of what you're actually dealing with), then one of the two solutions I mentioned id easier than redoing the schema.
Your foreign key definition has some logical problems. It forces the user_id to exists in both tables. The solution here is depended on the business needs and real data.
You can create a Person table with 1-1 relation to the student and the Teacher tables and then use the Person.Id column in the foreign key definition. This solution assumes that the students' and teachers' data may change differently.
As another way (which is explained in other answers), If your student and teachers' data is similar, you can combine both tables, and difference data by one added "Type" column.
SO you want to tell the system that your User must be in one of your tables .
it's not possible in databases logic but you can write a script that have a condition (IF exist) then insert you user data
notice : you have to remove your foreign keys .
its a wrong logic !
you are telling your system that your user is a student and a teacher to !
that is absolutely wrong .
I feel like there were some excellent responses in this thread, but I'm going to take a stab at giving you a different direction. I'll try to be clear on why, and try to acknowledge your situation as I do so.
Student/Teacher Data is Often Messy
As someone with experience normalizing data sets in higher education, the issue you've run into resonated with me. Educational users could be in all three categories (Student, Teacher, and User) or just one of them, depending on the how and why the category was linked. Worse, they can enter from multiple directions and end up with multiple unlinked accounts. More mature institutions and tools have protections against this, but I still see user-created databases and ten year old 'it was temporary' solutions that cause me existential pain.
The Main Stumbling Block
Any database with tables that independently define who is a user based on different criteria have a potential point of failure.
Foreign keys was the right direction to be thinking in for this problem. You want these tables to connect and you want them to stay consistent with one another, regardless of which side of the data gets altered. We just need to add a little extra.
One Table To Rule Them All
Before I go further, I want to say that it is possible to get all of the fields you're tracking into a single table, but having multiple tables with distinct purposes is an easy way to protect against changes later.
The foreign key table must inherit the key from another table, but people often say foreign keys can't be primary keys as well. Why?
Foreign keys are not automatically unique keys in the tables they're in. If there can be multiple fields tied to that same key, the table ends up worthless.
We fix that with the Unique constraint. Applied to a foreign key field, Unique essentially makes it act as a primary key would.
Sample Method
Below is an alternative design for what you seemed to be after, creating a master list of IDs that can link across all tables. I tossed in a few minor tracking fields that can be useful for debugging.
/*Create Tables*/
CREATE TABLE ID(
USER_ID int NOT NULL PRIMARY KEY AUTO_INCREMENT,
USER_CREATED timestamp
);
CREATE TABLE USER(
USER_ID int NOT NULL UNIQUE FOREIGN KEY REFERENCES ID(USER_ID),
USER_LOGIN VARCHAR(10) NOT NULL UNIQUE,
USER_PASSWORD VARCHAR(255) NOT NULL,
USER_NAME VARCHAR(50) NOT NULL
);
CREATE TABLE PERMISSIONS(
USER_ID int NOT NULL UNIQUE FOREIGN KEY REFERENCES ID(USER_ID),
STUDENT CHAR(1),
TEACHER CHAR(1)
);
This creates a flag for student and teacher that could both be true or both be false. If you want the code to force them into only one or the other, you can still have the permissions table do a USER_TYPE field instead. I suggest a null or neither value being possible in either case if you plan to use this for any length of time. Best of luck.

Will multiply insert requests to the same table with direct query and store-procedure cause collision?

Multiply users can call store procedure(SP), that will make some changes to mytable in SQL Server. This SP should insert some rows to mytable that has reference to itself through parentid column.
TABLE mytable(
id int identity(1,1) primary key,
name varchar(20) not null,
parentId int not null foreign key references mytable(id)
)
in order to insert row to such table, accordingly to other posts, I have 2 ways:
Allow null to parentid column by ALTER TABLE mytable alter column parentid int null;, insert the row, update parentid and than disable null to parentid
Allow IDENTITY by set identity_insert maytable on, insert dummy row with id=-1 and parentid=-1, insert the correct row with reference to -1, update the parentid to SCOPE_IDENTITY() and in the end set IDENTITY to off
The case:
Assume I take the 2nd way. SP managed to set identity_insert mytable on BUT didn't yet finished the execution of the rest SP. At this time, there are other INSERT requests(NOT through SP) to the mytable table like INSERT INTO mytable(name,parentid) VALUES('theateist', -1). No id is specified because they assumed that IDENTITY is off and therefore id is auto-incremental.
The Question:
Will this cause errors while inserting because IDENTITY, in this period of time, is ON and not auto-incremental any more and therefore it will require id specification? If yes, it will be better to use the 1st way, isn't it?
Thank you
identity_insert is a per-connection setting - you won't affect other connections/statements running against this table.
I definitely wouldn't suggest going the first way, if it could be avoided, since it could impact other users of the table - e.g. some other connection could do a broken insert (parentid=null) while the column definition allows it, and then your stored proc will break. Also, setting a column not null forces a full table scan to occur, so this won't work well as the table grows.
If you did stick with method 2, you've still got an issue with what happens if two connections run this stored proc simultaneously - they'll both want to insert the -1 row, at different times, and delete it also. You'll have conflicts.
I'm guessing the problem you're having is inserting the "roots" of the tree(s), since they have no parent, and so you're attempting to have them self referencing. I'd instead probably make the roots have a null parentid permanently. If there's some other key column(s), these could be used in a filtered index or indexed view to ensure that only one root exists for each key.
Imagine that we're building some form of family trees, and ignoring most of the realities of such beasts (such as most families requiring children to have two parents):
CREATE TABLE People (
PersonID int IDENTITY(1,1) not null,
Surname varchar(30) not null,
Forename varchar(30) not null,
ParentID int null,
constraint PK_People PRIMARY KEY (PersonID),
constraint FK_People_Parents FOREIGN KEY (ParentID) references People (PersonID)
)
CREATE UNIQUE INDEX IX_SoleFamilyRoot ON People (Surname) WHERE (ParentID is null)
This ensures that, within each family (as identified by the surname), exactly one person has a null ParentID. Hopefully, you can modify this example to fit your model.
On SQL Server 2005 and earlier, you have to use an indexed view instead.

Need help understanding this SQL (generated by doctrine)

i am actually reading Doctrine Reference: One to Many, Unidirectional with Join table. but this will probably be more of a SQL quesiton. basically, this is supposed to model a one to many, unidirectional relationship. i guess from the PHP code (in that link), its such that 1 user have many phonenumbers.
the question is from the SQL, it seems like 1 user can have many phonenumbers. and 1 phonenumber can only belong to 1 user. am i right?
CREATE TABLE User (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE users_phonenumbers (
user_id INT NOT NULL,
phonenumber_id INT NOT NULL,
UNIQUE INDEX users_phonenumbers_phonenumber_id_uniq (phonenumber_id),
PRIMARY KEY(user_id,
phonenumber_id)
) ENGINE = InnoDB;
CREATE TABLE Phonenumber (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id);
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id);
can't i just simplify the database to ... below ... no need for join tables and what not?
Users (id, name)
Phonenumbers (id, user [FK], number)
Correct, these are two valid approaches to the same problem. And yes, the unique index on users_phonenumbers means that each phone number can belong to only one user.
The design is actually suboptimal.
The idea must have been that there are telephone numbers, users, and that they can be linked many-to-many. Because of the unique index on phonenumberid hoever, each number can only be assigned to one user.
Then the whole users_phonenumbers has become redundant, because they could just have added a userid column on the phonenumbers table and save themselves a join.
BAd table design if you ask me.