There is a table that I want to have several statistics(summary) on it. The table name is "jobs" and some of the needed statistics are:
The number of jobs that are active, the number of jobs that are in inactive status, the number of jobs which need male force, the number of jobs which pay more than x amount per month, ...
I need a query to pull all this statistics form the "jobs" table and put in into one-row result. Query result should look something like this:
+---------------+---------------+---------------+-----+
| stats1_column | stats2_column | stats3_column | ... |
+---------------+---------------+---------------+-----+
| x | y | z | ... |
+---------------+---------------+---------------+-----+
After all, do you think I fetch table summary properly like what I'm doing? Or I'm wrong and there is a better way?
I'm working on a PHP project using MySQL database.
You will probably use COUNT() aggregate function for every category and then UNION ALL and then covert rows to column by GROUP_CONCAT(). Below is the possible query that you could have:
SELECT
GROUP_CONCAT(if(Category = 'Active Jobs', CategoryCount, NULL)) AS 'Active_Jobs',
GROUP_CONCAT(if(Category = 'Inactive Jobs', CategoryCount, NULL)) AS 'Inactive_Jobs'
FROM
(
SELECT 'Active Jobs' as Category, COUNT(*) as CategoryCount
FROM Jobs
WHERE Status = 'Active'
UNION ALL
SELECT 'Inactive Jobs' as Category, COUNT(*) as CategoryCount
FROM Jobs
WHERE Status = 'Inactive'
) tbl
See Sample Fiddle Demo
It would be something like this:
SELECT SUM(IF(active, 1, 0)) stats_active,
SUM(IF(active, 0, 1)) stats_inactive,
SUM(IF(needs_male, 1, 0)) stats_needs_male,
SUM(IF(pay > 1000, 1, 0)) stats_well_paid,
.... and so on ....
FROM jobs
The problem with this approach/query is that it is inefficient because it scans the entire table. If the table has lots of rows, performance will be affected.
If the table has proper indexes, a more efficient way to do it is to run a query for each stat you need. In PHP this would be something like (ignore typos):
$stats = array(
'stats_active' => 'SELECT COUNT(*) FROM jobs WHERE active',
'stats_inactive' => 'SELECT COUNT(*) FROM jobs WHERE NOT active',
'stats_needs_male' => 'SELECT COUNT(*) FROM jobs WHERE needs_male',
.... others here ....
);
$result = (object) array();
foreach($stats as $name => $query) {
$result->$name = db_fetch_single_result($query); // database specific code goes here
}
this is kind of a hard read, but what I think you want, but could be wrong is like this?
Select
stats1_column,
stats2_column,
stats3_colum
From
Jobs
This is pretty basic that why I am unsure if this is what you want.
Related
I have 4 dropdowns in the frontend, each representing a hierarchy level in the company:
Groups > Units > Departments > Teams
So users can select a group then it will only show units of that group and departments of that group and so on..
But users can also select multiple choices so they can select Group1, Group2, and it will show all the corresponding hierarchy "items".
I let the users select however they want - they can choose any department, any group and team and so on.
The goal is to count the number of results from the lowest of each selected hierarchy.
Example:
id
username
group
unit
department
team
1
user1
g1
u1
d1
t1
2
user2
g1
u1
d1
t2
3
user3
g3
u6
d12
t30
4
user4
g25
u54
d70
t88
The way I currently do it is to clean the selections so if for example a user clicked g1>u1>d1 and g25>u54>d70>t88, I build it with query builder like so:
foreach ($data as $row) {
$myQuery->orWhere(function($query) use ($filter) {
foreach ($filter as $column => $value) {
$query->where($column, '=', $value)
}
}
}
So the the raw SQL query would result in this:
SELECT * FROM table
WHERE (group='g1' AND unit='u1' AND department='d1')
OR WHERE (group='g25' AND unit='u54' AND department='d70' AND team='t88')
But I need to return the count for each, not the actual results.
I could do something like the following using UNION:
SELECT group
, unit
, department
, NULL as team
, COUNT(*) AS rows
FROM table
WHERE group='g1'
AND unit='u1'
AND department='d1'
GROUP
BY group
, unit
, department
UNION ALL
SELECT group
, unit
, department
, team
, COUNT(*) AS rows
FROM table
WHERE group='g25'
AND unit='u54'
AND department='d70'
AND team='t88'
GROUP
BY group
, unit
, department
, team
But I am not sure I can convert it do work with Query Builder.
Also, I feel like maybe the entire way I organize the data or build the initial query is wrong for the purpose.
Should I arrange the data differently completely? Or there is a way to achieve what I'm trying in this way?
As an idea, you can do something like this:
// prepare a query to operate with
$countQuery = DB::table('table');
// I don't know how do you get those filters, let's pretend it's like this
foreach ($filters as $index => $filter) {
// make a count subqueries for every bunch of filters and name it like filter0, filter1 etc.
$countQuery = $countQuery->addSelect(["filter".$index =>
function ($q) use ($filter) {
$q->selectRaw('COUNT(table.id)')
->from('table');
foreach ($filter as $column => $value) {
$q->where($column, '=', $value);
}
}]);
}
var_dump($countQuery->get());
I have queries inside a cfloop that makes the process very slow. Is there a way to make this query faster?
<cfquery name="GetCheckRegister" datasource="myDB">
SELECT * FROM CheckRegister, ExpenseType
Where PropertyID=10
and ExpenseType.ExpenseTypeID=CheckRegister.ExpenseTypeID
</cfquery>
<CFOUTPUT query=GetCheckRegister>
<cfquery name="GetVendorName" datasource="myDB"> SELECT * FROM Vendors WHERE VendorID=#VendorID#</cfquery>
<!--- I use the vendor name here --->
<cfset local.CreditDate = "" />
<cfquery name="getTenantTransactionDateFrom" dataSource="myDB">
Select TenantTransactionDate as fromDate From TenantTransactions
Where CheckRegisterID = #CheckRegisterID#
Order By TenantTransactionDate Limit 1
</cfquery>
<cfquery name="getTenantTransactionDateTo" dataSource="myDB">
Select TenantTransactionDate as ToDate From TenantTransactions
Where CheckRegisterID = #CheckRegisterID#
Order By TenantTransactionDate desc Limit 1
</cfquery>
<cfif getTenantTransactionDateFrom.fromDate neq "" AND getTenantTransactionDateTo.ToDate neq "">
<cfif getTenantTransactionDateFrom.fromDate eq getTenantTransactionDateTo.ToDate>
<cfset local.CreditDate = DateFormat(getTenantTransactionDateFrom.fromDate, 'mm/dd/yyyy') />
<cfelse>
<cfset local.CreditDate = DateFormat(getTenantTransactionDateFrom.fromDate, 'mm/dd/yyyy') & " - " & DateFormat(getTenantTransactionDateTo.ToDate, 'mm/dd/yyyy') />
</cfif>
</cfif>
<!--- I use the local.CreditDate here --->
<!--- Here goes a table with the data --->
</CFOUTPUT>
cfoutput works like a loop.
As others have said, you should get rid of the loop and use joins. Looking at your inner loop, the code retrieves the earliest and latest date for each CheckRegisterID. Instead of using LIMIT, use aggregate functions like MIN and MAX and GROUP BY CheckRegisterID. Then wrap that result in a derived query so you can join the results back to CheckRegister ON id.
Some of the columns in the original query aren't scoped, so I took a few guesses. There's room for improvement, but something like is enough to get you started.
-- select only needed columns
SELECT cr.CheckRegisterID, ... other columns
FROM CheckRegister cr
INNER JOIN ExpenseType ex ON ex.ExpenseTypeID=cr.ExpenseTypeID
INNER JOIN Vendors v ON v.VendorID = cr.VendorID
LEFT JOIN
(
SELECT CheckRegisterID
, MIN(TenantTransactionDate) AS MinDate
, MAX(TenantTransactionDate) AS MaxDate
FROM TenantTransactions
GROUP BY CheckRegisterID
) tt ON tt.CheckRegisterID = cr.CheckRegisterID
WHERE cr.PropertyID = 10
I'd highly recommend reading up on JOIN's as they're critical to any web application, IMO.
You should get all of your data in one query, then work with that data to output what you want. Multiple connections to a database almost always be more resource-intensive than getting the data in one trip and working with it. To get your results:
SQL Fiddle
Initial Schema Setup:
CREATE TABLE CheckRegister ( checkRegisterID int, PropertyID int, VendorID int, ExpenseTypeID int ) ;
CREATE TABLE ExpenseType ( ExpenseTypeID int ) ;
CREATE TABLE Vendors ( VendorID int ) ;
CREATE TABLE TenantTransactions ( checkRegisterID int, TenantTransactionDate date, note varchar(20) );
INSERT INTO CheckRegister ( checkRegisterID, PropertyID, VendorID, ExpenseTypeID )
VALUES (1,10,1,1),(1,10,1,1),(1,10,2,1),(1,10,1,2),(1,5,1,1),(2,10,1,1),(2,5,1,1)
;
INSERT INTO ExpenseType ( ExpenseTypeID ) VALUES (1), (2) ;
INSERT INTO Vendors ( VendorID ) VALUES (1), (2) ;
INSERT INTO TenantTransactions ( checkRegisterID, TenantTransactionDate, note )
VALUES
(1,'2018-01-01','start')
, (1,'2018-01-02','another')
, (1,'2018-01-03','another')
, (1,'2018-01-04','stop')
, (2,'2017-01-01','start')
, (2,'2017-01-02','another')
, (2,'2017-01-03','another')
, (2,'2017-01-04','stop')
;
Main Query:
SELECT cr.*
, max(tt.TenantTransactionDate) AS startDate
, min(tt.TenantTransactionDate) AS endDate
FROM CheckRegister cr
INNER JOIN ExpenseType et ON cr.ExpenseTypeID = et.ExpenseTypeID
INNER JOIN Vendors v ON cr.vendorID = v.VendorID
LEFT OUTER JOIN TenantTransactions tt ON cr.checkRegisterID = tt.CheckRegisterID
WHERE cr.PropertyID = 10
GROUP BY cr.CheckRegisterID, cr.PropertyID, cr.VendorID, cr.ExpenseTypeID
Results:
| checkRegisterID | PropertyID | VendorID | ExpenseTypeID | startDate | endDate |
|-----------------|------------|----------|---------------|------------|------------|
| 1 | 10 | 1 | 1 | 2018-01-04 | 2018-01-01 |
| 1 | 10 | 1 | 2 | 2018-01-04 | 2018-01-01 |
| 1 | 10 | 2 | 1 | 2018-01-04 | 2018-01-01 |
| 2 | 10 | 1 | 1 | 2017-01-04 | 2017-01-01 |
I only added 2 check registers, but CheckRegisterID 1 has 2 vendors and 2 Expense Types for Vendor 1. This will look like repeated data in your query. If your data isn't set up that way, you won't have to worry about it in the final query.
Use proper JOIN syntax to get the related data you need. Then you can aggregate that data to get the fromDate and toDate. If your data is more complex, you may need to look at Window Functions. https://dev.mysql.com/doc/refman/8.0/en/window-functions.html
I don't know what your final output looks like, but the above query gives you all of the query data in one pass. And each row of that data should give you what you need to output, so now you've only got one query to loop over.
It has been a long time since I did any ColdFusion development but a common rule of thumb would be to not call queries within a loop. Depending on what you are doing, loops can be considered an RBAR (row by agonizing row) operation.
You are essentially defining one query and looping over each record. For each record, you are doing three additional queries aka three additional database network calls per record. The way I see it, you have a couple options:
Rewrite your first query to already include the data you need within each
record check.
Leave your first query the way it is and create functionality that provides more information when the user interacts with the record and do it asynchronously. Something like a "Show Credit Date" link which goes out and gets the data on demand.
Combine the queries in your loop to be one query instead of the two getTenantTransaction... and see if performance improves. This reduces the RBAR database calls from three to two.
You always want to avoid having queries in a loop. Whenever you query the database, you have roundtrip (from server to database and back from database to server) which is slow by nature.
A general approach is to bulk data by querying all required information with as few statements as possible. Joining everything in a single statement would be ideal, but this obviously depends on your table schemes. If you cannot solve it using SQL only, you can transform your queries like this:
GetCheckRegister...
(no loop)
<cfquery name="GetVendorName" datasource="rent">
SELECT * FROM Vendors WHERE VendorID IN (#valueList(GetCheckRegister.VendorID)#)
</cfquery>
<cfquery name="getTenantTransactionDateFrom" dataSource="rent">
Select TenantTransactionDate as fromDate From TenantTransactions
Where CheckRegisterID IN (#valueList(GetCheckRegister.CheckRegisterID)#)
</cfquery>
etc.
valueList(query.column) returns a comma delimited list of the specified column values. This list is then used with MySQL's IN (list) selector to retrieve all records that belong to all the listed values.
Now you would only have a single query for each statement in your loop (4 queries total, instead of 4 times number of records in GetCheckRegister). But all records are clumped together, so you need to match them accordingly. To do this we can utilize ColdFusion's Query of Queries (QoQ), which allows you to query on already retrieved data. Since the retrieved data is in memory, accessing them is quick.
GetCheckRegister, GetVendorName, getTenantTransactionDateFrom, getTenantTransactionDateTo etc.
<CFOUTPUT query="GetCheckRegister">
<!--- query of queries --->
<cfquery name="GetVendorNameSingle" dbType="query">
SELECT * FROM [GetVendorName] WHERE VendorID = #GetCheckRegister.VendorID#
</cfquery>
etc.
</CFOUTPUT>
You basically moved the real queries out of the loop and instead query the result of the real queries in your loop using QoQ.
Regardless of this, make sure your real queries are fast by profiling them in MySQL. Use indices!
Using the main query and the loop to process the data could be faster if:
Using SELECT with only specific fields you need, to avoid fetching so many columns (instead of SELECT *), unless you are using all the fields:
SELECT VendorID, CheckRegisterId, ... FROM CheckRegister, ExpenseType ...
Using less subqueries in the loop, trying to join the table to the main query. For example, using the Vendors table in the main query (if it could be posible to join this table)
SELECT VendorID, CheckRegisterId, VendorName ... FROM CheckRegister, ExpenseType, Vendors ...
Finally, you can estimate the time of the process and detect the performance problem:
ROWS = Number of rows of the result in the main query
TIME_V = Time (ms) to get the result of GetVendorName using a valid VendorId
TIME_TD1 = Time (ms) to get the result of getTenantTransactionDateFrom using a valid CheckRegisterID
TIME_TD2 = Time (ms) to get the result of getTenantTransactionDateTo using a valid CheckRegisterID
Then, you can calculate the resulting time using TOTAL = ROWS * (TIME_V+ TIME_TD1 + TIME_TD2).
For example, if ROWS=10000 , TIME_V = 30, TIME_TD1 = 15, TIME_TD2 = 15 : RESULT = 10000 * (30 + 15 + 15) = 10000 * 60 = 600000 (ms) = 600 (sec) = 10 min
So, for 10000 rows, one milisecond of the loop results in 10 seconds added to the process.
When you have many resulting rows for the main query, you need to minimize the query time of each element in the loop. Each milisecond affects in the performance of the loop. So you need to make sure there are the right indexes for each field filtered for each query in the loop.
userid data_type, timespentaday
1 League of Legends 500
1 Hearthstone 1500
1 Hearthstone 1400
2 World of Warcraft 1200
1 Dota 2 100
2 Final Fantasy 500
1 Dota 2 700
Given this data. I would like to query the most time each user has spent on every.
Output desired:
User League Of Legends Hearthstone World of Warcraft Dota 2
1 500 1500 0 700
2 0 0 1200 0
Something along the lines of this is something I've tried
SELECT t1.* FROM user_info GROUP BY userid JOIN(
SELECT(
(SELECT max(timespentaday) where data_type='League of Legends'),
(SELECT max(timespentaday) where data_type='Hearhstone'),
(SELECT max(timespentaday) where data_type='Dota 2)'
FROM socialcount AS t2
) as t2
ON t1.userid = t2.userid
basically to do this you need the greatest n per group.. there is a good article on it but the gist is in mysql you have to use variables to even get close to this.. especially with doing a pivot on the table (a fake pivot since MySQL doesn't have native support for that).
SELECT userid,
MAX(CASE WHEN data_type = "League of Legends" THEN timespentaday ELSE 0 END) as "League of Legends",
MAX(CASE WHEN data_type = "Hearthstone" THEN timespentaday ELSE 0 END) as "Hearthstone",
MAX(CASE WHEN data_type = "Dota 2" THEN timespentaday ELSE 0 END) as "Dota 2",
MAX(CASE WHEN data_type = "World of Warcraft" THEN timespentaday ELSE 0 END) as "World of Warcraft",
MAX(CASE WHEN data_type = "Final Fantasy" THEN timespentaday ELSE 0 END) as "Final Fantasy"
FROM
( SELECT *, #A := if(#B = userid, if(#C = data_type, #A + 1, 1), 1) as count_to_use, #B := userid, #C := data_type
FROM
( SELECT userid, timespentaday, data_type
FROM gamers
CROSS JOIN(SELECT #A := 0, #B := 0, #C := '') temp
ORDER BY userid ASC, data_type ASC, timespentaday DESC
) t
HAVING count_to_use = 1
)t1
GROUP BY userid
DEMO
NOTE:
MySQL DOCS is quite clear on warnings about using user defined variables:
As a general rule, you should never assign a value to a user variable
and read the value within the same statement. You might get the
results you expect, but this is not guaranteed. The order of
evaluation for expressions involving user variables is undefined and
may change based on the elements contained within a given statement;
in addition, this order is not guaranteed to be the same between
releases of the MySQL Server. In SELECT #a, #a:=#a+1, ..., you might
think that MySQL will evaluate #a first and then do an assignment
second. However, changing the statement (for example, by adding a
GROUP BY, HAVING, or ORDER BY clause) may cause MySQL to select an
execution plan with a different order of evaluation.
I am not going to give you a query with the output format you desire, as implementing that pivot table is going to be a very ugly and poorly performing query, as well as something that is not scalable as the number of distinct games increases.
Instead, I will focus on how to query the data in the most straightforward manner and how to read it into a data structure that would be used by application logic to create the pivot view as desired.
First the query:
SELECT
userid,
data_type,
MAX(timespentaday) AS max_timespent
FROM social_count
GROUP BY userid, data_type
This would give results like
userid data_type max_timespent
------ --------- -------------
1 League of Legends 500
1 Hearthstone 1500
1 Dota 2 700
2 World of Warcraft 1200
2 Final Fantasy 500
Now when reading the results out of the database, you just read it into a structure that is useful. I will use PHP as example language, but this should be pretty easily portable to any langauge
// will hold distinct list of all available games
$games_array = array();
// will hold user data from DB
$user_data = array();
while ($row = /* your database row fetch mechanism here */) {
// update games array as necessary
if (!in_array($row['data_type'], $games_array)) {
// add this game to $games_array as it does not exist there yet
$games_array[] = $row['data_type'];
}
// update users array
$users[$row['userid']][$row['data_type']] = $row['max_timespent'];
}
// build pivot table
foreach($users as $id => $game_times) {
// echo table row start
// echo out user id in first element
// then iterate through available games
foreach($games_array as $game) {
if(!empty($game_times[$game])) {
// echo $game_times['game'] into table element
} else {
// echo 0 into table element
}
}
// echo table row end
}
You will not be able to build a query with a dynamic number of columns. You can do this query if you already know the game list, which I guess is not what you need.
BUT you can always post-process your results with any programming language, so you only have to retrieve the data.
The SQL query would look like this:
SELECT
userid AS User,
data_type AS Game,
max(timespentaday) AS TimeSpentADay
FROM
my_table
GROUP BY
userid
data_type
Then iterate over the results to fill any interface you want
OR
If and only if you can't afford any post-processing of any kind, you can retrieve the list of games first THEN you can build a query like the query below. Please bear in mind that this query is a lot less maintainable than the previous (beside being more difficult to build) and can and will cause you a lot of pain later in debugging.
SELECT
userid AS User,
max(CASE
WHEN data_type = 'Hearthstone' THEN timespentaday
ELSE NULL
END) AS Hearthstone,
max(CASE
WHEN data_type = 'League Of Legends' THEN timespentaday
ELSE NULL
END) AS `League Of Legends`,
...
FROM
my_table
GROUP BY
userid
The CASE contstruction is like an if in a procedural programming language, the following
CASE
WHEN data_type = 'League Of Legends' THEN timespentaday
ELSE NULL
END
Is evaluated to the value of timespentaday if the game is League Of Legends, and to NULL otherwise. The max aggregator simply ignore the NULL values.
Edit: added warning on the second query to explain the caveat of using a generated query thanks to Mike Brant's comment
I was given a task to show the CPU usage trend as part of a building process which also do regression test.
Each individual test case run has a record in the table RegrCaseResult. The RegrCaseResult table looks something like this:
id projectName ProjectType returnCode startTime endTime totalMetrics
1 'first' 'someType' 16 'someTime' 'someOtherTime' 222
The RegrCaseResult.totalMetrics is a special key which links to another table called ThreadMetrics through ThreadMetrics.id.
Here is how ThreadMetrics will look like:
id componentType componentName cpuTime linkId
1 'Job Totals' 'Job Totals' 'totalTime' 34223
2 'parser1' 'parser1' 'time1' null
3 'parser2' 'generator1' 'time2' null
4 'generator1' 'generator1' 'time3' null
------------------------------------------------------
5 'Job Totals' 'Jot Totals' 'totalTime' 9899
...
The rows with the compnentName 'Job Totals' is what the totalMetrics from RegrCaseResult table will link to and the 'totalTime' is what I am really want to get given a certain projectType. The 'Job Totals' is actually a summation of the other records - in the above example, the summation of time1 through time3. The linkId at the end of table ThreadMetrics can link back to RegrCaseResult.id.
The requirements also states I should have a way to enforce the condition which only includes those projects which have a consistent return code during certain period. That's where my initial question comes from as follows:
I created the following simple table to show what I am trying to achieve:
id projectName returnCode
1 'first' 16
2 'second' 16
3 'third' 8
4 'first' 16
5 'second' 8
6 'first' 16
Basically I want to get all the projects which have a consistent returnCode no matter what the returnCode values are. In the above sample, I should only get one project which is "first". I think this would be simple but I am bad when it comes to database. Any help would be great.
I tried my best to make it clear. Hope I have achieved my goal.
Here is an easy way:
select projectname
from table t
group by projectname
having min(returncode) = max(returncode);
If the min() and max() values are the same, then all the values are the same (unless you have NULL values).
EDIT:
To keep 'third' out, you need some other rule, such as having more than one return code. So, you can do this:
select projectname
from table t
group by projectname
having min(returncode) = max(returncode) and count(*) > 1;
select projectName from projects
group by projectName having count(distinct(returnCode)) = 1)
This would also return projects which has only one entry.
How do you want to handle them?
Working example: http://www.sqlfiddle.com/#!2/e7338/8
This should do it:
SELECT COUNT(ProjectName) AS numCount, ProjectName FROM (
SELECT ProjectName FROM Foo
GROUP BY ProjectName, ReturnCode
) AS Inside
GROUP BY Inside.ProjectName
HAVING numCount = 1
This groups all the ProjectNames by their names and return codes, then selects those that only have a single return code listed.
SQLFiddle Link: http://sqlfiddle.com/#!2/c52b6/11/0
You can try something like this with Not Exists:
Select Distinct ProjectName
From Table A
Where Not Exists
(
Select 1
From Table B
Where B.ProjectName = A.ProjectName
And B.ReturnCode <> A.ReturnCode
)
I'm not sure exactly what you're selecting, so you can change the Select statement to what you need.
Here is a structure of my tables:
Here is what I want as a result of a query (considering that user provides me with 2|4|1 pattern):
Here is what I tried:
SELECT parcel.TrackCode, parcelType.Name, GROUP_CONCAT(track.DateTime SEPARATOR '|') AS dt
FROM track
JOIN parcel ON track.ParcelID = parcel.ID
JOIN parcelType ON parcel.ParcelTypeID = parcelType.ID
JOIN event ON track.EventID = event.ID
GROUP BY parcel.ID;
The result is:
So the problem is that I need GROUP_CONCAT() to divide data to several fields (date where track.eventID = 3, date where track.eventID = 1, date where track.eventID = 5, date where track.eventID = 7 # considering that the pattern is 3|1|5|7). Any ideas?
I would suggest running two queries. First this one, to get the relevant events:
SELECT ParcelID, EventID, DateTime
FROM track
WHERE EventID IN(1, 2, 4)
Store the results of this query in a map of parcel ids to arrays of events where the key is the parcel ID and the value is another array. In that inner array, the key is the event ID and the value is the event date.
array(1 => array(
2 => '2012-05-15 15:33:00',
4 => '2012-05-22 11:35:41',
1 => '2012-05-04 18:58:30'
),
2 => array(
2 => '2012-07-01 09:05:56',
4 => '2012-07-14 13:32:00',
1 => '2012-06-27 12:44:32'
)
);
Then, use a query like this next one to get the list of parcels, and for each one, you can easily look in memory at the results of the previous query to find out the dates of each of those events, for any given parcel ID.
SELECT parcel.ID, parcel.TrackCode, parceltype.Name
FROM parcel
JOIN parceltype ON parceltype.ID = parcel.ParcelTypeID
Note: this answer is a trimmed version of the conversation that took place in the MySQL chat room
If you want the IDs also, you should GROUP_CONCAT the ids from track, they will be given in the same order too.
Or set an ORDER BY at the end of the query and it will manipulate that data as well.