I have a problem with a SQL query (i am using MySQL)
I got some tables, which have to find an output where they have to be multiplied.
First table lipid:
ID | name | mass | hg |
1 | PC 32:2 | 700 | PC |
2 | PC 32:1 | 800 | PC |
Second table FA:
ID | name | mass
1 | FA 16:1 | 300
2 | FA 16:0 | 400
3 | FA 16:2 | 200
a
Third table Cat:
ID | name | snpos | backbone | LC
1 | gpl | 2 | 25 | 1
Fourth table LC:
ID | name | mass
1 | PC | 75
I need a SQL query that can give me a result where i use something from all 4 tables.
I will need a result which can give me which composition of FA´s (table 2) that can give me the correct input lipid (table 1).
So i will input hg = PC and mass = 700. By this information it should give me which FA composition it could be.
From table 3 cat, it should use the snpos information to find out how many FA it should find in this case 2, and the backbone should be added to the result same as the mass from table 4 LC, else it will never get to the mass of input.
It would look something like:
FA 16:1 FA 16:1 (mass 600) + PC (mass 75) + backbone (25) = 700
This isn't intended as a definitive solution, nor even an advocation of the method; just a demonstration of principle...
DROP TABLE IF EXISTS lipid;
CREATE TABLE lipid
(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,name VARCHAR(12) NOT NULL UNIQUE
,mass INT NOT NULL
,hg CHAR(2) NOT NULL
);
INSERT INTO lipid VALUES
(1 ,'PC 32:2',700,'PC'),(2,'PC 32:1',800,'PC');
DROP TABLE IF EXISTS FA;
CREATE TABLE FA
(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,name VARCHAR(12) NOT NULL
,mass INT NOT NULL
);
INSERT INTO fa VALUES
(1 ,'FA 16:1',300),
(2 ,'FA 16:0',400),
(3 ,'FA 16:2',200);
DROP TABLE IF EXISTS Cat;
CREATE TABLE Cat
(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,name VARCHAR(12) NOT NULL
,snpos TINYINT NOT NULL
,backbone INT NOT NULL
,LC INT NOT NULL
);
INSERT INTO Cat VALUES
(1 ,'gpl',2,25,1);
DROP TABLE IF EXISTS LC;
CREATE TABLE LC
(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,name CHAR(2) NOT NULL
,mass INT NOT NULL
);
INSERT INTO LC VALUES (1,'PC',75);
SELECT l.id lipid_id
, l.name lipid_name
, l.mass lipid_mass
, l.hg
, fa.*
, c.id cat_id
, c.name cat_name
, c.snpos snpos
FROM lipid l
JOIN lc
ON lc.name = l.hg
JOIN cat c
ON c.lc = lc.id
JOIN
( SELECT 3 snpos,x.name name_x,x.mass mass_x,y.name name_y,y.mass mass_y,z.name name_z,z.mass mass_z, x.mass+y.mass+z.mass total FROM fa x JOIN fa y ON y.id < x.id JOIN fa z ON z.id < y.id
UNION
SELECT 2 ,x.name, x.mass,y.name,y.mass,NULL,0, x.mass+y.mass FROM fa x JOIN fa y ON y.id < x.id
UNION
SELECT 1,name,mass,NULL,0,NULL,0, mass FROM fa
) fa
ON fa.snpos = c.snpos
AND fa.total = 700-c.backbone-lc.mass
WHERE l.mass = 700
AND l.hg = 'PC';
+----------+------------+------------+----+-------+---------+--------+---------+--------+--------+--------+-------+--------+----------+-------+
| lipid_id | lipid_name | lipid_mass | hg | snpos | name_x | mass_x | name_y | mass_y | name_z | mass_z | total | cat_id | cat_name | snpos |
+----------+------------+------------+----+-------+---------+--------+---------+--------+--------+--------+-------+--------+----------+-------+
| 1 | PC 32:2 | 700 | PC | 2 | FA 16:2 | 200 | FA 16:0 | 400 | NULL | 0 | 600 | 1 | gpl | 2 |
+----------+------------+------------+----+-------+---------+--------+---------+--------+--------+--------+-------+--------+----------+-------+
Related
I have 3 mysql tables:
Users:
id | name | interest | user_id
1 | user1 | 1,2 | 1
2 | user2 | 1,2,3 | 2
Interests:
id | name
1 | interest1
2 | interest2
User_posts:
id | user_id | desc
1 | 23 | something..
2 | 31 | something..
What i want to achieve is i want join interest column from users table into user_posts table based on user_id with user_name from interests table which i have already done using this query:
select user_posts.*, users.interest as interest_ids, zaya.interests.name as interest_name
from user_posts
left join users ON user_posts.id = users.interest
left join interests ON user_posts.id = users.interest;
i get the following output with this query:
User_posts:
id | user_id | desc | interest_ids | interest_name
1 | 23 | something.. | 1,2 | interest1
2 | 31 | something.. | 1,2,3 | interest1
Output i want to achieve:
User_posts:
id | user_id | desc | interest_ids | interest_name
1 | 23 | something.. | 1,2 | interest1, interest2
2 | 31 | something.. | 1,2,3 | interest1, interest2, interest3
I believe this can be solved using the temporary table creation method and have tried solving this but i'm newbie in mysql queries and getting errors please help me with the correct query.
Thank you in advance.
I would use a sub query rather than a join if you're looking to just get back one col form the [interests] table.
Here is an example using STRING_SPLIT function to get the names :
DECLARE #users TABLE(
[ID] int IDENTITY(1,1)
,[NAME] varchar(32)
,[INTERESTS] varchar(32)
)
INSERT INTO #users
VALUES ('user1', '1,2'), ('user2', '1,2,3')
DECLARE #interests TABLE(
[ID] int IDENTITY(1,1)
,[NAME] varchar(32)
)
INSERT INTO #interests
VALUES ('interests1'), ('interests2'), ('interests3')
DECLARE #user_posts TABLE(
[ID] int IDENTITY(1,1)
,[USERID] int
,[DESC] varchar(32)
)
INSERT INTO #user_posts
VALUES (1, 'post1'), (2, 'post2')
SELECT
U.[ID]
,UP.[USERID]
,UP.[DESC]
,U.[INTERESTS]
,SUBSTRING((SELECT ',' + I2.[NAME]
FROM #interests I2
WHERE I2.[ID] IN (SELECT [name] FROM STRING_SPLIT(U.[INTERESTS], ','))
FOR XML PATH('')), 2, 1000) AS 'INTEREST_NAME'
FROM #user_posts UP
LEFT JOIN #users U
ON U.[ID] = UP.[USERID]
This should return :
ID
USERID
DESC
INTERESTS
INTEREST_NAME
1
1
post1
1,2
interests1,interests2
2
2
post2
1,2,3
interests1,interests2,interests3
I have this table:
CREATE TABLE one (
id bigint(11) primary key,
email varchar(100),
refer_link varchar(8),
referrer varchar (8)
);
When users submit forms they get a unique refer link (refer_link). When another user submit forms with that link in his referrer column is inserted that refer link.
So in example I will have this table:
id email refer_link referrer
---------------------------------------------------------
1 jerry#jerry.com ref11111
2 elaine#elaine.com ref22222 ref11111
3 george#george.com ref33333 ref22222
4 kramer#kramer.com ref44444 ref11111
5 cosmo#cosmo.com ref55555 ref44444
How to create this view?
email refer_email refer_count
--------------------------------------------------------
jerry#jerry.com 2
elaine#elaine.com jerry#jerry.com 1
george#george.com elaine#elaine.com 0
kramer#kramer.com jerry#jerry.com 1
cosmo#cosmo.com kramer#kramer.com 0
Thank you very much for help!
Try the below sub query,
$query = "select o.email,
IFNULL( (select email from one where o.referrer = refer_link ),'') as refer_email,
(select count(referrer) from one where referrer = o.refer_link ) as refer_count
from one as o order by id ";
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id SERIAL PRIMARY KEY
,email VARCHAR(50) NOT NULL
,referrer INT NULL
);
INSERT INTO my_table VALUES
(1,'jerry#jerry.com',NULL),
(2,'elaine#elaine.com',1),
(3,'george#george.com',2),
(4,'kramer#kramer.com',1),
(5,'cosmo#cosmo.com',4);
SELECT x.*, COUNT(y.id) refer_count FROM my_table x LEFT JOIN my_table y ON y.referrer = x.id GROUP BY x.id;
+----+-------------------+----------+-------------+
| id | email | referrer | refer_count |
+----+-------------------+----------+-------------+
| 1 | jerry#jerry.com | NULL | 2 |
| 2 | elaine#elaine.com | 1 | 1 |
| 3 | george#george.com | 2 | 0 |
| 4 | kramer#kramer.com | 1 | 1 |
| 5 | cosmo#cosmo.com | 4 | 0 |
+----+-------------------+----------+-------------+
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;
I have a MySQL database with the following tables and fields:
Student (id)
Class (id)
Grade (id, student_id, class_id, grade)
The student and class tables are indexed on id (primary keys). The grade table is indexed on id (primary key) and student_id, class_id and grade.
I need to construct a query which, given a class ID, gives a list of all other classes and the number of students who scored more in that other class.
Essentially, given the following data in the grades table:
student_id | class_id | grade
--------------------------------------
1 | 1 | 87
1 | 2 | 91
1 | 3 | 75
2 | 1 | 68
2 | 2 | 95
2 | 3 | 84
3 | 1 | 76
3 | 2 | 88
3 | 3 | 71
Querying with class ID 1 should yield:
class_id | total
-------------------
2 | 3
3 | 1
Ideally I'd like this to execute in a few seconds, as I'd like it to be part of a web interface.
The issue I have is that in my database, I have over 1300 classes and 160,000 students. My grade table has almost 15 million rows and as such, the query takes a long time to execute.
Here's what I've tried so far along with the times each query took:
-- I manually stopped execution after 2 hours
SELECT c.id, COUNT(*) AS total
FROM classes c
INNER JOIN grades a ON a.class_id = c.id
INNER JOIN grades b ON b.grade < a.grade AND
a.student_id = b.student_id AND
b.class_id = 1
WHERE c.id != 1 AND
GROUP BY c.id
-- I manually stopped execution after 20 minutes
SELECT c.id,
(
SELECT COUNT(*)
FROM grades g
WHERE g.class_id = c.id AND g.grade > (
SELECT grade
FROM grades
WHERE student_id = g.student_id AND
class_id = 1
)
) AS total
FROM classes c
WHERE c.id != 1;
-- 1 min 12 sec
CREATE TEMPORARY TABLE temp_blah (student_id INT(11) PRIMARY KEY, grade INT);
INSERT INTO temp_blah SELECT student_id, grade FROM grades WHERE class_id = 1;
SELECT o.id,
(
SELECT COUNT(*)
FROM grades g
INNER JOIN temp_blah t ON g.student_id = t.student_id
WHERE g.class_id = c.id AND t.grade < g.grade
) AS total
FROM classes c
WHERE c.id != 1;
-- Same thing but with joins instead of a subquery - 1 min 54 sec
SELECT c.id,
COUNT(*) AS total
FROM classes c
INNER JOIN grades g ON c.id = p.class_id
INNER JOIN temp_blah t ON g.student_id = t.student_id
WHERE c.id != 1
GROUP BY c.id;
I also considered creating a 2D table, with students as rows and classes as columns, however I can see two issues with this:
MySQL implements a maximum column count (4096) and maximum row size (in bytes) which may be exceeded by this approach
I can't think of a good way to query that structure to get the results I need
I also considered performing these calculations as background jobs and storing the results somewhere, but for the information to remain current (it must), they would need to be recalculated every time a student, class or grade record was created or updated.
Does anyone know a more efficient way to construct this query?
EDIT: Create table statements:
CREATE TABLE `classes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1331 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci$$
CREATE TABLE `students` (
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=160803 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci$$
CREATE TABLE `grades` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`student_id` int(11) DEFAULT NULL,
`class_id` int(11) DEFAULT NULL,
`grade` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_grades_on_student_id` (`student_id`),
KEY `index_grades_on_class_id` (`class_id`),
KEY `index_grades_on_grade` (`grade`)
) ENGINE=InnoDB AUTO_INCREMENT=15507698 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci$$
Output of explain on the most efficient query (the 1 min 12 sec one):
id | select_type | table | type | possible_keys | key | key_len | ref | rows | extra
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 | PRIMARY | c | range | PRIMARY | PRIMARY | 4 | | 683 | Using where; Using index
2 | DEPENDENT SUBQUERY | g | ref | index_grades_on_student_id,index_grades_on_class_id,index_grades_on_grade | index_grades_on_class_id | 5 | mydb.c.id | 830393 | Using where
2 | DEPENDENT SUBQUERY | t | eq_ref | PRIMARY | PRIMARY | 4 | mydb.g.student_id | 1 | Using where
Another edit - explain output for sgeddes suggestion:
+----+-------------+------------+--------+---------------+------+---------+------+----------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+---------------+------+---------+------+----------+----------------------------------------------+
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 14953992 | Using where; Using temporary; Using filesort |
| 2 | DERIVED | <derived3> | system | NULL | NULL | NULL | NULL | 1 | Using filesort |
| 2 | DERIVED | G | ALL | NULL | NULL | NULL | NULL | 15115388 | |
| 3 | DERIVED | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+----+-------------+------------+--------+---------------+------+---------+------+----------+----------------------------------------------+
I think this should work for you using SUM and CASE:
SELECT C.Id,
SUM(
CASE
WHEN G.Grade > C2.Grade THEN 1 ELSE 0
END
)
FROM Class C
INNER JOIN Grade G ON C.Id = G.Class_Id
LEFT JOIN (
SELECT Grade, Student_Id, Class_Id
FROM Class
JOIN Grade ON Class.Id = Grade.Class_Id
WHERE Class.Id = 1
) C2 ON G.Student_Id = C2.Student_Id
WHERE C.Id <> 1
GROUP BY C.Id
Sample Fiddle Demo
--EDIT--
In response to your comment, here is another attempt that should be much faster:
SELECT
Class_Id,
SUM(CASE WHEN Grade > minGrade THEN 1 ELSE 0 END)
FROM
(
SELECT
Student_Id,
#classToCheck:=
IF(G.Class_Id = 1, Grade, #classToCheck) minGrade ,
Class_Id,
Grade
FROM Grade G
JOIN (SELECT #classToCheck:= 0) t
ORDER BY Student_Id, IF(Class_Id = 1, 0, 1)
) t
WHERE Class_Id <> 1
GROUP BY Class_ID
And more sample fiddle.
Can you give this a try on the original data as well! It is only one join :)
select
final.class_id, count(*) as total
from
(
select * from
(select student_id as p_student_id, grade as p_grade from table1 where class_id = 1) as partial
inner join table1 on table1.student_id = partial.p_student_id
where table1.class_id <> 1 and table1.grade > partial.p_grade
) as final
group by
final.class_id;
sqlfiddle link
I have 3 tables with the following schema:
CREATE TABLE `devices` (
`device_id` int(11) NOT NULL auto_increment,
`name` varchar(20) default NULL,
`appliance_id` int(11) default '0',
`sensor_type` int(11) default '0',
`display_name` VARCHAR(100),
PRIMARY KEY USING BTREE (`device_id`)
)
CREATE TABLE `channels` (
`channel_id` int(11) NOT NULL AUTO_INCREMENT,
`device_id` int(11) NOT NULL,
`channel` varchar(10) NOT NULL,
PRIMARY KEY (`channel_id`),
KEY `device_id_idx` (`device_id`)
)
CREATE TABLE `historical_data` (
`date_time` datetime NOT NULL,
`channel_id` int(11) NOT NULL,
`data` float DEFAULT NULL,
`unit` varchar(10) DEFAULT NULL,
KEY `devices_datetime_idx` (`date_time`) USING BTREE,
KEY `channel_id_idx` (`channel_id`)
)
The setup is that a device can have one or more channels and each channel has many (historical) data.
I use the following query to get the last historical data for one device and all it's related channels:
SELECT c.channel_id, c.channel, max(h.date_time), h.data
FROM devices d
INNER JOIN channels c ON c.device_id = d.device_id
INNER JOIN historical_data h ON h.channel_id = c.channel_id
WHERE d.name = 'livingroom' AND d.appliance_id = '0'
AND d.sensor_type = 1 AND ( c.channel = 'ch1')
GROUP BY c.channel
ORDER BY h.date_time, channel
The query plan looks as follows:
+----+-------------+-------+--------+-----------------------+----------------+---------+---------------------------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+-----------------------+----------------+---------+---------------------------+--------+-------------+
| 1 | SIMPLE | c | ALL | PRIMARY,device_id_idx | NULL | NULL | NULL | 34 | Using where |
| 1 | SIMPLE | d | eq_ref | PRIMARY | PRIMARY | 4 | c.device_id | 1 | Using where |
| 1 | SIMPLE | h | ref | channel_id_idx | channel_id_idx | 4 | c.channel_id | 322019 | |
+----+-------------+-------+--------+-----------------------+----------------+---------+---------------------------+--------+-------------+
3 rows in set (0.00 sec)
The above query is currently taking approximately 15 secs and I wanted to know if there are any tips or way to improve the query?
Edit:
Example data from historical_data
+---------------------+------------+------+------+
| date_time | channel_id | data | unit |
+---------------------+------------+------+------+
| 2011-11-20 21:30:57 | 34 | 23.5 | C |
| 2011-11-20 21:30:57 | 9 | 68 | W |
| 2011-11-20 21:30:54 | 34 | 23.5 | C |
| 2011-11-20 21:30:54 | 5 | 316 | W |
| 2011-11-20 21:30:53 | 34 | 23.5 | C |
| 2011-11-20 21:30:53 | 2 | 34 | W |
| 2011-11-20 21:30:51 | 34 | 23.4 | C |
| 2011-11-20 21:30:51 | 9 | 68 | W |
| 2011-11-20 21:30:49 | 34 | 23.4 | C |
| 2011-11-20 21:30:49 | 4 | 193 | W |
+---------------------+------------+------+------+
10 rows in set (0.00 sec)
Edit 2:
Mutliple channel SELECT example:
SELECT c.channel_id, c.channel, max(h.date_time), h.data
FROM devices d
INNER JOIN channels c ON c.device_id = d.device_id
INNER JOIN historical_data h ON h.channel_id = c.channel_id
WHERE d.name = 'livingroom' AND d.appliance_id = '0'
AND d.sensor_type = 1 AND ( c.channel = 'ch1' OR c.channel = 'ch2' OR c.channel = 'ch2')
GROUP BY c.channel
ORDER BY h.date_time, channel
I've used OR in the c.channel where clause because it was easier to generated pro grammatically but it can be changed to use IN if necessary.
Edit 3:
Example result of what I'm trying to achieve:
+-----------+------------+---------+---------------------+-------+
| device_id | channel_id | channel | max(h.date_time) | data |
+-----------+------------+---------+---------------------+-------+
| 28 | 9 | ch1 | 2011-11-21 20:39:36 | 0 |
| 28 | 35 | ch2 | 2011-11-21 20:30:55 | 32767 |
+-----------+------------+---------+---------------------+-------+
I have added the device_id to the example but my select will only need to return channel_id, channel, last date_time i.e max and the data. The results should be the last record from the historical_data table for each channel for one device.
It seems that removing an re-creating the index on date_time by deleting and creating it again sped up my original SQL up to around 2secs
I haven't been able to test this, so I'd like to ask you to run it and let us know what happens.. if it gives you the desired result and if it runs faster than your current:
CREATE DEFINER=`root`#`localhost` PROCEDURE `GetLatestHistoricalData_EXAMPLE`
(
IN param_device_name VARCHAR(20)
, IN param_appliance_id INT
, IN param_sensor_type INT
, IN param_channel VARCHAR(10)
)
BEGIN
SELECT
h.date_time, h.data
FROM
historical_data h
INNER JOIN
(
SELECT c.channel_id
FROM devices d
INNER JOIN channels c ON c.device_id = d.device_id
WHERE
d.name = param_device_name
AND d.appliance_id = param_appliance_id
AND d.sensor_type = param_sensor_type
AND c.channel = param_channel
)
c ON h.channel_id = c.channel_id
ORDER BY h.date_time DESC
LIMIT 1;
END
Then to run a test:
CALL GetLatestHistoricalData_EXAMPLE ('livingroom', 0, 1, 'ch1');
I tried working it into a stored procedure so that even if you get the desired results using this for one device, you can try it with another device and see the results... Thanks!
[edit] : : In response to Danny's comment here's an updated test version:
CREATE DEFINER=`root`#`localhost` PROCEDURE `GetLatestHistoricalData_EXAMPLE_3Channel`
(
IN param_device_name VARCHAR(20)
, IN param_appliance_id INT
, IN param_sensor_type INT
, IN param_channel_1 VARCHAR(10)
, IN param_channel_2 VARCHAR(10)
, IN param_channel_3 VARCHAR(10)
)
BEGIN
SELECT
h.date_time, h.data
FROM
historical_data h
INNER JOIN
(
SELECT c.channel_id
FROM devices d
INNER JOIN channels c ON c.device_id = d.device_id
WHERE
d.name = param_device_name
AND d.appliance_id = param_appliance_id
AND d.sensor_type = param_sensor_type
AND (
c.channel IN (param_channel_1
,param_channel_2
,param_channel_3
)
c ON h.channel_id = c.channel_id
ORDER BY h.date_time DESC
LIMIT 1;
END
Then to run a test:
CALL GetLatestHistoricalData_EXAMPLE_3Channel ('livingroom', 0, 1, 'ch1', 'ch2' , 'ch3');
Again, this is just for testing, so you'll be able to see if it meets your needs..
I would first add an index on the devices table ( appliance_id, sensor_type, name ) to match your query. I don't know how many entries are in this table, but if large, and many elements per device, get right to it.
Second, on your channels table, index on ( device_id, channel )
Third, on your history data, index on ( channel_id, date_time )
then,
SELECT STRAIGHT_JOIN
PreQuery.MostRecent,
PreQuery.Channel_ID,
PreQuery.Channel,
H2.Data,
H2.Unit
from
( select
c.channel_id,
c.channel,
max( h.date_time ) as MostRecent
from
devices d
join channels c
on d.device_id = c.device_id
and c.channel in ( 'ch1', 'ch2', 'ch3' )
join historical_data h
on c.channel_id = c.Channel_id
where
d.appliance_id = 0
and d.sensor_type = 1
and d.name = 'livingroom'
group by
c.channel_id ) PreQuery
JOIN Historical_Data H2
on PreQuery.Channel_ID = H2.Channel_ID
AND PreQuery.MostRecent = H2.Date_Time
order by
PreQuery.MostRecent,
PreQuery.Channel