Yii2 Combine 2 unrelated models with some shared properties into one Gridview - php

I have 2 separate models (for 2 separate tables) containing some similar properties, which I want to combine into one GridView. The models are unrelated.
Model 1:
+----+------+----------------+-----+
| id | name | email | age |
+----+------+----------------+-----+
| 1 | Bob | bob#bob.bob | 22 |
| 2 | Ross | ross#ross.ross | 24 |
+----+------+----------------+-----+
Model 2:
+----+-------+-----------------+----------+-----------+
| id | name | email | location | Interests |
+----+-------+-----------------+----------+-----------+
| 1 | Mr | mr#mr.mr | Middle | Computers |
| 2 | Robot | robot#robot.bot | Nowhere | Hacking |
+----+-------+-----------------+----------+-----------+
I want to export the following data to a CSV (using kartik/Grid/Gridview/ExportMenu), which works similarly to a GridView:
+----+-------+-----------------+----------+-----+-----------+
| id | name | email | Location | Age | Interests |
+----+-------+-----------------+----------+-----+-----------+
| 1 | Mr | mr#mr.mr | Middle | | Computers |
| 2 | Robot | robot#robot.bot | Nowhere | | Hacking |
| 3 | Bob | bob#bob.bob | | 22 | |
| 4 | Ross | ross#ross.ross | | 24 | |
+----+-------+-----------------+----------+-----+-----------+
The export widget works the same as a CGridView. You supply a dataProvider (could have pagination) and the exported CSV contains all rows.
Currently I'm using Search models to return 2 ActiveDataProviders, and then I combine them with ArrayDataProvider:
$searchModel = new RegisteredUserSearch([$argumentsArray1]);
$dataProvider1 = $searchModel->search(Yii::$app->request->queryParams);
$dataProvider1->pagination = ['pageSize' => 12];
$collectedEmailSearchModel = new CollectedEmailSearch([$argumentsArray2]);
$dataProvider2 = $collectedEmailSearchModel->search(Yii::$app->request->queryParams);
$dataProvider2->pagination = ['pageSize' => 12];
$data = array_merge($dataProvider1->getModels(), $dataProvider2->getModels());
$dataProvider = new ArrayDataProvider([
'allModels' => $data,
'pagination'=>[
'pageSize'=> 0
]
]);
Using $dataProvider1 or $dataProvider2 as the GridView's dataProvider works fine, but using the combined $dataProvider results in exporting only 24 rows. I tried changing the pageSize of the dataProviders to 0, but that doesn't appear to make a difference.

You can make relationship between models on same email. For example
In Model1.php you must define relationship in this case by same email
public function getSameEmail()
{
return $this->hasOne(Model2::className(),['email'=>'email']);
}
To display in grid view values from other table you must join it in searchModel1.php file. You are joining tables by same email.
after validation you can add relationship you define to query. Like this
$query->joinWith('sameEmail');
And you will get values of columns from another table. Be careful with columns with same name.

Related

PHP: Best practise / API Function to consolidate SQL data 1:N to a 2d array

I often come across a situation where I have a 1:N relation, e.g. a table of items and another table with additional metadata / attributes for every item.
Consider this example:
users
+-----------+-----------+
| user_id | username |
+-----------+-----------+
| 1 | max |
| 2 | john |
| 3 | elton |
| 4 | tom |
| 5 | dave |
+-----------+-----------+
user_profile_data
+-----+-----------+-----------+
| uid | field_id | field_val |
+-----+-----------+-----------+
| 1 | 1 | a |
| 1 | 2 | b |
| 2 | 1 | c |
| 2 | 2 | d |
| 3 | 1 | e |
| 3 | 2 | f |
| 3 | 3 | g |
| 4 | 1 | h |
| 4 | 4 | i |
+-----+-----------+-----------+
Now I have two questions:
I want to select extended user-data, every (uid, field_id) combination is unique, usually I do it this way:
SELECT u.user_id, u.username, upd.field_id, upd.field_val
FROM users u
LEFT JOIN user_profile_data upd ON u.user_id = upd.uid
I get a row for every user/field combination and would need to "re-sort" in php because usually I want to have an array which contains every User with a Subarray of extended attributes, for example like this:
$users = array(1 => array('username' => max, 'data' => array(1 => 'a', 2 => 'b')), 2 => array('username' => 'john', ...));
Is there a standardized way of simplifying this and/or what's the best-practise in such cases? Is there already something build-in in PHP (not an external SQL framework)?
Thanks
Something like this should work (didn't test, sorry)
$users = [];
foreach($results as $result) { // assuming $results contains DB results
if (!isset($users[$result['user_id']])) {
$user[$result['user_id']] = [];
$user[$result['user_id']]['username'] = $result['username'];
$user[$result['user_id']]['data'] = [];
}
$user[$result['user_id']]['data'][$result['field_id']] = $result['field_val'];
}
This is not generic code, you should adapt it for each table schema, but I do not know a simplier way to do it. Or you spam your SQL server by not doing "JOIN" query... :/ (I think your sql is better :) )

Laravel. How to handle sort_order column shift

I have such a table, and SomeModel, that represents it
| id | name | sort_order |
| --------------- | ---------------- | ---------------- |
| 1 | A | 1 |
| 2 | B | 2 |
| 3 | C | 3 |
| 4 | D | 8 |
I'm implementing an API for this table. How to handle properly sort_order column shift during inserting the same values.
For example:
I want to save such an object:
{
"name": "B2",
"sort_order": 2
}
and receive such a table:
| id | name | sort_order |
| --------------- | ---------------- | ---------------- |
| 1 | A | 1 |
| 5 | B2 | 2 |
| 2 | B | 3 | <--- initially was 2
| 3 | C | 4 | <--- initially was 3
| 4 | D | 8 |
How to implement it?
I'd do it like this:
use App\SomeModel;
use Illuminate\Http\Request;
public function insert_data(Request $request){
SomeModel::where("sort_order", ">=", $request->sort_order)->increment("sort_order");
SomeModel::create($request->all());
}
Since you have some model you can do this:
In your AppServiceProvider
Event::listen("eloquent.inserting: ".SomeModel::class, function (SomeModel $model) {
SomeModel::where("sort_order", ">=", $model->sort_order)
->update([ "sort_order" => \DB::raw("sort_order+1") ])
});
The idea is to update all other models with a sort order higher or equal to the one you're inserting and increment it by one. You might want to do something similar with the deleting event to fill in the gaps and perhaps the updating event to swap sort orders.
You can (and should) dig deeper into the event system for models by reading the documentation
Of course you could always set the sort_order column as unique and create a DB trigger to keep the sort orders in check for you which is probably better since this is a data integrity concern.

Laravel 5.1 Eloquent with() and max() using multiple has-many relationships

I'm trying to use Eloquent to find the max value of a column on the last table of a multiple has-many relationship.
Given the following table structure.
Buildings
+----+---------------+
| id | building_name |
+----+---------------+
| 1 | Building 1 |
| 2 | Building 2 |
+----+---------------+
Rooms
+----+-----------+-------------+
| id | room_name | building_id |
+----+-----------+-------------+
| 1 | Room 1 | 1 |
| 2 | Room 2 | 1 |
| 3 | Room 3 | 2 |
+----+-----------+-------------+
maintenancelog
+----+-------------------+---------+---------------------+
| id | maintenance_value | room_id | timestamp |
+----+-------------------+---------+---------------------+
| 1 | Cleaned | 1 | 2015-09-06 00:54:59 |
| 2 | Cleaned | 1 | 2015-09-06 01:55:59 |
| 3 | Cleaned | 2 | 2015-09-06 02:56:59 |
| 4 | Cleaned | 2 | 2015-09-06 03:57:59 |
| 5 | Cleaned | 3 | 2015-09-06 04:58:59 |
| 6 | Cleaned | 3 | 2015-09-06 05:59:59 |
+----+-------------------+---------+---------------------+
I'd like to see if it's possible to generate an eloquent statement that would retrieve the building name, room name, and ONLY the LAST maintenance log date value.
The following works to give me a collection of ALL the values.
$buildings = Building::with('rooms.maintenancelog')->get();
but this errors out, and it looks like it's trying to call max(maintenancelog.timestamp) on the buildings table..
$buildings = Building::with('rooms.maintenancelog')->max('maintenancelog.timestamp')->get();
Error returned:
.....(SQL: select max(`maintenancelog`.`timestamp`) as aggregate from `buildings`)
Am I just asking too much from eloquent, and should I just use the basic query builder
Add the following relationship to the Rooms model...
public function latestMaintenance()
{
return $this->hasOne('App\Maintenancelog')->latest();
}
and change the Eloquent statement to..
$buildings = Building::with('rooms.latestMaintenance')->get();
referenced Getting just the latest value on a joined table with Eloquent

Avoid duplicate values and get other data set in same rows in mysql database

I have a database table that contain several projects. I want to show each project in a tab view. so I want to get each project name once and fetch other details under that project name. My table looks like below.
+-------------+----------+--------------+---------+-------------+
| activity_id | activity | project_name | user_id | description |
+-------------+----------+--------------+---------+-------------+
| 1 | A | New Project | 5 | Project |
| 2 | B | New Project | 5 | Project |
| 3 | C | New Project | 5 | Project |
| 4 | D | New Project | 5 | Project |
| 5 | E | New Project | 5 | Project |
| 6 | A | Old Project | 5 | New one |
| 7 | B | Old Project | 5 | New one |
| 8 | C | Old Project | 5 | New one |
| 9 | A | Another One | 5 | Test 01 |
| 10 | B | Another One | 5 | Test 01 |
| 11 | C | Another One | 5 | Test 01 |
| 13 | D | Another One | 5 | Test 01 |
+-------------+----------+--------------+---------+-------------+
Is there a way to get this table like below array?
['New Project'] =>
[0] =>
['activity_Id'] => '1'
['activity'] => 'A'
['user_id'] => '5'
['description'] => 'project'
[1] =>
['activity_Id'] => '2'
['activity'] => 'B'
['user_id'] => '5'
['description'] => 'Project'
and like vice..?
This would do the trick:
$stm = $pdo->query('SELECT * FROM tablename');
while( $row = $stm->fetch(PDO::FETCH_ASSOC) )
{
$info = array(
'activity_id' => $row['activity_id'],
'activity' => $row['activity'],
'user_id' => $row['user_id'],
'description' => $row['description']
);
if( array_key_exists($row['project_name'], $array) && is_array($array[$row['project_name']]) )
{
$array[$row['project_name']][] = $info;
}
else
{
$array[$row['project_name']] = array();
$array[$row['project_name']][] = $info;
}
}
two queries will do it easily,
first query to fetch distinct project name and from second query data according to first query project name.
query select distinct project_name from table
for loop on this result
query select activity_Id, activity, user_id, description from table where project_name = result from first query
for loop on this result
make array as you wish
No, AFAIK, you can't get this kind of nesting directly with active record -now in CI3 known as Query Builder- (but you probably could achieve it with ORM, frameworks like CakePHP and Laravel have their own implementations).
The only way is to create a new array and iterate over the result array you got from the model in order to build the structure you need.
You can check CI3 docs about Query Builder here: http://www.codeigniter.com/userguide3/database/query_builder.html

MySQL: Updating values of "children" from their "parents"

I have a database with rows of "parents" and "children". Similar entries, but one entry is generic version of the more specific child. However, I want these entries to match exactly in certain columns.
Here's an example of my database:
| ID | IsChildOfID | Food | Type |
| 1 | | | Fruit |
| 2 | 1 | Apple | Fruit |
| 3 | 1 | Pear | Vegetable |
| 4 | 1 | Banana | Vegetable |
| 5 | | | Vegetable |
| 6 | 5 | Lettuce | Fruit |
| 7 | 5 | Celery | Vegetable |
| 8 | 5 | Cabbage | Fruit |
In this example there are 2 parents and 6 children. The value of "type" field is inconstant with some of the children. I want to be able to find any children in the database and replace it with their parent's value in only some of the columns. Is this possible with purely MySQL or do I need do it with php? Thanks.
UPDATE name_of_table SET Type = "Fruit" WHERE IsChildOfID = 1
and
UPDATE name_of_table SET Type = "Vegetable" WHERE IsChildOfID = 5
But if you want to do it dynamicaly please use php or some other language...
Also I would prefer to use 2 tables for this kind of data...
Generally, when you use parent/children relationships in sql, you should make two separate database tables for each. In your case, you should create a database entitled "types" and include a type_id for each element in the child table.
Example
Child table:
| ID | TYPE_ID | Food |
| 2 | 1 | Apple |
| 3 | 2 | Pear |
| 4 | 2 | Banana |
| 6 | 1 | Lettuce |
| 7 | 2 | Celery |
| 8 | 1 | Cabbage |
Type table:
| ID | Type |
| 1 | Fruit |
| 2 | Vegetable |
You can then reference it by looping through the type table, and using a sql statement like
$types = mysql_query ( 'SELECT * FROM type_table');
WHILE ( $type = mysql_fetch_array ( $types ) )
{
$sql = 'SELECT * FROM child_table WHERE TYPE_ID = "' . $type['type'] . '"';
}
Similar answer here:
UPDATE multiple tables in MySQL using LEFT JOIN
I was going to write this:
UPDATE foods c
JOIN foods p ON p.id = c.IsChildOfId
SET c.type = p.type
WHERE p.isChildOfId IS NULL
But then upon further reading of the link above, not sure you can reference the target table. Worth a try.

Categories