Working on building out a php file to create a JSON for usage in a web application for keeping track of trucking margins for specific shipping routes. The goal is to have a set of data which I can insert into dropdowns for each customer the company works with to keep track of averaged revenues and profits for each customer’s shipping lanes. Essentially, I need to query a list of every customer for the past two years, every combined destination/origination/type of truck, and then display the averages for profit, revenue, and count of shipments meeting the earlier criteria.
I’m working in PHP 7, phpMyAdmin.
$currentDate = date('Y-m-d');
$twoYearsAgo = Date('Y-m-01', strtotime($currentDate . " -2 years + 1 month"));
$customers = "SELECT DISTINCT customer_name FROM `wo_main_alldata` WHERE ship_date BETWEEN '$twoYearsAgo' AND '$currentDate'";
$customerResult = mysqli_query($conn, $customers);
$customerList= array();
while ($row = mysqli_fetch_array($customerResult)) {
$customerList[] = $row[0];
}
$lanesArray = array();
foreach ($customerList as $customerName){
$laneData = "SELECT DISTINCT type_of_shipment, pickup_city, pickup_state, consignee_city, consignee_state
FROM wo_main_alldata
WHERE customer_name = '$customerName'
AND pickup_city != ''";
$lanesResult = mysqli_query($conn, $laneData);
while ($row2 = mysqli_fetch_array($lanesResult)){
$equipment = $row2[0];
$pu_city = $row2[1];
$pu_state = $row2[2];
$dest_city = $row2[3];
$dest_state = $row2[4];
$laneAverages = "SELECT AVG(proj_revenue), AVG(proj_gross_profit), COUNT(pro_num) FROM wo_main_alldata WHERE type_of_shipment = '$equipment' AND pickup_city = '$pu_city' AND pickup_state = '$pu_state' AND consignee_city = '$dest_city' AND consignee_state = '$dest_state'";
$lanesAverageResult = mysqli_query($conn, $laneAverages);
while ($row3 = mysqli_fetch_array($lanesAverageResult)){
}
mysqli_free_result($lanesAverageResult);
}
}
I’ve yet to implement the specifics for pushing the data to the array for output, but as it stands currently, this file can take up to 10 minutes to fully process as some customers have 2000+ shipping routes in the past two years.
Is there any way to make this preform in a faster manner?
EDIT: I’m aware that I need to be using prepared statements, for right now all of this is being done locally and I’m simply attempting to optimize run time.
EDIT 2: SOLUTION!
SELECT customer_name, type_of_shipment, pickup_city, pickup_state, consignee_city, consignee_state, AVG( proj_revenue ) , AVG( proj_gross_profit ) , COUNT( pro_num )
FROM wo_main_alldata
WHERE ship_date
BETWEEN '$twoYearsAgo'
AND '$currentDate'
AND pickup_city != ''
GROUP BY customer_name, type_of_shipment, pickup_city, pickup_state, consignee_city, consignee_state
2
Answers
One of the main reasons this is taking so long to run is because it’s making a lot of separate calls to the database. From what I understand in some cases you are making over 2000+ separate SQL queries per customer. You are going to want to look into condensing these down using subqueries
and/or joins.https://www.guru99.com/sub-queries.html
Edit
An example of using subqueries to condense fetching the shipping routes for each customer would be something like so. (Untested query, but it gives a rough idea on how to implement subqueries)
I am really hesitate in posting an answer, as the SQL here is causing all kinds of extra work your doing in PHP. Without the DB schema and some sample data I feel like I am just walking blind though this. For example what are all these
DISTINCT
things doing here, maybe they are needed, maybe they are extra … I have no way to know.That said here goes,
These are all the same table so it makes no sense to query the same data again, take these 2 Queries for example
You can combine them with something like this:
The first query pulls all the “Distinct” customer names, then you loop over that and use that name to lookup the next set of data in the Same Table.
This lookup will void the distinct you used in the fist one, but (yea) you have it here too. It voids it because the second query says, “Give me all records where customer_name = something”, so if the name is in there repeatedly you find all of those with the second query. The distinct may have been important when they were separate, to control the
foreach ($customerList as $customerName){
loop. But, we no longer need that loop.When we combine these we add the
customer_name
to the Select of the second one, and also add theWHERE
bits in. Then we can remove the condition that tied these togethercustomer_name = '$customerName'
because we no longer need it as it becomescustomer_name=customer_name
which is just “this row”.Now doesn’t that look much cleaner. It also eliminates all this code:
Both pick data from the same table, the second one (in the loop) simply takes the username from the first query, and looks it up again in the same table.
For the last query, you are (again) just back referencing the same data
All the stuff in the where condition comes directly from the results of the other query, so you can just eliminate that. And that leaves us with this:
I can’t really test this so you may have to make some adjustments, I am just sort of feeling my way though the logic of it. I am pretty sure that the columns in the top level query must also be in the inner sub query. Specifically
f.proj_revenue
,f.proj_gross_profit
andf.pro_num
. Probably you will get something likeUnknown column 'f.proj_gross_profit' in 'field list'
There are a few ways to fix this, by joining again on the table.
I am not really sure the best way around that as I don’t know what must be distinct. That really complicates it because if you put the ID in as above it can poison your Distinct call, by being unique per row. You may be able to just do it all in one query:
But it’s too abstract for me to tell. Don’t be afraid to take PHPmyAdmin (or whatever you manage the DB with) and work on the query right in there. That way you can play with it outside of any coding and get it just the way you want.
IN any case, if you find yourself making round trips to the DB for the same data chances are you can do it in one slightly more complex query. It’s tempting if you don’t know SQL well, but are fair at PHP to just do simple queries and deal with it in PHP.
At first this seems the “easy” way, but every bit of work you can have the DB do saves you 2 or 3 bits of work in PHP. Your code will be smaller, lighter weight, simpler and easier to read. For example (Provided you can combine these as suggested) you code becomes this:
So we just eliminated
25+
lines of PHP, with a slightly more complex query.PS sorry this is so long..
Hope it helps!