MySQL query to find location of the last entry of an device - mysql

I have two tables (in a SQL Server database) as following:
create table tblDevices(
idDevice varchar(255) not null,
(...)
primary key(idGUID));
create table tblEnvironmentLog(
ID int not null auto_increment,
idDevice varchar(30) not null,
txtLocation varchar(255),
datDate date,
datTime time,
primary key(ID));
Each event in tblEnvironmentLog belongs to a device in tblDevice, and each record has a date and a location(<- location of the device). An device may have several records(at least one).
I need a SQL query that finds for each idDevice in tblDevices the location of the latest record among all its record.
I've tried to write the query for long time, but couldn't find the solution, so any help or hint will be welcomed.

So, you basically want to get the rows that correspond to the latest date/time for each device. You've specified both MySQL and SQL Server. This is for SQL Server.
SELECT *
FROM tblDevices t1
INNER JOIN tblEnvironmentLog t2
ON t1.idDevice = t2.idDevice
WHERE t2.ID = (SELECT TOP 1 t3.ID
FROM tblEnvironmentLog t3
WHERE t2.idDevice = t3.idDevice
ORDER BY t3.datDate DESC, t3.datTime DESC)

You need something like this -
SELECT t1.*, t2.datDate, t2.datTime
FROM tblDevices t1
JOIN tblEnvironmentLog t2 on t1.idDevice = t2.idDevice
WHERE t2.id in ( SELECT MAX(ID) FROM tblEnvironmentLog GROUP BY idDevice)

Try a query like below. In case you need to know better about how to add date time individual components correctly, see this answer
Explanation:
We use an inner query (as E) to find the max date time per idDevice, and then join it to tblDevices (as D) as well as tblEnvironmentLog (as E2) to get desired results.
Query
select
DISTINCT -- this is needed as many result pair may come up with same values
D.iDDevice, E2.Location
from tblDevices D left join
(
Select
idDevice,
max( cast(datDate as datetime)+cast (datTime as dattime)) as dt
from tblEnvironmentLog
group by idDevice) E
on D.idDevice =E.idDevice
left join tblEnvironmentLog E2 on E.idDevice=E2.idDevice and
cast(E2.datDate as datetime)+cast(E2.datTime as datetime)=E.dt

Related

How to select max / distinct record in MySQL using a deleted_at column

I am trying to select distinct rows under the following two rules:
If its deleted_at date is null then it is the most recent record, select it
If it is the latest deleted_at date (and there's not a record with a NULL), it is also the most recent record, select it
Consider this table:
The result I am looking for would be:
I'm using MySQL mariaDB v10.1.33 which does not have all the functions I am use to.
NULL was being ignored so I use a
coalesce(fc.deleted_at, CURRENT_TIMESTAMP())
to trick it into being the latest date. That way I can use max() function to select it. However, when I use this it is mismatching the data in the rows! i.e. this:
SELECT max(coalesce(fc.deleted_at, CURRENT_TIMESTAMP())), folder_id, code
FROM folder_code fc
WHERE fc.folder_id = 5683
returns:
I did some reading and this is a common problem where it seems to be ordering and selecting the max of each column independent of the row it is associated with and there are suggestions to use group by and order by to overcome it. However when I do this I get the same result i.e. this also returns the same as above:
SELECT max(coalesce(fc.deleted_at, CURRENT_TIMESTAMP())) as maxdeleteddate, fc.folder_id, fc.code
FROM folder_code fc
WHERE fc.folder_id = 5683
GROUP BY fc.folder_id
ORDER BY maxdeleteddate desc
How to I achieve my desired result?
Thank you
This is how I would do it:
SELECT f1.*
FROM folder f1
INNER JOIN (
SELECT folder_id,
NULLIF(MAX(IF(deleted_at IS NULL,NOW(),deleted_at)),NOW()) AS deleted_at
FROM folder
GROUP BY folder_id
) f2 ON f2.folder_id = f1.folder_id AND f2.deleted_at <=> f1.deleted_at
And here's a fiddle: https://www.db-fiddle.com/f/wzCYktpavBNnJu2uejPpe9/1
The idea is to get the groupwise-max, then join your table against itself. If you simply group the rows, you are not guaranteed to get the correct values for non-aggregated columns.
There is also a trick with deleted_at column, using NOW() if it's null, then using NULLIF() to set it back to NULL for the join.
This approach also benefits from the fact that it potentially uses indexes if they exist.
If you are using MySQL 8+, then you may use ROW_NUMBER here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY folder_id
ORDER BY -ISNULL(deleted_at), deleted_at DESC) rn
FROM folder_code
)
SELECT folder_id, code, deleted_at
FROM cte
WHERE rn = 1;
Demo
The ORDER BY clause used in the call to ROW_NUMBER places all records having a NULL deletion date after those records have a date, for each group of folder_id records. Then, the second level of sorting places more recent deletion date records first. This means that for those folders have a NULL record, it would appear first, otherwise the most recent record would appear first.
Here is an old school solution which might also work:
SELECT f1.folder_id, f1.code, f1.deleted_at
FROM folder_code f1
INNER JOIN
(
SELECT folder_id,
CASE WHEN COUNT(*) = COUNT(deleted_at)
THEN MAX(deleted_at) END AS max_deleted_at
FROM folder_code
GROUP BY folder_id
) f2
ON f1.folder_id = f2.folder_id AND
(f1.deleted_at = f2.max_deleted_at OR
(f1.deleted_at IS NULL AND f2.max_deleted_at IS NULL));
Demo
One way to get the latest date is to make sure there is no later date. Your approach to replace NULL with a high date is good and can be used for this.
select *
from folder_code fc
where not exists
(
select *
from folder_code fc2
where fc2.folder_id = fc.folder_id
and coalesce(fc2.deleted_at, date '9999-12-31') > coalesce(fc.deleted_at, date '9999-12-31')
);
You can try below - using correlated subquery
DEMO
select * from t1 a
where coalesce(deleted_at,CURRENT_TIMESTAMP()) =
(select max(coalesce(deleted_at,CURRENT_TIMESTAMP())) from t1 a1 where a.folder_id=a1.folder_id)
OUTPUT:
older_id code deleted_at
5333 12VA1 2019-09-27
5683 12SR1-X

MySQL Long Response Time

I have a valid MySQL Query that selects the latest occupancy percentage of a table from each community entered in my DB, but it seems to be scanning the entire DB of entries as the lookup time takes roughly 3-4 seconds.
With the details provided in the query below, can someone provide me with a faster/better way to lookup the latest timestamp field for each community? - I need the query to select every community entered, with the latest timestamp, but the limit for each community selected should be 1 (meaning community named "Test Community" will have possibly hundreds of submissions but I need the latest entered Timestamp selected, along with the same selection for every community entered in the table)
SELECT t1.reportID, t1.communityID, t1.region, t1.percentOccupied,
t1.TIMESTAMP, Communities.fullName
FROM NightlyReports t1
INNER JOIN Communities On t1.communityID = Communities.communityID
WHERE t1.TIMESTAMP = ( SELECT MAX( TIMESTAMP ) FROM NightlyReports WHERE
t1.communityID = NightlyReports.communityID )
AND t1.region = 'GA' ORDER BY percentOccupied DESC
In my experience, correlated subqueries often have rather poor performance; try this instead:
SELECT t1.reportID, t1.communityID, t1.region, t1.percentOccupied
, t1.TIMESTAMP, Communities.fullName
FROM NightlyReports AS t1
INNER JOIN Communities ON t1.communityID = Communities.communityID
INNER JOIN (
SELECT communityID, MAX( TIMESTAMP ) AS lastTimestamp
FROM NightlyReports
WHERE region = 'GA'
GROUP BY communityID
) AS lastReports ON t1.communityID = lastReports.communityID
AND t1.TIMESTAMP = lastReports.lastTimestamp
WHERE t1.region = 'GA'
ORDER BY percentOccupied DESC
Your query is fine. For this query (which is rewritten just a bit):
SELECT nr.reportID, nr.communityID, nr.region, nr.percentOccupied,
nr.TIMESTAMP, c.fullName
FROM NightlyReports nr INNER JOIN
Communities c
ON nr.communityID = c.communityID
WHERE nr.TIMESTAMP = (SELECT MAX(nr2.TIMESTAMP)
FROM NightlyReports nr2
WHERE nr.communityID = nr2.communityID
) AND
nr.region = 'GA'
ORDER BY percentOccupied DESC;
You want indexes on:
NightlyReports(region, timestamp, communityid)
NightlyReports(communityid, timestamp)
Communities(communityID) (this may already exist)
The correlated subquery is not per se a problem.

MINUS operator in MySQL query [duplicate]

I am trying to perform a MINUS operation in MySql.I have three tables:
one with service details
one table with states that a service is offered in
another table (based on zipcode and state) shows where this service is not offered.
I am able to get the output for those two select queries separately. But I need a combined statement that gives the output as
'SELECT query_1 - SELECT query_2'.
Service_Details Table
Service_Code(PK) Service Name
Servicing_States Table
Service_Code(FK) State Country PK(Service_Code,State,Country)
Exception Table
Service_Code(FK) Zipcode State PK(Service_Code,Zipcode,State)
MySql does not recognise MINUS and INTERSECT, these are Oracle based operations. In MySql a user can use NOT IN as MINUS (other solutions are also there, but I liked it lot).
Example:
select a.id
from table1 as a
where <condition>
AND a.id NOT IN (select b.id
from table2 as b
where <condition>);
MySQL Does not supports MINUS or EXCEPT,You can use NOT EXISTS, NULL or NOT IN.
Here's my two cents... a complex query just made it work, originally expressed with Minus and translated for MySql
With MINUS:
select distinct oi.`productOfferingId`,f.name
from t_m_prod_action_oitem_fld f
join t_m_prod_action_oitem oi
on f.fld2prod_action_oitem = oi.oid;
minus
select
distinct r.name,f.name
from t_m_prod_action_oitem_fld f
join t_m_prod_action_oitem oi
on f.fld2prod_action_oitem = oi.oid
join t_m_rfs r
on r.name = oi.productOfferingId
join t_m_attr a
on a.attr2rfs = r.oid and f.name = a.name;
With NOT EXISTS
select distinct oi.`productOfferingId`,f.name
from t_m_prod_action_oitem_fld f
join t_m_prod_action_oitem oi
on f.fld2prod_action_oitem = oi.oid
where not exists (
select
r.name,f.name
from t_m_rfs r
join t_m_attr a
on a.attr2rfs = r.oid
where r.name = oi.productOfferingId and f.name = a.name
The tables have to have the same columns, but I think you can achieve what you are looking for with EXCEPT... except that EXCEPT only works in standard SQL! Here's how to do it in MySQL:
SELECT * FROM Servicing_states ss WHERE NOT EXISTS
( SELECT * FROM Exception e WHERE ss.Service_Code = e.Service_Code);
http://explainextended.com/2009/09/18/not-in-vs-not-exists-vs-left-join-is-null-mysql/
Standard SQL
SELECT * FROM Servicing_States
EXCEPT
SELECT * FROM Exception;
An anti-join pattern is the approach I typically use. That's an outer join, to return all rows from query_1, along with matching rows from query_2, and then filtering out all the rows that had a match... leaving only rows from query_1 that didn't have a match. For example:
SELECT q1.*
FROM ( query_1 ) q1
LEFT
JOIN ( query_2 ) q2
ON q2.id = q1.id
WHERE q2.id IS NULL
To emulate the MINUS set operator, we'd need the join predicate to compare all columns returned by q1 and q2, also matching NULL values.
ON q1.col1 <=> q2.col2
AND q1.col2 <=> q2.col2
AND q1.col3 <=> q2.col3
AND ...
Also, To fully emulate the MINUS operation, we'd also need to remove duplicate rows returned by q1. Adding the DISTINCT keyword would be sufficient to do that.
In case the tables are huge and are similar, one option is to save the PK to new tables. Then compare based only on the PK. In case you know that the first half is identical or so add a where clause to check only after a specific value or date .
create table _temp_old ( id int NOT NULL PRIMARY KEY )
create table _temp_new ( id int NOT NULL PRIMARY KEY )
### will take some time
insert into _temp_old ( id )
select id from _real_table_old
### will take some time
insert into _temp_new ( id )
select id from _real_table_new
### this version should be much faster
select id from _temp_old to where not exists ( select id from _temp_new tn where to.id = tn.id)
### this should be much slower
select id from _real_table_old rto where not exists ( select id from _real_table_new rtn where rto.id = rtn.id )

Limiting selected rows in SQL Server

In the query below I want to retrieve #MaximumRecords rows, so that no ProjectId will have rows left out beyond #MaximumRecords.
For example if #MaximumRecords=100, and ProjectId=7 has records at rows number 99-102, I wish to retrieve only rows with ProjectId=1 to ProjectId=6 (The query will run again later starting at ProjectId=7). How do I do that?
SELECT TOP (#MaximumRecords)
t1.ProjectId,
t1.Row2,
t2.Row3
FROM Table1 t1
JOIN Table2 t2 ON t1.ProjectId = t2.ProjectId
ORDER BY
t1.ProjectId ASC
WHERE
t1.ProjectId > #InitialProjectId
I worked up a solution using the Sales.SalesOrderHeader and Sales.SalesOrderDetail tables in the AdventureWorks2008R2 database using the technique to get a running total described here.
The basic idea is to get the running total of the count for each SalesOrderID (ProjectID in your case) and then select all of the data for each SalesOrderID where the running total of the count is less than #MaximumRecords. You would then need to capture the maximum ID in the data returned and use that value in the next run of your query.
This task gets a little easier with SQL Server 2012 which is also described in the link given above.
Here it is...
USE AdventureWorks2008R2
IF OBJECT_ID('tempdb..#Test', 'U') IS NOT NULL DROP TABLE #Test;
DECLARE #MaximumRecords INT
DECLARE #InitialSalesOrderID INT
SET #MaximumRecords = 500
SET #InitialSalesOrderID = 43663
SELECT a.SalesOrderID, COUNT(*) AS 'Count'
INTO #Test
FROM Sales.SalesOrderHeader a
INNER JOIN Sales.SalesOrderDetail b ON a.SalesOrderID = b.SalesOrderID
WHERE a.SalesOrderID > #InitialSalesOrderID
GROUP BY a.SalesOrderID
SELECT * FROM Sales.SalesOrderHeader a
INNER JOIN Sales.SalesOrderDetail b ON a.SalesOrderID = b.SalesOrderID
WHERE a.SalesOrderID IN (
SELECT
a.SalesOrderID
FROM
#Test a
WHERE (
SELECT
SUM(Count)
FROM
#Test b
WHERE
b.SalesOrderID <= a.SalesOrderID
) < #MaximumRecords
)
Noel

Can't find a way to reduce 2 SQL queries to 1 without killing performance

I have the following SQL query which works absolutely fine:
SELECT COUNT(*), COUNT(DISTINCT `fk_match_id`)
FROM `pass`
WHERE `passer` IN ('48717','33305','49413','1640')
AND `receiver` IN ('48717','33305','49413','1640');
The numbers in the IN clause are player ID's, and can be obtained from another table in the database called player. Each row in this table has a player ID, a team_id and a match_id which is a foreign key to the match table.
I would like to automatically obtain those player ID's using the match_id. I can do this as follows:
SELECT COUNT(*), COUNT(DISTINCT `fk_match_id`)
FROM `pass`
WHERE `passer` IN
(
SELECT player_id
FROM `player`
WHERE `team_id` = someTeamID
AND `match_id` = someMatchID)
AND `receiver` IN
(
SELECT player_id
FROM `player`
WHERE `team_id` = someTeamID
AND `match_id` = someMatchID
)
)
However, apparentyly using subqueries is infamously slow and indeed, it's far too slow to use. Even using join, as follows, is far too slow:
SELECT COUNT(*), COUNT(DISTINCT `fk_match_id`)
from `pass` st1
INNER JOIN `player` st2
ON (st1.passer = st2.player_id OR st1.receiver = st2.player_id);
That too, is far too slow. So want to know if it is possible to do what I can do in 2 queries in effectively 0.0 seconds (fetching the players id's in one query and then running the first query takes virtually no time at all) in just one query, or if that is completely impossible.
Any help would be greatly appreciated.
Thanks a lot!
EDIT::
The relevant table structures are as follows:
Player:
Pass:
I want to calculate the number of passes every player has made to another player in a given line up in history. I have a match id and a team id. I can obtain the players involved in a particular match for a team by querying the player table:
SELECT player_id
FROM `player`
WHERE `team_id` = someTeamID
AND `match_id` = someMatchID
This returns something like:
1803,1930,13310,1764,58845,15157,51938,2160,18892,12002,4101,14668,80979,59013
I then want to query the pass table and return every row where one of those id's is in the passer and the receiver columns.
You need a composite index on (passer, receiver):
After adding it, try the JOIN:
SELECT COUNT(*), COUNT(DISTINCT fk_match_id)
FROM pass
INNER JOIN player AS p
ON pass.passer = p.player_id
INNER JOIN player AS r
ON r.player_id = pass.passer ;
If you want these results for a specific (team_id, match_id) combination, add an (team_id, match_id, player_id) index and then use:
SELECT COUNT(*), COUNT(DISTINCT fk_match_id)
FROM pass
INNER JOIN player AS p
ON p.team_id = someTeamID
AND p.match_id` = someMatchID
AND p.player_id = pass.passer
INNER JOIN player AS r
ON r.team_id = someTeamID
AND r.match_id` = someMatchID
AND r.player_id = pass.receiver ;