Minimize SQL queries - php

I have created a php code for listing the patients who have
1-email
2-zero future treatments
3-equal to or more than 1 finished treatments
4-latest finished treatment date is equal to 3 weeks ago from today
The problem is, I have more than 50K patients and for each patient, querying the above conditions taking a lot of time.
Is there a way of merging the sql queries into one, rather than for every patient having 3 or more queries which makes around 200k queries?
the code is below:
$today = date('Y-m-d');
$three_weeks_ago = date('Y-m-d', strtotime($today.'-3 weeks'));
$patients = $db->query("
SELECT
dg_patients_patients.id,
dg_patients_patients.first_name,
dg_patients_patients.last_name,
dg_patients_patients.email,
dg_clinics.clinic_name,
dg_clinics.clinic_address,
dg_clinics.clinic_phone
FROM dg_patients_patients
LEFT JOIN dg_clinics ON dg_patients_patients.clinic_id = dg_clinics.id
WHERE dg_patients_patients.email <> '' ORDER BY dg_patients_patients.first_name ASC ");
$now = date('Y-m-d H:i:s');
foreach ($patients as $row){
$patientID = $row['id'];
//Get Patient Future Treatments
$check_future_treatments = $db->column("SELECT id FROM dg_patient_treatment_finance WHERE treatment_type = :a1 AND patient_id = :a2 ",array("a1"=>"1","a2"=>"$patientID"));
$future_treatments = count($check_future_treatments);
//Get Patient Finished Treatments
$check_finished_treatments = $db->column("SELECT id FROM dg_patient_treatment_finance WHERE treatment_type = :a1 AND patient_id = :a2 ",array("a1"=>"2","a2"=>"$patientID"));
$finished_treatments = count($check_finished_treatments);
if($future_treatments == 0 && $finished_treatments > 0 ) {
$latest_finished_treatment_date = $db->single("SELECT plan_date FROM dg_patient_treatment_finance WHERE patient_id = :pid ORDER BY plan_date DESC LIMIT 1 ", array("pid"=>"$patientID"));
if($latest_finished_treatment_date == $three_weeks_ago){
echo $patientID.'- '.$row['first_name'].' '.$row['last_name'].' - '.$row['email'].'<br>';
}
}

You could try to LEFT JOIN on dg_patient_treatment_finance, use a GROUP BY and use SUM in combination with the CASE statement.
And at the same time calculate the MAX plan_date.
SELECT
p.id, p.first_name, p.last_name, p.email,
c.clinic_name, c.clinic_address, c.clinic_phone,
SUM(case when tf.treatment_type = 1 then 1 else 0 end) as total_treatment_type_1,
SUM(case when tf.treatment_type = 2 then 1 else 0 end) as total_treatment_type_2,
MAX(tf.plan_date) as max_plan_date
FROM dg_patients_patients p
LEFT JOIN dg_clinics c ON (p.clinic_id = c.id)
LEFT JOIN dg_patient_treatment_finance tf ON (p.id = tf.patient_id and tf.treatment_type IN (1,2))
WHERE p.email <> ''
GROUP BY
p.id, p.first_name, p.last_name, p.email,
c.clinic_name, c.clinic_address, c.clinic_phone
ORDER BY p.first_name, p.last_name
Then you can also simplify the calculation of $future_treatments and $finished_treatments by taking the content from the calculated sums.

Related

How to find first and last values within query sorted by another column

I am new to coding and this is the most complex query I have tried writing.
I am trying to create a query that will find the first and last entry for meters sorted by bunker_id within a date range. There are 12 different systems that have their meters captured when they are used.
I have several MySQL tables to track usage of systems and component configurations.
One table has a log of meters for all the systems. What I am trying to do is query this table between Date A and Date B, and receive the first and last meter values within the date range for each system. They systems may not be used everyday, but on occasion will have multiple entries in a day.
I am looking to have a query run through POST on a web page with selectors for the days and the system id's. The data will be output into an HTML table.
date
bunker_id
power_on_hours
01-01-2022
A
26115.50
01-02-2022
B
28535.13
01-02-2022
A
26257.38
01-03-2022
B
28682.73
What I am trying to return
bunker_id
starting_meters
ending_meters
A
26115.50
26257.38
B
28535.13
28682.73
The query that I have sets the starting and ending hours as the same value. I tried using MAX and MIN, but everything breaks if someone were to enter 0 for the meter.
SELECT
lu_bunkers.bunker_name as 'bunker_name',
lu_bunkers.bunker_sn,
SUM(system_utilization.hours_used) as 'total_hours',
SUM(CASE WHEN system_utilization.use_id = '1' THEN system_utilization.hours_used ELSE 0 END) as 'maintenance_hours',
SUM(CASE WHEN system_utilization.use_id = '2' THEN system_utilization.hours_used ELSE 0 END) as 'working_rf_hours',
SUM(CASE WHEN system_utilization.use_id = '3' THEN system_utilization.hours_used ELSE 0 END) as 'working_no_rf_hours',
SUM(CASE WHEN system_utilization.use_id = '4' THEN system_utilization.hours_used ELSE 0 END) as 'acd_hours',
((SUM(system_utilization.hours_used))/ ((DATEDIFF('2022-02-24', '2021-01-01')+1)*5/7*12))*100 as net_utilization,
((DATEDIFF('2022-02-24', '2021-01-01')+1)*(5/7)*12) as num_hours,
(SELECT system_meters.power_on_hours WHERE system_utilization.date_used BETWEEN '2021-01-01' AND '2022-02-24' ORDER BY system_utilization.date_used DESC LIMIT 1) as 'ending_hours',
(SELECT system_meters.power_on_hours WHERE system_utilization.date_used BETWEEN '2021-01-01' AND '2022-02-24' ORDER BY system_utilization.date_used ASC LIMIT 1) as 'starting_hours'
FROM system_utilization
LEFT JOIN lu_bunkers ON system_utilization.bunker_id = lu_bunkers.bunker_id
LEFT JOIN lu_use ON system_utilization.use_id = lu_use.use_id
LEFT JOIN system_meters ON system_utilization.id = system_meters.utilization_id
WHERE system_utilization.date_used BETWEEN '2021-01-01' AND '2022-02-24' AND system_utilization.bunker_id LIKE '%'
GROUP BY system_utilization.bunker_id
ORDER BY lu_bunkers.bunker_name
2 possible solutions
Using Joins
SELECT
a.`bunker_id`,
b.`power_on_hours` as `starting_meters`,
c.`power_on_hours` as `ending_meters`
FROM `yourtable` a
LEFT JOIN `yourtable` b
ON (
SELECT `date`
FROM `yourtable`
WHERE `bunker_id` = a.`bunker_id`
ORDER BY `date`
LIMIT 1
) = b.`date`
AND a.`bunker_id` = b.`bunker_id`
LEFT JOIN `yourtable` c
ON (
SELECT `date`
FROM `yourtable`
WHERE `bunker_id` = a.`bunker_id`
ORDER BY `date` DESC
LIMIT 1
) = c.`date`
AND a.`bunker_id` = c.`bunker_id`
GROUP BY a.`bunker_id`
Using subqueries on columns
SELECT
a.`bunker_id`,
(
SELECT `power_on_hours`
FROM `yourtable`
WHERE `bunker_id` = a.`bunker_id`
ORDER BY `date` LIMIT 1
) as `starting_meters`,
(
SELECT `power_on_hours`
FROM `yourtable`
WHERE `bunker_id` = a.`bunker_id`
ORDER BY `date` DESC LIMIT 1
) as `ending_meters`
FROM `yourtable` a
GROUP BY a.`bunker_id`

How to join two query in MYSQL

I have 2 MYSQL base queries which dependent on each other, here are my quires
#$query = "SELECT * FROM coins_tokens";
$row = $db->Execute($query);
foreach ($row as $rowItem) {
$name = $rowItem['ct_id'];
#$sql1 = "SELECT * FROM historical_data WHERE `name` = '".$name."' GROUP BY name LIMIT 30";
$row2 = $db->Execute($sql1);
foreach ($row2 as $rowItem2){
$market_cap = $rowItem2['market_cap'];
if($market_cap >= 500000000){
}
}
}
It slow down my whole process and take lot of time to execute, as there are more then 1400 results in coins_tokens, then there are more then 600000 records again 1st table, in both table ct_id and name are conman.
And what I am trying to do is to get the currencies which have more then 500million market_cap in last 7 days. So am fetching the currencies from 1st table and there historical data from 2nd table and checking if market_cap there increased in last 7 days.
Here is the structure and data of historical_data table:
SELECT
c.*,
d.`date`,
d.market_cap
FROM coins_tokens AS c
LEFT JOIN historical_data AS d ON c.ct_id = d.name
WHERE d.market_cap >= '$mketcapgrter'
AND DATE(d.`date`) >= CURRENT_DATE() - INTERVAL 30 DAY
GROUP BY d.name
ORDER BY d.market_cap DESC LIMIT 100

Combining two SQL queries PDO

I'm quite stuck on the following issue. I have a series of tables:
What I want to do is get all the information on a room, assuming that the amount of bookings don't exceed the room number available for that Room.
So to get my Room details my SQL is this:
SELECT Rooms.RoomID as RoomID,
RoomName, NumOfRooms,
MaxPeopleExistingBeds,
MaxExtraBeds,
MaxExtraPeople,
CostPerExtraPerson,
MaximumFreeChildren,
IncludeBreakfast,
MinRate
FROM Rooms, RoomDetails
WHERE Rooms.AccommodationID = :aid AND
Rooms.RoomID = RoomDetails.RoomID
GROUP BY RoomName
Which upon return gets me a list of details for those rooms as follows:
I then use this query to get the number of bookings, and the ID of the room:
SELECT Booking.RoomID,
count(Booking.RoomID) as Bookings
FROM Booking
WHERE ArriveDate >= :aDate AND
DepartDate <= :dDate AND
AccommodationID = :aid
GROUP BY RoomID
I then combine both and feed the two arrays back in one array using this function:
public function get_availability($aid, $aDate, $dDate) {
$stmt = $this->db->prepare('SELECT Rooms.RoomID as RoomID, RoomName, NumOfRooms, MaxPeopleExistingBeds, MaxExtraBeds, MaxExtraPeople, CostPerExtraPerson, MaximumFreeChildren, IncludeBreakfast, MinRate FROM Rooms, RoomDetails WHERE Rooms.AccommodationID = :aid AND Rooms.RoomID = RoomDetails.RoomID GROUP BY RoomName');
$stmt->bindValue(':aid', $aid);
$stmt->execute();
$rooms = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt2 = $this->db->prepare('SELECT Booking.RoomID, count(Booking.RoomID) as Bookings FROM Booking WHERE ArriveDate >= :aDate AND DepartDate <= :dDate AND AccommodationID = :aid GROUP BY RoomID');
$stmt2->bindValue(':aid', $aid);
$stmt2->bindValue(':aDate', $aDate);
$stmt2->bindValue(':dDate', $dDate);
$stmt2->execute();
$bookings = $stmt2->fetchAll(PDO::FETCH_ASSOC);
$room = array($rooms, $bookings);
return (!empty($room)) ? $room : false;
}
The thing is, what I actually want to do is only return the room details where NumOfRooms is less than the number of Bookings.
So for instance where I have $bookings, if it tells me that for room ID 4, I have 3 bookings for a set period, and my NumOfRooms is 1. Then I know that I have no capacity that week to take any more bookings on. If however I have 1 booking and one capacity then that is still full. But if I have NumOfRooms of 2, and bookings amount to 1, I know I have room.
So basically if NumOfRooms > BookingCount then the room is available.
How can I amalgamate both queries and simplify my code to make this possible?
I.E to put it simply, how do I select all of the info from RoomDetails given an ArriveDate in Booking and a DepartDate and a RoomID, where NumOfRooms > count(Booking.RoomID) (Where it is within those dates and the room id is equal to the room id of Rooms).
Your problem can be solved by simply updating the SQL statement itself:
SELECT r.RoomID AS RoomID,
RoomName,
NumOfRooms,
MaxPeopleExistingBeds,
MaxExtraBeds,
MaxExtraPeople,
CostPerExtraPerson,
MaximumFreeChildren,
IncludeBreakfast,
MinRate
FROM Rooms r
JOIN RoomDetails rd
ON r.RoomID = rd.RoomID
JOIN (
SELECT b.RoomID,
AccommodationID,
count(b.RoomID) AS Bookings
FROM Booking b
WHERE ArriveDate >= :aDate
AND DepartDate <= :dDate
GROUP BY RoomID
) t
ON t.AccommodationID = r.AccommodationID
WHERE r.AccommodationID = :aid
AND t.Bookings < NumOfRooms
GROUP BY RoomName
You can select out all of the booking counts per room for the desired date range as a subquery, and then LEFT JOIN that subquery against the list of your rooms filtered by your desired AccommodationID and the desired NumOfRooms > BookingCount criteria. The key here is in the join type used for this subquery, as an inner join would limit your results to only rooms that actually had bookings.
SELECT Rooms.RoomID as RoomID,
RoomName, NumOfRooms,
MaxPeopleExistingBeds,
MaxExtraBeds,
MaxExtraPeople,
CostPerExtraPerson,
MaximumFreeChildren,
IncludeBreakfast,
MinRate,
BookingCount
FROM Rooms
INNER JOIN RoomDetails on Rooms.RoomID = RoomDetails.RoomID
LEFT JOIN (
SELECT Booking.RoomID,
count(Booking.RoomID) as BookingCount
FROM Booking
WHERE ArriveDate >= :aDate AND
DepartDate <= :dDate
GROUP BY Booking.RoomID
) RoomBookings ON Rooms.RoomID = RoomBookings.RoomID
WHERE Rooms.AccommodationID = :aid
AND NumOfRooms > BookingCount
GROUP BY RoomName

How can I add a trendline in pchart?

I am creating charts for a admin dashboard in our in-house CRM, however I am having a bit of trouble adding a trend line. I can't seem to find any documentation for a built in function so have attempted to do it using an sql query which is clunky and slow.
SELECT days.date,
(SELECT count(app.app_id) FROM li_appointments.li_appointments as app
where app_datetime >= date_add(days.date, interval -7 day)
and app_datetime <= days.date) / 5 as average
FROM li_appointments.li_days as days
where date >= date_add(date(now()), interval -30 day)
and date <= date(now())
It takes about 12 seconds just to grab the data for this one line on the chart. Is there any native way to do this with pChart, or is there a better PHP option available?
Here is the full code I am using right now with no trend line:
$getapps = "SELECT days.date, app.app_id as scheduled
,cnc.app_id as cnc
,dnc.app_id as dnc
,cancel.app_id as cancel
,covered.app_id as covered
FROM li_appointments.li_days as days
left Join (SELECT date(app_datetime) as app_datetime
, count(app_id) as app_id
FROM li_appointments.li_appointments
group by date(app_datetime))
as app on days.date = app.app_datetime
left Join (SELECT date(app_datetime) as app_datetime
, count(app_id) as app_id
FROM li_appointments.li_appointments
Where app_id in (select app_id from li_app_cnc)
group by date(app_datetime))
as cnc on days.date = cnc.app_datetime
left Join (SELECT date(app_datetime) as app_datetime
, count(app_id) as app_id
FROM li_appointments.li_appointments
Where app_id in (select app_id from li_app_dnc)
group by date(app_datetime))
as dnc on days.date = dnc.app_datetime
left Join (SELECT date(app_datetime) as app_datetime
, count(app_id) as app_id
FROM li_appointments.li_appointments
Where app_id in (select app_id from li_app_canceled)
group by date(app_datetime))
as cancel on days.date = cancel.app_datetime
left Join (SELECT date(app_datetime) as app_datetime
, count(app_id) as app_id
FROM li_appointments.li_appointments
Where terp_id is not null and terp_id <> ''
group by date(app_datetime))
as covered on days.date = covered.app_datetime
Where date > adddate(now(), Interval -30 day)
and date <= date(now())
group by date";
$result = mysql_query($getapps );
$date = array();
$scheduled = array();
$cnc = array();
$dnc = array();
$canceled = array();
$covered = array();
while($row = mysql_fetch_array($result)){
array_push($date, $row['date']);
array_push($scheduled, $row['scheduled']);
array_push($cnc, $row['cnc']);
array_push($dnc, $row['dnc']);
array_push($canceled, $row['cancel']);
array_push($covered, $row['covered']);
}
/* CAT:Line chart */
/* pChart library inclusions */
include("../class/pData.class.php");
include("../class/pDraw.class.php");
include("../class/pImage.class.php");
/* Create and populate the pData object */
$MyData = new pData();
$MyData->addPoints($scheduled,"Scheduled");
$MyData->addPoints($cnc,"CNC");
$MyData->addPoints($dnc,"DNC");
$MyData->addPoints($canceled,"canceled");
$MyData->addPoints($covered,"covered");
$MyData->setSerieTicks("Probe 2",4);
$MyData->setSerieTicks("Probe 3",4);
$MyData->setAxisName(0,"Appointments");
$MyData->addPoints($date,"Labels");
$MyData->setSerieDescription("Labels","Months");
$MyData->setAbscissa("Labels");

Advice on SELECT statement and query

I need to either have a masive select statement or multiple queries. I need to break down data into specific timeframes during the day. This one works great for the first interval, but after that I'm stumped. If I have multiple queries, I'm having trouble with my "mysql_fetch_array", as to the syntax (using something like 'else' and going through them).
SELECT U.user_name,ROUND(SUM((TA.task_average*TC.completed)/60),2) AS equiv1, S.submit_date,
SUM(TC.completed) AS ttasks1,
FROM `summary` S
JOIN users U ON U.user_id = S.user_id
JOIN tasks TA ON TA.task_id = S.task_id
JOIN tcompleted TC ON TC.tcompleted_id = S.tcompleted_id
JOIN minutes M ON M.minutes_id = S.minutes_id
WHERE DATE(submit_date) = curdate( )
AND TIME(submit_date) BETWEEN '06:00:00' and '07:59:59'
GROUP BY U.user_name
LIMIT 0 , 30
My fetch array ( I would need to have a bunch more, but how to I combine them?)
<?php
while($rows=mysql_fetch_array($result1)){
?>
ok, given that you want the data across regular 2 hourly intervals, you could try something like this:
SELECT FLOOR(hour(S.submit_date)/2)*2, U.user_name,ROUND(SUM((TA.task_average*TC.completed)/60),2) AS equiv1, S.submit_date
SUM(TC.completed) AS ttasks1,
FROM `summary` S
JOIN users U ON U.user_id = S.user_id
JOIN tasks TA ON TA.task_id = S.task_id
JOIN tcompleted TC ON TC.tcompleted_id = S.tcompleted_id
JOIN minutes M ON M.minutes_id = S.minutes_id
WHERE DATE(submit_date) = curdate( )
GROUP BY U.user_name, FLOOR(hour(S.submit_date)/2)
LIMIT 0 , 30
where FLOOR(hour(S.submit_date)/2)*2 will map each hour to the first (even) hour of every 2 and you can group by this value. ie.
0, 1 -> 0
2, 3 -> 2
4, 5 -> 4
etc...
update with php included:
some notes:
i've used mysqli
i've left joined the original query with an hours derived table to ensure there are no
'gaps' in the time intervals (assumed 6:00 - 20:00)
i've ordered by user, so we as we loop through the results we can print table cells for a given user and then print a new table row when the user changes.
here's the code:
echo '<table border=1><tr><td></td>';
for ($i=6; $i<=18; $i=$i+2) {
echo '<td colspan=2>'.$i.' - '.($i+2).'</td>';
}
$mysqli = new mysqli('MY_HOST', 'MY_USER', 'MY_PASSWORD', 'MY_DATABASE');
$sql = "
SELECT user_name, IFNULL(equiv1,0) AS equiv1, IFNULL(ttasks1,0) AS ttasks1, hour
FROM
(
SELECT 6 AS hour
UNION SELECT 8
UNION SELECT 10
UNION SELECT 12
UNION SELECT 14
UNION SELECT 16
UNION SELECT 18
) hours LEFT JOIN
(
SELECT FLOOR(hour(S.submit_date)/2)*2 as task_hour, U.user_name, ROUND(SUM((TA.task_average*TC.completed)/60),2) AS equiv1, S.submit_date
SUM(TC.completed) AS ttasks1
FROM `summary` S
JOIN users U ON U.user_id = S.user_id
JOIN tasks TA ON TA.task_id = S.task_id
JOIN tcompleted TC ON TC.tcompleted_id = S.tcompleted_id
JOIN minutes M ON M.minutes_id = S.minutes_id
WHERE DATE(submit_date) = curdate( )
GROUP BY U.user_name, FLOOR(hour(S.submit_date)/2)
LIMIT 0 , 30
) task_summary
ON hours.hour = task_summary.task_hour
ORDER BY user_name, hour
";
$result = $mysqli->query($sql);
$user_name = '';
while ($row = $result->fetch_assoc()) {
if ($user_name <> $row['user_name']){
echo '</tr><tr><td>'.$row['user_name'].'</td>'; //start a new row if user changes
$user_name = $row['user_name']; //update user variable for checking on next iteration
}
echo '<td>'.$row['equiv1'].'</td>';
echo '<td>'.$row['ttasks1'].'</td>';
}
echo '</tr><table>';
This is in regard to your question "how to combine the fetch array".
Why not have an array to store all the values coming from your fetch array like the one below.
<?php
$customArray = array();
while($rows=mysql_fetch_array($result1)){
$customArray['field1'] = $row['field1'];
$customArray['field2'] = $row['field2'];
}
//another fetch array
while($rows=mysql_fetch_array($result2)){
$customArray['field1'] = $row['field1'];
$customArray['field2'] = $row['field2'];
}
//now $customArray will have all the required values you need.
//This is not a great option as it is making the logic expensive.
//Are you going to use this in a cron job ?
?>

Categories