Conditionally execute a subquery with MySQL - mysql

I have a view which describes some statistics of another table, e.g.:
create table people (
playerid INT not null auto_increment,
name VARCHAR(70),
primarykey (playerid)
);
create view personinfo_oranges (playerid, number_of_oranges) as
select playerid, count(orangeid) from oranges
...
;
Unfortunately, there's loads of data in the tables that person_info looks at (e.g. oranges) and the view is very slow to load. This is apparently a known issues with views. So I created another table that stores the data for some of the people (e.g. when they become allergic to oranges):
create table person_summary (
playerid INT not null,
number_of_oranges INT,
foreign key (playerid) references players(playerid)
);
Ideally I would like to create a view that searches person_summary, and, if not found, looks in personinfo_oranges. I could do this logic in my Model layer, but how can I do it in MySQL?

Related

How to structure my database model for a simple web application and organize my SQL data model

I am working on a web app. I have a question about the SQL parts. I am currently creating a food rating interface. The user should be able to login and then rate the food.
Currently I use a custom database (users) for the login page. I also use a separate database (review) for the reviews. my current database structure makes no sense.
In the attached image you can see the tables, they are currently separate databases with only this one table each. I use MySQL extension in VSCode.
How can I combine the tables into one database? I am looking for some ideas for this problem.
Preferably the username should only be needed for login and not have to be entered separately in the rating (as unfortunately still current).
In the future, the user should be directed after login, to a page where he can select his dish, then he should rate it. Each dish should save the ratings individually, to distinguish this of course.
I suggest that you do use the user table as a reference for reviews, and also a table dish with the details of each dish.
Here are suggested table definitions, to fine-tune to your needs:
Credits to Mishi for the improvement to foreign key syntax to make it more portable.
I've put the creation of the table review last because some SQL engines check that the table exists when we declare a foreign key.
create table users
(
uid int primary key,
username varchar(25),
password varchar(25),
created_at date
);
create table dish
(
did int primary key,
nom varchar(25),
description varchar(250),
last_rating int
);
create table review
(
rid int primary key,
user_id int ,
dish_id int ,
user_rating int ,
user_reveiw varchar(100),
review_date datetime,
foreign key (user_id) references users(uid),
foreign key (dish_id) references dish(did)
);
db<>fiddle here

MySQL auto assign foreign key ID

I have a main table called results. E.g.
CREATE TABLE results (
r_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
r_date DATE NOT NULL,
system_id INT NOT NULL,
FOREIGN KEY (system_id) REFERENCES systems(s_id) ON UPDATE CASCADE ON DELETE CASCADE
);
The systems table as:
CREATE TABLE systems (
s_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
system_name VARCHAR(50) NOT NULL UNIQUE
);
I'm writing a program in Python with MySQL connector. Is there a way to add data to the systems table and then auto assign the generated s_id to the results table?
I know I could INSERT into systems, then do another call to that table to see what the ID is for the s_name, to add to the results table but I thought there might be quirk in SQL that I'm not aware of to make life easier with less calls to the DB?
You could do what you describe in a trigger like this:
CREATE TRIGGER t AFTER INSERT ON systems
FOR EACH ROW
INSERT INTO results SET r_date = NOW(), system_id = NEW.s_id;
This is possible only because the columns of your results table are easy to fill in from the data the trigger has access to. The auto-increment fills itself in, and no additional columns need to be filled in. If you had more columns in the results table, this would be harder.
You should read more about triggers:
https://dev.mysql.com/doc/refman/8.0/en/create-trigger.html
https://dev.mysql.com/doc/refman/8.0/en/triggers.html

Adding constraints in the MySQL database to guarantee uniqueness

My SQL table has the following DDL
CREATE TABLE `new_table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`family_id` int(11) NOT NULL,
`name` varchar(45) NOT NULL,
PRIMARY KEY (`id`)
)
I want to hold the family names in this simple table. In order to do this I have a microservice where the caller sends via JSON the family details:
{
"family_id" : 1,
"names": ["name1", "name2"]
}
The id is generated via auto increment from MySQL.
So the above JSON will finally trigger two insert statements:
insert (family_id, name) values ( 1, name1)
insert (family_id, name) values ( 1, name2)
The problem arises when a new request comes with a family_id that exists in the table. This should not be allowed, and I am doing a query in order to search if the family_id exists or not. If it exists an exception is raised. How can I avoid this query? The table schema can be altered if needed. Would it be OK if a could add something like a "request id", or a guid to establish uniqueness per request?
All data should be on the same table.
Below an example of the table with some data
(from Comment) I cannot create a second table. Eveything should be kept in one table.
You should normalize your schema and use two tables.
Family and (I assume) Person. Then you can use a UNIQUE constraint for the family_id and add the family_id as foreign key into the Person table.
You need two tables.
CREATE TABLE Families (
family_id MEDIUMINT UNSIGNED AUTO_INCREMENT,
...
PRIMARY KEY(family_id)
);
CREATE TABLE FamilyNames (
family_id MEDIUMINT UNSIGNED, -- not auto-inc here
name VARCHAR(66) NOT NULL,
...
PRIMARY KEY(family_id, name) -- note "composite"
);
A PRIMARY KEY is a UNIQUE KEY is a KEY.
You say you cannot add a second table. But why? You mention needing to generate a particular JSON? Can't that simply be done via a JOIN of the two tables if necessary?
If you can't create a second table to model the constraint properly, then you will have to resort to serializing inserts:
LOCK TABLES new_table WRITE;
Use a SELECT to check if the family id exists in the table.
If the family id is not present, INSERT your new data.
UNLOCK TABLES;
It's necessary to lock the table because otherwise you will have a race condition. Two sessions could check if the family id exists, both find that it does not exist, and then both proceed with their INSERT. If you lock the table, then one session will acquire the lock and do its work, while the other session must wait for the lock, and by the time it acquires the lock, its check will find that the family id has been inserted by the first session.
This method is usually considered bad for concurrency, which can limit your throughput if you have many requests. But if you have infrequent requests, the impact to throughput will be minimal.
This is a workaround for a design problem, but I figured I might as well post it. You can generate a query such as the following:
INSERT INTO `new_table` (`family_id`, `name`)
SELECT * FROM (
SELECT 1, 'name1'
UNION ALL
SELECT 1, 'name2'
) x
LEFT JOIN `new_table` n ON n.family_id = 1
WHERE n.family_id IS NULL
Then check the number of rows affected to determine if it was successful or not.

Is it possible to create a Unique (Index) constraint across multiple tables?

Is there a way to create a unique index across tables in a MySQL database?
By unique, I mean,
table A ids=1,2,5,7...
table B ids=3,4,6,8...
I think, it is not possible by directly creating a constraint. But there are some solutions in order for you to get unique IDs.
One suggestion would be is by using TRIGGER. Create a BEFORE INSERT trigger on both tables which checks the ID for existence during INSERT or UPDATE.
Second, is by creating a third table which contains UNIQUE numbers and the other tables: TableA and TableB will then be reference on the third one.
Like JW says, this would probably work well with using a third table. In MySQL, you can use identity fields to make this easier. A very simple example would be using these tables (simple versions of the tables with just names without knowing any further info):
CREATE TABLE a
(
`id` int,
`name` varchar(100),
PRIMARY KEY(`id`)
)
ENGINE = INNODB;
CREATE TABLE b
(
`id` int,
`name` varchar(100),
PRIMARY KEY(`id`)
)
ENGINE = INNODB;
CREATE TABLE c
(
`id` int auto_increment,
`intable` varchar(10) not null,
PRIMARY KEY(`id`)
)
ENGINE = INNODB;
Then, when you want to insert a value on either table, do (a sample inserting 'Joe' into a):
INSERT INTO c (`intable`) VALUES ('a');
INSERT INTO a (`id`, `name`)
SELECT LAST_INSERT_ID() AS id, 'Joe' AS name;
The only reason for the intable entry in c is so you know which table it was created for. Any sort of value to insert into c can be used instead.

mysql foreign key for entire table

I am new to databases, but I have gone over some tutorials and learned most of the essentials (I am a long time programmer). However, I need some help getting around the limitations of relational databases. If I have a picture I can create a simple table for it as such
"CREATE TABLE picture(
file VARCHAR(150),
rating INT
)";
If I want to keep track of who rates for the picture, I can either hard code a preset number of voters like so (in this example 3 anonymous votes)
"CREATE TABLE picture(
file VARCHAR(150),
rating INT,
vote1 INT,
vote2 INT,
vote3 INT
)";
Or for an unlimited number I can create a new table as such
"CREATE TABLE ratingTemplate
(
rater INT,
rating INT
)";
But for every picture entry in the picture table I want to have a reference to this entire ratingTemplate table. Is there any proper way to use foreign keys to achieve this? Currently I am micromanaging it by creating new ratingTemplate tables and making sure to store their names in the corresponding picture table entry.
Yes, you would have to have a table that keeps references to picture table as well as user table.
CREATE TABLE IF NOT EXISTS PICTURE (
PICTURE_ID BIGINT NOT NULL AUTO_INCREMENT,
FILE VARCHAR(250),
PRIMARY KEY (PICTURE_ID)
);
CREATE TABLE IF NOT EXISTS USER (
USER_ID BIGINT NOT NULL AUTO_INCREMENT,
--....REST OF COLUMNS HERE...
PRIMARY KEY(USER_ID)
);
CREATE TABLE IF NOT EXISTS PICTURE_RATING (
RATING_ID BIGINT NOT NULL AUTO_INCREMENT,
PICTURE_ID BIGINT NOT NULL,
USER_ID BIGINT NOT NULL,
RATING DOUBLE,
PRIMARY KEY (RATING_ID),
FOREIGN KEY (PICTURE_ID) REFERENCES PICTURE(PICTURE_ID),
FOREIGN KEY (USER_ID) REFERENCES USER(USER_ID)
);
You have not identified the primary key for the 'picture' table, but as presented the primary key would appear to be the 'file' column. You would need to add a column to the 'ratingTemplate' table named 'file' that would also be be varchar(150. Your primary key in the 'ratingTemplate' would be a combination of 'file' and 'rater'. The 'file' column in the 'ratingTemplate' table would be a foriegn key to the 'file' column int he 'picture' table.
Sample Query:
SELECT picture.file, rater, rating
FROM picture INNER JOIN ratingTemplate ON picture.file = ratingTemplate.file
WHERE picture.file = 'filenameiwant'
Another approach would be to add a surrogate primary key to the 'picture' table.
Perhaps named 'FileId' as an integer. In that case, you would use the 'FileId' column in the 'ratingTemplate' as the foriegn key instead of 'file'. For large data sets this would execute faster and use less space.
Sample query:
FROM picture INNER JOIN ratingTemplate ON picture.FileId = ratingTemplate.FileId
WHERE picture.FileId = 257