I want to join two tables in a special way, first table is devices which has a list of devices.
The second table is datalog which is where abit of data is stored for everytime a device in devices gets polled.
Devices Table:
+----------+------------+----------------------------+---------------------+
| deviceId | deviceName | deviceDescription | timeCreated |
+----------+------------+----------------------------+---------------------+
| 1 | System 1 | Main System in Server Room | 2010-01-01 00:00:00 |
| 2 | System 2 | Outdoor System | 2010-01-01 00:00:00 |
+----------+------------+----------------------------+---------------------+
DataLog Table:
+----+---------------------+----------+-----------+---------+
| id | time_stamp | DeviceId | FuelLevel | Voltage |
+----+---------------------+----------+-----------+---------+
| 1 | 2010-01-01 00:00:00 | 1 | 60 | 220 |
| 2 | 2010-01-01 00:00:00 | 2 | 20 | 221 |
| 3 | 2010-01-02 00:00:00 | 1 | 100 | 219 |
| 4 | 2010-01-02 00:00:00 | 2 | 100 | 222 |
| 5 | 2010-01-03 00:00:00 | 1 | 80 | 219 |
| 6 | 2010-01-03 00:00:00 | 2 | 99 | 220 |
+----+---------------------+----------+-----------+---------+
Currently I am getting the latest data for each device using a query on the DataLog table with:
Where DeviceId = 1 ORDER BY timestamp DESC LIMIT 1
What I would like is one query to return a list of all devices, with the columns joined with the latest data for each device like this:
+----------+------------+----------------------------+---------------------+-----------+---------+
| deviceId | deviceName | deviceDescription | time_stamp |FuelLevel | Voltage |
+----------+------------+----------------------------+---------------------+-----------+---------+
| 1 | System 1 | Main System in Server Room | 2010-01-03 00:00:00 | 80 | 219 |
| 2 | System 2 | Outdoor System | 2010-01-03 00:00:00 | 99 | 220 |
+----------+------------+----------------------------+---------------------+-----------+---------+
You can't do the "limit 1" at the outer level, you loose what you are looking for... ALL devices last entry. Use a pre-query for the last ID of each device, then join back...
select
Devices.*,
DataLog.Time_Stamp,
DataLog.FuelLevel,
DataLog.Voltage
from
( select DeviceID,
max( ID ) LastActionID
from
DataLog
group by
1 ) LastInstance
join DataLog
on LastInstance.LastActionID = DataLog.ID
join Devices
on LastInstance.DeviceID = Devices.DeviceID
order by
Devices.DeviceName
Per your last comment, I would actually change to something like...
Update your device table with a "LastLogID". Then, via a trigger an insert into your DataLog table, update the Device table immediately with that new ID... This way, you never need to pre-query the data log directly.. You'll already HAVE the last ID and run from that directly to the data log joined by that ID.
I know it's horrible, not elegant and time consuming, but this query works:
SELECT deviceId,deviceName,deviceDescription,
(SELECT time_stamp FROM datalog
WHERE datalog.DeviceId=devices.deviceId
ORDER BY time_stamp DESC LIMIT 0,1) time_stamp,
(SELECT FuelLevel FROM datalog
WHERE datalog.DeviceId=devices.deviceId
ORDER BY time_stamp DESC LIMIT 0,1) FuelLevel,
(SELECT Voltage FROM datalog
WHERE datalog.DeviceId=devices.deviceId
ORDER BY time_stamp DESC LIMIT 0,1) Voltage
FROM devices
I tried to have a single subquery retrieving multiple columns, but MySql complains because it wants only one column.
try
by the way if u want only latest row then u can search it by auto increment field (datalog_table.id)
SELECT dvc.deviceId,dvc.deviceName,dvc.deviceDescription,
dtl.time_stamp,dtl.FuelLevel,dtl.Voltage
FROM device_table dvc
INNER JOIN datalog_table dtl
ON dtl.DeviceId=dvc.deviceId
ORDER BY dtl.id LIMIT 1
SELECT
d.deviceId, d.deviceName, d.deviceDescription,
dl.time_stamp, dl.FuelLevel, dl.Voltage
FROM Device d, DataLog dl
WHERE d.deviceId=dl.deviceID
ORDER BY time_stamp DESC
LIMIT 1
Related
I have a table with these columns
-----------------------------------------------------------------------
|id | deviceId | totalMethaneInGrams | totalFeedInMinutes | date |
-----------------------------------------------------------------------
|1 |141 | 402 |305 |2020-10-13 |
|2 |141 | 410 |368 |2020-10-13 |
|3 |145 | 361 |300 |2020-10-13 |
-----------------------------------------------------------------------
Now i want to calculate an average of totalMethaneInGrams and totalFeedInMinutes for a subset of devices. where date is less than some day. Group them by devicedId, order them by avg(totalMethaneInGrams) and get a global rank of those devices based on avg(totalMethaneInGrams).
This what i have up until now,
SELECT
deviceId,
ROUND(avg(totalFeedInMinutes),2) as methane,
ROUND(avg(totalMethaneInGrams)) as feed
FROM sensor_data
WHERE
deviceId IN (141,123,145) AND date < '2020-10-14'
GROUP BY deviceId
ORDER BY methane
Now what i don't understand is how to calculate global rank. My understanding is we need to calculate rank of all devices in the table. Then i can just search for the devices in the returned global dataset. Can it be done in a single query ?
mysql does not do rank over multiple columns well (eg feed within methane) a workaround is to do separately and join
SELECT t.deviceId,
ROUND(avg(totalMethaneInGrams),2) methane,
rank() over (order by ROUND(avg(totalMethaneInGrams),2 ) desc) as rankmethane,
max(feed) feed,
max(rankfeed) rankfeed
fROM t
join
(SELECT
deviceId,
ROUND(avg(totalFeedInMinutes)) as feed,
rank() over (order by ROUND(avg(totalFeedInMinutes),2 ) desc) as rankfeed
fROM t
WHERE deviceId IN (141,123,145) AND date < '2020-10-14'
group by deviceid) s
on s.deviceid = t.deviceid
WHERE t.deviceId IN (141,123,145) AND date < '2020-10-14'
GROUP BY t.deviceId ;
+----------+---------+-------------+------+----------+
| deviceId | methane | rankmethane | feed | rankfeed |
+----------+---------+-------------+------+----------+
| 141 | 406.00 | 1 | 337 | 2 |
| 145 | 361.00 | 2 | 400 | 1 |
+----------+---------+-------------+------+----------+
2 rows in set (0.002 sec)
Having a hard time wrapping my mind around what seems should be a simply query.
So let's say we have a table that keeps track of amount of widgets/balloons in each store by date. How would you get a list of stores and their latest widget/balloons count?
i.e.
mysql> SELECT * FROM inventory;
+----+------------+-------+---------+---------+
| id | invDate | store | widgets | balloons|
+----+------------+-------+---------+---------+
| 1 | 2011-01-01 | 3 | 50 | 35 |
| 2 | 2011-01-04 | 2 | 50 | 35 |
| 3 | 2013-07-04 | 3 | 12 | 78 |
| 4 | 2020-07-04 | 2 | 47 | 18 |
| 5 | 2020-08-06 | 2 | 16 | NULL |
+----+------------+-------+---------+---------+
5 rows in set (0.00 sec)
Would like the result table to list all stores and their latest inventory of widgets/baloons
store, latest widgets, latest balloons
+-------+-----------+---------+
| store | widgets | baloons |
+-------+-----------+---------+
| 2 | 16 | NULL |
| 3 | 12 | 78 |
+-------+-----------+---------+
or grab latest non NULL value for balloons.
This works for all versions of MySQL
select i.*
from inventory i
join
(
select store, max(invDate) as maxDate
from inventory
group by store
) tmp on tmp.store = i.store
and tmp.maxDate = i.invDate
With MySQL 8+ you can do window functions:
with cte as
(
select store, widgets, balloons,
ROW_NUMBER() OVER(PARTITION BY store ORDER BY invDate desc) AS rn
from inventory
)
select * from cte where rn = 1
You can use a correlated sub query to get latest record for each store
select i.*
from inventory i
where i.invDate = (
select max(invDate)
from inventory
where i.store = store
)
order by i.store
DEMO
I am running a mysql - 10.1.39-MariaDB - mariadb.org binary- database.
I am having the following table:
| id | date | api_endpoint | ticker | open | high | low | close | volume |
|------|---------------------|--------------|--------|-----------|-----------|-----------|-----------|-----------|
| 18 | 2019-08-07 00:00:00 | daily | AAPL | 195.41000 | 199.56000 | 193.82000 | 199.04000 | 33364400 |
| 19 | 2019-08-06 00:00:00 | daily | AAPL | 196.31000 | 198.07000 | 194.04000 | 197.00000 | 35824800 |
| 20 | 2019-08-05 00:00:00 | daily | AAPL | 197.99000 | 198.65000 | 192.58000 | 193.34000 | 52393000 |
| 21 | 2019-08-02 00:00:00 | daily | AAPL | 205.53000 | 206.43000 | 201.62470 | 204.02000 | 40862100 |
| 44 | 2019-08-01 00:00:00 | monthly | AAPL | 213.90000 | 218.03000 | 206.74000 | 208.43000 | 54017900 |
| 5273 | 1999-09-07 00:00:00 | monthly | AAPL | 73.75000 | 77.93800 | 73.50000 | 76.37500 | 246198400 |
I am calculating returns using mysql:
SELECT *
,(CLOSE - (SELECT (t2.close)
FROM prices t2
WHERE t2.date < t1.date
ORDER BY t2.date DESC
LIMIT 1 ) ) / (SELECT (t2.close)
FROM prices t2
WHERE t2.date < t1.date
ORDER BY t2.date DESC
LIMIT 1 ) AS daily_returns
FROM prices
The above query adds a column daily_returns to my table.
I would like to get the top 5 highest daily_returns. I tried to use ORDER BY, however, this does not work with a calculated column.
Any suggestions how to get the top 5 highest daily_returns?
Update: MySQL 8
SELECT
prices.*,
prices.close - LAG(prices.close) OVER w AS daily_return
FROM prices
WHERE api_endpoint = 'daily'
WINDOW w AS (ORDER BY prices.`date` ASC)
ORDER BY daily_return DESC
LIMIT 5;
MySQL 5.7 & Lower
Use MySQL variable to store close value of last day. Compare it with close value to the current row to do the calculation.
SELECT
*
FROM (
SELECT
prices.*,
(`close` - #old_close) / #old_close AS daily_return, -- Use #old_case, currently it has value of old row, next column will set it to current close value.
#old_close:= `close` -- Set #old_close to close value of this row, so it can be used in next row
FROM prices,
(SELECT #old_close:= 0 as o_c) AS t -- Initialize old_close as 0
WHERE api_endpoint = 'daily'
ORDER BY `date` ASC -- return is calculated based on last day close, so keep it sorted based on ascending order of date
) AS tt
ORDER BY daily_return DESC
LIMIT 5;
Reference: How to get diff between two consecutive rows
I have customer table with 10 columns. In the table customer id is repeated. I need to take only one record every customer but randomly.
Let suppose customer table contain total 10000 records. But distinct customers is only 500.
So i need only 500 distinct customer data randomly.
I am using mysql 5.7.
Consider the following...
SELECT * FROM my_table;
+----+-------------+
| id | customer_id |
+----+-------------+
| 1 | 1 |
| 2 | 1 |
| 3 | 3 |
| 4 | 5 |
| 5 | 3 |
| 6 | 2 |
| 7 | 1 |
| 8 | 4 |
| 9 | 5 |
| 10 | 2 |
| 11 | 3 |
| 12 | 1 |
| 13 | 4 |
+----+-------------+
SELECT id
, customer_id
FROM
( SELECT id
, customer_id
, CASE WHEN #prev=customer_id THEN #i:=#i+1 ELSE #i:=1 END i
, #prev:=customer_id
FROM
( SELECT id
, customer_id
FROM my_table
ORDER
BY customer_id
, RAND()
) x
JOIN (SELECT #prev:=null,#i:=0) vars
) n
WHERE i = 1
ORDER
BY customer_id;
-- sample output, different each time --
+----+-------------+
| id | customer_id |
+----+-------------+
| 12 | 1 |
| 10 | 2 |
| 3 | 3 |
| 8 | 4 |
| 9 | 5 |
+----+-------------+
You do not want to ORDER BY RAND() because that will be extremely slow for a large table because it will actually sort all of those random records.
Instead pick a random int less than the number of rows in the table (random_num_less_than_row_count) and do this which is faster but not perfect.
SELECT * FROM atable LIMIT $random_num_less_than_row_count, 1
Or if u have a primary key that is an auto_increment you can pick a random int less than the highest id in the table (random_num_less_than_last_id) do the following which is pretty fast.
SELECT * FROM atable WHERE id >= $random_num_less_than_last_id ORDER BY id ASC LIMIT 1
I did a >= and an ORDER BY id ASC so that if you are missing ids you'll still get a result. But if you have many large gaps you need the slower first option above.
Not sure about it but it is a beginner level query which might to get the desired result
SELECT Distinct column FROM table
ORDER BY RAND()
LIMIT 500
PS: This code isn't in mysql 5.7. And if anyone have a better query more than happy to get corrected
I've got a custom written analytics system running and I'm trying to write a query that returns users who landed on a specific page as their first hit. The relevant parts of the table is setup as such, with some simple data:
pageviews Table
+----+---------------------+----------+-------------+
| id | time_in | users_id | articles_id |
+----+---------------------+----------+-------------+
| 0 | 2013-08-15 00:00:00 | 0 | 0 |
| 1 | 2013-08-16 00:00:00 | 0 | 1 |
| 2 | 2013-08-17 00:00:00 | 1 | 1 |
| 3 | 2013-08-18 00:00:00 | 1 | 0 |
| 4 | 2013-08-19 00:00:00 | 1 | 1 |
| 5 | 2013-08-20 00:00:00 | 2 | 1 |
+----+---------------------+----------+-------------+
NOTE: The ID fields in my DB are actually using GUIDs, not ints as in this simple example.
Now, if I want to see who read article 1 as their first hit, I want my query to return users 1 and 2, but not 0, as user 0 saw article 0 as their first hit on the site. Conversely, if I want to see who read article 0 first, the query would only return user 0.
Here is my query thus far:
SELECT
*
FROM
pageviews
WHERE
articles_id = 1
GROUP BY
users_id
ORDER BY
time_in
But this returns distinct user IDs for all users who've read article 1, not filtering out the users who did not see it as their first result. I feel like I'm going the wrong direction with my query, so I'm turning towards you guys.
Thanks in advance.
One way to do it
SELECT v.users_id
FROM pageviews v JOIN
(
SELECT users_id, MIN(time_in) time_in
FROM pageviews
GROUP BY users_id
) q ON v.users_id = q.users_id AND v.time_in = q.time_in
WHERE v.articles_id = 1
Output:
| USERS_ID |
------------
| 1 |
| 2 |
Here is SQLFiddle demo
Another way:
SELECT users_id
FROM pageviews p
WHERE articles_id = 1
AND time_in = (SELECT MIN(time_in) from pageviews p2 WHERE p2.users_id = p.users_id)
SQLFiddle here