I have the following table structure and using Yii2 ActiveRecord methods I'd like to extract the number of bookings (OrderLine) a supplier has for each day for the next week (0 entries also required). So some way of getting a row per day per supplier, with num_bookings or potentially 0 depending on the supplier.
/--------------------\ /------------\
| OrderLine |------------------|Availability|
|--------------------| 0..n 1 |------------|
|ID {PK} | |ID {PK} |
|availabilityID {FK} | |start |
|line_status | \------------/
|supplierID {FK} |
\--------------------/
| 1
|
|
| 1
/----------\
| Supplier |
|----------|
|ID {PK} |
\----------/
Querying the database directly, using DAO, with the following SQL gives me (almost) the desired result,
select count(ol.ID) as num_bookings,
day(from_unixtime(a.start)) as order_day,
ol.supplierID
from order_line ol left join
availability a on ol.availabilityID = a.ID
where ol.line_status = "booked"
and a.start >= 1451952000 //magic number for midnight today
and a.start <= 1452556800 //magic number for seven days from now
group by order_day, ol.supplierID;
something along the lines of
------------------------------------
| num_bookings|order_day|supplierID|
------------------------------------
| 1 | 5 | 3 |
| 2 | 5 | 7 |
| 1 | 6 | 7 |
| 1 | 7 | 7 |
------------------------------------
So there should be entries of 0 for the days the given Supplier has no bookings, like so
------------------------------------
| num_bookings|order_day|supplierID|
------------------------------------
| 1 | 5 | 3 |
| 0 | 6 | 3 |
| 0 | 7 | 3 |
| 2 | 5 | 7 |
| 1 | 6 | 7 |
| 1 | 7 | 7 |
------------------------------------
[days 8+ omitted for brevity...]
I've got some php/Yii code which will [eventually] give me something similar but involves multiple queries and database connections as follows,
$suppliers = Supplier::find()->all(); // get all suppliers
$start = strtotime('tomorrow');
$end = strtotime('+7 days', $start); // init times
// create empty assoc array with key for each of next 7 days
$booking_counts[date('D j', $start)] = 0;
for ($i=1; $i<7; ++$i) {
$next = strtotime('+'.$i." days", $start);
$booking_counts[date('D j', $next)] = 0;
}
foreach ($suppliers as $supplier) {
$bookings = OrderLine::find()
->joinWith('availability')
->where(['order_line.supplierID' => $supplier->ID])
->andWhere(['>=', 'availability.start', $start])
->andWhere(['<=', 'availability.start', $end])
->andWhere(['order_line.line_status' => 'booked'])
->orderBy(['availability.start' => SORT_ASC])
->all();
$booking_count = $booking_counts;
foreach ($bookings as $booking) {
$booking_count[date('D j', $booking->availability->start)] += 1;
}
}
This gives me an array for each supplier with the count stored under the appropriate day's index but that feels quite inefficient.
Can I refactor this code to return the desired data with fewer database calls and less 'scaffold' code?
This could be is the trasposition of your firt select
$results = OrderLine::find()
->select('count(order_line.ID) as num_bookings, day(from_unixtime(availability.start)) as order_day', order_line.supplierID )
->from('order_line')
->leftjoin('availability', 'order_line.availabilityID = availability.ID')
->where( 'order_line.line_status = "booked"
and a.start >= 1451952000
and a.start <= 1452556800')
->groupBy(order_day, order_line.supplierID)
->orderBy(['availability.start' => SORT_ASC])
->all();
in this way you should obtain a row for supplierID (and order_day) avoinding the foreach on supplier
For getting the data in $results->num_bookings and order_day you need add
public $num_bookings;
public $order_day;
in your OrderLine model
I hope this is what you are looking for.
Related
I have a table prices with where I store more times in a day more values referred to a customer like this:
Table prices:
| id | customer_id | value_1 | value_2 | value_3 | created_at |
-------------------------------------------------------------------------
| 1 | 10 | 12345 | 122 | 10 | 2021-08-11 10:12:40 |
| 2 | 10 | 22222 | 222 | 22 | 2021-08-11 23:56:20 |
| 3 | 12 | 44444 | 444 | 44 | 2021-08-12 08:12:10 |
| 4 | 10 | 55555 | 555 | 55 | 2021-08-13 14:11:20 |
| 5 | 10 | 66666 | 666 | 66 | 2021-08-13 15:15:30 |
| 6 | 10 | 77777 | 777 | 77 | 2021-08-13 16:12:50 |
I have some filters on that table to retrieve only records with date greater than X and/or lower than Y, sort records by value_1 or value_2, etc...
With that filters I have to take only 1 record for each day of a customer specified.
I'm able to get the record with the highest value_1 for example, by using sql function max() and group by date.
// Init query
$query = Price::query();
// Take the greatest value of value1
$query = $query->selectRaw(
'max(value_1) as highest_value_1, ' .
'date(created_at) as date'
);
// If defined, add a greater or equals
if ($from) $query->where("created_at", ">=", $from);
// If defined add a lower or equals
if ($to) $query->where("created_at", "<=", $to);
// Get results for current customer only, grupping by date and ordering it
$query = $query->where('customer_id', $id)->groupBy('date')
->orderBy('date', 'DESC');
// Fetch records
$records = $query->get();
But now I would like to have only the last record for each day of a customer specified.
I need an eloquent/sql solution because the date range to search may be large and the table has a lot of records.
How can I archive that?
Thanks
Not a complete solution (no customer filter nor laravel use), but I would use something like that in pure sql :
SELECT
CONCAT(EXTRACT(YEAR_MONTH FROM created_at),EXTRACT(DAY FROM created_at)) AS day,
MAX(created_at)
FROM mytable
GROUP BY day;
Of course, you may use other function to group by day (like string regexp or substr).
I have a leave date data in one month with various types of dates I try to match the data by date this month. when I make it based on row data only one date appears. I am a little less aware of the logic if it is an array.
My table
| ID | id | start_date | end_date |
| ____|________|______________|______________|
| 1 | x1 | 2018-11-05 | 2018-11-05 |
| 1 | x1 | 2018-11-12 | 2018-11-15 |
| 3 | x1 | 2018-11-19 | 2018-11-21 |
My script
$timesheet = $this->db->select('*')
->where('MONTH(start_date)', 11)
->where('YEAR(start_date)', 2018)
->where('id', 'x1')
->get();
$result = $timesheet->row_array();
$day_start=date_create($result['start_date']);
$day_end=date_create($result['end_date']);
for ($x = 1; $x <= 30; $x++) {
if($x >=$day_start->format('d') and $x <= $day_end->format('d')){
echo "<td class='bg-warning'>Y</td>";
}else{
echo "<td>N</td>";
}
}
/** MY result data **/
| Date | ... | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ... |
----------------------------------------------------------------
| Result| ... | N | N | N | Y | Y | N | N | N | N | ... |
/** the results I expected **/
| Date | ... | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ... |
----------------------------------------------------------------
| Result| ... | N | Y | N | Y | Y | N | Y | Y | Y | ... |
If you want to merge all of the records together, you will need to build up an array of the days (I create it using array_fill() and set each one to "N" to start).
You then iterate over each result row and add in the Y's to the appropriate elements. You then loop over from the start to end day using a for() loop to fill in the days with a 'Y'.
Finally you can output the $dates array which has all of the various rows added into it.
$dates = array_fill(1, 30, "N");
foreach ($timesheet->result_array() as $result) {
$start = (int)$result['start_date']->format('d');
$end = (int)$result['end_date']->format('d');
for ( $i = $start; $i <= $end; $i++ ) {
$dates[$i] = "Y";
}
}
foreach ( $dates as $day ) {
if($day == "Y") {
echo "<td class='bg-warning'>Y</td>";
}else{
echo "<td>N</td>";
}
}
You will probably want to change the array_fill() to have the correct number of days for the month you are working with, but this is something you can sort out as and when you need it.
I have search SO and found answers where people GROUP BY but haven't come across an answer where it displays the ability to GROUP BY (in this case date) and then get the user with the highest # of entries (in each of the date results).
I have my table set up as follows.
+----+------+-----------+-----------------+---------------------+
| id | name | reference | email | date |
+----+------+-----------+-----------------+---------------------+
| 1 | dan | 56453 | dan#example.com | 2015-05-01 09:00:01 |
| 2 | bob | 34564 | bob#example.com | 2015-05-01 09:21:03 |
| 3 | dan | 08948 | dan#example.com | 2015-05-01 09:30:08 |
| 4 | bob | 43775 | bob#example.com | 2015-05-02 09:01:43 |
| 5 | bob | 67210 | bob#example.com | 2015-05-02 09:04:13 |
| 6 | dan | 22195 | dan#example.com | 2015-05-02 09:09:11 |
+----+------+-----------+-----------------+---------------------+
Each day users log in and input a reference. Each day they could be logging many entries.
First, what I'm trying to do is GROUP BY the days and output the data.
So $day[0] which should equal "2015-05-01", should output those first 3 rows and who (and their count) has the highest number of entries for that day.
So output should be:
+----+------+-----------+-----------------+---------------------+
| Results of 2015-05-01 |
+----+------+-----------+-----------------+---------------------+
| 1 | dan | 56453 | dan#example.com | 2015-05-01 09:00:01 |
| 2 | bob | 34564 | bob#example.com | 2015-05-01 09:21:03 |
| 3 | dan | 08948 | dan#example.com | 2015-05-01 09:30:08 |
+----+------+-----------+-----------------+---------------------+
+----+------+-----------+-----------------+---------------------+
| Top user/s for 2015-05-01 |
+-----+-----+---------------------------------------------------+
| dan | 2 | |
+-----+-----+---------------------------------------------------+
So I need to be able to output the data for each day including the user with the most entries for that particular day.
My understanding is that I'd need to do something like this but can't quite grasp it.
$query = mysqli_query('SELECT * FROM mydata GROUP BY DATE(mydata.date as $date)');
$groupcount = 0;
while ($row = #mysqli_fetch_array($query))
{
$rowcount = 0;
$date[$groupcount] = $date;
$dayquery = mysqli_query('SELECT * FROM mydata WHERE date = $date)');
while ($entry = #mysqli_fetch_array($dayquery))
$data[] = array($row['$rowcount']['name'], $row['$rowcount']['reference'], $row['$rowcount']['email'], $row['$rowcount']['date']);
foreach ($data as $d) {
echo "Name: " . $d[0] . ", Reference: " . $d[1] . ", Email: " . $d[2] . ", Date: " . $d[3];
}
$rowcount++;
}
So something like that but also having another query that will output the person and the sum of the highest entries for that day. I just don't want to add another query as I think there would be a better way of structuring this to include COUNT in another.
Could potentially be multiple people with the same number of entries for the day so I'm trying to accommodate for that.
This should give you what you are looking for all in one query.
SELECT
max(m.id), -- since this column is unique you can ignore it if it's not important, or using min/max to get one of the values
max(m.reference), -- since this column appears to be unique you can ignore it if it's not important, or using min/max to get one of the values
m.name,
m.email,
cast(m.date as date) date,
count(*) count
FROM
mydata m
GROUP BY
m.name,
m.email,
cast(m.date as date)
ORDER BY
m.date DESC,
m.count DESC,
m.name ASC,
m.email ASC;
This is the bookings table I'm using for my query
+----------------------+
| event_id | person_id |
+----------------------+
| 5 | 7 |
| 4 | 7 |
| 3 | 7 |
| 4 | 5 |
| 3 | 5 |
| 5 | 3 |
+----------------------+
This table shows that person_id 7 has 3 bookings, 5 has 2 bookings and 3 has 6 bookings.
Currently, I'm using this query to get the total number of bookings per person.
$query='
SELECT
bookings.person_id,
COUNT(bookings.person_id) AS total,
bookings.event_id,
users.display_name
FROM bookings
INNER JOIN users ON bookings.person_id=users.id
WHERE users.id=bookings.person_id
GROUP BY bookings.person_id';
$result = mysql_query($query);
if($result) {
while($row = mysql_fetch_array($result))
{
/* total bookings per user */
$value = $row['total'];
$sum += $value;
/* events booked per user */
$events....
/* Displaying results */
echo "<tr width='500'>";
echo "<td>".$row['person_id']."</td>";
echo "<td>".$row['display_name']."</td>";
echo "<td>".$row['total']."</td>";
echo "<td>".$events."</td>";
echo "</tr>";
}
This works okay and gives me:
+-----------------------------------+
| ID | NAME | Total Bookings |
+-----------------------------------+
| 7 | Bob | 3 |
| 5 | Jane | 2 |
| 3 | Joe | 1 |
+-----------------------------------+
I'm seeking help to get this to display the events booked by each person (like the 4th columns below):
+------------------------------------------------+
| ID | NAME | Total Bookings | Event IDs |
+------------------------------------------------+
| 7 | Bob | 3 | 5,4,3 |
| 5 | Jane | 2 | 4,3 |
| 3 | Joe | 1 | 5 |
+------------------------------------------------+
Could you please help me getting there.
Thanks.
GROUP_CONCAT https://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
$query='
SELECT
bookings.person_id,
COUNT(bookings.person_id) AS total,
GROUP_CONCAT(bookings.event_id) as event_ids,
users.display_name
FROM bookings
INNER JOIN users ON bookings.person_id=users.id
WHERE users.id=bookings.person_id
GROUP BY bookings.person_id';
A bit different query but same result:
SELECT
bookings.person_id,
COUNT(
bookings.person_id
) AS total,
users.display_name,
GROUP_CONCAT(
bookings.event_id
ORDER BY
bookings.event_id
) AS events_list
FROM
bookings,
users
WHERE
bookings.person_id=users.id
GROUP BY
bookings.person_id
ORDER BY
bookings.person_id
I don't know if for a large data, the execution time is less, more or equal.
I have a query to get the total number of duration of when the user become internal,hm,rec status. I wanted to ask, is it possible to sort it by total number of duration when it's re calculated from another table? Please see my query structure to understand further.
The user can be assigned into many status. It can be that he was in internal status for 4th times,rc for 2 times and etc.
$sql = "SELECT * FROM user WHERE creation_date < NOW()";
$qry = mysql_query($sql);
$res = array();
while($row=mysql_fetch_assoc($qry))
{
$res[] = $row;
$res['duration'] = getTimeDuration($row['userId'],$row['startDate']);
}
function getTimeDuration($userId,$startDate);
{
$sql = "SELECT statusDate FROM userStatus WHERE userId=$userId AND status IN('internal','hm','rec') ORDER BY statusDate DESC
$qry = mysql_query($sql);
While($row=mysql_fetch_assoc($qry))
{
$diff = $row['statusDate']-$startDate;
$duration = $duration + $diff;
}
return $duration;
}
This is the table structure
user Table
+----------+--------------+
| userId | startDate |
+----------+--------------+
| 1 | 2011-09-18 |
+----------+--------------+
| 2 | 2012-05-25 |
+----------+--------------+
userStatus Table
+----------+--------------+--------------+
| userId | statusDate | status |
+----------+--------------+--------------+
| 1 | 2012-09-18 | internal |
+----------+--------------+--------------+
| 1 | 2012-10-18 | hm |
+----------+--------------+--------------+
| 1 | 2012-10-25 | rec |
+----------+--------------+--------------+
| 1 | 2012-11-05 | manager |
+----------+--------------+--------------+
| 1 | 2012-11-15 | assistant |
+----------+--------------+--------------+
| 1 | 2012-12-05 | internal |
+----------+--------------+--------------+
| 2 | 2012-10-05 | rec |
+----------+--------------+--------------+
| 2 | 2012-11-05 | internal |
+----------+--------------+--------------+
Output:
http://screencast.com/t/BsgouWSc5H3m
In my screenshot, USER ID: 1 had a total time duration all in all for 1589. USER ID: 2 has a duration of 297. What I want is to add the ability to sort base on the total time of duration. In this case, if DESC USER ID 1 will show first. Then, if ASC USER ID: 2 will show first.