How work with SQL and multiple join or merge them - mariadb

I need to retrieve events from table, with skills requirements and user engagements and skills.
For a project, I have :
an users table
a skills table with inheritance from others skills
an users_skills table with skills of users
an events table
an events_skills table with the quantity of a required skills for the events
an events_users table with users engagements on events
CREATE TABLE `users` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` char(32) NOT NULL
);
CREATE TABLE `skills` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` char(64) NOT NULL,
`parent_id` int(11) unsigned DEFAULT NULL,
FOREIGN KEY (`parent_id`) REFERENCES `skills` (`id`)
);
CREATE TABLE `users_skills` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` int(11) unsigned NOT NULL,
`skill_id` int(11) unsigned NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
FOREIGN KEY (`skill_id`) REFERENCES `skills` (`id`)
);
CREATE TABLE `events` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` char(64) NOT NULL
);
CREATE TABLE `events_skills` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`event_id` int(11) unsigned NOT NULL,
`skill_id` int(11) unsigned NOT NULL,
`quantity` smallint unsigned NOT NULL,
FOREIGN KEY (`event_id`) REFERENCES `events` (`id`),
FOREIGN KEY (`skill_id`) REFERENCES `skills` (`id`)
);
CREATE TABLE `events_users` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`event_id` int(11) unsigned NOT NULL,
`user_id` int(11) unsigned NOT NULL,
FOREIGN KEY (`event_id`) REFERENCES `events` (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
);
I want to retrieve all events, with skills requirments and users participation.
My first idea was to use LEFT JOIN :
SELECT e.id, e.name, es.skill_id, s.name skill_name, es.quantity,
eu.user_id, u.name user_name, us.skill_id user_skill_id,
uss.name user_skill_name
FROM events e
LEFT JOIN events_skills es ON es.event_id = e.id
LEFT JOIN skills s ON s.id = es.skill_id
LEFT JOIN events_users eu ON eu.event_id = e.id
LEFT JOIN users u ON u.id = eu.user_id
LEFT JOIN users_skills us ON us.user_id = u.id
LEFT JOIN skills uss ON uss.id = us.skill_id;
id name skill_id skill_name quantity user_id user_name user_skill_id user_skill_name
1 eve1 2 ski2 3 1 use1 2 ski2
1 eve1 3 ski2-2 1 1 use1 2 ski2
1 eve1 2 ski2 3 2 use2 NULL NULL
1 eve1 3 ski2-2 1 2 use2 NULL NULL
2 eve2 NULL NULL NULL NULL NULL NULL NULL
2 differents events, second without users and skills. First, with 2 users, first with one skills, seconds without skills, and finally, two skills requirements.
It work "fine", but it's verry ugly, and I think it can be very slow if an event or an user has too many skills.
Also, each event has a lot of lines, and it's difficult to sort them (if I want to display only 20 events...).
To improve, I think I can load all skills separately and remove the two LEFT JOIN for table skills.
But, can I merge all skills and quantity in one column, and users in others ? Did you have betters solutions/improvements ?
id name skills users
1 eve1 2,3;3,1 1,2;2,NULL
2 eve2 NULL NULL
Thanks for your help.

Another way to do what Thorsten suggested:
select e.*,
( SELECT group_concat(skill_id order by skill_id separator ',')
from events_skills
WHERE event_id = e.id
) AS skills,
( SELECT group_concat(user_id order by user_id separator ',' )
from events_users
WHERE event_id = e.id
) AS users
from events e
order by e.id;
Each subquery needs an extra join to get the "name" of the skill and user instead of the "id".
May I suggest you use the name of the skill as its id.
When you have a many-many mapping table, be sure to use the pair of ids as the PRIMARY KEY and have an INDEX in the opposite order. And don't have an AUTO_INCREMENT. Details: http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table

A result with skills and users as comma-separated strings is exactly how I would do this, too. Use GROUP_CONCAT for this.
select e.*, s.skills, u.users
from events e
left join
(
select event_id, group_concat(skill_id order by skill_id separator ',') as skills
from events_skills
group by event_id
) s on s.event_id = e.id
left join
(
select event_id, group_concat(user_id order by user_id separator ',') as users
from events_users
group by event_id
) u on u.event_id = e.id
order by e.id;

Related

Constraints about

What constraints are copied with table when we create table by using create table as select statement..?
I don't know about MySQL, but - as of Oracle - only the NOT NULL constraint. Even if there was a column designed as a primary key (which - implicitly - means that it can't be NULL), newly created table won't have that constraint.
For example:
SQL> create table test_1
2 (id number primary key,
3 name varchar2(20) not null);
Table created.
SQL> select constraint_name, constraint_Type from user_constraints where table_name = 'TEST_1';
CONSTRAINT_NAME C
-------------------- -
SYS_C009194 C --> "C"heck (NOT NULL) constraint
SYS_C009195 P --> "P"rimary key constraint
SQL> create table test_2 as select * from test_1;
Table created.
SQL> select constraint_name, constraint_Type from user_constraints where table_name = 'TEST_2';
CONSTRAINT_NAME C
-------------------- -
SYS_C009196 C --> that's the "C"heck - NOT NULL constraint
SQL> desc test_2
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
NAME NOT NULL VARCHAR2(20)
SQL>
MySQL would only preserve NOT NULL as constarint
CREATE TABLE tab1(id int AUTO_INCREMENT PRIMARY KEY)
create table tab2
(id int primary key,
col1 varchar(20) not null,
col2 int UNIQUE,
col3 int, FOREIGN KEY (col3) REFERENCES tab1(id))
CREATE TABLE tab3 AS SELECT * FROM tab2
Records: 0 Duplicates: 0 Warnings: 0
SHOW CREATE TABLE tab3
Table
Create Table
tab3
CREATE TABLE `tab3` (  `id` int NOT NULL,  `col1` varchar(20) NOT NULL,  `col2` int DEFAULT NULL,  `col3` int DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
fiddle

mariadb char(250)*20 vs text(5000) for perfomance

I have about 100 million texts(text length 4500~5000). I'm going to put them in mariadb, and the query I use is as follows.
SELECT TT_TEXT FROM TEXT_TABLE WHERE TT_HASH = UNHEX('MD5 HASH');
Which perfomance would be better for TEXT_TABLE1 or TEXT_TABLE2? The capacity of the database doesn't matter. Just the select perfomance.
# TABLE1
CREATE TABLE `TEXT_TABLE1` (
`TT_INDEX` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Index',
`TT_HASH` BINARY(16) NOT NULL COMMENT 'MD5 hash for `TT_TEXT`',
`TT_TEXT` TEXT(5000) NOT NULL COMMENT 'Text for 4500~5000 length',
PRIMARY KEY (`TT_INDEX`),
UNIQUE KEY (`TT_HASH`)
) ENGINE=InnoDB COMMENT 'Text table';
vs
# TABLE2
CREATE TABLE `TEXT_TABLE2` (
`TT_INDEX` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Index',
`TT_HASH` BINARY(16) NOT NULL COMMENT 'MD5 hash for `TT_TEXT`',
`TT_TEXT1` CHAR(250) NOT NULL COMMENT 'Text for 4500~5000 length',
...
`TT_TEXT20` CHAR(250) NOT NULL COMMENT 'Text for 4500~5000 length',
PRIMARY KEY (`TT_INDEX`),
UNIQUE KEY (`TT_HASH`)
) ENGINE=InnoDB COMMENT 'Text table';

SQL query to relate 2 tables

I want to relate following 2 tables in sqlite3. What I understood from other examples is that we should have some common field between each table, so I added order_ID.
1) How to write sqlite queries for creating the relation between these tables?
2) How to manage Table 2, where same order can have multiple products, so order ID is repeated. An order can have min 1 and max 10 products. So it has dynamic range of 1-10.
table 1:
order_ID date buyer ship_chr
001 01/01 abc 15
002 05/01 xyz 10
table 2:
order_ID prod quantity rate
001 pen 50 2
001 paper 25 1
001 pin 50 2
002 paper 25 1
002 pen 100 2
It looks like you want to store orders and information about those orders. First, make an orders table.
create table orders (
id integer primary key autoincrement,
created_at timestamp not null default current_timestamp,
buyer text not null,
ship_chr text not null
)
Note that instead of order_id, the primary key of a table is just id.
It's not required, but it is a convention I like as it keeps primary and foreign keys distinct.
Also note that I'm using the timestamp type to store dates, this will make working with those dates much easier as you can use SQLite date functions.
Now we need a table for information about what is in each order.
create table order_products (
id integer primary key autoincrement,
order_id integer not null references orders(id),
product text not null,
quantity integer not null,
rate integer not null
)
This sets up a one-to-many relationship betweeen orders and order_products.
One order can have many products. You can link these tables together using
a join. Here's how you'd get
the buyer for each product.
select o.buyer, op.product, op.quantity
from order_products op
join orders o on o.id = op.order_id
abc|pen|50
abc|paper|25
abc|pin|50
xyz|paper|25
xyz|pen|100
join orders o on o.id = op.order_id says for every row in order_products find one in orders where order.id matches the row's order_id and treat them both as a single row.
From here you'll probably want to make products and buyer their own tables
as well to store any information about the buyers and products. It also ensures
that the products and buyers exist avoiding typos.
create table buyers (
id integer primary key autoincrement,
name text not null,
address text not null,
phone text not null
);
create table products (
id integer primary key autoincrement,
name text not null,
stock integer not null default 0
);
create table orders (
id integer primary key autoincrement,
created_at timestamp not null default current_timestamp,
buyer_id integer references buyers(id) not null,
ship_chr text not null
);
create table order_products (
id integer primary key autoincrement,
order_id integer not null references orders(id),
product_id integer not null references products(id),
quantity integer not null,
rate integer not null
);
Then you can join everything together to get information about products and buyers.
select b.name, p.name, op.quantity
from order_products op
join orders o on o.id = op.order_id
join buyers b on b.id = o.buyer_id
join products p on p.id = op.product_id
name|name|quantity
abc|pen|50
abc|paper|25
abc|pin|50
xyz|paper|25
xyz|pen|100
SQL Fiddle
If you don't do this now it will be harder to do later.

Get result from different tables - join

I have following tables in my DB
CREATE TABLE [author_details] (
[_id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
[name] TEXT NOT NULL,
[surname] TEXT NOT NULL,
[middle_name] TEXT NULL
);
CREATE TABLE [authors] (
[_id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[book_id] INTEGER NOT NULL,
[author_id] INTEGER NOT NULL
);
CREATE TABLE [books] (
[_id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
[title] TEXT NOT NULL,
[publisher_id] INTEGER NOT NULL,
[isbn] VARCHAR(10) UNIQUE NULL,
[ean] VARCHAR(13) UNIQUE NULL,
[pages] INTEGER DEFAULT '0' NULL,
[year] INTEGER NOT NULL,
[edition] TEXT NULL
);
CREATE TABLE [publishers] (
[_id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[name] TEXT NOT NULL
);
I want a list of all books with details, I've used following query:
SELECT b.title,b.isbn,b.ean,b.year,b.pages,b.edition,
CASE
WHEN ad.middle_name IS NULL
THEN ad.name||" "||ad.surname
ELSE ad.name||" "||ad.middle_name||" "||ad.surname
END AS author, p.name
FROM books AS b, authors AS a, author_details AS ad, publishers AS p
INNER JOIN authors, author_details, publishers ON b._id=a.book_id AND ad._id=a.author_id AND b.publisher_id=p._id
GROUP BY b._id
It returns All books but only one author for books with multiple authors. How to write the query to get all authors per book?
To get the values from all records in a group, you have to use the group_concat function:
SELECT b.title,b.isbn,b.ean,b.year,b.pages,b.edition,
group_concat(CASE
...
END) AS author, p.name
FROM ...
Additionally, you need to use the correct join syntax.
In your query, you are joining every table twice, which results in lots up duplicate records.
There are two equivalent syntaxes for joins.
Either use a plain list of tables, and WHERE:
...
FROM books AS b,
authors AS a,
author_details AS ad,
publishers AS p
WHERE b._id = a.book_id
AND a.author_id = ad._id
AND b.publisher_id = p._id
...
or use the JOIN operator for each join, with a join condition for each join:
...
FROM books AS b
JOIN authors AS a ON b._id = a.book_id
JOIN author_details AS ad ON a.author_id = ad._id
JOIN publishers AS p ON b.publisher_id = p._id
...
Try to use group_concat():
SELECT b.title,b.isbn,b.ean,b.year,b.pages,b.edition,
GROUP_CONCAT(CASE
WHEN ad.middle_name IS NULL
THEN ad.name||" "||ad.surname
ELSE ad.name||" "||ad.middle_name||" "||ad.surname
END) AS author,
p.name
FROM
.........

How do i display a count from another table in a gridview?

I building a blog for school i would like to display the count of comments for each thread made. However I'm a bit lost to how to achieve this goal any help would be great thank you!
i have 2 tables
CREATE TABLE `blog_message` (
`MessageID` int(30) NOT NULL AUTO_INCREMENT,
`Username` varchar(45) NOT NULL,
`Message` text,
`AddedDate` datetime DEFAULT NULL,
`Title` varchar(45) DEFAULT NULL,
PRIMARY KEY (`MessageID`)
)
CREATE TABLE `blog_comments` (
`CommentID` int(30) NOT NULL AUTO_INCREMENT,
`MessageID` int(30) DEFAULT NULL,
`Author` varchar(45) DEFAULT NULL,
`CommentMessage` text,
`AddedDate` datetime DEFAULT NULL,
PRIMARY KEY (`CommentID`),
KEY `blog_comments_ibfk_1` (`MessageID`),
CONSTRAINT `blog_comments_ibfk_1` FOREIGN KEY (`MessageID`) REFERENCES `blog_message` (`MessageID`)
)
my goal is
to display in a gridview
int a table format
Comment count | title | Username | Date of creation
The following is MySQL syntax, not sure what you're using. But this will return a list MessageIds and the number of comments they have.
----------------------------
| MessageId | comment_count|
----------------------------
| 1234 | 34 |
----------------------------
SELECT bm.MessageId, count(bc.CommentId) as comment_count
FROM blog_comments bc, blog_message bm
WHERE bm.MessageId = bc.MessageId
GROUP BY bm.MessageId
If you want the Author and AddedDate just add it to the SELECT statement (i.e. SELECT bm.MessageId, count(bc.CommentId) as comment_count, bm.AddedDate, bm.Author).
Try the following
SELECT
bm.MessageID, Count(bc.CommentID) as Cnt, Title, Username, AddedDate
FROM blog_message bm
LEFT OUTER JOIN blog_comments bc
ON bm.MessageID = bc.MessageID
GROUP BY bm.MessageID,Title, Username, AddedDate

Resources