How to join two query in MYSQL - php

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

Related

Optimizing the SQL Query to get data from large amount MySQL database

I am having a problem getting data from a large amount MySQL database.
With the below code it is ok to get the list of 10K patients and 5K appointments which is our test server.
However, on our live server, the number of patients is over 100K and the number of appointments is over 300K and when I run the code after a while it gives 500 error.
I need the list of the patients whose patient_treatment_status is 1 or 3 and has no appointment after one month from their last appointment. (The below code is working for small amount of patients and appointments)
How can I optimise the first database query so there will be no need the second database query in the foreach loop?
<?php
ini_set('memory_limit', '-1');
ini_set('max_execution_time', 0);
require_once('Db.class.php');
$patients = $db->query("
SELECT
p.id, p.first_name, p.last_name, p.phone, p.mobile,
LatestApp.lastAppDate
FROM
patients p
LEFT JOIN (SELECT patient_id, MAX(start_date) AS lastAppDate FROM appointments WHERE appointment_status = 4) LatestApp ON p.id = LatestApp.patient_id
WHERE
p.patient_treatment_status = 1 OR p.patient_treatment_status = 3
ORDER BY
p.id
");
foreach ($patients as $row) {
$one_month_after_the_last_appointment = date('Y-m-d', strtotime($row['lastAppDate'] . " +1 month"));
$appointment_check = $db->single("SELECT COUNT(id) FROM appointments WHERE patient_id = :pid AND appointment_status = :a0 AND (start_date >= :a1 AND start_date <= :a2)", array("pid"=>"{$row['id']}","a0"=>"1","a1"=>"{$row['lastAppDate']}","a2"=>"$one_month_after_the_last_appointment"));
if($appointment_check == 0){
echo $patient_id = $row['id'].' - '.$row['lastAppDate'].' - '.$one_month_after_the_last_appointment. '<br>';
}
}
?>
First off, this subquery likely does not do what you think it does.
SELECT patient_id, MAX(start_date) AS lastAppDate
FROM appointments WHERE appointment_status = 4
Without a GROUP BY clause, that subquery will simply take the maximum start_date of all appointments with appointment_status=4, and then arbitrarily pick one patient_id. To get the results you want you'll need to GROUP BY patient_id.
For your overall question, try the following query:
SELECT
p.id, p.first_name, p.last_name, p.phone, p.mobile,
LatestApp.lastAppDate
FROM
patients p
INNER JOIN (
SELECT patient_id,
MAX(start_date) AS lastAppDate
FROM appointments
WHERE appointment_status = 4
GROUP BY patient_id
) LatestApp ON p.id = LatestApp.patient_id
WHERE
(p.patient_treatment_status = 1
OR p.patient_treatment_status = 3)
AND NOT EXISTS (
SELECT 1
FROM appointments a
WHERE a.patient_id = p.patient_id
AND a.appointment_status = 1
AND a.start_date >= LatestApp.lastAppDate
AND a.start_date < DATE_ADD(LatestApp.lastAppDate,INTERVAL 1 MONTH)
)
ORDER BY
p.id
Add the following index, if it doesn't already exist:
ALTER TABLE appointments
ADD INDEX (`patient_id`, `appointment_status`, `start_date`)
Report how this performs and if the data appears correct. Provide SHOW CREATE TABLE patient and SHOW CREATE TABLE appointments for further assistance related to performance.
Also, try the query above without the AND NOT EXISTS clause, together with the second query you use. It is possible that running 2 queries may be faster than trying to run them together, in this situation.
Note that I used an INNER JOIN to find the latest appointment. This will result in all patients that have never had an appointment to not be included in the query. If you need those added, just UNION the results those found by selecting from patients that have never had an appointment.

TOP 10 article visitor counting via PDO PHP

I wanted to know if it is possible to obtain the 10 most viewed articles that week (Between today and 7 days back) using PDO PHP.
The main problem is that on two separate tables. Primary table is the table of articles. And the second table is a table visitors by IP.
Posts (ARTICLE TABLE):
1.ID (text)
2.TITLE (text)
3.TEXT (text)
Visitor (COUNTER TABLE):
1.ID (number)
2.IP (text)
3.DATE (TIMESTAMP)
4.ID_POSTS (text)
The full php code:
$week_start = date('Y-m-d',time()+( 1 - date('w'))*24*3600);
$week_end = date('Y-m-d',time()+( 7 - date('w'))*24*3600);
$query = "SELECT * FROM visitor WHERE DATE BETWEEN '".$week_start."' AND '".$week_end."' LIMIT 0, 10 ";
$result = $db->prepare($query);
$result->execute();
$i=1;
while($row = $result->fetch(PDO::FETCH_ASSOC)) {
$post[$i]=$row[ID];
$i++;
}
for ($i = 1; $i <= 10; $i++) {
$query = "SELECT * FROM POSTS WHERE ID_POST = '".$post[$i]."' LIMIT 0, 10";
$result = $db->prepare($query);
$result->execute();
while($row = $result->fetch(PDO::FETCH_ASSOC)) {
echo<<<PRINT
$row[ID].$row[TITLE]: $row[text]
PRINT;
}
}
The problem I think is that you have to count how many people were at the table wrote the secondary then move the primary table.
Steps:
1. Count how many entered each article each week by the secondary table
2. extract the 10 Most Viewed same week
3. present the 10 most read article in the same week by the main table
Thanks in advance.
For such a query, I would expect a COUNT(), GROUP BY, ORDER BY, and LIMIT 10. Hence:
SELECT id_post, COUNT(*) as cnt
FROM visitor v
WHERE DATE BETWEEN '".$week_start."' AND '".$week_end."'
GROUP BY id_post
ORDER BY cnt DESC
LIMIT 0, 10 ;
Note: The WHERE clause always followed the FROM clause.
Also, you should not be embedding dates in the query string. You should learn to use parameters instead.
select * from (select id, count(1) as cnt
from visitor where date > (NOW()- INTERVAL 7 DAY) group by id) v1, posts p
where v1.id = p.id
order by v1.cnt desc
limit 10
Not tested.

Random allocation until all options are used

I have a table of available teams teams, with 24 different options.
I have another table entries, where each row is an allocation of one team to a user.
When an entry is created, a random team that has not been picked is allocated. However, if all the teams have been allocated (this can happen multiple times), only teams not yet allocated in this round of allocation are available.
For example, if my teams are A, B, C and D:
If there is an entry for A in entries, only B, C and D are available
If A, B, C and D have been picked, they are all available again
IF A has 3 entries, B has 3 entries, C has 2 entries and D has 2 entries, only C and D are available, until they all have the same number of entries
My code for this is convoluted:
//Make array of teams
for($i=1;$i<=24;$i++) $team[$i] = 1;
//Get entries from database
$stmt = $dbh->prepare("SELECT `team` FROM `entries`");
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
//Create array of available teams
$numRows = $stmt->rowCount();
while($numRows >= 24) {
for($i=1;$i<=24;$i++) {
$team[$i] = $team[$i]+1;
}
$numRows = $numRows - 24;
}
//Remove entries for teams in array
foreach($rows as $row) $team[$row["team"]] = $team[$row["team"]]-1;
foreach($team as $i => $v) if($v > 0) $available[] = $i;
There must be a more straightforward method to accomplish this; how can this be done?
The following gives you the number of assignments for each team:
SELECT team, COUNT(*) FROM entries GROUP BY team;
This gives you the minimum count for any team:
SELECT MIN(count) FROM (
SELECT COUNT(*) as count FROM entries GROUP BY team
)
To get the teams with the minimum count - those being available - but those two queries together into one:
SELECT teamcounts.team
FROM
(SELECT team, COUNT(*) as num FROM entries GROUP BY team) as teamcounts
WHERE
teamcounts.num = (
SELECT MIN(num) FROM (
SELECT COUNT(*) as num FROM entries GROUP BY team
) as tcounts
)
To get also those teams not yet included in entries we have to use the team table as well, removing all teams not currently available for selection:
SELECT teams.name
FROM teams
WHERE teams.name NOT IN (
SELECT teamcounts.team
FROM
(SELECT team, COUNT(*) as num FROM entries GROUP BY team) as teamcounts
WHERE
teamcounts.num != (
SELECT MIN(num) FROM (
SELECT COUNT(*) as num FROM entries GROUP BY team
) as tcounts
)
)
I haven't found a solution that works solely in SQL, however I've created the following query:
SELECT `id`, `num_selected` FROM
(SELECT `id`, SUM(is_selected) AS `num_selected` FROM
(SELECT t.`id`, CASE WHEN e.`team` IS NULL THEN 0 ELSE 1 END AS is_selected FROM `entries` e RIGHT JOIN `teams` t ON t.`id` = e.`team`)
AS `table1`
GROUP BY `id`)
AS `table2` GROUP BY `id` ORDER BY `num_selected` ASC, `id` ASC
This includes all the team rows that have NO entries yet, and the result is a table that has every team in one column, and alongside them is the number of selections.
Then, in PHP, I simply take the lowest value of selections (this will be the first row, as I've ordered by num_selected ASC) and only use other rows with that value as possible options:
$baseNum = $rows[0]["num_selected"];
foreach($rows as $row){
if($row["num_selected"]===$baseNum) $availableTeams[] = $row["id"];
}
However, ideally I'd have a solution that takes place solely in the SQL query!

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

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