How to add UNION ALL programmatically in a loop? - laravel

I need to construct a query of the following structure:
SELECT
...
FROM table
WHERE
...
GROUP BY
...
UNION ALL
SELECT
...
FROM table
WHERE
...
GROUP BY
...
UNION ALL
SELECT
...
FROM table
WHERE
...
GROUP BY
...
.. and so on..
I get some data in an array, then I iterate it in a foreach loop where I construct each SELECT individually followed by a UNION ALL (except the last).
But I am not able to find a way to add that UNION ALL after every SELECT (Except for the last one):
$query = DB::table('table');
foreach ($data as $item) {
$query->.. // construct the SELECT
$query->unionAll($query); // does not work
}
How can I do it?
Update: Some more accurate code of what I'm using:
$query = DB::table('table');
foreach ($data as $item) {
foreach ($item as $key => $value) {
if (in_array($key, $info) {
$query->addSelect($key);
}
}
$query->selectRaw("COUNT(CASE...)");
$query->selectRaw("COUNT(CASE...)");
foreach ($item as $key => $value) {
if (in_array($key, $info) {
$query->where($key, "=", $value);
}
}
foreach ($item as $key => $value) {
$query->addSelect($key);
}
foreach ($item as $key => $value) {
$query->groupBy($key);
}
}

In this case, unionAll would need to take a new instance of the query builder each time.
You'd either need to set up the initial query outside of the loop or put a check inside the look to set the variable e.g.:
$query = null;
foreach ($data as $item) {
if (!$query) {
$query = DB::table($item['table'])
->where(...) // whatever else you would use to build up the query for the array
;
continue;
}
$query->unionAll(
DB::table($item['table'])
->where(...) // Again, whatever else you would use to build up the query
);
}
$results = $query->get();

Related

Refactor Query Builder Laravel

I want to do repetitive get data and foreach on several tables (see example below). Is there a way to write the code in a cleaner way instead of repeating the same code for all the tables?
$xs = DB::table('table1')->where('text', 'like', '%string')->get();
foreach ($xs as $x) {
..
}
$ys = DB::table('table2')->where('text', 'like', '%string')->get();
foreach ($ys as $y) {
..
}```
My approach is using array and foreach
$tables = ['table1', 'table2'];
results = [];
foreach($tables as $table) {
$data = DB::table($table)->where('text', 'like', '%string')->get();
foreach($data as $d) {
// your logic here
}
$results[] = ; // return a value from each query to array
}
You can write a base function and pass tableName to it and execute certain action
public function getData($tableName) {
$query = DB::table($tableName)->where('text', 'like', '%string')->get();
foreach ($query as $row) {
...
}
// return result;
}
$tables = ['table1', 'table2', 'table3'];
$queryResponse = [];
foreach($tables as $tableName) {
$queryResponse[$tableName] = $this->getData($tableName);
}

Laravel query groubBy condition

I have a dB query where I would like to groupBy() only when conditions are met without using union because of pagination.
Unfortunately groupBy() seems to only work when called on the entire query outside of the loop.
This was made for dynamic filtering from $filterArr. Depending on the array I need to select from different columns of the table.
When the $key=='pattern' I would need the distinct results from its column.
the query looks something like this
select `col_1`, `col_2`, `col_3`
from `mytable`
where (`color` LIKE ? or `pattern` LIKE ? or `style` LIKE ?)
group by `col_2` //<< i need this only for 'pattern' above and not the entire query
Heres the model:
// $filterArr example
// Array ( [color] => grey [pattern] => stripe )
$query = DB::table('mytable');
$query = $query->select(array('col_1', 'col_2', 'col_3'), DB::raw('count(*) as total'));
$query = $query->where(function($query) use ($filterArr){
$ii = 0;
foreach ($filterArr as $key => $value) {
if ($key=='color'){
$column = 'color';
}else if ($key=='style'){
$column = 'style';
}else if ($key=='pattern'){
$column = 'pattern';
$query = $query->groupBy('col_2'); // << !! does not work
}
if($ii==0){
$query = $query->where($column, 'LIKE', '%'.$value.'%');
}
else{
$query = $query->orWhere($column, 'LIKE', '%'.$value.'%');
}
$ii++;
}
});
$query = $query->orderBy('col_2', 'asc')->simplePaginate(30);
I think you can simplify your code a bit:
$query = DB::table('mytable');
$query = $query->select(array('col_1', 'col_2', 'col_3'), DB::raw('count(*) as total'));
$query = $query->where(
collect($filterArr)
->only(['color','style','pattern'])
->map(function ($value, $key) {
return [ $key, 'like', '%'.$value.'%', 'OR' ];
})->all()
)->when(array_key_exists('pattern', $filterArr), function ($query) {
return $query->groupBy('col_2');
});
$query = $query->orderBy('col_2', 'asc')->simplePaginate(30);

Nested loop caused slow in performance

foreach ($id_prs as $ids) {
list($id_pr, $id_cart) = explode('-', $ids);
foreach ($id_cart as $id) {
$r = Cart::find($id);
/// column to update value
$r->save();
}
}
This is how my loop looks like.
E.g. $id_prs consist of 10 data, while each id_prs may consist of 20 data etc.
From there, i found will take longer time to loop when a lots of data from each $id_cart.
How can i solve the performance issue, is there any solution?
This is known as n+1 problem, every time you iterate inside foreach additional query is created.
So this line is the evil, because it's querying data every iteration.
$r = Cart::find($id);
You can make it better like this:
$cart = Cart::all();
foreach ($id_prs as $ids) {
list($id_pr, $id_cart) = explode('-', $ids);
foreach ($id_cart as $id) {
// Get it from collection rather than query it from database
$cart->where('id', $id)->first()->save();
}
}
Where you acctualy query all carts inside variable as a collection, and you just manipulating with that collection localy without touching database (only when you hit save method).
Your code and variables are not clear enough. But a better solution is:
foreach ($id_prs as $ids) {
list($id_pr, $id_cart) = explode('-', $ids);
$items = Cart::whereIn('id', $id_cart) -> get();
foreach($items as $item) {
Cart::where('id', $item -> id) -> limit(1) -> update([
// Columns to update
]);
}
}
If you want to update same value for all the records
$allIds = [];
foreach ($id_prs as $ids) {
list($id_pr, $id_cart) = explode('-', $ids);
foreach ($id_cart as $id) {
$allIds[] = $id;
}
}
$cart = Cart::whereIn('id',$allIds)->update(['columns'=>'value']);

Showing the lowest pid / parent id number first

I have this function below which get the pid numbers from my database. The pid is parent id.
public function test() {
$arr = array();
foreach ($this->make_parent_list() as $result) {
$arr[] = $result;
}
print_r(implode(',', $arr));
}
The out put is as follows 4, 1
But I need it to print the lowest number first. 1, 4
Question How can I make sure when i view the parent id / pid that it
will show the lowest number first I have tried
$this->db->order_by('pid', 'desc'); and $this->db->order_by('pid',
'asc');
public function make_parent_list() {
$this->db->where('fid', '5');
$query = $this->db->get('forum');
$return = array();
foreach ($query->result() as $category)
{
$this->db->where('fid', $category->pid);
$this->db->order_by('pid', 'desc');
$query = $this->db->get('forum');
$return[$category->pid] = $category->pid;
foreach ($query->result() as $category)
{
$return[$category->pid] = $category->pid;
}
}
return $return;
}
Solved
I had to created a $results variable and then wrap in sort() outside foreach loop
$arr = array();
$results = $this->make_parent_list();
sort($results);
foreach ($results as $result) {
$arr[] = $result;
}
echo implode(',', $arr);
Now out put 1,4
I think you got a bit lost... The order_by asc,desc does work. Your function has order_by desc and an unnecessary foreach etc.
Assumptions: The forum table has at least the two columns pid and fid. I created a table with
fid , pid
5 1
5 4
If you simplify your function to...
// Given the fid, return an array of pid values in ascending order
public function make_parent_list() {
$this->db->where('fid', '5'); // Hardcoded for testing.
$this->db->order_by('pid', 'asc');
$query = $this->db->get('forum');
$pid_list = array();
foreach ($query->result() as $category) {
$pid_list[] = $category->pid;
}
return $pid_list;
}
Then you only need
$results = $this->make_parent_list();
echo implode(',', $results);
That will give you a result of 1,4.
If you change asc to desc in the order_by in the function then you will get 4,1.

Laravel foreach "where" with eloquent

I have certain fields and data values which cannot be hardcoded into the query. I'm trying to get something like this:
return Listing::where('id', $id)
->where(function($query) use ($input) {
->where('field_1', 'foo_1')
->where('field_2', 'foo_2')
->where('field_3', 'foo_3')
}
->get();
**Here's what I have **
return Listing::where('id', $id)
->where(function($query) use ($input) {
$i = 0;
foreach ($input as $key => $value) {
$i++;
// ->where('field_1', red_1); // Desired output
->where("where(field_{$i},".$value."_1)");
// $query = $query."where(field_{$i},".$value."_1)"."<br>";
// return $query prints out the following
/*
field_1 red_1,
field_2 foo_1,
field_3 bar_3
*/
}
})
->get();
Something like this should work:
$listing = Listing::where('id', $id);
foreach ($input as $key => $value) {
$i++;
// ->where('field_1', red_1); // Desired output
$listing->where("where(field_{$i},".$value."_1)");
}
$results = $listing->get();
$query = Listing::where('id', $id);
$i = 0;
foreach ($input as $key => $value) {
$i++;
$query->where('field_'.$i,$value.'_'.$i);
}
return $query->get();
One you're not chaining correctly and two you are mis-using the querybuilder closure. If you want to execute logic like a loop then you have to break down the query. Furthermore using a where closure is like writing a parenthesis around your where conditions.
Something like:
$query->where('bacon', $foo)
$query->where(function ($query) use ($bar, $baz){
$query->where('apple', $bar);
$query->orWhere('orange', $baz)
});
Would roughly translate to:
WHERE bacon = $foo AND (apple = $bar OR orange = $baz)

Resources