mysql GROUP_CONCAT - mysql

I want to list all users with their corropsonding user class.
Here are simplified versions of my tables
CREATE TABLE users (
user_id INT NOT NULL AUTO_INCREMENT,
user_class VARCHAR(100),
PRIMARY KEY (user_id)
);
INSERT INTO users VALUES
(1, '1'),
(2, '2'),
(3, '1,2');
CREATE TABLE classes (
class_id INT NOT NULL AUTO_INCREMENT,
class_name VARCHAR(100),
PRIMARY KEY (class_id)
);
INSERT INTO classes VALUES
(1, 'Class 1'),
(2, 'Class 2');
And this is the query statement I am trying to use but is only returning the first matching user class and not a concatenated list as hoped.
SELECT user_id, GROUP_CONCAT(DISTINCT class_name SEPARATOR ",") AS class_name
FROM users, classes
WHERE user_class IN (class_id)
GROUP BY user_id;
Actual Output
+---------+------------+
| user_id | class_name |
+---------+------------+
| 1 | Class 1 |
| 2 | Class 2 |
| 3 | Class 1 |
+---------+------------+
Wanted Output
+---------+---------------------+
| user_id | class_name |
+---------+---------------------+
| 1 | Class 1 |
| 2 | Class 2 |
| 3 | Class 1, Class 2 |
+---------+---------------------+
Thanks in advance

This is not the best design, but here's the query you want:
SELECT user_id, GROUP_CONCAT(class_name)
FROM users
JOIN classes
ON FIND_IN_SET(class_id, user_class)
GROUP BY
user_id

This breaks rules of database normalization. Don't store many-to-many values in a comma-separated list. Instead make another table user_classes with fields user_id and class_id. This table is simply used as a link table between the other two classes. After this you can use GROUP_CONCAT to do what you want.

Related

Inserting new data in a table

I have created a basic table for learning purposes.
CREATE TABLE friends (
id INT,
name TEXT,
birthday DATE
);
Added some data...
INSERT INTO friends (id,name,birthday)
VALUES (1,'Jo Monro','1948-05-30');
INSERT INTO friends (id,name,birthday)
VALUES (2, 'Lara Johnson','1970-03-03');
INSERT INTO friends (id,name,birthday)
VALUES (3,'Bob Parker', '1962-09-3');
And I realised that I forgot to include the email column.
I added the column...
ALTER TABLE friends
ADD COLUMN email;
..but how can I add now data to this new column only?
I have tried WHERE statements, rewriting the INSERT INTO statements with and without the other column names but nothing worked?
What am I missing here?
Thank you!
Insert the emails into a temporary table, then update the real table with that.
CREATE TABLE friends (
id INT auto_increment primary key,
name VARCHAR(100),
birthday DATE
);
INSERT INTO friends (name, birthday) VALUES
('Jo Monro','1948-05-30')
, ('Lara Johnson','1970-03-03')
, ('Bob Parker', '1962-09-3');
ALTER TABLE friends ADD COLUMN email VARCHAR(100);
select * from friends
id | name | birthday | email
-: | :----------- | :--------- | :----
1 | Jo Monro | 1948-05-30 | null
2 | Lara Johnson | 1970-03-03 | null
3 | Bob Parker | 1962-09-03 | null
--
-- temporary table for the emails
--
CREATE TEMPORARY TABLE tmpEmails (
name varchar(100) primary key,
email varchar(100)
);
--
-- fill the temp
--
insert into tmpEmails (name, email) values
('Jo Monro','jo.monro#unmail.net')
, ('Lara Johnson','lara.johnson#unmail.net')
, ('Bob Parker', 'UltimateLordOfDarkness#chuni.byo');
--
-- update the real table
--
update friends friend
join tmpEmails tmp
on friend.name = tmp.name
set friend.email = tmp.email
where friend.email is null;
select * from friends
id | name | birthday | email
-: | :----------- | :--------- | :-------------------------------
1 | Jo Monro | 1948-05-30 | jo.monro#unmail.net
2 | Lara Johnson | 1970-03-03 | lara.johnson#unmail.net
3 | Bob Parker | 1962-09-03 | UltimateLordOfDarkness#chuni.byo
db<>fiddle here

Generating another column for table based on values in existing table

Assuming following table:
CREATE TABLE `item`(
`item_id` int(11) NOT NULL AUTO_INCREMENT,
`item_serial_number` varchar(255) DEFAULT NULL,
`item_type` varchar(255) DEFAULT NULL,
PRIMARY KEY (`item_id`)
)
and following inserts:
INSERT INTO `item` (`item_id`, `item_serial_number`, `item_type`) VALUES
(1, '11232141', 'laptop'),
(2, '22343252', 'printer'),
(3, '34234244', 'printer'),
(4, '78678678', 'charger');
Which SQL commands should I look at/for if I want to add another column, namely item_type_id which will look up on every item_type varchar field value and generate values like this:
------------------------
|ID| SERIAL | TYPE |
------------------------
|1 | 11232141 | laptop |
|2 | 22343252 | printer|
|3 | 34234244 | printer|
|4 | 78678678 | charger|
------------------------
->
----------------------------------
|ID| SERIAL | TYPE | TYPE_ID |
----------------------------------
|1 | 11232141 | laptop | 1 |
|2 | 22343252 | printer| 2 |
|3 | 34234244 | printer| 2 |
|4 | 78678678 | charger| 3 |
----------------------------------
If you want to do this correctly, create a types table:
create table types (
type_id int auto_increment primary key,
type varchar(255)
);
insert into types (type)
select distinct type
from item;
Then, fix your current table:
alter table item add type_id int;
alter table item add constraint fk_types_type_id
foreign key (type_id) references types (type_id);
Then set the value:
update item i join
types t
on i.type = t.type
set i.type_id = t.type_id;
And finally drop the old column:
alter table item drop column type;
Try this-
SELECT item_id AS ID,
item_serial_number AS Serial,
item_type AS Type,
CASE
WHEN item_type = 'laptop' THEN 1
WHEN item_type = 'printer' THEN 2
WHEN item_type = 'charger' THEN 3
END TYPE_ID
FROM `inventory_item_temp`
You can use a lookup table if the list is big. If I guess you have the lookup table as below-
Table Name: Type_id_lookup
item_type type_id
laptop 1
printer 2
charger 3
Mobile 4 -- Sample other item_type
Then the query will be-
SELECT
A.item_id AS ID,
A.item_serial_number AS Serial,
A.item_type AS Type,
B.type_id TYPE_ID
FROM `inventory_item_temp` A
LEFT JOIN `Type_id_lookup` B ON A.item_type = B.item_type
You can use the DENSE_RANK analytical function:
select ID, SERIAL, TYPE,
DENSE_RANK() OVER (ORDER BY SERIAL DESC NULLS LAST) AS TYPE_ID
FROM YOUR_TABLE
But note that, This will give ID 1 to the printer, 2 to laptop and 3 to the charger. but it will be dynamic so no need to give static values in the query.
Cheers!!

When to use new table mySql

When should one create a new table in a database and use new ids? Example in a database for tickets for concerts, should I create a new table for each concert then delete it afterwards?
Keeping all the tickets in one database with unique ID's for each does not sound like a scalable solution.
This is a theory question rather than a practical coding question.
Here is a sample example of how you could do this in a relational pattern:
SCHEMA:
Table: USER
+---------------+-------------------+---------------+
| UserID | First Name | Last Name |
+---------------+-------------------+---------------+
| 1 | John | Doe |
|---------------|-------------------|---------------|
| 2 | Jane | Doe |
+---------------+-------------------+---------------+
Table: CONCERT
+---------------+-------------------+-----------+---------------+
| ConcertID | Concert Name | Artist | Date |
+---------------+-------------------+-----------+---------------+
| 1 | Rock Concert | Rob | Jan-1-2014 |
|---------------|-------------------|-----------|---------------|
| 2 | Rap Concert | Jay | Feb-3-2014 |
+---------------+-------------------+-----------+---------------+
Table: TICKET
+-----------+---------------+---------------+
| UserID | ConcertID | Quantity |
+-----------+---------------+---------------+
| 1 | 1 | 1 |
|-----------|---------------|---------------|
| 1 | 2 | 3 |
|-----------|---------------|---------------|
| 2 | 1 | 2 |
+-----------+---------------+---------------+
Raw SQL to create above schema:
CREATE TABLE USER (
`UserID` int unsigned primary key,
`First Name` varchar(4) not null,
`Last Name` varchar(3) not null
) engine=innodb charset=utf8;
INSERT INTO USER
(`UserID`, `First Name`, `Last Name`)
VALUES
(1, 'John', 'Doe'),
(2, 'Jane', 'Doe')
;
CREATE TABLE CONCERT (
`ConcertID` int unsigned primary key,
`Concert Name` varchar(12) not null,
`Artist` varchar(3) not null,
`Date` datetime not null
) engine=innodb charset=utf8;
INSERT INTO CONCERT
(`ConcertID`, `Concert Name`, `Artist`, `Date`)
VALUES
(1, 'Rock Concert', 'Rob', '2014-01-01 00:00:00'),
(2, 'Rap Concert', 'Jay', '2014-02-03 00:00:00')
;
CREATE TABLE TICKET (
`Ticket No` int unsigned primary key auto_increment,
`UserID` int unsigned,
`ConcertID` int unsigned,
Foreign Key(`UserID`) references `USER`(`UserID`),
Foreign Key(`ConcertID`) references `CONCERT`(`ConcertID`)
) engine=innodb charset=utf8;
INSERT INTO TICKET
(`Ticket No`, `UserID`, `ConcertID`)
VALUES
(1, 1, 1),
(2, 1, 2),
(3, 2, 1)
;
You need to break apart your tables in such a way that you have one-to-one or one-to-many relationships without having a many-to-many relationship. By using a simple three table setup as described above, one user can purchase multiple tickets to multiple concerts. However, because of the itermediary table TICKET, we are never left with a many-to-many relationship. This also allows us to maintain referential integrity by tying the UserID in the TICKET table to the UserID in the USER table. In addtion, the ConcertID in the TICKET table to the ConcertID in the CONCERT table.
Finally, we are able to generate some simple queries to pull back information we need by joining the tables together. I've included two sample queries below:
SAMPLE QUERY #1 - Retrieve all tickets of a particular user by first and last name
SELECT
`conc`.`Concert Name`,
`conc`.`Artist`,
`conc`.`Date`,
`tick`.`Ticket No`
FROM `CONCERT` AS `conc`
INNER JOIN `TICKET` AS `tick` ON `conc`.`ConcertID` = `tick`.`ConcertID`
INNER JOIN `USER` AS `user` ON `tick`.`UserID` = `user`.`UserID`
WHERE `user`.`First Name` = "John" AND `user`.`Last Name` = "Doe";
Result:
+--------------------+--------------+---------------------------------+-----------------+
| Concert Name | Artist | Date | Ticket No |
+--------------------+--------------+---------------------------------+-----------------+
| Rock Concert | Rob | January, 01 2014 00:00:00+0000 | 1 |
|--------------------|--------------|---------------------------------|-----------------|
| Rap Concert | Jay | February, 03 2014 00:00:00+0000 | 2 |
+--------------------+--------------+---------------------------------+-----------------+
SAMPLE QUERY #2 - Retrieve total number of tickets for a given concert by artist name
SELECT
COUNT(`tick`.`Ticket No`)
FROM `TICKET` as `tick`
INNER JOIN `CONCERT` AS `conc` ON `tick`.`ConcertID` = `conc`.`ConcertID`
WHERE `conc`.`Artist` = "Rob";
Result:
+--------------------+
| Ticket Count |
+--------------------+
| 2 |
+--------------------+
As an extra note, because we are using foreign key constraints, we have indexes defined on the particular columns. This helps SQL better manage and scale the queries/data leading to continued performance even with large numbers (millions) of rows.
Hope this helps. From here, you should be able to develop a lot of different queries to pull back all the information you desire. An exact working demo of the above code can be found below:
DEMO: sqlfiddle.com
A compromise solution to this would be to create a new table in the same database for each concert, and to delete tables if there are space concerns. This would allow you to keep old data around without cluttering one table too much.

Database design and join operations among database tables

The theme of this question is to maintain the user comments over my website.
I had around 25000 articles on my website(of different categories) and each article has a comments section below it.Since the number of comments increased over 70,000 I decided to divide the articles into various tables depending on its category articles_of_type_category and a corresponding comments table article_category_comments for each table,assuming that it would improve the performance in future (though currently its working fine)
Now I have two questions :
1) Should I divide the database or there will no degradation in performance if table grows further in size?
2)If yes,then I have some problem in SQL for join operation for the new database design.On the comments page for each article I show the comments,name of the person who made the comment and his points.
So suppose user is viewing the article 3, hence I need to obtain the following detail to show on the page of article 3
-------------------------------------------------------------------------------------------
serial#| comment | name_who_made_this_comment | points | gold | silver | bronze
-------------------------------------------------------------------------------------------
| | | | | |
| | | | | |
by joining these three tables
user_details
+----+--------+----------+
| id | name | college |
+----+--------+----------+
| 1 | naveen | a |
| 2 | rahul | b |
| 3 | dave | c |
| 4 | tom | d |
+----+--------+----------+
score (this table stores the user points like stackoverflow)
+----+--------+------+--------+--------+---------+
| id | points | gold | silver | bronze | user_id |
+----+--------+------+--------+--------+---------+
| 1 | 2354 | 2 | 9 | 25 | 3 |
| 2 | 4562 | 1 | 9 | 11 | 2 |
| 3 | 1123 | 7 | 9 | 11 | 1 |
| 4 | 3457 | 0 | 9 | 4 | 4 |
+----+--------+------+--------+--------+---------+
comments (this table stores comment, id of the article on which it was made,and user id)
+----+----------------------------+-------------+---------+
| id | comment | article_id | user_id |
+----+----------------------------+-------------+---------+
| 1 | This is a nice article | 3 | 1 |
| 2 | This is a tough article | 3 | 4 |
| 3 | This is a good article | 2 | 7 |
| 4 | This is a good article | 1 | 3 |
| 5 | Please update this article | 4 | 4 |
+----+----------------------------+-------------+---------+
I tried something like
select * from comments join (select * from user_details join points where user_details.id=points.user_id)as joined_temp where comments.id=joined_temp.u_id and article_id=3;
This is a response to this comment, "#DanBracuk:It would really be useful if you give an overview by naming the tables and corresponding column names"
Table category
categoryId int not null, autoincrement primary key
category varchar(50)
Sample categories could be "Fairy Tale", "World War I", or "Movie Stars".
Table article
articleId int not null, autoincrement primary key
categoryId int not null foreign key
text clob, or whatever the mysql equivalent is
Since the comment was in response to my comment about articles and categories, this answer is limited to that.
I would start with a table with articles and categories. Then use a bridge table to link the both. My advice would be to index the categories in the bridge table. This would speed up the access.
Example of table structure:
CREATE TABLE Article (
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
title varchar(100) NOT NULL
);
INSERT INTO Article
(title)
VALUES
('kljlkjlkjalk'),
('aiouiwiuiuaoijukj');
CREATE TABLE Category (
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
name varchar(100)
);
INSERT INTO Category
(name)
VALUES
('kljlkjlkjalk'),
('aiouiwiuiuaoijukj');
CREATE TABLE Article_Category (
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
article_id int,
category_id int
);
INSERT INTO Article_Category
(article_id, category_id)
VALUES
(1,1),
(1,2);
CREATE TABLE User_Details
(`id` int, `name` varchar(6), `college` varchar(1))
;
INSERT INTO User_Details
(`id`, `name`, `college`)
VALUES
(1, 'naveen', 'a'),
(2, 'rahul', 'b'),
(3, 'dave', 'c'),
(4, 'tom', 'd')
;
CREATE TABLE Score
(`id` int, `points` int, `gold` int, `silver` int, `bronze` int, `user_id` int)
;
INSERT INTO Score
(`id`, `points`, `gold`, `silver`, `bronze`, `user_id`)
VALUES
(1, 2354, 2, 9, 25, 3),
(2, 4562, 1, 9, 11, 2),
(3, 1123, 7, 9, 11, 1),
(4, 3457, 0, 9, 4, 4)
;
CREATE TABLE Comment
(`id` int, `comment` varchar(26), `article_id` int, `user_id` int)
;
INSERT INTO Comment
(`id`, `comment`, `article_id`, `user_id`)
VALUES
(1, 'This is a nice article', 3, 1),
(2, 'This is a tough article', 3, 4),
(3, 'This is a good article', 2, 7),
(4, 'This is a good article', 1, 3),
(5, 'Please update this article', 4, 4)
;
Try this:
SQLFiddle Demo
Best of luck.
70000 elements are not so many. In fact the number is close to nothing. Your problem lies in bad design. I have a table with many millions of records and when I request to the application server which executes complex queries in the backend and it responds in less than a second. So you are definitely doing something in sub-optimal design. I think that a detailed answer would take too much space and effort (as we have a complete science built on your question) which is out of scope in this website, so I choose to point you to the right direction:
Read about normalization (1NF, 2NF, 3NF, BCNF and so on) and compare it to your design.
Read about indexing and other implicit optimizations
Optimize your queries and minimize the number of queries
As to answer your concrete question: No, you should not "divide" your table. You should fix the structural errors in your database schema and optimize the algorithms using your database.

Automating table normalization

I have a table with this structure (simplified):
artID: 1
artName: TNT
ArtBrand: ACME
...
And I want to normalize it making a separate table for the brand (it will have additional data about every brand)
So I want to end up with this
article table:
artID: 1
artName: TNT
brandID: 1
...
brand table
brandID: 1
brandName: ACME
brandInfo: xyz
....
This table have way too many brands to do this manually.
Any easy way to automate this?
I'm using MySQL
As the other answers suggested, you can use the INSERT ... SELECT syntax to do something like this:
INSERT INTO brands (brandName)
SELECT artBrand
FROM original
GROUP BY artBrand;
INSERT INTO articles (artName, brandID)
SELECT o.artName, b.brandID
FROM original o
JOIN brands b ON (b.brandName = o.artBrand);
Test case:
CREATE TABLE original (artID int, artName varchar(10), artBrand varchar(10));
CREATE TABLE articles (artID int auto_increment primary key, artName varchar(10), brandID int);
CREATE TABLE brands (brandID int auto_increment primary key, brandName varchar(10));
INSERT INTO original VALUES (1, 'TNT1', 'ACME1');
INSERT INTO original VALUES (2, 'TNT2', 'ACME1');
INSERT INTO original VALUES (3, 'TNT3', 'ACME1');
INSERT INTO original VALUES (4, 'TNT4', 'ACME2');
INSERT INTO original VALUES (5, 'TNT5', 'ACME2');
INSERT INTO original VALUES (6, 'TNT6', 'ACME3');
INSERT INTO original VALUES (7, 'TNT7', 'ACME3');
INSERT INTO original VALUES (8, 'TNT8', 'ACME3');
INSERT INTO original VALUES (9, 'TNT9', 'ACME4');
Result:
SELECT * FROM brands;
+---------+-----------+
| brandID | brandName |
+---------+-----------+
| 1 | ACME1 |
| 2 | ACME2 |
| 3 | ACME3 |
| 4 | ACME4 |
+---------+-----------+
4 rows in set (0.00 sec)
ELECT * FROM articles;
+-------+---------+---------+
| artID | artName | brandID |
+-------+---------+---------+
| 1 | TNT1 | 1 |
| 2 | TNT2 | 1 |
| 3 | TNT3 | 1 |
| 4 | TNT4 | 2 |
| 5 | TNT5 | 2 |
| 6 | TNT6 | 3 |
| 7 | TNT7 | 3 |
| 8 | TNT8 | 3 |
| 9 | TNT9 | 4 |
+-------+---------+---------+
9 rows in set (0.00 sec)
I would use create table as select
... syntax to create the brands
table with generated id-s
create the brand_id column, and fill it up with the generated id-s from the brands table, using the existing brand columns in article table.
remove the brand columns from article table except of course brand_id
create the foreign key...
Generating brands table should be fairly simple:
CREATE TABLE brands (
id INT PRIMARY KEY AUTO_INCREMENT,
brand_name VARCHAR(50),
brand_info VARCHAR(200)
);
INSERT INTO brands VALUES (brand_name)
SELECT ArtBrand FROM Table
GROUP BY ArtBrand;
Similar story with creating relations between your original table and new brands table, just that select statement in your insert will look like this:
SELECT t.artId, b.id
FROM table t JOIN brands b ON (t.ArtBrand = b.brand_name)