Join MySQL tables on bad table - mysql

I have inherited what I suspect is some poor SQL table design and, as an SQL newbie, I'm struggling to come up with a query to join and concatenate the data.
Here are the two tables containing the data:
user_space:
+----+---------+---------+---------+----------+----------+----------+
| id | space_1 | space_2 | space_3 | units_s1 | units_s2 | units_s3 |
+----+---------+---------+---------+----------+----------+----------+
| 1 | 128 | 128 | 6 | 3 | 3 | 4 |
| 2 | 1 | 128 | 2 | 4 | 3 | 4 |
| 3 | 100 | 100 | 100 | 3 | 3 | 3 |
+----+---------+---------+---------+----------+----------+----------+
space_units:
+------+--------------+
| type | description |
+------+--------------+
| 1 | KB |
| 2 | MB |
| 3 | GB |
| 4 | TB |
+------+--------------+
Here is what I'm hoping to be able to produce:
+----+---------+---------+---------+
| id | total_1 | total_2 | total_3 |
+----+---------+---------+---------+
| 1 | 128GB | 128GB | 6TB |
| 2 | 1TB | 12GB8 | 2TB |
| 3 | 100GB | 100GB | 100GB |
+----+---------+---------+---------+
The last 3 'units_s*' columns in the user_space table relate to the 'type' column in the space_units table.
Could anyone please help me with a suitable query? I've been at this for ages now and can't figure it out. Unfortunately, I'm not allowed to drop the tables and implement them properly.

select
us.id,
concat(trim(us.space_1), trim(su1.description)) as total_1,
concat(trim(us.space_2), trim(su2.description)) as total_2,
concat(trim(us.space_3), trim(su3.description)) as total_3
from user_space us
inner join space_units su1 on (us.units_s1 = su1.type)
inner join space_units su2 on (us.units_s2 = su2.type)
inner join space_units su3 on (us.units_s3 = su3.type)
order by us.id
+----+---------+---------+---------+
| id | total_1 | total_2 | total_3 |
+----+---------+---------+---------+
| 1 | 128GB | 128GB | 6TB |
| 2 | 1TB | 128GB | 2TB |
| 3 | 100GB | 100GB | 100GB |
+----+---------+---------+---------+
EDIT: Used Format text as a table to format the output
create table user_space
(
id integer,
space_1 char(5),
space_2 char(5),
space_3 char(5),
units_s1 integer,
units_s2 integer,
units_s3 integer
);
insert into user_space values (1, 128,128,6, 3, 3, 4);
insert into user_space values (2, 1,128,2, 4, 3, 4);
insert into user_space values (3, 100,100,100, 3, 3, 3);
create table space_units
(
type integer,
description char(5)
);
insert into space_units values (1, 'KB');
insert into space_units values (2, 'MB');
insert into space_units values (3, 'GB');
insert into space_units values (4, 'TB');
EDIT: SQL Fiddle for this problem here

Related

How to create MySQL histogram type buckets based on column value

I have the following table and I am trying to create histogram style buckets through MySQL.
| Id | Values |
| -------- | --------- |
| 1 | 5 |
| 2 | 7 |
| 3 | 9 |
| 4 | 11 |
| 5 | 15 |
| 6 | 31 |
| 7 | 32 |
| 8 | 43 |
What I am trying to achieve is as following:
| bucket | count |
| -------- | --------- |
| 0-9 | 3 |
| 10-19 | 2 |
| 20-29 | 0 |
| 30-39 | 2 |
| 40-49 | 1 |
Does anyone know how we can get this in a clean way?
One possible way is to create a reference table for the bucket list then LEFT JOIN it with your table. Try the following steps.
Create a table bucket_list for example:
CREATE TABLE bucket_list (
id INT NOT NULL AUTO_INCREMENT,
startno INT,
endno INT,
PRIMARY KEY(id));
Insert values into bucket_list:
INSERT INTO bucket_list (startno, endno)
VALUES
(0, 9),
(10, 19),
(20, 29),
(30, 39),
(40, 49),
(50, 59),
(60, 69),
(70, 79);
Create a query to return expected result:
SELECT CONCAT(a.startno,'-',a.endno) AS bucket,
SUM(CASE WHEN b.val IS NULL THEN 0 ELSE 1 END) AS COUNT
FROM bucket_list a
LEFT JOIN mytable b ON b.val BETWEEN a.startno AND a.endno
GROUP BY bucket;
Here's a fiddle: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=7fee426efa2b1f1e39377bb7beb68b62

Mysql - joining labels and values table and including not referenced labels as null

The following problem has been bugging me for some time now. I have two tables in my database.
First I have a table holding labels, referenced values.
| option_id | option_name |
|-----------|-------------|
| 1 | Blue |
| 2 | Red |
| 3 | Black |
Then I have the second table with the actual values
| record_id | option_id | first_name | profession |
|-----------|-----------|------------|------------|
| 1 | 2 | James | Clerk |
| 2 | 2 | Ethan | Clerk |
| 3 | 1 | Marian | Nurse |
| 4 | 3 | Bob | Nurse |
| 5 | 3 | Paul | Nurse |
How can I join these two tables in MySQL so I get all the options listed for each of the professions even when there is no reference value so it will show up as NULL?
So the table would look something like:
| profession | option_name | first_name |
|------------|-------------|------------|
| Clerk | Blue | NULL |
| Clerk | Red | James |
| Clerk | Red | Ethan |
| Clerk | Black | NULL |
| Nurse | Blue | Marian |
| Nurse | Red | NULL |
| Nurse | Black | Bob |
| Nurse | Black | Paul |
Any help would be appreciated. Here is the sample database
CREATE TABLE options (
option_id INT,
option_name TEXT
);
INSERT INTO options VALUES (1, 'Blue');
INSERT INTO options VALUES (2, 'Red');
INSERT INTO options VALUES (3, 'Black');
CREATE TABLE records (
record_id INT,
option_id INT,
first_name TEXT,
profession TEXT
);
INSERT INTO records VALUES (1, 2, 'James','Clerk');
INSERT INTO records VALUES (2, 2, 'Ethen','Clerk');
INSERT INTO records VALUES (3, 1, 'Marian','Nurse');
INSERT INTO records VALUES (4, 3, 'Bob', 'Nurse');
INSERT INTO records VALUES (5, 3, 'Paul', 'Nurse');
You can enumerate the professions from the records table, cross join table options to generate all possible combinations, then bring the records table with a left join:
select p.profession, o.option_name, r.first_name
from (select distinct profession from records) p
cross join options o
left join records r
on r.option_id = o.option_id
and r.profession = p.profession
order by p.profession, o.option_name

How to specify which rows should be kept by GROUP BY and which should be collapsed

The application I am working on contains the part where users can create albums and upload images to it. The application creates several resizes of the image so that the large version is not served to the user. All the information about files is stored in the database in the following structure
Album table has an id and name
Photo table has a list of photos and each of them knows what album it belongs to
There also is a photo_versions. Each photo_versions stores the ID of an object from Photo table.
This logic is represented by the following schema:
CREATE TABLE albums(`id` int, `name` varchar(255));
INSERT INTO albums (id, name) VALUES
(1, "one"),
(2, "two"),
(3, "three");
CREATE TABLE photos(`id` int, `albums_id` int, `title` varchar(255));
INSERT INTO photos (id, albums_id, title) VALUES
(1, 1, "a"),
(2, 1, "b"),
(3, 1, "c");
CREATE TABLE photos_versions(`id` int, `photos_id` int, `width` int, `height` int);
INSERT INTO photos_versions (photos_id, width, height) VALUES
(1, 1000, 800),(1, 800, 600), (1, 600, 400),
(2, 1000, 800), (2, 800, 600), (2, 600, 400),
(3, 1000, 800), (3, 800, 600), (3, 600, 400);
User interface has the ability to request a specific height and the back end which I am working on should return the closest existing in database. I am working on request that should do it. It starts with joining all these tables:
SELECT *
FROM albums a
INNER JOIN photos p ON p.albums_id = a.id
INNER JOIN photos_versions pv ON pv.photos_id = p.id;
That results in the following table:
+------+------+----+-----------+-------+------+-----------+-------+--------+
| id | name | id | albums_id | title | id | photos_id | width | height |
+------+------+----+-----------+-------+------+-----------+-------+--------+
| 1 | one | 1 | 1 | a | NULL | 1 | 1000 | 800 |
| 1 | one | 1 | 1 | a | NULL | 1 | 800 | 600 |
| 1 | one | 1 | 1 | a | NULL | 1 | 600 | 400 |
| 1 | one | 2 | 1 | b | NULL | 2 | 1000 | 800 |
| 1 | one | 2 | 1 | b | NULL | 2 | 800 | 600 |
| 1 | one | 2 | 1 | b | NULL | 2 | 600 | 400 |
| 1 | one | 3 | 1 | c | NULL | 3 | 1000 | 800 |
| 1 | one | 3 | 1 | c | NULL | 3 | 800 | 600 |
| 1 | one | 3 | 1 | c | NULL | 3 | 600 | 400 |
+------+------+----+-----------+-------+------+-----------+-------+--------+
9 rows in set (0.00 sec)
Now, we need to group by photos_id (because we want to end up with the closest version of given photo). So, the request turns into that:
SELECT *
FROM albums a
INNER JOIN photos p ON p.albums_id = a.id
INNER JOIN photos_versions pv ON pv.photos_id = p.id
GROUP BY photos_id;
Which results in the following table:
+------+------+----+-----------+-------+------+-----------+-------+--------+
| id | name | id | albums_id | title | id | photos_id | width | height |
+------+------+----+-----------+-------+------+-----------+-------+--------+
| 1 | one | 1 | 1 | a | NULL | 1 | 1000 | 800 |
| 1 | one | 2 | 1 | b | NULL | 2 | 1000 | 800 |
| 1 | one | 3 | 1 | c | NULL | 3 | 1000 | 800 |
+------+------+------+-----------+-------+------+-----------+-------+--------+
3 rows in set (0.00 sec)
However, it does not necessarily keeps the row with the property (having the height closest to the one I specified). How do I GROUP BY the photos_id and choose the one with closest height?
P.S. SQL Fiddle is attached - http://sqlfiddle.com/#!9/84f4f/1
One approach here is to add an additional join to your query which will restrict to photos having the closest height for each photo id group.
SELECT a.*, p.*, pv1.*
FROM albums a
INNER JOIN photos p
ON p.albums_id = a.id
INNER JOIN photos_versions pv1
ON pv1.photos_id = p.id
INNER JOIN
(
SELECT photos_id, MIN(ABS(height - SOME_HEIGHT)) AS diff
FROM photos_versions
GROUP BY photos_id
) pv2
ON pv1.photos_id = pv2.photos_id AND
MIN(ABS(pv1.height - SOME_HEIGHT)) = pv2.diff
You can replace SOME_HEIGHT with whatever value you obtain from the search by height.

How to sum the values of column on duplicate key?

I have a table like this:
// mytable
+----+--------+-------+-------+
| id | name | key | value |
+----+--------+-------+-------+
| 1 | jack | 1 | 10 |
| 2 | peter | 1 | 5 |
| 3 | jack | 2 | 5 |
| 4 | ali | 1 | 2 |
| 5 | jack | 1 | 5 |
| 6 | jack | 1 | 10 |
| 7 | bert | 4 | 2 |
| 8 | peter | 2 | 10 |
| 9 | bert | 4 | 5 |
+----+--------+-------+-------+
Now I want to sum the numbers of value where both name and key are identical. So, I want this output:
// mynewtable
+----+--------+-------+-------+
| id | name | key | value |
+----+--------+-------+-------+
| 1 | jack | 1 | 25 |
| 2 | peter | 1 | 5 |
| 3 | jack | 2 | 5 |
| 4 | ali | 1 | 2 |
| 7 | bert | 4 | 7 |
| 8 | peter | 2 | 10 |
+----+--------+-------+-------+
Is it possible to I do that?
Edit: How can I do that for insert?
// mytable
+----+--------+-------+-------+
| id | name | key | value |
+----+--------+-------+-------+
| 1 | jack | 1 | 25 |
| 2 | peter | 1 | 5 |
| 3 | jack | 2 | 5 |
| 4 | ali | 1 | 2 |
| 7 | bert | 4 | 7 |
| 8 | peter | 2 | 10 |
+----+--------+-------+-------+
Inserting these rows:
+----+--------+-------+-------+
| 10 | jack | 1 | 5 |
+----+--------+-------+-------+
+----+--------+-------+-------+
| 11 | bert | 1 | 2 |
+----+--------+-------+-------+
What I want: (output)
// mynewtable
+----+--------+-------+-------+
| id | name | key | value |
+----+--------+-------+-------+
| 1 | jack | 1 | 30 |
| 2 | peter | 1 | 5 |
| 3 | jack | 2 | 5 |
| 4 | ali | 1 | 2 |
| 7 | bert | 4 | 7 |
| 8 | peter | 2 | 10 |
| 11 | bert | 1 | 2 |
+----+--------+-------+-------+
You have to group by more columns.
select name, key, sum(value) from mytable group by name, key;
Group by name, key
select name, key, sum(value) as value
from mytable group by name,key
check this
CREATE TABLE #testing_123
([id] int, [name] varchar(5), [key] int, [value] int)
;
INSERT INTO #testing_123
([id], [name], [key], [value])
VALUES
(1, 'jack', 1, 10),
(2, 'peter', 1, 5),
(3, 'jack', 2, 5),
(4, 'ali', 1, 2),
(5, 'jack', 1, 5),
(6, 'jack', 1, 10),
(7, 'bert', 4, 2),
(8, 'peter', 2, 10),
(9, 'bert', 4, 5)
;
query used was
select min(id) id ,name,[key],sum(value) value from #testing_123 group by name,[key] order by 1
output after insert
For the first part (to get the id column in the way requested), you could work along:
INSERT INTO mynewtable
(id, name, `key`, `value`)
SELECT
MIN(id), name, `key`, SUM(`value`)
FROM mytable
GROUP BY name, `key`
;
Now, provided mynewtable is defined with a unique index on name and key like
CREATE TABLE mynewtable
(id INT, name VARCHAR(5), `key` INT, `value` INT, UNIQUE (name, `key`));
you'd get the requested result with
INSERT INTO mynewtable
(id, name, `key`, `value`)
VALUES
(10, 'jack', 1, 5),
(11, 'bert', 1, 2)
ON DUPLICATE KEY UPDATE `value` = `value` + VALUES(`value`)
;
Beware:
It requires the unique index on name and key to work.
It might not work correctly, if there are other unique indexes and/or a primary key on the same table as well.
NB:
Please try to avoid the use of reserved words such as value and key for, e.g., column names.

How to query multiple tables using a single query?

I want my tables to output something like this
---------------------------------------------------------------------------------------------
| date | location | time | delegate 1 | delegate 2 |
|--------------------------------------------------------------------------------------------
| 2015-12-07 | Table 1 | 9:00 | first_name_4 last_name_4 | first_name_5 last_name_5 |
|--------------------------------------------------------------------------------------------
| | 9:30 | first_name_4 last_name_4 | first_name_6 last_name_6 |
|--------------------------------------------------------------------------------------------
| | 9:30 | first_name_3 last_name_3 | first_name_7 last_name_7 |
|--------------------------------------------------------------------------------------------
| | 9:00 | first_name_3 last_name_3 | first_name_7 last_name_7 |
|--------------------------------------------------------------------------------------------
Here are the tables on my db
meetings table
-------------------------------------------------------------------------------------------------
| id | date_id | time_id | location_id | delegate_id_1 | delegate_id_2 | status |
|------------------------------------------------------------------------------------------------
| 1 | 1 | 1 | 1 | 4 | 5 | A |
|------------------------------------------------------------------------------------------------
| 2 | 1 | 2 | 1 | 4 | 6 | A |
|------------------------------------------------------------------------------------------------
| 3 | 1 | 1 | 1 | 2 | 6 | P |
|------------------------------------------------------------------------------------------------
| 4 | 1 | 2 | 1 | 1 | 3 | A |
|------------------------------------------------------------------------------------------------
| 5 | 1 | 1 | 1 | 1 | 3 | A |
|------------------------------------------------------------------------------------------------
users table
-----------------------------------------
| id | first_name | last_name |
|----------------------------------------
| 1 | first_name_1 | last_name_1 |
|----------------------------------------
| 2 | first_name_2 | last_name_2 |
|----------------------------------------
| 3 | first_name_3 | last_name_3 |
|----------------------------------------
| 4 | first_name_4 | last_name_4 |
|----------------------------------------
| 5 | first_name_5 | last_name_5 |
|----------------------------------------
| 6 | first_name_6 | last_name_6 |
|----------------------------------------
locations table
-----------------------------
| id | location_name |
|----------------------------
| 1 | Table 1 |
|----------------------------
time table
-------------------------
| id | meeting_time |
|------------------------
| 1 | 9:00:00 |
|------------------------
| 1 | 9:30:00 |
|------------------------
dates table
-------------------------
| id | meeting_date |
|------------------------
| 1 | 2015-12-07 |
|------------------------
| 2 | 2015-12-08 |
|------------------------
| 3 | 2015-12-09 |
|------------------------
My initial query goes like this
-- $query_date
SELECT meeting_date
FROM dates
WHERE meeting_date = '2015-12-07'
-- $query_location
SELECT location_name.location
from location
LEFT JOIN meetings
ON meetings.location_id=location.id
LEFT JOIN date
ON meetings.date_id=date.id
WHERE meeting_date.dates = '2015-12-07'
Now, here's the part where I got it wrong.
-- $query_final
SELECT meeting_time.time, delegate1.first_name AS first_name_1,
delegate1.last_name AS last_name_1, delegate2.first_name AS first_name_2,
delegate2.last_name AS last_name_2
FROM meetings
INNER JOIN users delegate1
ON meetings.delegate_id_1=users.id
LEFT JOIN users delegate2
ON meetings.delegate_id_2=users.id
WHERE meetings.status='A'
The results on my last query give me unexpected results since the results show more entries than my meetings table.
I know the queries I made are costly but I don't know how to make a more optimized query. I don't even know if it's possible to get the results into a single query only. Any help well do. Thanks.
You can bring back everything with a single query with the right JOIN.
Be Careful, when you use column name on SQL, the syntax is TABLE.COLUMN_NAME, it seem you mistake on the order quit often...
I changed some table name as you sometime use an s at the end and sometime no.
As time and date are SQL keyword, it's better with s everywhere
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE meetings (`id` int, `date_id` int, `time_id` int, `location_id` int, `delegate_id_1` int, `delegate_id_2` int, `status` varchar(1));
INSERT INTO meetings (`id`, `date_id`, `time_id`, `location_id`, `delegate_id_1`, `delegate_id_2`, `status`)
VALUES (1, 1, 1, 1, 4, 5, 'A'),
(2, 1, 2, 1, 4, 6, 'A'),
(3, 1, 1, 1, 2, 6, 'P'),
(4, 1, 2, 1, 1, 3, 'A'),
(5, 1, 1, 1, 1, 3, 'A');
CREATE TABLE users (`id` int, `first_name` varchar(12), `last_name` varchar(11));
INSERT INTO users (`id`, `first_name`, `last_name`)
VALUES (1, 'first_name_1', 'last_name_1'),
(2, 'first_name_2', 'last_name_2'),
(3, 'first_name_3', 'last_name_3'),
(4, 'first_name_4', 'last_name_4'),
(5, 'first_name_5', 'last_name_5'),
(6, 'first_name_6', 'last_name_6');
CREATE TABLE locations (`id` int, `location_name` varchar(7));
INSERT INTO locations (`id`, `location_name`)
VALUES (1, 'Table 1');
CREATE TABLE times (`id` int, `meeting_time` varchar(7));
INSERT INTO times (`id`, `meeting_time`)
VALUES (1, '9:00:00'),
(2, '9:30:00') ;
CREATE TABLE dates (`id` int, `meeting_date` varchar(10)) ;
INSERT INTO dates (`id`, `meeting_date`)
VALUES (1, '2015-12-07'),
(2, '2015-12-08'),
(3, '2015-12-09') ;
Query 1:
-- $query_final
SELECT locations.location_name,
`times`.meeting_time,
delegate1.first_name AS first_name_1,
delegate1.last_name AS last_name_1,
delegate2.first_name AS first_name_2,
delegate2.last_name AS last_name_2
FROM meetings
LEFT JOIN locations
ON meetings.location_id=locations.id
LEFT JOIN dates
ON meetings.date_id=`dates`.id
LEFT JOIN times
ON meetings.time_id=`times`.id
INNER JOIN users delegate1
ON meetings.delegate_id_1 = delegate1.id
LEFT JOIN users delegate2
ON meetings.delegate_id_2 = delegate2.id
WHERE
meetings.status = 'A'
AND dates.meeting_date = '2015-12-07'
Results:
| location_name | meeting_time | first_name | last_name | first_name | last_name |
|---------------|--------------|--------------|-------------|--------------|-------------|
| Table 1 | 9:00:00 | first_name_1 | last_name_1 | first_name_3 | last_name_3 |
| Table 1 | 9:30:00 | first_name_1 | last_name_1 | first_name_3 | last_name_3 |
| Table 1 | 9:00:00 | first_name_4 | last_name_4 | first_name_5 | last_name_5 |
| Table 1 | 9:30:00 | first_name_4 | last_name_4 | first_name_6 | last_name_6 |