SQLite unique constraint on multiple column without order - sql

I need help on a problem in my table definitions : I have a table which will be defined, for example, like this :
id, primary key
friend0_id, foreign key on table users
friend1_id, foreign key on table users
The problem is I do not want to have multiple times the couple (friend0_id, friend1_id), whatever the order the are in the table.
I tried to define an UNIQUE constraint on the couple (friend0_id, friend1_id), but the columns order defined in the constraint (here friend0_id, THEN friend1_id) matters. So :
| id | friend0_id | friend1_id |
|----|------------|------------|
| 1 | 3 | 4 | -> OK
| 2 | 4 | 3 | -> OK, as the columns order in index matters
| 3 | 3 | 4 | -> Not OK, constraint prevent this
I would like the id 2 and 3 in the example to be disallowed, but I can't figure how. Do you have some tips for me ?
Thank you,
naccyde

As #mu too short mentioned, the way is to use (greatest(friend0_id, friend1_id), least(friend0_id, friend1_id)), so, now I have a working 2 columns order free unique constraint. I did it in SQLite this way (which could not be the better) :
Create a trigger which set min(friend0_id, friend1_id) to friend0 and max(friend0_id, friend1_id) to friend1 :
CREATE TRIGGER friend_fixed_id_order_trigger
AFTER INSERT ON friends
BEGIN UPDATE friends
SET
friend0_id = min(NEW.friend0_id, NEW.friend1_id),
friend1_id = max(NEW.friend0_id, NEW.friend1_id)
WHERE friends.id = NEW.id;
END
Then, set a unique constraint on the couple (friend0_id, friend1_id) :
CREATE UNIQUE INDEX `friends_unique_relation_index`
ON `contacts` (`friend0_id` ,`friend1_id`)
And it works !
EDIT : If someone need this tip, do not forget to create an update trigger too, otherwise an update request could break the mechanism.

Related

How can I update the table in SQL?

I've created a table called Youtuber, the code is below:
create table Channel (
codChannel int primary key,
name varchar(50) not null,
age float not null,
subscribers int not null,
views int not null
)
In this table, there are 2 channels:
|codChannel | name | age | subscribers | views |
| 1 | PewDiePie | 28 | 58506205 | 16654168214 |
| 2 | Grandtour Games | 15 | 429 | 29463 |
So, I want to edit the age of "Grandtour Games" to "18". How can I do that with update?
Is my code right?
update age from Grandtour Games where age='18'
No, in update, you'll have to follow this sequence:
update tableName set columnWanted = 'newValue' where columnName = 'elementName'
In your code, put this:
update Channel set age=18 where name='Grandtour Games'
Comments below:
/* Channel is the name of the table you'll update
set is to assign a new value to the age, in your case
where name='Grandtour Games' is referencing that the name of the Channel you want to update, is Grandtour Games */
alter table changes the the schema (adding, updating, or removing columns or keys, that kind of thing).
Update table changes the data in the table without changing the schema.
So the two are really quite different.
Here is your answer -
-> ALTER is a DDL (Data Definition Language) statement
UPDATE is a DML (Data Manipulation Language) statement.
->ALTER is used to update the structure of the table (add/remove field/index etc).
Whereas UPDATE is used to update data.
Hope this helps!

SQLITE: Transaction to check constraints after commit

I'm currently working on an sqlite table where I have to do the following:
ID | Name | SortHint
---|-------|---------
0 | A | 1
1 | B | 2
2 | C | 3
ID is the primary key and SortHint is a column with the UNIQUE-constaint. What I have to do is to modify the table, for example:
ID | Name | SortHint
---|-------|---------
0 | A | 3
1 | B | 1
2 | C | 2
The Problem: Because of the UNIQUE I can't simply update one row after another. I tried:
BEGIN TRANSACTION;
UPDATE MyTable SET SortHint = 3 WHERE ID= 0;
...
COMMIT;
But the first update query immideatly fails with:
UNIQUE constraint failed: MyTable.SortHint Unable to fetch row
So, is there a way to "disable" the unique constaint for a transaction and only check all of them once the transaction is committed?
Notes:
I can't modify the table
It works if I only use SortHint values that are not already in the table
I know how to "workaround" this problem, but I would like to know if there is a way to do this as described above
One possibility is to drop the unique constraint and then add it again. That is a little bit expensive, though.
Another would be to set the values to negative values:
UPDATE MyTable
SET SortHInt = - SortHint;
UPDATE MyTable
SET SortHint = 3
WHERE ID = 0;
. . .
If you cannot modify the table, you are not able to remove the constraint.
A workaround could be to change the SortHint to a range that is not in use.
For example you could add 10,000 to all of them. Commit.
Then change to the right number at once which have become free now.
Maybe test afterwards that no numbers of 10,000 or higher exist anymore.

error while creating a sqlite trigger

I have these tables:
+-------------------------+
| Movies |
+-----+--------+----------+
| _id | title | language |
+--+--+--------+----------+
|
+-------------------------+
|
+----------------------------------+
| Movie_Cast |
+------------+----------+----------+
| id | actor_id | movie_id |
+------------+-----+----+----------+
|
+-------------+
|
+-------------------+
| Actors |
+----------+--------+
| actor_id | name |
+----------+--------+
What i'm trying to do is to delete a movies row, delete also the related rows from the junction table (movie_cast). And finally delete, from actors table, all the rows that are not referenced in the movie_cast table.
this is the tables schema:
create table movies (_id INTEGER PRIMARY KEY, title TEXT, language TEXT);
create table movie_cast (id INTEGER PRIMARY KEY,
actor_id INTEGER REFERENCES actors(actor_id) ON DELETE RESTRICT,
movie_id INTEGER REFERENCES movies(_id) ON DELETE CASCADE);
create table actors (actor_id INTEGER PRIMARY KEY, actor TEXT UNIQUE);
right now, when the user deletes a movies entry, movie_cast rows referencing the movies._id are also deleted. (i had some troubles with that, but then i used "PRAGMA foreign_keys = ON;" ) so far so good! To delete the actors rows, i thought i could create a trigger that tries to delete actors entries based on the movie_cast.actor_id we just deleted, but since i'm using "ON DELETE RESTRIC", if the actor has still a reference it would abort the delete.
But i'm not even being able to prove it, because i'm getting an error when creating the trigger:
CREATE TRIGGER Delete_Actors AFTER DELETE ON movie_cast
FOR EACH ROW
BEGIN
DELETE FROM actors WHERE actors.actor_id = OLD.actor_id;
END;
SQL Error [1]: [SQLITE_ERROR] SQL error or missing database (near "actor_id": syntax error)
[SQLITE_ERROR] SQL error or missing database (near "actor_id": syntax error)
It seems, it doesn't know what OLD is. What am i doing wrong here?
UPDATE:
It looks like a sqlite configuration problem. I'm using DBeaver SQLite 3.8.2, and it seems to be a problem with the temporary file, but to be honest i don't know how to fix it even reading the possible solution:
It's failing to create the temporary file required for a statement journal.
It's likely any statement that uses a temporary file will fail.
http://www.sqlite.org/tempfiles.html
One way around the problem would be to configure SQLite not to use
temp files using "PRAGMA temp_store = memory;".
Or ensure that the environment variable SQLITE_TMPDIR is set to
the path of a writable directory. See also:
http://www.sqlite.org/c3ref/temp_directory.html
So, I am going to assume it works and try it directly on my android app.
It was something really s****p. For DBeaver the trigger creation is a complex-statement, and delimiters were not working either, so it was needed to select the whole statement then press ctrl+enter.
Anyway, the statement works. But for a better results I got rid of "ON DELETE RESTRICT" from movie_cast.actor_id. and created a conditional trigger, that executes the delete from actors table, only when there are no more actor_ids equal to the one just deleted(OLD):
CREATE TRIGGER Delete_Actors
AFTER DELETE ON movie_cast
FOR EACH ROW
WHEN (SELECT count(id) FROM movie_cast WHERE actor_id = OLD.actor_id) = 0
BEGIN
DELETE FROM actors WHERE actors.actor_id = OLD.actor_id;
END;

SQL - keep values with UPDATE statement

I have a table "news" with 10 rows and cols (uid, id, registered_users, ....) Now i have users that can log in to my website (every registered user has a user id). The user can subscribe to a news on my website.
In SQL that means: I need to select the table "news" and the row with the uid (from the news) and insert the user id (from the current user) to the column "registered_users".
INSERT INTO news (registered_users)
VALUES (user_id)
The INSERT statement has NO WHERE clause so i need the UPDATE clause.
UPDATE news
SET registered_users=user_id
WHERE uid=post_news_uid
But if more than one users subscribe to the same news the old user id in "registered_users" is lost....
Is there a way to keep the current values after an sql UPDATE statement?
I use PHP (mysql). The goal is this:
table "news" row 5 (uid) column "registered_users" (22,33,45)
--- 3 users have subscribed to the news with the uid 5
table "news" row 7 (uid) column "registered_users" (21,39)
--- 2 users have subscribed to the news with the uid 7
It sounds like you are asking to insert a new user, to change a row in news from:
5 22,33
and then user 45 signs up, and you get:
5 22,33,45
If I don't understand, let me know. The rest of this solution is an excoriation of this approach.
This is a bad, bad, bad way to store data. Relational databases are designed around tables that have rows and columns. Lists should be represented as multiple rows in a table, and not as string concatenated values. This is all the worse, when you have an integer id and the data structure has to convert the integer to a string.
The right way is to introduce a table, say NewsUsers, such as:
create table NewsUsers (
NewsUserId int identity(1, 1) primary key,
NewsId int not null,
UserId int not null,
CreatedAt datetime default getdaete(),
CreatedBy varchar(255) default sysname
);
I showed this syntax using SQL Server. The column NewsUserId is an auto-incrementing primary key for this table. The columns NewsId is the news item (5 in your first example). The column UserId is the user id that signed up. The columns CreatedAt and CreatedBy are handy columns that I put in almost all my tables.
With this structure, you would handle your problem by doing:
insert into NewsUsers
select 5, <userid>;
You should create an additional table to map users to news they have registeres on
like:
create table user_news (user_id int, news_id int);
that looks like
----------------
| News | Users|
----------------
| 5 | 22 |
| 5 | 33 |
| 5 | 45 |
| 7 | 21 |
| ... | ... |
----------------
Then you can use multiple queries to first retrieve the news_id and the user_id and store them inside variables depending on what language you use and then insert them into the user_news.
The advantage is, that finding all users of a news is much faster, because you don't have to parse every single idstring "(22, 33, 45)"
It sounds like you want to INSERT with a SELECT statement - INSERT with SELECT
Example:
INSERT INTO tbl_temp2 (fld_id)
SELECT tbl_temp1.fld_order_id
FROM tbl_temp1
WHERE tbl_temp1.fld_order_id > 100;

MySQL data version control

Is there any way to setup MySQL to every time a row is changed, then a row to another table/database is created with what the data was originally? (with time stamping)
If so how would I go about doing it?
E.g.
UPDATE `live_db`.`people`
SET `live_db`.`people`.`name` = 'bob'
WHERE `id` = 1;
Causes this to happen before the update:
INSERT INTO `changes_db`.`people`
SELECT *
FROM `live_db`.`people`
WHERE `live_db`.`people`.`id` = 1;
And if you did it again it would result in something like this:
`live_db`.`people`
+----+-------+---------------------+
| id | name | created |
+----+-------+---------------------+
| 1 | jones | 10:32:20 12/06/2010 |
+----+-------+---------------------+
`changes_db`.`people`
+----+-------+---------------------+
| id | name | updated |
+----+-------+---------------------+
| 1 | billy | 12:11:25 13/06/2010 |
| 1 | bob | 03:01:54 14/06/2010 |
+----+-------+---------------------+
The live DB needs to have a created time stamp on the rows, and the changes DB needs to have a time stamp of when the live DB row was updated.
The changes DB will also have no primary keys and foreign key constraints.
I'm using InnoDB and MySQL 5.1.49 but can upgrade if required.
Use a Trigger
MySQL support for triggers started with MySQL version 5.0.2.
You can create a trigger:
DELIMITER \\
CREATE TRIGGER logtrigger BEFORE UPDATE ON live_db.people
FOR EACH ROW BEGIN
INSERT INTO changes_db.people(id,name,updated) VALUES(OLD.id,OLD.name,now());
END;
\\
This is how I ended up doing it
DELIMITER |
# Create the log table
CREATE TABLE IF NOT EXISTS `DB_LOG`.`TABLE`
LIKE `DB`.`TABLE`|
# Remove any auto increment
ALTER TABLE `DB_LOG`.`TABLE` CHANGE `DB_LOG`.`TABLE`.`PK` `DB_LOG`.`TABLE`.`PK` INT UNSIGNED NOT NULL|
# Drop the primary keys
ALTER TABLE `DB_LOG`.`TABLE` DROP PRIMARY KEY|
#Create the trigger
DROP TRIGGER IF EXISTS `DB`.`update_TABLE`|
CREATE TRIGGER `DB`.`update_TABLE` BEFORE UPDATE ON `DB`.`TABLE` FOR EACH ROW
BEGIN
INSERT INTO `DB_LOG`.`TABLE`
SELECT `DB`.`TABLE`.*
FROM `DB`.`TABLE`
WHERE `DB`.`TABLE`.`PK` = NEW.`PK`;
END|
DELIMITER ;
Sorry to comment on an old post, but I was looking to solve this exact problem! Thought I would share this information.
This outlines a solution perfectly:
http://www.hirmet.com/mysql-versioning-records-of-tables-using-triggers