wierd case of 'unique' constraint (possible case for exclusion) | Postgres - sql

I have a question regarding constraint(unique or exclusion in this case I guess). I cant get my head around this simple case:
For example I have this simplified version of a table:
create table user
(
id serial not null
username varchar(30) not null
constraint user_username_key
unique,
is_active boolean not null,
company_id integer not null
constraint company_user_company_company_company_id
references company_company
deferrable initially deferred
I would like to create such constraint to satisfy following conditions:
If we have at least one (or possible few records) referencing same company_id (lets say = 3) that we must(it should exists) have one and only one username that ends on ‘-support’ (lets say ‘user1-support’) with is_active = True.
Wrong scenarios:
• we have one or more records but there are no any username that ends on `‘-support’` amoung this records
• we have one or more records and we have multiple usernames that ends on `‘-support’` amoung this records
If we have only one entry – then username should be ended with ‘-support’
Thanks
Sorry if question is naive
postgres 11 version

Filtered unique index could be used:
CREATE UNIQUE INDEX idx ON "user"
(company_id,(CASE WHEN username LIKE '%-support' THEN 'support' ELSE username END))
WHERE is_Active = True;
db<>fiddle demo

Related

How to enforce a uniqueness constraint in a SQL database based on a certain condition

I'm working on a SQL database, and I have the following table:
Workout Routine
id serial PRIMARY KEY,
user_id integer
REFERENCES users (id)
ON DELETE CASCADE
NOT NULL,
name varchar(255) NOT NULL,
active boolean DEFAULT TRUE,
UNIQUE(user_id, name)
Currently, the combination of user_id, and name are supposed to be unique, meaning that a user cannot have two workout routines with the same name. However, this is not quite what I want.
Instead, I would want the combination user_id and name to be unique only in cases where active = true. In other words, a user should be able to have multiple workouts with the same name that are inactive, but should not be allowed to have duplicates that are active.
Is there a way to enforce this in this table?
A partial index can be used for this:
CREATE UNIQUE INDEX ON table_name (user_id, name) WHERE active;
The fiddle
You can use a partial index to achieve this. The index will only be used for queries that include the active column, and will only be used for queries that include the active column with a value of true. This means that queries that do not include the active column will not use the index, and queries that include the active column with a value of false will not use the index.
CREATE UNIQUE INDEX workout_routine_user_id_name_active
ON workout_routine (user_id, name)
WHERE active = true;
I'm an Oracle guy - but perhaps I can be of help here. Yes, you can model what you want in several ways.
One way is to create two tables, one historical, the other current. The historical would have no unique index other than the PK on the surrogate key ID, whereas the current would also have a unique index on user_id and name.
The second way, using a single table, is to add a nullable date field that represents the closed/inactive date. NULL means active, non-NULL (a date value) would mean inactive. Create a unique index (not a PK) on user_id,name,inactive_date. If SQL Server is like Oracle and allows NULL values in a unique constraint but not multiple NULL values, that will enforce that there can be only one instance of a name for a user_id that is current (having a NULL inactive_date), but allows there to be many inactive rows since they would all have different date values.
If SQL Server acts differently than Oracle then check out the "NULLS NOT DISTINCT" option.

How to create and normalise this SQL relational database?

I am trying to design an SQL lite database. I have made a quick design on paper of what I want.
So far, I have one table with the following columns:
user_id (which is unique and the primary key of the table)
number_of_items_allowed (can be any number and does not have to be
unique)
list_of_items (a list of any size as long as it is less than or equal
to number_of_items_allowed and this list stores item IDs)
The column I am struggling the most with is list_of_items. I know that a relational database does not have a column which allows lists and you must create a second table with that information to normalise the database. I have looked at a few stack overflow answers including this one, which says that you can't have lists stored in a column, but I was not able to apply the accepted answer to my case.
I have thought about having a secondary table which would have a row for each item ID belonging to a user_id and the primary key in that case would have been the combination of the item ID and the user_id, however, I was not sure if that would be the ideal way of going about it.
Consider the following schema with 3 tables:
CREATE TABLE users (
user_id INTEGER PRIMARY KEY,
user TEXT NOT NULL,
number_of_items_allowed INTEGER NOT NULL CHECK(number_of_items_allowed >= 0)
);
CREATE TABLE items (
item_id INTEGER PRIMARY KEY,
item TEXT NOT NULL
);
CREATE TABLE users_items (
user_id INTEGER NOT NULL REFERENCES users(user_id) ON UPDATE CASCADE ON DELETE CASCADE,
item_id INTEGER NOT NULL REFERENCES items (item_id) ON UPDATE CASCADE ON DELETE CASCADE,
PRIMARY KEY(user_id, item_id)
);
For this schema, you need a BEFORE INSERT trigger on users_items which checks if a new item can be inserted for a user by comparing the user's number_of_items_allowed to the current number of items that the user has:
CREATE TRIGGER check_number_before_insert_users_items
BEFORE INSERT ON users_items
BEGIN
SELECT
CASE
WHEN (SELECT COUNT(*) FROM users_items WHERE user_id = NEW.user_id) >=
(SELECT number_of_items_allowed FROM users WHERE user_id = NEW.user_id)
THEN RAISE (ABORT, 'No more items allowed')
END;
END;
You will need another trigger that will check when number_of_items_allowed is updated if the new value is less than the current number of the items of this user:
CREATE TRIGGER check_number_before_update_users
BEFORE UPDATE ON users
BEGIN
SELECT
CASE
WHEN (SELECT COUNT(*) FROM users_items WHERE user_id = NEW.user_id) > NEW.number_of_items_allowed
THEN RAISE (ABORT, 'There are already more items for this user than the value inserted')
END;
END;
See the demo.

How to reference foreign key from more than one column (Inconsistent values)

I Have table three tables:
The first one is emps:
create table emps (id number primary key , name nvarchar2(20));
The second one is cars:
create table cars (id number primary key , car_name varchar2(20));
The third one is accounts:
create table accounts (acc_id number primary key, woner_table nvarchar2(20) ,
woner_id number references emps(id) references cars(id));
Now I Have these values for selected tables:
Emps:
ID Name
-------------------
1 Ali
2 Ahmed
Cars:
ID Name
------------------------
107 Camery 2016
108 Ford 2012
I Want to
Insert values in accounts table so its data should be like this:
Accounts:
Acc_no Woner_Table Woner_ID
------------------------------------------
11013 EMPS 1
12010 CARS 107
I tried to perform this SQL statement:
Insert into accounts (acc_id , woner_table , woner_id) values (11013,'EMPS',1);
BUT I get this error:
ERROR at line 1:
ORA-02291: integrity constraint (HR.SYS_C0016548) violated - parent key not found.
This error occurs because the value of woner_id column doesn't exist in cars table.
My work require link tables in this way.
How Can I Solve This Problem Please ?!..
Mean: How can I reference tables in previous way and Insert values without this problem ?..
One-of relationships are tricky in SQL. With your data structure here is one possibility:
create table accounts (
acc_id number primary key,
emp_id number references emps(id),
car_id number references car(id),
id as (coalesce(emp_id, car_id)),
woner_table as (case when emp_id is not null then 'Emps'
when car_id is not null then 'Cars'
end),
constraint chk_accounts_car_emp check (emp_id is null or car_id is null)
);
You can fetch the id in a select. However, for the insert, you need to be explicit:
Insert into accounts (acc_id , emp_id)
values (11013, 1);
Note: Earlier versions of Oracle do not support virtual columns, but you can do almost the same thing using a view.
Your approach should be changed such that your Account table contains two foreign key fields - one for each foreign table. Like this:
create table accounts (acc_id number primary key,
empsId number references emps(id),
carsId number references cars(id));
The easiest, most straightforward method to do this is as STLDeveloper says, add additional FK columns, one for each table. This also bring along with it the benefit of the database being able to enforce Referential Integrity.
BUT, if you choose not to do, then the next option is to use one FK column for the the FK values and a second column to indicate what table the value refers to. This keeps the number of columns small = 2 max, regardless of number of tables with FKs. But, this significantly increases the programming burden for the application logic and/or PL/SQL, SQL. And, of course, you completely lose Database enforcement of RI.

primary key constraint error when doing an update

I am having a bit of challenge doing mass update in SQL server. I have a table with composite primary key( studentid,login,pass). They have a common login and password as they are in a group. I m doing an (again all same login and pass) update and setting their login and pass to new values where the field, class group =x.But I get a duplicate primary key violation error. Any idea why?
Thanks
Given that you have a weird primary key, it's easy to see how it can be violated. For example, say student 1 has two rows in the table:
studentid login pass group
1 bobama reallyborninkenia politician
1 bobama2 raisetaxes politician
And say you update the group politician:
update StudentTable
set login = 'bobama'
, pass = 'justpwndromney'
where [group] = 'politician'
Then you'd get a primary key violation, since there would be two rows with the same (studentid, login, pass) combination.
If that is weird, that's because your primary key is weird. I'd expect the primary key to be just (studentid).
well clearly there is a clash, you are are creating a studentid/login/password combination that exists in the table already. This query should show you where the existing rows that clash with your proposed changes are:
select t.* from [your-table] t
join (select * from [your-table] where [class-group] = x) proposed-change
on proposed-change.[studentid] = [t.studentid]
where t.login = 'proposed-login' and t.password = 'proposed-password'

a sql question about linking tables

I wonder why these statements are not the same (the first one worked for me)
AND user_thread_map.user_id = $user_id
AND user_thread_map.thread_id = threads.id
AND user_thread_map.user_id = users.id
AND user_thread_map.thread_id = threads.id
AND users.id = $user_id
Shouldn't they be the same? In the 2nd one I linked all the tables in the first 2 lines, then I tell it to select where users.id = $user_id.
Can someone explain why the 2nd statement doesn't work? Because I thought it would.
Assuming you're getting no rows returned (you don't really say what the problem is, so I'm guessing a bit here), my first thought is that there are no rows in users where id is equal to $user_id.
That's the basic difference between those two SQL segments, the second is a cross-join of the user_thread_map, threads and users tables. The first does not join with users at all, so that's where I'd be looking for the problem.
It appears that your user_thread_map table is a many-to-many relationship between users and threads. If that is true, are you sure you have a foreign key constraint between the ID fields in that table to both corresponding other tables, something like:
users:
id integer primary key
name varchar(50)
threads:
id integer primary key
thread_text varchar(100)
user_thread_map:
user_id integer references users(id)
thread_id integer references threads(id)
If you have those foreign key constraints, it should be impossible to end up with a user_thread_map(user_id) value that doesn't have a corresponding users(id) value.
If those constraints aren't there, a query can tell you which values need to be fixed before immediately adding the constraints (this is important to prevent the problem from re-occurring), something like:
select user_thread_map.user_id
from user_thread_map
left join users
on user_thread_map.user_id = users.id
where users.id is null
The first one would select records from table user_thread_map with user_id = $user_id, irrespective of whether a record in table user existed with that id. The second query would only return something if the related record in user is found.