SQL JOIN : Prefix fields with table name - mysql

I have the following tables
CREATE TABLE `constraints` (
`id` int(11),
`name` varchar(64),
`type` varchar(64)
);
CREATE TABLE `groups` (
`id` int(11),
`name` varchar(64)
);
CREATE TABLE `constraints_to_group` (
`groupid` int(11),
`constraintid` int(11)
);
With the following data :
INSERT INTO `groups` (`id`, `name`) VALUES
(1, 'group1'),
(2, 'group2');
INSERT INTO `constraints` (`id`, `name`, `type`) VALUES
(1, 'cons1', 'eq'),
(2, 'cons2', 'inf');
INSERT INTO `constraints_to_group` (`groupid`, `constraintid`) VALUES
(1, 1),
(1, 2),
(2, 2);
I want to get all constraints for all groups, so I do the following :
SELECT groups.*, t.* FROM groups
LEFT JOIN
(SELECT * FROM constraints
LEFT JOIN constraints_to_group
ON constraints.id=constraints_to_group.constraintid) as t
ON t.groupid=groups.id
And get the following result :
id| name | id | name type groupid constraintid
-----------------------------------------------------
1 | group1 | 1 | cons1 | eq | 1 | 1
1 | group1 | 2 | cons2 | inf | 1 | 2
2 | group2 | 2 | cons2 | inf | 2 | 2
What I'd like to get :
group_id | group_name | cons_id | cons_name | cons_type | groupid | constraintid
-------------------------------------------------------------------------------------
1 | group1 | 1 | cons1 | eq | 1 | 1
1 | group1 | 2 | cons2 | inf | 1 | 2
2 | group2 | 2 | cons2 | inf | 2 | 2
This is an example, in my real case my tables have much more columns so using the SELECT groups.name as group_name, ... would lead to queries very hard to maintains.

Try this way
SELECT groups.id as group_id, groups.name as group_name ,
t.id as cons_id, t.name as cons_name, t.type as cons_type,
a.groupid , a.constraintid
FROM constraints_to_group as a
JOIN groups on groups.id=a.groupid
JOIN constraints as t on t.id=a.constraintid

The only difference I see are the names of the columns? Use for that mather an AS-statement.
SELECT
groups.id AS group_id,
groups.name AS group_name,
t.id AS cons_id,
t.name AS cons_name,
t.groupid, t.constraintid
FROM groups
LEFT JOIN
(SELECT * FROM constraints
LEFT JOIN constraints_to_group
ON constraints.id=constraints_to_group.constraintid) as t
ON t.groupid=groups.id
Besides, a better join-construction is:
SELECT G.id AS group_id,
G.name AS group_name,
CG.id AS cons_id,
CG.name AS cons_name,
C.groupid, C.constraintid
FROM constraints_to_group CG
LEFT JOIN constraints C
ON CG.constraintid = C.id
LEFT JOIN groups G
ON CG.groupid = G.id;

Possible duplicate of this issue

Related

Sql join with sum up duplicates

1)The sample data set looks like this:
create table user(
user_id int,
name varchar(10),
surname varchar(10)
);
insert into user(user_id, name, surname) values
(1, 'a', 'aa'),
(2, 'b', 'bb'),
(3, 'c', 'cc');
create table books(
user_id int,
book_name varchar(10)
);
insert into books(user_id, book_name) values
(1, 'book1'),
(1, 'book2'),
(1, 'book3'),
(2, 'book1');
create table expanses(
id int,
user_id int,
amount_spent int,
date timestamp
);
insert into expanses(id, user_id, amount_spent, date)
values
(1,1,10, '2020-02-03'),
(2,1,10, '2020-02-03'),
(3,1,30, '2020-02-02'),
(4,1,12, '2020-02-01'),
(5,1,15, '2020-01-31'),
(6,1,13, '2020-01-15'),
(7,2,15, '2020-02-01'),
(8,3,20, '2020-02-01');
2)The result which I want:
| CountUsers | amount_spent |
|---------|--------------|
| 2 | 77 |
Explanation: I want to count
a) how many users have book1 or book2 and
b) how much total they spend on a date between 2020-02-01 - 2020-02-03.
Now how the query should look like?
I am using MySQL version 8.
I have tried:
SELECT count(*)
, sum(amount_spend) as total_amount_spend
FROM
( select sum(amount_spend) as amount_spend
FROM expanses e
LEFT
JOIN books b
ON b.user_id = e.user_id
WHERE (b.book_name ='book1' or b.book_name ='book2')
and e.date between '2020-02-01' and '2020-02-03'
GROUP
BY e.user_id) src'
And the result is wrong because the select clause from the inside(a little bit modified to show you more clearly):
select amount_spend as amount_spend
FROM expanses
LEFT JOIN books ON books.user_id = expanses.user_id WHERE (books.book_name ='book1' or books.book_name ='book2') and expanses.date between '2020-02-01' and '2020-02-03'
3)Will return something like this:
| user_id | amount_spent | book_name |
|---------|--------------|-----------|
| 1 | 10 | book1 |
| 1 | 30 | book1 |
| 1 | 30 | book1 |
| 1 | 12 | book1 |
| 1 | 10 | book2 |
| 1 | 10 | book2 |
| 1 | 30 | book2 |
| 1 | 12 | book2 |
| 2 | 15 | book1 |
4)So if sum this up, we will get
| CountUsers | amount_spent |
|---------|--------------|
| 2 | 139 |
5)Which is wrong, because there are duplicates.
If we add DISTINCT to sum(DISTINCT amount_spend)
it will be also wrong because it will give the following result
| CountUsers | amount_spent |
|---------|--------------|
| 2 | 67 |
To summarize, you can see in table 3) there are some duplicates of amount_spent cause the book_name.(one to many relationships)
How to avoid duplicating amount_spent but stay with book_name?
Fiddle
select count(distinct user_id)
, sum(amount_spent)
from expanses
where expanses.date between '2020-02-01' and '2020-02-03'
and user_id in (select user_id from books where book_name in('book1','book2'))
https://www.db-fiddle.com/f/26ifPWyRRKGp9YVQXg1qje/0
Here's an idea for a)
SELECT COUNT(DISTINCT user_id) total FROM books WHERE book_name IN ('book1','book2');
Here's an idea for b)
SELECT SUM(amount_spent) total_spent
FROM expanses e
WHERE e.date BETWEEN '2020-02-01' AND '2020-02-03'
AND EXISTS
( SELECT *
FROM books b
WHERE b.user_id = e.user_id
AND b.book_name IN ('book1','book2')
);
Here's and idea for combining a) and b)
SELECT SUM(amount_spent) total_spent
, (SELECT COUNT(DISTINCT user_id) total FROM books WHERE book_name IN ('book1','book2')) total_customers
FROM expanses e
WHERE e.date BETWEEN '2020-02-01' AND '2020-02-03'
AND EXISTS
( SELECT *
FROM books b
WHERE b.user_id = e.user_id
AND b.book_name IN ('book1','book2')
);

phpmyadmin update m to n relationship table to remove duplicates from separate table

I have 2 tables.
CREATE TABLE designs
( game_id INT NOT NULL,
des_id INT NOT NULL,
PRIMARY KEY(game_id, des_id),
FOREIGN KEY(game_id) REFERENCES Game(id),
ON UPDATE CASCADE)
CREATE TABLE designer
( name VARCHAR(30) NOT NULL,
id INT NOT NULL,
PRIMARY KEY(id),
FOREIGN KEY(id) REFERENCES designs(des_id),
ON UPDATE CASCADE);
Lets say I have data:
designs:
0---0
0---1
1---2
2---3
2---4
.............................
designer:
Bob---0
Jill---1
Bob---2
Rob---3
Jill---4
After the update, I would like the "designs" table to look like:
0---0
0---1
1---0
2---3
2---1
What update query would I need to accomplish this?
Some queries I tried are:
UPDATE designs
SET des_id = (
SELECT a.id
FROM designer as a
JOIN designer as b
ON a.name=b.name AND a.id < b.id
WHERE des_id = b.id);
...
UPDATE `designs` as a
JOIN designer as b
ON a.des_id=b.id
SET a.des_id = b.id
WHERE b.id = (
SELECT c.id
FROM designer as c
LEFT JOIN designer as d
ON c.name=d.name
WHERE c.id<d.id)
Here's one idea. Note that it uses an documented hack in the form of a 'group by/order by' trick:
UPDATE designs d
JOIN
( select d1.id matcher_id
, d2.id select_id
from `designer` d1
JOIN designer d2
ON d1.name = d2.name
group
by d1.id
Order
by d2.id
) x
ON x.matcher_id = d.des_id
SET d.des_id = select_id
Your LEFT JOIN idea is almost right, but here's another idea which is faster...
DROP TABLE IF EXISTS designs;
CREATE TABLE designs
( game_id INT NOT NULL
, designer_id INT NOT NULL
, PRIMARY KEY(game_id, designer_id)
);
DROP TABLE IF EXISTS designers;
CREATE TABLE designers
( name VARCHAR(30) NOT NULL
, designer_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
);
INSERT INTO designs VALUES
(1,1),
(1,2),
(2,3),
(3,4),
(3,5);
INSERT INTO designers VALUES
('Bob',1),
('Jill',2),
('Bob',3),
('Rob',4),
('Jill',5);
SELECT * FROM designs;
+---------+-------------+
| game_id | designer_id |
+---------+-------------+
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 3 | 4 |
| 3 | 5 |
+---------+-------------+
SELECT * FROM designers;
+------+-------------+
| name | designer_id |
+------+-------------+
| Bob | 1 |
| Jill | 2 |
| Bob | 3 |
| Rob | 4 |
| Jill | 5 |
+------+-------------+
UPDATE designs g
JOIN designers d
ON d.designer_id = g.designer_id
JOIN designers x ON x.name = d.name
JOIN
( SELECT name
, MIN(designer_id) min_designer_id
FROM designers
GROUP
BY name
) y
ON y.name = x.name
AND y.min_designer_id = x.designer_id
SET g.designer_id = x.designer_id;
SELECT * FROM designs;
+---------+-------------+
| game_id | designer_id |
+---------+-------------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 3 | 2 |
| 3 | 4 |
+---------+-------------+
Actually, in the special case of an UPDATE, I think this will work just as well, and I'm not really sure that it's any less performative...
UPDATE designs g
JOIN designers x
ON x.designer_id = g.designer_id
JOIN designers y
ON y.name = x.name
AND y.designer_id < x.designer_id
SET g.designer_id = y.designer_id;

can not get correct results with group by in mysql

I have 2 SQL tables
CREATE TABLE A(
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
name CHAR(1) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE B(
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
A_id INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO A VALUES (1, 'A'), (2, 'B'), (3, 'C'), (4, 'A');
INSERT INTO B VALUES (1, 1), (2, 2), (3, 4), (4, 4);
The tables look this way:
select * from A;
+----+------+
| id | name |
+----+------+
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | A |
+----+------+
select * from B;
+----+------+
| id | A_id |
+----+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 4 |
| 4 | 4 |
+----+------+
Now I want to find out how many each of the elements from table A are there in table B. Using other words I want to see:
A = 3
B = 1
C = 0
I tried to do this with: SELECT name, count(*) FROM A, B WHERE A.id = A_id GROUP BY A.id;, but it returns something completely weird. Can someone help me?
Query
SELECT a.name,COUNT(b.A_id) as `count`
FROM A a
LEFT JOIN B b
ON a.id=b.A_id
GROUP BY a.name;
Fiddle Demo
You just need a left outer join to handle the condition where there are no A's in B:
SELECT A.Name, COUNT(b.id)
FROM A
LEFT OUTER JOIN B on A.id = B.a_id
GROUP BY A.Name;
SqlFiddle here
You should use a LEFT JOIN, and not GROUP BY A.id, but instead by name:
SELECT A.name, COUNT(B.A_id)
FROM A
LEFT JOIN B ON A.id = B.A_id
GROUP BY A.name;

nested select to fetch date in SQL

There is 3 table.
items_table:
iid
ititle
itext
tag_list_table
tid
tname;
tag_ref_table
iid
tid //foreign key for tag_list_table
per record in tag_list_table have one or more tags.
for example:
tag_list_table:
iid=1,itltle="this is title";itext="this is full text";
iid=2,itltle="this is title2";itext="this is full text2";
tag_list_table
tid=1 tname="red"
tid=2 tname="green"
tid=3 tname="yellow"
tid=4 tname="orange"
tag_ref_table
iid=1 tid=1
iid=1 tid=2
iid=1 tid=3
iid=2 tid=4
I want to have result like this:
row1:
1-
this is title
this is full text
red,green,yellow
row2:
2-
this is title2
this is full text2
orange
I tried these:
SELECT i.ititle,i.itext,t.tname
FROM tag_ref_table as i
LEFT JOIN tag_ref_table t WHERE tid=iid
LEFT JOIN tag_list_table r WHERE tid=??????....
If the "red,green,yellow" values are concatenated into a single string, in MySQL you could use GROUP_CONCAT function like this:
SELECT i.*, group_concat(l.tname SEPARATOR ',') as names
FROM items_table as i
JOIN tag_ref_table r ON r.iid=i.iid
JOIN tag_list_table l ON r.tid= l.tid
GROUP BY i.iid
Documentation: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_group-concat
Further examples: http://www.giombetti.com/2013/06/06/mysql-group_concat/
You are looking for GROUP_CONCAT
SELECT it.ititle, it.itext, GROUP_CONCAT(tlt.tname)
FROM tag_ref_table as trt
INNER JOIN items_table it ON trt.iid = it.iid
INNER JOIN tag_list_table tlt ON trt.tid = tlt.tid
GROUP BY it.ititle, it.itext;
SqlFiddle here
Unless your tag_ref_table Junction table allows for NULLS (which would be unusual in such a table), I would recommend switching to an INNER JOIN.
You can use CONCAT() and GROUP_CONCAT() in your select:
SELECT CONCAT(t1.iid,'- ',t1.ititle,' ',t1.itext,' ',t1.tname) FROM
(SELECT i.iid,i.ititle,i.itext,GROUP_CONCAT(r.tname) tname
FROM items_table i
INNER JOIN tag_ref_table t ON i.iid = t.iid
INNER JOIN tag_list_table r ON t.tid = r.tid
GROUP BY i.ititle,i.itext,i.iid)t1;
you can see complete answer in:
SQL Fiddle
DROP TABLE items;
CREATE TABLE items
(item_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,item_title VARCHAR(20)
,item_text VARCHAR(20) NOT NULL
);
DROP TABLE tags;
CREATE TABLE tags
(tag_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,tag_name VARCHAR(12) NOT NULL
);
DROP TABLE items_tags;
CREATE TABLE items_tags
(item_id INT NOT NULL
,tag_id INT NOT NULL
,PRIMARY KEY(item_id,tag_id)
);
INSERT INTO items VALUES
(1,"this is title1","this is full text1"),
(2,"this is title2","this is full text2");
INSERT INTO tags VALUES
(1,"red"),
(2,"green"),
(3,"yellow"),
(4,"orange");
INSERT INTO items_tags VALUES
(1,1),
(1,2),
(1,3),
(2,4);
mysql> SELECT * FROM items;
+---------+----------------+--------------------+
| item_id | item_title | item_text |
+---------+----------------+--------------------+
| 1 | this is title1 | this is full text1 |
| 2 | this is title2 | this is full text2 |
+---------+----------------+--------------------+
2 rows in set (0.00 sec)
SELECT * FROM tags;
+--------+----------+
| tag_id | tag_name |
+--------+----------+
| 1 | red |
| 2 | green |
| 3 | yellow |
| 4 | orange |
+--------+----------+
SELECT * FROM items_tags;
+---------+--------+
| item_id | tag_id |
+---------+--------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 4 |
+---------+--------+
SELECT i.*
, GROUP_CONCAT(t.tag_name ORDER BY t.tag_id) tags
FROM items i
JOIN items_tags it
ON it.item_id = i.item_id
JOIN tags t
ON t.tag_id = it.tag_id
GROUP
BY i.item_id;
+---------+----------------+--------------------+------------------+
| item_id | item_title | item_text | tags |
+---------+----------------+--------------------+------------------+
| 1 | this is title1 | this is full text1 | red,green,yellow |
| 2 | this is title2 | this is full text2 | orange |
+---------+----------------+--------------------+------------------+
Note that the GROUP/GROUP_CONCAT part can be performed just as well (if not better) at the application level (e.g. a simple PHP loop acting upon an ordered array)

How to rewrite a NOT IN subquery as join

Let's assume that the following tables in MySQL describe documents contained in folders.
mysql> select * from folder;
+----+----------------+
| ID | PATH |
+----+----------------+
| 1 | matches/1 |
| 2 | matches/2 |
| 3 | shared/3 |
| 4 | no/match/4 |
| 5 | unreferenced/5 |
+----+----------------+
mysql> select * from DOC;
+----+------+------------+
| ID | F_ID | DATE |
+----+------+------------+
| 1 | 1 | 2000-01-01 |
| 2 | 2 | 2000-01-02 |
| 3 | 2 | 2000-01-03 |
| 4 | 3 | 2000-01-04 |
| 5 | 3 | 2000-01-05 |
| 6 | 3 | 2000-01-06 |
| 7 | 4 | 2000-01-07 |
| 8 | 4 | 2000-01-08 |
| 9 | 4 | 2000-01-09 |
| 10 | 4 | 2000-01-10 |
+----+------+------------+
The columns ID are the primary keys and the column F_ID of table DOC is a not-null foreign key that references the primary key of table FOLDER. By using the 'DATE' of documents in the where clause, I would like to find which folders contain only the selected documents. For documents earlier than 2000-01-05, this could be written as:
SELECT DISTINCT d1.F_ID
FROM DOC d1
WHERE d1.DATE < '2000-01-05'
AND d1.F_ID NOT IN (
SELECT d2.F_ID
FROM DOC d2 WHERE NOT (d2.DATE < '2000-01-05')
);
and it correctly returns '1' and '2'. By reading
http://dev.mysql.com/doc/refman/5.5/en/rewriting-subqueries.html
the performance for big tables could be improved if the subquery is replaced with a join. I already found questions related to NOT IN and JOINS but not exactly what I was looking for. So, any ideas of how this could be written with joins ?
The general answer is:
select t.*
from t
where t.id not in (select id from s)
Can be rewritten as:
select t.*
from t left outer join
(select distinct id from s) s
on t.id = s.id
where s.id is null
I think you can apply this to your situation.
select distinct d1.F_ID
from DOC d1
left outer join (
select F_ID
from DOC
where date >= '2000-01-05'
) d2 on d1.F_ID = d2.F_ID
where d1.date < '2000-01-05'
and d2.F_ID is null
If I understand your question correctly, that you want to find the F_IDs representing folders which only contains documents from before '2000-01-05', then simply
SELECT F_ID
FROM DOC
GROUP BY F_ID
HAVING MAX(DATE) < '2000-01-05'
Sample Table and Insert Statements
CREATE TABLE `tleft` (
`id` int(2) NOT NULL,
`name` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `tright` (
`id` int(2) NOT NULL,
`t_left_id` int(2) DEFAULT NULL,
`description` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
INSERT INTO `tleft` (`id`, `name`)
VALUES
(1, 'henry'),
(2, 'steve'),
(3, 'jeff'),
(4, 'richards'),
(5, 'elon');
INSERT INTO `tright` (`id`, `t_left_id`, `description`)
VALUES
(1, 1, 'sample'),
(2, 2, 'sample');
Left Join : SELECT l.id,l.name FROM tleft l LEFT JOIN tright r ON l.id = r.t_left_id ;
Returns Id : 1, 2, 3, 4, 5
Right Join : SELECT l.id,l.name FROM tleft l RIGHT JOIN tright r ON l.id = r.t_left_id ;
Returns Id : 1,2
Subquery Not in tright : select id from tleft where id not in ( select t_left_id from tright);
Returns Id : 3,4,5
Equivalent Join For above subquery :
SELECT l.id,l.name FROM tleft l LEFT JOIN tright r ON l.id = r.t_left_id WHERE r.t_left_id IS NULL;
AND clause will be applied during the JOIN and WHERE clause will be applied after the JOIN .
Example : SELECT l.id,l.name FROM tleft l LEFT JOIN tright r ON l.id = r.t_left_id AND r.description ='hello' WHERE r.t_left_id IS NULL ;
Hope this helps