Eloquent simplify / combine query - mysql

I'm trying to simplify the following query. I have a meter and a relation (+ or -) and I would like to sum date-ranges based on the meter criteria. The positive meters should be summed up and the negative ones subtracted from the sum. As showed below, I split the meter array into two arrays ($meter_plus, $meter_minus) with ids only, both for sum values, but $meter_minus should be subtracted.
// Edit: Fetching meters
$begin = new \DateTime($from);
$end = new \DateTime($to);
$end = $end->modify('+1 day');
// find points and meters by group
$grouping = App\Grouping::with('points.meters')->find($group_id);
$meter_plus = [];
$meter_minus = [];
// each group has one-to-many points, each point has one-to-many meters
foreach($grouping->points as $point) {
foreach($point->meters as $meter) {
if($meter->Relation == '+') {
array_push($meter_plus, $meter);
} else {
array_push($meter_minus, $meter);
}
}
}
// Edit2: Point - Meter relation
public function meters()
{
return $this->belongsToMany('App\EnergyMeter', 'meteringpoint_energymeter_relation', 'point_id', 'meter_id')
->whereHas('users', function ($q) {
$q->where('UserID', Auth::id());
})
->where('Deleted', 0)
->select('*', 'meteringpoint_energymeter_relation.Relation')
->orderBy('EMNumber');
}
--
$plus = Data::selectRaw('sum(values) as data')
->where('PointOfTime', '>', $begin->format('U'))
->where('PointOfTime', '<=', $end->format('U'))
->whereIn('meter_id', collect($meter_plus)->lists('id'))
->first();
$minus = Data::selectRaw('sum(values) as data')
->where('PointOfTime', '>', $begin->format('U'))
->where('PointOfTime', '<=', $end->format('U'))
->whereIn('meter_id', collect($meter_minus)->lists('id'))
->first();
$data = $plus->data - $minus->data
This works fine but I would like to
improve the query
calculate the final sum in query

Use whereBetween for your PointOfTime range and sum instead of selectRaw. Also, if $meter_plus is already an array of just the id's, you don't need to do whatever it is you're doing with collect. You might also want to indicate a table to query.
$plus = Data::table('some_table')
->sum('values')
->whereBetween('PointOfTime',array($begin->format('U'), $end->format('U')))
->whereIn('meter_id', $meter_plus)
->sum('values')
->get();

Related

Is there a single Apps Script function equivalent to MATCH() with TRUE?

I need to write some functions that involve the same function as the Sheets function MATCH() with parameter 'sort type' set to TRUE or 1, so that a search for 35 in [10,20,30,40] will yield 2, the index of 30, the next lowest value to 35.
I know I can do this by looping over the array to search, and testing each value against my search value until a value greater than the search value is found, but it seems to me there must be a shorthand way of doing this. We don't have to do this when seeking an exact value; we can just use indexOf(). I was surprised when I first learned that indexOf() does not have a parameter for search type, but can only return a -1 if an exact value is not found.
Is there no function akin to indexOf() that will do this, or is it actually necessary to loop over the array every time you need to do this?
Probably you're looking for the array.find() method. The impelentation could be something like this:
var arr = [10,20,30,40]
// make a copy of the array, reverse it and do find with condition
var value = arr.slice().reverse().find(x => x < 35)
console.log(value) // output --> 30 (first element less than 35 in the reversed array)
var index = arr.indexOf(value)
console.log(index) // output --> 2 (index of the element in the original array)
https://www.w3schools.com/jsref/jsref_find.asp
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
There is another method array.findIndex(). Probably you can use it as well:
var arr = [10,20,30,40]
// find more or equal 35 and return previous index
var index = arr.findIndex(x => x >= 35) - 1
console.log(index) // output --> 2
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
Try this:
function lfunko(tgt = 35) {
Logger.log([10,20,30,40].reduce((a,c,i) => { a.r = (a.x >= c)? i:a.r;return a;},{x:tgt}).r)
}

How to add legend for a bar chart with different colors in dc.js?

Below is the code snippet for a barchart with colored bars:
var Dim2 = ndx.dimension(function(d){return [d.SNo, d.something ]});
var Group2 = Dim2.group().reduceSum(function(d){ return d.someId; });
var someColors = d3.scale.ordinal().domain(["a1","a2","a3","a4","a5","a6","a7","a8"])
.range(["#2980B9","#00FFFF","#008000","#FFC300","#FF5733","#D1AEF1","#C0C0C0","#000000"]);
barChart2
.height(250)
.width(1000)
.brushOn(false)
.mouseZoomable(true)
.x(d3.scale.linear().domain([600,800]))
.elasticY(false)
.dimension(Dim2)
.group(Group2)
.keyAccessor(function(d){ return d.key[0]; })
.valueAccessor(function(d){return d.value; })
.colors(someColors)
.colorAccessor(function(d){return d.key[1]; });
How do I add a legend to this chart?
Using composite keys in crossfilter is really tricky, and I don't recommend it unless you really need it.
Crossfilter only understands scalars, so even though you can produce dimension and group keys which are arrays, and retrieve them correctly, crossfilter is going to coerce those arrays to strings, and that can cause trouble.
Here, what is happening is that Group2.all() iterates over your data in string order, so you get keys in the order
[1, "a1"], [10, "a3"], [11, "a4"], [12, "a5"], [2, "a3"], ...
Without changing the shape of your data, one way around this is to sort the data in your legendables function:
barChart2.legendables = function() {
return Group2.all().sort((a,b) => a.key[0] - b.key[0])
.map(function(kv) {
return {
chart: barChart2,
name: kv.key[1],
color: barChart2.colors()(kv.key[1]) }; }) };
An unrelated problem is that dc.js takes the X domain very literally, so even though [1,12] contains all the values, the last bar was not shown because the right side ends right at 12 and the bar is drawn between 12 and 13.
So:
.x(d3.scale.linear().domain([1,13]))
Now the legend matches the data!
Fork of your fiddle (also with dc.css).
EDIT: Of course, you want the legend items unique, too. You can define uniq like this:
function uniq(a, kf) {
var seen = [];
return a.filter(x => seen[kf(x)] ? false : (seen[kf(x)] = true));
}
Adding a step to legendables:
barChart2.legendables = function() {
var vals = uniq(Group2.all(), kv => kv.key[1]),
sorted = vals.sort((a,b) => a.key[1] > b.key[1] ? 1 : -1);
// or in X order: sorted = vals.sort((a,b) => a.key[0] - b.key[0]);
return sorted.map(function(kv) {
return {
chart: barChart2,
name: kv.key[1],
color: barChart2.colors()(kv.key[1]) }; }) };
Note that we're sorting by the string value of d.something which lands in key[1]. As shown in the comment, sorting by x order (d.SNo, key[0]) is possible too. I wouldn't recommend sorting by y since that's a reduceSum.
Result, sorted and uniq'd:
New fiddle.

Laravel - How to paginate here?

I have this code and I want to paginate $shares.
How can I archive this?
$level = Share::join('follows', 'shares.user_id', '=', 'follows.user_id')
->where('follows.follower_id', Auth::user()->id)
->where('follows.level', 1)
->get(array('shares.*'));
//get 10% of shares
$count = Share::count()/10;
$count = round($count);
$top10 = Share::orderBy('positive', 'DESC')
->take($count)
->get();
$shares = $top10->merge($level);
//get only unique from shares
$unique = array();
$uniqueShares = $shares->filter(function($item) use (&$unique) {
if (!in_array($item->id, $unique)) {
$unique[] = $item->id;
return true;
} else {
return false;
}
});
//order by id
$shares = $uniqueShares->sortBy(function($share)
{
return -($share->id);
});
return View::make('layout/main')
->with('shares', $shares);
lots of reudandant unnecessary codes here.
1st:
$level = Share::join('follows', 'shares.user_id', '=', 'follows.user_id')
->where('follows.follower_id', Auth::user()->id)
->where('follows.level', 1)
->get(array('shares.*'));
Why you are taking ALL the records only to discard it later?
2nd:
$shares = $top10->merge($level); Why you are merging the two arrays?
3rd:
$uniqueShares = $shares->filter(function($item) use (&$unique) {
if (!in_array($item->id, $unique)) {
$unique[] = $item->id;
return true;
} else {
return false;
}
});
You HAD to wrote this snippet because above in 2nd, you merged the two arrays which will yield duplicated entries. So why merging?
4th:
//order by id
$shares = $uniqueShares->sortBy(function($share)
{
return -($share->id);
});
And here comes the actual data which you actually want.
So let's recape
You need
10% of total shares
order by some positive column
order by amount of shares perhaps as i am guessing.
To use the inbuilt paginate(), you'l need paginate() that's a must.
Rest is simple.
count the total result. round(Share::count()/10)
put it in paginate() as the 1st arguement.
Add the order by clause whichever is necessary.
looking at the code, it doesn't look like you will/should have duplicated data which may haved added the distinct and group by clause.
use remember in Share::count()/10; to Cache it. You don't need to run the query over and over again.
and you're done.
The way you are merging your queries you may need to manually create it the pagination in your blade, then send a variable to "take" the next set you want.
Read the Laravel Docs for more info on implementing it into your views and manually creating it.
http://laravel.com/docs/pagination
Try this, should be good to go
Share::join('follows', 'shares.user_id', '=', 'follows.user_id')
->where('follows.follower_id', Auth::user()->id)
->where('follows.level', 1)
->paginate(20);
Maybe you would like to specify columns to select in select() method.

Show missing dates inside loop even if they don't exist inside MYSQL

I have following mysql query
SELECT count(order_id), date FROM tbl_order WHERE campaign_status = 'In Progress' or campaign_status = 'Pending' GROUP BY DATE_FORMAT(date,'%d %b %y')
and then following loop
<?php do { ?>
['<?php echo $row_chartData['date']; ?>', <?php echo $row_chartData['count(order_id)']; ?>],
<?php } while ($row_chartData = mysql_fetch_assoc($chartData)) ?>
this loop is used to create data for my chart. Now the problem is that there are certain days that users dont make orders in my store so those dates are not stored inside database, so when I loop trough those dates are not showed in above results and inside the chart.
The question I have, is there any way to show those missing dates in loop above even if they dont exist inside mysql database.
Thanks for help.
Well, in this case, create a temporary array based on the date range, e.g. if you want to show the graph from May 1, to May 31.
Loop from 1 to 30,
set $data[i] = 0;
Now loop through the db records and set
$data['date'] = $row['count']
Yes.
Firstly make $row_chartData as array instead of a mysql_result.
1) find the min date in the range or whichever date you want to start with
function _getDate($row) {
return $row['date'];
}
$dates = array_map('_getDate', $row_chartData);
$minDate = min($dates);
2) find the max date in the range or whichever date you want to end with
$maxDate = min($dates);
$dateRangeArray = array();
$date = $minDate;
while($date < $maxDate) {
$dateRangeArray[] = $date;
$date = date('Y-m-d', strtotime($date . ' +1 day'));
}
3) make the key of each element in your $row_chartdata array be the value of the date index
foreach($row_chartData as $key => $row) {
$row_chartData[$row['date']] = $row;
unset($row_chartData[$key]);
}
4) iterate over each of the days in the range and match that date to the index in your $row_chartdata array
foreach($dateRangeArray as $date) {
if(isset($row_chartData[$date])) {
//do whatever
}
}

Multidimensional Array insert into Mysql rows

I have an Array (twodimensional) and i insert it into my database.
My Code:
$yourArr = $_POST;
$action = $yourArr['action'];
$mysql = $yourArr['mysql'];
$total = $yourArr['total'];
unset( $yourArr['action'] , $yourArr['mysql'] , $yourArr['total'] );
foreach ($yourArr as $k => $v) {
list($type,$num) = explode('_item_',$k);
$items[$num][$type] = $v;
$pnr= $items[$num][pnr];
$pkt= $items[$num][pkt];
$desc= $items[$num][desc];
$qty= $items[$num][qty];
$price= $items[$num][price];
$eintragen = mysql_query("INSERT INTO rechnungspositionen (artikelnummer, menge, artikel, beschreibung,preis) VALUES ('$pnr', '$qty', '$pkt', '$desc', '$price')");
}
I get 5 inserts in the Database but only the 5th have the informations i want. The firsts are incomplete.
Can someone help me?
Sorry for my english.
check if You have sent vars from browser in array (like
input name="some_name[]" ...
also You can check, what You get at any time by putting var_dump($your_var) in any place in script.
good luck:)
You probably want to have your query and the 5 assignments above that outside of the foreach. Instead in a new loop which only executes once for every item instead of 5 times. Your indentation even suggests the same however your brackets do not.
Currently it is only assigning one value each time and executing a new query. After 5 times all the variables are assigned and the last inserted row finally has everything proper.
error_reporting(E_ALL);
$items = array();
foreach($yourArr as $k => $v) {
// check here if the variable is one you need
list($type, $num) = explode('_item_', $k);
$items[$num][$type] = $v;
}
foreach($items as $item) {
$pnr = mysql_real_escape_string($item['pnr']);
$pkt = mysql_real_escape_string($item['pkt']);
$desc = mysql_real_escape_string($item['desc']);
$qty = mysql_real_escape_string($item['qty']);
$price = mysql_real_escape_string($item['price']);
$eintragen = mysql_query("INSERT INTO rechnungspositionen (artikelnummer, menge, artikel, beschreibung,preis) VALUES ('$pnr', '$qty', '$pkt', '$desc', '$price')");
}
Switching on your error level to E_ALL would have hinted in such a direction, among else:
unquoted array-keys: if a constant of
the same name exists your script will
be unpredictable.
unescaped variables: malformed values
or even just containing a quote which
needs to be there will fail your
query or worse.
naïve exploding: not each $_POST-key
variable will contain the string
item and your list will fail, including subsequent use of $num