I've read a few of the similar threads on here and after many hours of banging my head, I still can't get this to work.
I have 2 tables within MySQL:
- bookings
- booking_status_history
The structure and data of the booking table:
id pt_id client_id timestamp notes
62 30 15 2015-02-02 07:00:00
The structure and data of the booking_status_history table:
(note booking_id is a foreign key to the booking table above)
id booking_id timestamp description details status
11 62 2015-02-02 00:40:42 Client made Booking (pending) test_detailsi pending
12 62 2015-02-02 00:40:45 PT confirmed booking (confirmed) test_details2 pending
18 62 2015-02-02 02:45:15 Client cancelled Booking (Medical Emergency) test_details3 pending
What I am trying to achieve is getting the LATEST value from the status column for each booking (this test data only shows 1 booking and 3 history values).
At the moment I have only achieved pulling back all of the history results and not just the latest row.
I am using the following PHP MySQLi library: https://github.com/joshcam/PHP-MySQLi-Database-Class#join-method
This is my current code which seems to return all of the history. I've tried using variations of MAX and GROUPBY as I've seen work for others in older threads but for some reason I just can't apply it to this scenario.
$date = date('Y-m-d H:i:s', time());
$this->db->join("booking_status_history h", "b.id=h.booking_id", "LEFT");
$this->db->where('client_id', $id_param);
$this->db->where('b.timestamp', $date, ">");
$this->db->where('h.status', 'pending');
$this->db->orderBy("b.timestamp","desc");
$next_booking = $this->db->get("booking b", NULL, "b.*, h.status");
return $next_booking;
As you can see from the output below, this is returning all 3 history values:
Array (
[0] => Array (
[id] => 62
[pt_id] => 30
[client_id] => 15
[timestamp] => 2015-02-02 07:00:00
[notes] => [status] => pending
)
[1] => Array (
[id] => 62
[pt_id] => 30
[client_id] => 15
[timestamp] => 2015-02-02 07:00:00
[notes] => [status] => pending
)
[2] => Array (
[id] => 62
[pt_id] => 30
[client_id] => 15
[timestamp] => 2015-02-02 07:00:00
[notes] => [status] => pending
)
)
I appreciate not everyone is going to be familiar with this PHP library so even if someone can explain it to me via raw SQL, that would be awesome.
What I am trying to do is (in pseudo code):
SELECT everything
FROM bothtables
WHERE booking.id=booking_status_history.booking_id
AND latest entry in booking_status_table
I hope that makes sense. It's 5am and I've been up all night having a huge code block.
Thanks!
This is what seems to have cracked it for me! Many thanks to Jhecht for spending time helping me out :-)
SELECT b.*, h.booking_id, h.details, h.status FROM booking b JOIN booking_status_history h ON b.id = h.booking_id WHERE h.id = (SELECT MAX(id) FROM booking_status_history WHERE booking_id = b.id)
You can go for comparison with id from status table.It will go like this.
SELECT * FROM booking_status_history_table bs,booking_table bt
WHERE bs.booking_id=bt.id
and bs.id in
(select max(id) from booking_status_history_table group by booking_id)
The stock solution looks like this...
SELECT b.*
, x.timestamp
, x.description
, x.status
FROM booking b
JOIN booking_status_history x
ON x.booking_id = b.id
JOIN
( SELECT booking_id,MAX(id) max_id FROM booking_status_history GROUP BY booking_id ) y
ON y.booking_id = x.booking_id
AND y.max_id = x.id;
Related
I have query like this,
SELECT * FROM users ORDER BY score
So, the result is like this.
Array
(
[0] => stdClass Object
(
[userid] => 3
[user] => John Doe
[score] => 50
)
[1] => stdClass Object
(
[userid] => 1
[user] => Mae Smith
[score] => 38
)
[2] => stdClass Object
(
[userid] => 2
[user] => Mark Sam
[score] => 26
)
)
But, I want to add a rank using find_in_set query. So the result might be like this. So that the user can view their ranks when they login to their account.
Array
(
[0] => stdClass Object
(
[userid] => 3
[user] => John Doe
[score] => 50
[rank] => 1
)
[1] => stdClass Object
(
[userid] => 1
[user] => Mae Smith
[score] => 38
[rank] => 2
)
[2] => stdClass Object
(
[userid] => 2
[user] => Mark Sam
[score] => 26
[rank] => 3
)
)
I tried this one.
$listOfUser = array();
foreach($users as $user) {
$listOfUser[] = $user->userid;
}
And used another query
$userid = 2 // => id of loggedin user
SELECT *, find_in_set($userid, $listOfUser) as rank FROM users where userid=$userid ORDER BY score
So, I got this result
Array
(
[1] => stdClass Object
(
[userid] => 2
[user] => Mark Sam
[score] => 26
[rank] => 3
)
)
Which is somehow correct. But, is there another way of querying that result using only one SQL query and without using foreach loop?
Something like this.
$userid = 2 // => id of loggedin user
SELECT *, find_in_set($userid, (SELECT * FROM users ORDER BY score)) as rank FROM users where userid=$userid ORDER BY score
But I got this error Subquery returns more than 1 row
If You don't insist on using find_in_set, you can get result with simple join. You ask for list of users (p) and for each user you ask, how many users have better score than him or her (c):
SELECT p.userid, COUNT(c.userid) AS rank
FROM users AS p
LEFT JOIN users AS c ON c.score > p.score
GROUP BY p.userid
This works even if you add other conditions, like WHERE p.userid = 123.
If more users have the same score, the ranks would look like 0,1,2,2,2,5,6.
In your query, you can add counter, like this:
set #n:=0;
SELECT #i := #i + 1 AS rank, * FROM users ORDER BY score
The rank here is relative to the score distribution across all users. I believe you should try something originally proposed in this answer:
SELECT users.*,
#rownum := #rownum + 1 as rank
FROM users
CROSS JOIN (select #rownum := 0) r
ORDER BY score DESC
What it does is basically order all users by score, and assign each of them an incremental value "rank". So the top scorer would have a rank of 1, the second scorer would have a rank of 2 etc.
Keep in mind that this solution is not "fair" - each user will have a different rank, even if all users have the same score. If you try to rank users as they do in sports (if two top competitors have the same score, they both take 1st place, and the next best competitor takes 3rd place, not second), you should think of a different solution.
I have two tables
Meetings:
m_id ProjectName
1 Test
2 Test2
Meeting_next:
id fk_m_id Meetingdate status
1 1 9-1-2018 0
1 1 10-1-2018 0
1 1 13-1-2018 1
I want to join this two tables when I left join it I will get duplicate value
Expected output
Array
(
[0] => Array
(
[m_id] => 1
[ProjectName] => test
[meetingDate] =>13-1-2018
)
[1] => Array
(
[m_id] => 2
[ProjectName] => test2
[meetingDate] =>
)
)
I tried -
select * from meetings left join meeting_next on meetings.m_id= meeting_next.fk_m_id where meeting_next.status=1 order by m_id desc
myOutput:
Array
(
[0] => Array
(
[m_id] => 1
[ProjectName] => test
[meetingDate] =>13-1-2018
) )
Bad luck I got only first Project name. I need second too. Please help me. Any help would be appreciated.
Your WHERE condition filters the number of rows to only the row of the first project.
If you want to show both projects, even if there are no meetings with status 1, you need to move the condition to the join condition:
select *
from meetings
left join meeting_next
on meetings.m_id= meeting_next.fk_m_id
and meeting_next.status=1
order by m_id desc
Now you will get all rows from meetings with only the matching entries from meeting_next.
I have a database for clock in clock out system The table structure consists of time_id, user_id, weekNo, ClockYear, EntryDate, StartTime, EndTime and updated_at
basically the startTime is clock in and EndTime is clocked out time what im trying to do is run a query to get me the total worked hours by a user on a sepcific date total instead of total hours worked per individual entry. so currently i have the following query:
SELECT CONCAT(users.first_name," ",users.last_name) AS theUser,
time_logs.*, TIMEDIFF(endTime,startTime) AS totTime FROM time_logs
LEFT JOIN users ON time_logs.user_id = users.user_id WHERE
time_logs.entryDate >= "2016-08-17" AND time_logs.entryDate <= "2016-08-25" order by user_id ASC
and this returns the results as
[0] => Array
(
[theUser] => Sam Johnson
[timeId] => 14
[user_id] => 1
[weekNo] => 34
[clockYear] => 2016
[entryDate] => 2016-08-23
[startTime] => 2016-08-23 16:16:30
[endTime] => 2016-08-23 17:36:14
[updated_at] => 2016-08-23 17:36:14
[totTime] => 01:19:44
)
[1] => Array
(
[theUser] => Sam Johnson
[timeId] => 15
[user_id] => 1
[weekNo] => 34
[clockYear] => 2016
[entryDate] => 2016-08-24
[startTime] => 2016-08-24 01:00:00
[endTime] => 2016-08-24 05:15:00
[updated_at] =>
[totTime] => 04:15:00
)
[3] => Array
(
[theUser] => Bob Doe
[timeId] => 19
[user_id] => 2
[weekNo] => 34
[clockYear] => 2016
[entryDate] => 2016-08-24
[startTime] => 2016-08-24 10:00:00
[endTime] => 2016-08-24 13:15:00
[updated_at] =>
[totTime] => 03:15:00
)
As you can see it gives total time for that specific entry but what i want to get is just one entry for each user with the total time totalled up for all entries by that user on that day combined so for user 1
[0] => Array
(
[theUser] => Sam Johnson
[user_id] => 1
[weekNo] => 34
[clockYear] => 2016
[entryDate] => 2016-08-23
[totTime] => 05:34:44
)
can anyone help with this not sure if i can do it using a query
You are almost there!
Assuming that totTime is a Time Field (Not a DateTime field) and I HOPE user_id is unique
Sorry I put XXX as I didn't want to spend the time writing this fully out
Select CONCAT(XXX) theUser, user_id,min(weekNo) weekNo,min(clockYear) clockYear, min(entryDate) entryDate,sum(TIMEDIFF(endTime,startTime)) totTime FROM XXX ORDER BY user_id GROUP BY user_id
That should give you what you want. The key is GROUP BY which you didn't have. You can change the MIN commands to MAX if that's what you want to show.
theUser will be picked from the last entry I believe, user_id should remain the same and the other fields will be calculated by the GROUP BY and I had to repeat the field names on the sum and min functions because sum and min return a unique fieldname
I have a table called salary_raise which looks like:
id | employee_id | salary | year | month | date
1 | 1 | 1000 | 2014 | 2 | 2014-01-30
2 | 1 | 1200 | 2015 | 3 | 2015-02-20
3 | 1 | 1300 | 2015 | 4 | 2015-03-29
... and so on for multiple employees. It keeps records only when a raise of salary occurs (and this can occur random over a period of time).
Right now I can extract an array which looks like $salary['year']['month'] = $salary_value:
[2014]
[2] => 1000
[2015]
[3] => 1200
[4] => 1300
I need to extract a full report with the salary in each month up to date (filling automatically the months/years missing), like:
[2014]
[2] => 1000
[3] => 1000
[4] => 1000
[5] => 1000
[6] => 1000
[7] => 1000
[8] => 1000
[9] => 1000
[10] => 1000
[11] => 1000
[12] => 1000
[2015]
[1] => 1000
[2] => 1000
[3] => 1200
[4] => 1300
[5] => 1300
I can't figure it out how to do it by using php and/or mysql.
Many thanks!
In order to 'fill the gaps' you need to source the information somewhere.
Easiest way to do this in mysql is to have some generic calendar tables, which list single values for things like month numbers, years etc.
So given a table months(month), and a table years(year), the former contains 1..12 and the latter contanis 2010..2017 (for example), we can cross join the two of those to give us every month for every year, and then left join the salary_raise table to this.
That will leave us a table with a few entries and a lot of nulls, so then we need to use some variables to populate the null values with the last salary entry.
select `year`, `month`, if(salary is null, #prev_sal, #prev_sal := salary) salary
from (
select `years`.`year`, `months`.`month`, salary
from `years` cross join `months`
left join salary_raise sr
on sr.month = `months`.`month`
and sr.year = `years`.`year`
and sr.employee_id = 1
where (`years`.`year` = 2014 or `years`.`year` = 2015)
order by `years`.`year` asc, `months`.`month` asc
) q cross join (select #salary := null) qq
demo here
Of course you'll need to limit that to not check dates in the future -- that should be pretty easy tho.
I am trying to select all orders for last 30 days from one customer, so I need to have customer_id = "$customer_id" and count how many orders I have per each day for that one customer.
I need to end up with array like this
Array (
[1] => Array (
[orders] => 41
[date] => 2011-06-13 17:43:50
)
[2] => Array (
[orders] => 11
[date] => 2011-07-13 17:43:50
)
[4] => Array (
[orders] => 2
[date] => 2011-12-13 17:43:50
)
and so on... for 30 days, if some day I dont have any orders, I dont need array or [orders] = 0 ...
}
I have table named "orders" with id, customer_id and date field.
I found this questions SQL query for Calculating Total No. of Orders per Day? but its not helping me, or I dont understand it very well, btw I am beginner. Thanks!
p.s. what I managed to do, is to select all orders for last 30 days.
$this->db->query("SELECT * FROM orders WHERE customer_id=" . $customer['id'] . " AND date > ADDDATE(CURDATE(), INTERVAL -30 DAY)")->result_array();
Use MySQL EXTRACT function to fetch day from your date field and then group by results according to this. I haven't try it but the following query should work:
SELECT COUNT(*) AS orders, date_field
FROM your_table
WHERE customer_id=$my_cusotmer
GROUP BY EXTRACT(DAY FROM date_field)