MySQL join "connected" intervals - mysql

Lets say I have table
teach_subject(teacher_id, subject_id, min_grade_of_school, max_grade_of_school, color_in_timetable)
Example data:
teacher_id subject_id min_grade_of_school max_grade_of_school color_in_timetable
1 2 1 4 #A00
1 2 5 6 #0A0
1 2 9 11 #00A
1 3 1 7 #AA0
It is not allowed that min_grad_of_school > max_grad_of_school, but maybe they are equal.
It is also not allowed that for a given tuple (t_id_new, s_id_new, min_grade_new, max_grade_new, color_new) there exists an other tuple (t_id, s_id, min_grade, max_grade, color) in the table holding
t_id_new = t_id and s_id_new = s_id and
( min_grade <= min_grade_new <= max_grade or min_grade <= max_grade_new <= max_grade )
So for the given example a new tuple like (1,2,6,11,#FFF) or (1,2,2,7,#FFF) is not possible.
So far no problem.
Now I want to ignore the color and join the "connected" rows, i.e. if teacher 1 teaches subject 2 from the 1st grade to the 4th grade, and from the 5th grade to the 6th grade, you can also say he teaches subject 2 from 1st to 6th grade.
So I want to "join" the tuples (1,2,1,4) and (1,2,5,6) to (1,2,1,6) but i dont want to join (1,2,4,5) and (1,2,9,11), since ther is a (integer) gap between 5 and 9.
I just have no idea if there is a way to do this with MySQL. At the moment I just select all the data an edit the selected data with PHP. Is there a MySQL way to directly select what I want or should stick to PHP?
Edit
Example result (for the above example data) :
teacher_id subject_id min_grade_of_school max_grade_of_school color_in_timetable
1 2 1 6 #A00
1 2 9 11 #00A
1 3 1 7 #AA0
Edit 2
Maybe can use a stored procedure?

DROP TABLE IF EXISTS teach_subject;
CREATE TABLE teach_subject
(teacher_id INT NOT NULL
,subject_id INT NOT NULL
,min_g INT NOT NULL
,max_g INT NOT NULL
,color_in_timetable CHAR(4) NOT NULL
,PRIMARY KEY(teacher_id,subject_id,min_g)
);
INSERT INTO teach_subject VALUES
(1,2,1,4,'#A00'),
(1,2,5,6,'#0A0'),
(1,2,9,11,'#00A'),
(1,3,1, 7,'#AA0');
SELECT a.teacher_id
, a.subject_id
, a.min_g
, MIN(c.max_g) max_g
, a.color_in_timetable
FROM teach_subject a
LEFT
JOIN teach_subject b
ON b.teacher_id = a.teacher_id
AND b.subject_id = a.subject_id
AND b.min_g = a.max_g - 1
LEFT
JOIN teach_subject c
ON c.teacher_id = a.teacher_id
AND c.subject_id = a.subject_id
AND c.min_g >= a.min_g
LEFT
JOIN teach_subject d
ON d.teacher_id = a.teacher_id
AND d.subject_id = a.subject_id
AND d.min_g = c.max_g + 1
WHERE b.teacher_id IS NULL
AND c.teacher_id IS NOT NULL
AND d.teacher_id IS NULL
GROUP
BY a.teacher_id,a.subject_id,a.min_g;
+------------+------------+-------+-------+--------------------+
| teacher_id | subject_id | min_g | max_g | color_in_timetable |
+------------+------------+-------+-------+--------------------+
| 1 | 2 | 1 | 6 | #A00 |
| 1 | 2 | 9 | 11 | #00A |
| 1 | 3 | 1 | 7 | #AA0 |
+------------+------------+-------+-------+--------------------+

Related

Select tree path in MySQL table

I have a MySQL table like this:
| CategoryId | Name | CategoryParentId |
|------------|---------------|------------------|
| 0 | Tech Support | (null) |
| 1 | Configuration | 0 |
| 2 | Questions | 1 |
| 3 | Sales | (null) |
| 4 | Questions | 3 |
| 5 | Other | (null) |
This is the output I desire when a query the ID 2 (for example):
Tech Support/Configuration/Questions
How do I do this without having to do multiple joins?
Fiddle
EDIT: Not sure if is the best way to do this, but I solved by creating a function:
DELIMITER $$
CREATE FUNCTION get_full_tree (CategoryId int) RETURNS VARCHAR(200)
BEGIN
SET #CategoryParentId = (SELECT CategoryParentId FROM category c WHERE c.CategoryId = CategoryId);
SET #Tree = (SELECT Name FROM category c WHERE c.CategoryId = CategoryId);
WHILE (#CategoryParentId IS NOT NULL) DO
SET #ParentName = (SELECT Name FROM category c WHERE c.CategoryId = #CategoryParentId);
SET #Tree = CONCAT(#ParentName, '/', #Tree);
SET #CategoryParentId = (SELECT CategoryParentId FROM category c WHERE c.CategoryId = #CategoryParentId);
END WHILE;
RETURN #Tree;
END $$
DELIMITER ;
I can now do this query:
SELECT CategoryId, get_full_tree(CategoryId) FROM category
You could create a new table, lets name it as hierarchy (could be a better name) where we would store all the ancestry of a category.
CREATE TABLE `hierarchy` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`parent` int(11) NOT NULL,
`child` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
For example in this case for Questions i.e. ID->2 we will have the below entries:
id parent child
====================
6 0 2
7 1 2
8 2 2
For the whole example the content of the table would be:
id parent child
===========================
1 0 0
2 3 3
3 5 5
4 0 1
5 1 1
6 0 2
7 1 2
8 2 2
9 3 4
10 4 4
Now whenever you want to retrieve the whole ancestry of node execute the below query:
select name from category where id in (select parent from hierarchy where child = 2 order by id ASC)
The above query will return all the ancestry names for the Questions (ID->2) i.e.
name
==================
Tech Support
Configuration
Questions
For completeness shake below is the content for category table
id Name
============================
0 Tech Support
1 Configuration
2 Questions
3 Sales
4 Questions
5 Other
N.B. This is just an idea i am sure you can definitely build more elegant solution on top of it.
If you are using MySQL 8 or above you can use Common Table Expressions for recursive queries. Query would be the following
WITH RECURSIVE CategoryPath (CategoryId, Name, path) AS
(
SELECT CategoryId, Name, Name as path
FROM category
WHERE CategoryParentId IS NULL
UNION ALL
SELECT c.CategoryId, c.Name, CONCAT(cp.path, ' / ', c.Name)
FROM CategoryPath AS cp JOIN category AS c
ON cp.CategoryId = c.CategoryParentId
)
SELECT * FROM CategoryPath ORDER BY path;

Sql Min query condition with join

I'm having two tables
abserve_hotels
hotel_id name trendy
1 A 1
2 B 1
3 C 0
4 D 0
4 E 0
6 G 0
7 F 0
abserve_hotel_rooms
room_id room_prize hotel_id
1 235 1
2 500 2
3 1000 1
4 2356 7
5 800 7
Here, I'm using this following query
SELECT `h`.*,`ar`.* from `abserve_hotel_rooms` as `ar` JOIN `abserve_hotels` as `h` ON `ar`.`hotel_id` = `h`.`hotel_id` WHERE `h`.`trendy` =1 LIMIT 5
But,when I using this query will retrieve the hotel_id two times if it having two rooms in that hotel..
i.e.,
hotel_id name trendy room_id room_prize
1 A 1 1 235
1 A 1 3 1000
2 B 1 2 500
But,I need only the minimum of room_prize if the hotel_id having two rooms,
For example,
hotel_id name trendy room_id room_prize
1 A 1 1 235
2 B 1 2 500
Like this,Someone help me..
Use a MIN with GROUP BY will do.
SQLFiddle:
http://sqlfiddle.com/#!9/46ff3/1
SELECT `h`.*,`ar`.room_id, MIN(`ar`.room_prize) as min_room_prize
from `abserve_hotel_rooms` as `ar` JOIN `abserve_hotels` as `h` ON `ar`.`hotel_id` = `h`.`hotel_id`
WHERE `h`.`trendy` =1
group by h.hotel_id
LIMIT 5
SQLFiddle output:
hotel_id name trendy room_id min_room_prize
1 A 1 1 235
2 B 1 2 500
DROP TABLE IF EXISTS hotels;
CREATE TABLE hotels
(hotel_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,name CHAR(1) NOT NULL
,trendy TINYINT NOT NULL
);
INSERT INTO hotels VALUES
(1,'A',1),
(2,'B',1),
(3,'C',0),
(4,'D',0),
(5,'E',0),
(6,'G',0),
(7,'F',0);
DROP TABLE IF EXISTS rooms;
CREATE TABLE rooms
(room_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,room_prize INT NOT NULL
,hotel_id INT NOT NULL
);
INSERT INTO rooms VALUES
(1, 235,1),
(2, 500,2),
(3,1000,1),
(4,2356,7),
(5, 800,7);
SELECT h.*
, x.room_id
, x.room_prize
FROM hotels h
JOIN rooms x
ON x.hotel_id = h.hotel_id
JOIN
( SELECT hotel_id
, MIN(room_prize) room_prize
FROM rooms
GROUP
BY hotel_id
) y
ON y.hotel_id = x.hotel_id
AND y.room_prize = x.room_prize
WHERE h.trendy = 1
ORDER
BY h.hotel_id
LIMIT 5;
+----------+------+--------+---------+------------+
| hotel_id | name | trendy | room_id | room_prize |
+----------+------+--------+---------+------------+
| 1 | A | 1 | 1 | 235 |
| 2 | B | 1 | 2 | 500 |
+----------+------+--------+---------+------------+

How to count and compare value of colume in the same table

I need to create procedure which will find the worst user in one table by counting status with 'P' and 'U' calculate ratio then compare it with other users, take that user id and find it in another table and write all user information that are in two tables. And i call that procedure from java application.
Table Rezervacija
id | SifKorisnikPK | Status
1 | 1 | 'P'
2 | 1 | 'U'
3 | 1 | 'U'
4 | 2 | 'U'
5 | 2 | 'P'
6 | 2 | 'P'
7 | 2 | 'P'
8 | 2 | 'P'
9 | 3 | 'U'
10 | 3 | 'U'
11 | 3 | 'P'
12 | 3 | 'P'
13 | 3 | 'P'
14 | 3 | 'P'
So the user with id 2 is worst user because of 4 P's, and one U, so his ratio is 3 P. Then it's should go to Korisnik table and return all the info for user with id 2
I try with this but can't get any return values
CREATE PROCEDURE sp_getBadPremiumUsers
AS
BEGIN
DECLARE #BrLr int
DECLARE #BrDr int
SELECT #BrLr = (SELECT COUNT(*) FROM Rezervacija A
INNER JOIN Rezervacija B
ON A.SifKorisnikPK = B.SifKorisnikPK
WHERE A.Status = 'P')
SELECT #BrDr = (SELECT COUNT(*) FROM Rezervacija A
INNER JOIN Rezervacija B
ON A.SifKorisnikPK = B.SifKorisnikPK
WHERE A.Status = 'U')
SELECT * INTO #PremiKoris FROM Korisnik
INNER JOIN PremiumKorisnik
ON SifKorisnik = SifKorisnikPK
ALTER TABLE #PremiKoris
DROP COLUMN Password
SELECT * FROM #PremiKoris
WHERE #BrLr > #BrDr
DROP TABLE #PremiKoris
END
GO
You can get the worse user using:
select r.SifKorisnikPK
from Rezervacija r
where status = 'P'
group by SifKorisnikPK
order by count(*) desc
limit 1;
You can then use this in a query to get more information:
select k.*
from PremiumKorisnik k join
(select r.SifKorisnikPK
from Rezervacija r
where status = 'P'
group by SifKorisnikPK
order by count(*) desc
limit 1
) r
on r.SifKorisnikPK = k.SifKorisnikPK
This does in one query what you describe you want to do.

Finding cooccuring values in MYSQL weak relation table

I have a weak relation table, called header, it is basically just three ID's: id is an autoincrement primary key, did points to the id of table D and hid points to the id of table H. D and H are irrelevant here.
I want to find for any value of hid, the other values of hid that shares did with the original hid. An example:
id | did | hid
===============
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 1
5 | 2 | 4
6 | 2 | 5
7 | 3 | 2
8 | 3 | 6
For hid = 1 I would thus like to find id = {2,3,5,6} as those are the rows that have did in common with hid = 1.
I can do this by creating some arrays in PHP and running through all possible values of hid and respective did, but this is a quite slow process for large tables. I was wondering if there is a clever kind of JOIN or similar statement that could be used to find the cooccuring values of hid.
If I have understood you correctly:-
SELECT a.hid, GROUP_CONCAT(b.id)
FROM header a
INNER JOIN header b
ON a.did = b.did
AND b.hid != 1
WHERE a.hid = 1
GROUP BY a.hid
SQL fiddle:-
http://www.sqlfiddle.com/#!2/9aa26/1
Maybe this:
SELECT d.id
FROM (
SELECT *
FROM header
WHERE header.hid =1
) AS h
JOIN header AS d ON d.did = h.did
WHERE d.hid !=1

mysql join query

I have two tables that I need to join. These are:
art
id | art
--------
1 | A
2 | B
3 | C
4 | D
5 | E
6 | F
7 | G
8 | H
9 | I
and
Sess
artid | sessid
--------------
1 | 1
2 | 1
3 | 1
4 | 1
1 | 2
4 | 2
5 | 2
6 | 2
1 | 3
2 | 3
7 | 3
4 | 3
where Sess.artid is a foregin key to art.id.
From the tables above we can see that there are 3 sessions: A,B,C,D, A,D,E,F and A,B,G,D.
I want to get a ranking of the arts that occur along with art A. Something like:
D=3
B=2
How could I form such a query in mysql or postgres?
You need to join twice the session table to get the article sharing the same session.
Then join one time with article for the filter clause, and another time to get the name of the other article in the other session.
SELECT aSameSession.art, count(*)
FROM art a
INNER JOIN Sess s
ON a.id = s.artid
INNER JOIN Sess sSameArticle
ON sSameArticle.sessid = s.sessid
INNER JOIN art aSameSession
ON sSameArticle.artid = aSameSession.id
WHERE A.art = 'A'
AND aSameSession.art <> 'A'
GROUP BY aSameSession.art
Output :
B 2
C 1
D 3
E 1
F 1
G 1
This version could be a little difficult to understand, so here a version just with the ID of the article, which is much more simple :
SELECT sSameArticle.artid, count(*)
FROM Sess s
INNER JOIN Sess sSameArticle
ON sSameArticle.sessid = s.sessid
WHERE s.artid = 1
AND sSameArticle.artid != 1
GROUP BY sSameArticle.artid
Output :
2 2
3 1
4 3
5 1
6 1
7 1
Adding the name of the article is just cosmetic.
Something like this, perhaps:
select art,count(*)
from sessid
left join art on art.id=artid
where sessid in (select sessid from sess where artid=1)
group by artid;
?
Example of table structure and join queries on PostgreSQL
CREATE TABLE arts (
arts_id serial PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE sessions (
sessions_id integer NOT NULL,
arts_id integer NOT NULL REFERENCES arts
);
SELECT arts.name, count(sessions_id)
FROM arts
JOIN sessions USING (arts_id)
GROUP BY arts.name
ORDER BY count(sessions_id) DESC;
SELECT a.art, count(*) as ranking
FROM art a, sess s
WHERE a.id = s.artid
group by a.art
order by count(*) DESC;
For a statement in ANSI-92 syntax have a look at Konerak's answer.