Laravel - Efficient Way to Query Monthly Data per Category - php

I'm working on a project that requires me to generate a report similar to this:
Category Total Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
-----------------------------------------------------------------------
Category 1 97 10 10 10 10 10 5 10 10 7 5 5 5
Category 2 120 10 10 10 10 10 10 20 10 5 5 10 10
Category 3 5 0 0 0 3 0 2 0 0 0 0 0 0
This report needs to be generated from data similar to this:
id category_id type year month amount
----------------------------------------------------------
1 1 16 2016 1 4
2 1 23 2016 1 6
3 1 76 2016 2 3
4 1 27 2016 2 3
5 1 18 2016 2 4
In the report, each month's number is simply the sum of the amount for that particular category, year, and month. I can do this just fine by querying the database for each month and for each category, but this type of approach doesn't scale very well if I need to generate a report with about a hundred categories because it would take over 1,200 queries (100 categories X 12 months)!
Any suggestions of how to make this more efficient?
Thanks!

The solution is grouping by two columns.I added the necessary code to format the results as you want.
Item::where('year', $year)
->groupBy('month', 'category_id')
->selectRaw('sum(amount) as sum, month, category_id')
->orderBy('category_id')
->orderBy('month')
->get()
->groupBy('category_id')
->map(function($item){
$item = $item->pluck('sum', 'month');
$item['total'] = $item->sum();
return $item;
});

Is this what you're looking for?
Category::where('year', 2016)
->select('month', 'year', DB::raw('SUM(amount) as amountTotal'))
->groupBy('month', 'year')
->get();

Related

How can I get data from 2 table based on specific condition

In MySQL database, I have 3 table called supplier, vehicle, and assigned table.
vehicle table:
vid vehicleName noOfSeat sid
========================================
13 x 10 28
14 x 8 28
15 x 12 28
16 x 14 29
17 x 4 29
18 x 8 30
assigned table
asgid bid sid vid did seatBooked assigned_start assigned_end
============================================================================
56 15 28 13 17 3 06/01/2018 02:01 06/01/2018 04:02
57 15 28 14 15 2 06/01/2018 02:01 06/01/2018 04:02
58 15 28 15 16 3 06/01/2018 02:01 06/01/2018 04:02
In a reservation page, I need to assign a vehicle to a selected date which is 06/01/2018 02:01 To 06/01/2018 04:02
Now I want to show all vehicle of a selected supplier as well as the number of seat from the vehicle table
But the condition is:
Those vehicles will be shown which has the number of seats available for my selected date: 6/01/2018 02:01 To 06/01/2018 04:02
For example:
It's must be shown following vehicle with the number of seats:
vid Mp pf Seat available
===========================
13 7 ( why 7? because on my selected date 3 seats booked)
14 6 ( why 6? because on my selected date 2 seats booked)
15 9 ( why 9? because on my selected date 3 seats booked)
16 14
17 4
18 8
Its something like bus ticket system.
My Current Query:
Currently, I am using the following query but it just removes those vid which is in my selected date but it's incorrect because as you can see that on my selected date few seats are available!!!
// my selected date: From = '$timestart' and To = '$timeend'
foreach ($sid as $key => $value) {
$vehicle->rowQuery("select * from vehicle where vid not in (
select vid from assigned where assigned_start BETWEEN '$timestart' AND '$timeend' AND assigned_end BETWEEN '$timestart' AND '$timeend' ) AND sid = $value ");
}
I can't imagine how can I solve it: =(
Your help is highly appreciated.
You can do something like this:
select vehicle.*, (noOfSeat -
(select sum(seatBooked)
from assigned
where sid = 28
and (assigned_start between '2018-01-06 02:01' and '2018-01-06 04:02' )
and (assigned_end between '2018-01-06 02:01' and '2018-01-06 04:02')
)) as seatsAvailable
from vehicle
having seatsAvailable > 0
http://sqlfiddle.com/#!9/c6bcb6/15

Decrement cells when records are deleted

So I have a simple MySQL table (block) as such:
SELECT * FROM `block` WHERE 1 ORDER BY `year`, `month`, `day`, `block`;
id year month day block te status
20000 2015 12 28 1 100000 1
20001 2015 12 28 2 100000 1
20002 2015 12 28 3 100001 1
20003 2015 12 28 4 100001 1
20004 2015 12 29 1 100001 1
20005 2015 12 29 2 100001 1
20006 2015 12 29 3 100002 1
20066 2015 12 30 1 100003 1
20078 2015 12 30 1 100007 1
20070 2015 12 30 1 100004 1
20067 2015 12 30 2 100003 1
20071 2015 12 30 2 100004 1
20079 2015 12 30 2 100007 1
20072 2015 12 30 3 100004 1
20080 2015 12 30 3 100007 1
20068 2015 12 30 3 100003 1
20069 2015 12 30 4 100003 1
20073 2015 12 30 4 100004 1
20074 2015 12 31 1 100004 1
20075 2015 12 31 1 100000 1
20076 2015 12 31 2 100000 1
20077 2015 12 31 3 100000 1
20007 2016 1 1 1 100017 1
20008 2016 1 1 2 100017 1
20009 2016 1 1 3 100017 1
My question is, how can I shift all the remaining rows up if I delete a contiguous section of rows?
For example, if I delete all blocks for 2015-12-28, I want all the remaining blocks to decrement to occupy the unused blocks on the schedule.
Let me know if this is confusing or unclear. Thanks.
I think a bit more background is required. What is your eventual goal?
Two options...
First Option
Check out the link below on numbering rows in MySQL. You can use something like this to make your blocks dynamic.
http://www.xaprb.com/blog/2006/12/02/how-to-number-rows-in-mysql/
Second Option
If you are pulling data by day, just pull the data in order of the id and you can just assume that the blocks are in order in whatever application you use the data in.
For example, if I delete all blocks for 2015-12-28, I want all the remaining blocks to decrement to occupy the unused blocks on the schedule.
You do not actually specify what decrementing means. I think you want to shift the dates in all the blocks. But look here:
20000 2015 12 28 1 100000 1
20001 2015 12 28 2 100000 1
20002 2015 12 28 3 100001 1
20003 2015 12 28 4 100001 1
20004 2015 12 29 1 100001 1
20005 2015 12 29 2 100001 1
20006 2015 12 29 3 100002 1
20066 2015 12 30 1 100003 1
We have four blocks for 2015-12-28 and three for 2015-12-29. Say we delete the four 28's. What happens? Do three of the 29's shift to 28's, and the 30 become 29? Or do the three 29's and the 30 shift to fill the four places left by the 28's?
Depending on the strategy you need to choose different strategies. Perhaps you should define what exactly a block is, and what data is outside a "block", and supply a couple examples of a shift (with different numbers of days - e.g., 28 28 29 30 and you delete the two 28. BTW, can you delete only one? If so, what would happen then?).
Say each date can hold a fixed number of records
For example you can first count how many rows you're going to delete
SELECT COUNT(*) FROM ... WHERE day=28 AND month=12 AND year=2015
and then select the next items that have a later date. You then shift them, but you need to do this iteratively (once the 29's are gone, the 30's will shift, and so on). This may become quite expensive to calculate.
But in this case you might separate the date from the data:
TableA
2015 12 28
2015 12 28
2015 12 28
TableB
20000 100000 1
20001 100000 1
Now you need to join the tables in such a way that you can... well, join them. Whatever their cardinality. You might have a longer TableA than TableB, or vice versa.
This would be pretty easy in some RDBMS. In MySQL it's pretty awful (you can't even use CREATE VIEW to simplify the syntax because you'd need either a variable or a subquery in the view), and for small data sets you'd be better off by selecting TableA, then selecting TableB, and using a cycle in PHP:
// Run the query from tableA to get the dates
$TableADataSet = array();
while ($tuple = SQLFetch($rs)) {
$TableADataSet = $tuple;
}
// Run the query from tableB to get the rest
$TableBDataSet = array();
while ($tuple = SQLFetch($rs)) {
$TableBDataSet = $tuple;
}
// Now put them together.
for (;;) {
if (empty($TableADataSet)) {
if (empty($TableBDataSet)) {
break;
}
$TupleA = $EmptyRowFormattedAsTableA;
$TupleB = array_pop($TableBDataSet);
} else {
$TupleA = array_pop($TableADataSet);
if (empty($TableBDataSet)) {
$TupleB = $EmptyRowFormattedAsTableB;
} else {
$TupleB = array_pop($TableBDataSet);
}
}
// Now you have a row with dates and data "aligned".
// Missing data are taken by the sample rows EmptyRowFormattedAs... .
}
The result could be (simplifying)
2015 12 28 RowA
2015 12 28 RowB
2015 12 29 RowC
NULL NULL NULL RowD
and if you delete RowB, the rightmost part of the rows shifts upwards:
2015 12 28 RowA
2015 12 28 RowC
2015 12 29 RowD
In MySQL you first need to number the rows, and you have no RECNO() or ROW_NUMBER() function, so you do it like
SELECT #a:=#a+1 AS ranka,data1.* FROM ( SELECT * FROM TableA ORDER BY year, month, day ) AS data1, (SELECT #a:=0) AS init1;
and get your dates with a row number.
1 2015 12 28
2 2015 12 28
3 2015 12 29
You do the same for TableB. But now you have to do the "horizontal join" trick. We can be in one of these three cases:
B is longer A is longer Same length
A B A B A B
A B A B A B
- B A - A B
The "A B" part we can get with a JOIN. The "A -" part we get with a LEFT JOIN with a WHERE TableB.primarykey IS NULL. The "- B" part we get with a RIGHT JOIN WHERE TableA.primarykey IS NULL. Finally we put it all together with a UNION. And these three queries will need to be made with the awkward syntax above.
The (quite convoluted) query coming out of all this is
SELECT * FROM
( SELECT #a:=#a+1 AS rank1a, data1.*
FROM ( SELECT * FROM TableA ORDER BY year, month, day ) AS data1,
(SELECT #a:=0) AS init1
) AS ta
JOIN
( SELECT #b:=#b+1 AS rank1b, data2.*
FROM ( SELECT * FROM TableB ORDER BY something ) AS data2,
(SELECT #b:=0) AS init2 )
) AS tb
ON (rank1a = rank1b)
UNION
SELECT * FROM
( SELECT #c:=#c+1 AS rank2a, data3.*
FROM ( SELECT * FROM TableA ORDER BY year, month, day ) AS data3,
(SELECT #c:=0) AS init3
) AS tc
LEFT JOIN
( SELECT #d:=#d+1 AS rank2b, data4.*
FROM ( SELECT * FROM TableB ORDER BY something ) AS data4,
(SELECT #d:=0) AS init4 )
) AS td
ON (rank2a = rank2b) WHERE ( rank2b IS NULL )
UNION
SELECT * FROM
( SELECT #e:=#e+1 AS rank3a, data5.*
FROM ( SELECT * FROM TableA ORDER BY year, month, day ) AS data5,
(SELECT #e:=0) AS init5
) AS te
LEFT JOIN
( SELECT #f:=#f+1 AS rank3b, data6.*
FROM ( SELECT * FROM TableB ORDER BY something ) AS data6,
(SELECT #f:=0) AS init6 )
) AS tf
ON (rank3a = rank3b) WHERE ( rank3a IS NULL )
which, from a performance point of view, is probably a hog.

Mysql sum up the values of rows

SLNO UpazillaID CityArea VillageArea CharArea HillArea HaorArea TotalArea Year
6 1 11 44 11 11 11 11 2014
7 2 12 13 14 13 13 13 2013
I want to sum CityArea, VillageArea, CharArea, HillArea, HaorArea, TotalArea Where UpazillaID in 1,2.
Select (column1 + column2 + column3....) as totalcolumn. This will give you the sum per row.
SELECT (SUM(CityArea)+SUM(VillageArea)+SUM(CharArea)+SUM(CharArea)+SUM(CharArea)+SUM(TotalArea))
AS Total
FROM your_table
WHERE UpazillaID = 1

Count number of records per week giving massive number of NULL

I have tried about 10 different variations of queries similar to the following:
SELECT WEEKOFYEAR( dateline ) AS weekno, COUNT( 1 ) AS posts
FROM post
GROUP BY WEEKOFYEAR( dateline )
LIMIT 0 , 30
The resulting data is always a few results and about 600,000 "NULL" like so:
weekno posts
NULL 591843
1 57
2 42
3 25
4 44
5 9
6 38
7 15
8 41
9 10
10 130
11 77
12 69
13 36
14 25
15 25
16 24
17 44
18 42
19 14
20 49
21 70
22 41
23 40
24 57
25 40
26 31
Here is the table structure for dateline:
# Name Type Collation Attributes Null Default Extra
7 dateline int(10) UNSIGNED No 0
All of the rows have a value in dateline.
I need to get some sort of group by week working because ultimately I want to graph out the post activity with and without a specified forum section. I don't know why I'm getting all of these NULL results.
I think your date is stored in unixtime integer format, assuming that it is unixtime try FROM_UNIXTIME function
SELECT WEEKOFYEAR(FROM_UNIXTIME(dateline)) AS weekno, COUNT( 1 ) AS posts
FROM post
GROUP BY WEEKOFYEAR(FROM_UNIXTIME(dateline))
LIMIT 0 , 30
To return weeks from different year try this
SELECT
FROM_UNIXTIME(dateline, '%Y') Year,
WEEKOFYEAR(FROM_UNIXTIME(dateline)) AS weekno, COUNT( 1 ) AS posts
FROM post
GROUP BY FROM_UNIXTIME(dateline, '%Y'), WEEKOFYEAR(FROM_UNIXTIME(dateline))
LIMIT 0 , 30
DEMO HERE

CodeIgniter table class loops through db records 3 times - why?

This is my controller code (using Colin Williams' Template class):
$this->load->library('table');
$table['records'] = $this->db->get_where('data', array('category_1' => 'weight'));
$this->template->write_view('content', 'vw/weight_vw', $table, TRUE);
And my view code:
<div class="grid_16">
<?php echo $this->table->generate($records); ?>
</div>
Look at what I get
2 1 29 2011-01-01 10 weight
4 1 29 2010-11-03 11 weight
5 1 29 2011-05-02 10 weight
6 1 42 2011-07-11 23 weight // the database only has records up to here
2 1 29 2011-01-01 10 weight // from here on it's repeated twice
4 1 29 2010-11-03 11 weight
5 1 29 2011-05-02 10 weight
6 1 42 2011-07-11 23 weight
2 1 29 2011-01-01 10 weight
4 1 29 2010-11-03 11 weight
5 1 29 2011-05-02 10 weight
6 1 42 2011-07-11 23 weight
Any ideas why this is happening? None of my code is inside a loop or anything like that.
Use
$this->output->enable_profiler()
to see the db queries.
Take the query and run it against your db directly.
It's most likey you have a bad join in your database model.

Categories