Handling One to Many relations when Exporting Data into CSV - php

i have a database which contains 11 tables 9 of which have a one to many relation with a User Information table.
User Table:
ID , Name , Age , phone , . .......
on basis of ID the relation is with tables for e.g
User_cars
ID,User_ID(fk),Make , Model , .....
What i need to be able to do is present this data in DB in a CSV file. Since the There is a one to many relation a simple join doesnt work as rows are duplicated.(Not presentable Format for the client :S)
Im using Yii as web app and need some extension which can present it in some readable way.
or maybe a php script can do the task as well.

You can handle it in one of two ways:
1. Use GROUP_CONCAT (as #Dave mentioned in the comment - this is the link: MYSQL PHP API - Displaying & Fetching multiple rows within rows from another table).
There is a downside however, GROUP_CONCAT has a limit (by default 1024 characters), which you can change with the server variable group_concat_max_len, but you may not have access to do so.
This will look pretty much like this:
ID | Name | Car Model | Year
1 John BMW, Audi, Renault 1999, 2000, 2003
2 David Mercedes, Ford 2000, 2005
This can get very complicated if there are 30-40 entries per user, and non-readable.
2. The second option is to export it in the following format (not hard to do, you just iterate through all the cars a user has, but write the user only in the first iteration).
ID | Name | Car Model | Year
1 John BMW 1999
Audi 2000
Renault 2003
2 David .....
Here's some sample code (I use plenty of made up methods, that have suggestive names).
$csvArray = array();
foreach ($users as $user) {
$cars = $user->getCars(); //random method name. Grabs an array with the cars this user has :)
$firstIteration = 1;
foreach ($cars as $car) {
if ($firstIteration == 1) { // we only set the CSV row the first time we iterate through cars
$csvSingle['ID'] = $user->getId(); // more random method names
$csvSingle['Name'] = $user->getName();
$firstIteration = 0;
}
$csvSingle['car_model'] = $car->getCarModel();
$csvSingle['car_year'] = $car->getCarYear();
$csvArray[] = $csvSingle; // now we add the single row to the big CSV array.
}
}
fputcsv($handle, $csvArray); // $handle is the file you export to

i like first variant by #"Vlad Preda" and in your controller or component for export you can use something like this:
get users with cars (and cars must fill in user model relations)
$users = User::model()->with('cars')->findAll();
and then
foreach ($users as $user) {
$csvOneUser = array('id'=>$user->id, "name"=>$user->name);
if (!$user->cars) {
$csvAll[] = $csvOneUser;
continue;
}
foreach ($user->cars as $car) {
$car_attributes = $car->getAttributes();
foreach ($car_attributes as $k=>$v) {
$csvOneUser[$k][] = $v;
}
}
}

Related

Algolia: How To Create a Refinement List with the following Data

Im trying to create a Refinement List with the following product data:
Table 1: Product
Table 2: Product Details.
With Table Product, all the desired data is retrieved perfectly creating a json file very similar to algolia ecommerce best buy example.
{
“id”: "3953367"
“name”: “360fly - Panoramic 360° HD Video Camera - Black”,
“popularity”: 9999,
“rating”: 5,
“objectID”: “9131042”
},
On the other hand, Table 2 -> Product Details have the following structure:
id - productId - name - value.
1 - 3953367 - Operating System - Android 4.4 KitKat
2 - 3953367 - Voice Activated - Yes
3 - 3953367 - Processor Speed - 1.2 gigahertz
As you can see, 1 single product can display more than 3 options for facet.
Operating System
Voice Activated
Processor Speed
But I dont know how to structure that data to create a refinement list like this:
As an example, how can I create the json to allow people to refine using Operating System.
I tried something like:
$record[$this->productdetails->map(function($data) {return [$data[‘name’]];})->toArray()]
=
$this->productdetails->map(function($data) {return [$data[‘value’]]; })->toArray();
but in this example i receive error:
Illegal offset type
Any Example Appreciated. Im using Laravel Scout with Algolia.
Based on user #daniel-h approach I updated my code.
public function toSearchableArray()
{
$record['name'] = $this->productName;
$record['description'] = $this->productSpecification;
$record['brand'] = $this->productBrand;
$record['color'] = $this->color;
$new_array = [];
foreach ($this->productdetails as $record) {
$new_array[$record['name']] = $record['value'];
}
return $record;
}
Currently Im receiving array_merge(): Argument #1 is not an array
UPDATE:
the solution was:
// Add the product details to the array
foreach ($this->productDetails as $detailsRecord) {
$record[$detailsRecord->name] = $detailsRecord->value;
}
I dont know exactly what you want, but the error is because you can't give an array as index to the $record. Should look more like that: $record[1] or $record['key'].
Are you looking for something like that:
$new_array = [];
foreach ($this->productdetails as $data) {
$new_array[$data['name']] = $data['value'];
}

Laravel - How to pass different values for pivot data table in sync() function

My form sends a $request->assets that I am able to sync using the following:
$user->assets()->sync($request->assets === null ? [] : $syncData);
The complexity (not a problem) arises when I try to retrieve values from a serial number array that is sent back as $request->serialnumber
I have set up the serial numbers in such a way that the serial number array index corresponds to my id column in the assets table. E.g. if Mobile has a value of 1 in the database, it is located in $request->serialnumber[1], and for something that would have an id of 2, would be placed in $request->serialnumber[2] and so on.
I have done the following so far, in order to insert the correct serial numbers for the correct asset() relationship:
$serialData = [];
$pivotData = [];
$syncData = [];
//for every assigned asset, build an array of their serial numbers from the serial number array...
if(isset($request->assets))
{
for($i = 0; $i<count($request->assets);$i++)
$serialData[$i] = $request->serialnumber[$i];
}
//if some serials were set, use them for pivot data
if(count($serialData)>0)
{
$filledArray = array_fill_keys($serialData,"serialnumber");
$pivotData = array_flip($filledArray);
}
$syncData = array_combine($request->assets, $pivotData);
I know its a long drawn out way, so I'm wondering if there's an easier way to do this?

Caching PHP variables for later use

What is the best way in PHP to do variable caching? For example, let's assume I have a table, with 4 rows.
name | job
--------------------------
Justin Smith | Plumber
Jack Sparrow | Carpenter
Justin Smith | Plumber
Katie White | Doctor
Which is built like:
foreach($people as $person) {
echo $person->name;
echo get_job($person->name);
}
And the function call get_job() looks like:
function get_job($name) {
//This is pseudo code below
$row = MySQL->Query("SELECT job FROM people WHERE name = $name");
return $row->job;
}
As you can see, once we get the job of Justin Smith, further down we shouldn't and don't need to do a full MySQL query again, since we know it is Plumber.
I was thinking of doing a global variable which is a key=>value array like:
global $jobs = array("Justin Smith" => "Plumber",
"Jack Sparrow" => "Carpenter",
"Katie White" => "Doctor");
Then in the get_job() function I simply just check if the name exists in the array before querying. If not, insert the name and job into the array and return the job.
Basically, is there a better way to do this that is more elegant?
There is many possible solutions. You can store the SQL result in an array that you can use on multipe places on the page. Instead of global you should use static:
function get_job($name)
{
static $people_jobs;
if( !isset($people_jobs[$name]) || empty($people_jobs[$name]) )
{
$row = MySQL->Query("SELECT job FROM people WHERE name = $name");
$people_jobs[$name] = $row->job;
}
return $people_jobs[$name];
}
This function will do a MySQL query only once for a person, no matter how many time you call get_job($name);
this is the typical n + 1 problem where you make 1 query to get the list of "person" and then you make one query for each person to get the "job".
Depending how they are related.. maybe you could get both using one single query. For example: if the relation is a Nx1 (1 person has 1 Job, and 1 job can be used for N Persons) then your initial query should be something like:
SELECT p.name, j.job FROM Person p INNER JOIN Job j ON (p.job_id = j.id)
If the relation is a NxN, it gets tricky :P,
Hope this helps

Complex Zend Query from same user table

I have a rather unique set of conditions and orders in which I need to retrieve data from a "sellers" table for an application I'm building in Zend framework.
The client is basically requesting an application where the directory page lists sellers in a very particular order, which is:
Sellers who have been approved in the last 7 days (then order by #4 below)
Then, selllers who have paid for upgraded features on the site, and are more the 7 days old (then order by #4 below)
Then, Sellers who are more than 7 days old and are more than 7 days old (then order by #4 below)
For all of the above, secondary order by would be their launch date, then alpha by business name
I'm trying to figure out the most effective way to write an action helper that will return the data in the correct sequence above, knowing that some of my views only need 1,2 (and 4), whereas other views within the application will need all 4.
Right now, I've been writing two or three separate queries, and passing them to 2 or 3 partialloop's inside the view, but I strive for properly written code, and would like to either combine my 3 queries into one object I can pass to one partial loop, or.... write one query. How can this be done?
Here's my helper at the moment:
class Plugin_Controller_Action_Helper_ListSellers extends Zend_Controller_Action_Helper_Abstract
{
//put your code here
public function direct($regulars = false, $filter = false)
{
$dateMod = $this->dateMod = new DateTime();
$dateMod->modify('-7 days');
$formattedDate = $dateMod->format('Y-m-d H:i:s');
// get sellers initialized in last 7 days
$sellerTable = new Application_Model_DbTable_Seller();
// get sellers initialized in last 7 days
$select = $sellerTable->select()->setIntegrityCheck(false);
$select->from(array('b' => 'seller'),array('sellerID', 'businessName','sellerPicture'));
// select firstName, lastName, picture from user table, and businessName and sellerID from seller table. All records from seller table
$select->join(array('u' => 'user'), 's.userID = u.userID', array('firstName', 'lastName'));
$select->order('s.launchDate DESC','s.businessName ASC');
$select->where('s.active = 1 AND s.contentApproval = 1 AND s.paymentApproval = 1');
$select->where('s.launchDate > ?', $formattedDate);
if($filter){ $select->where('s.categoryID = ?', $filter);}
$newSellers = $sellerTable->fetchAll($select);
$query = $sellerTable->select()->setIntegrityCheck(false);
$query->from(array('b' => 'seller'),array('sellerID', 'businessName','sellerPicture'));
// select firstName, lastName, picture from user table, and businessName and sellerID from seller table. All records from seller table
$query->join(array('u' => 'user'), 's.userID = u.userID', array('firstName', 'lastName'));
$query->order('s.launchDate DESC','s.businessName ASC');
$query->where('s.active = 1 AND s.contentApproval = 1 AND s.paymentApproval = 1 AND s.featured = 1');
$query->where('s.launchDate < ?', $formattedDate);
if($filter){ $select->where('s.categoryID = ?', $filter);}
$featuredSellers = $sellerTable->fetchAll($query);
if($regulars){
$where = $sellerTable->select()->setIntegrityCheck(false);
$where->from(array('b' => 'seller'),array('sellerID', 'businessName','sellerPicture'));
// select firstName, lastName, picture from user table, and businessName and sellerID from seller table. All records from seller table
$where->join(array('u' => 'user'), 's.userID = u.userID', array('firstName', 'lastName'));
$where->order('s.launchDate DESC','s.businessName ASC');
$where->where('s.active = 1 AND s.contentApproval = 1 AND s.paymentApproval = 1 AND s.featured IS NULL');
$where->where('s.launchDate < ?', $formattedDate);
$regularSellers = $sellerTable->fetchAll($where);
}
}
}
I don't see any limits being applied to your queries. So does that mean you really want to select all matching records? For scalability reasons I'd guess that the answer should be no, there will be limits applied. In this case, you may just have to do 3 different queries.
But if there are no limits to be applied, then you could do a single simple query that selects all sellers, unfiltered and unsorted, and do your sorting and filtering in view helpers or just in your views.
Regardless, I recommend not putting database queries inside your controller layer, assuming you want to use the Model-View-Controller pattern which Zend is built for. Controllers should be thin. Your models should handle all database queries and just spit out the results into your controllers. I use the Data Mapper pattern extensively. Something like:
$mapper = new Application_Model_SellerMapper();
$newSellers = $mapper->fetchNewSellers();
$featuredSellers = $mapper->fetchFeaturedSellers();
$regularSellers = $mapper->fetchRegularSellers();
Each of your fetchX() methods would return an array of Application_Model_Seller instances, rather than Zend_Db_Table_Row instances.
This way you maintain Separation of Concerns and Single Responsibility Principle better, for more maintainable code. Even if you're the only developer on the project over the long-term, 6 months from now you won't remember what you wrote and why. And if someone else comes on the project, clarity becomes really important.

Recursing Properly Through Related Entities

I have a set of Organizations and their Board Members.
All organizations have board members and many board members are on the board of more than one organization.
I am using JIT Hypertree to illustrate their relationships. The JIT Hypertree schema requires that one item be the parent of all and is drawn based on a single JSON array.
I would love to have the re-centering event query and re-populate the graph based on the change. Then 2 levels would be fine but I have not been able to work out how to do that.
The code I have at present recurses manually for three levels from the starting organization but what I want is to re-curse through all related records.
So it would start with an Org and add Org's array of children (board members). Then fetch all of the boards (other than current Org) for each board member and add those as children of the board member.
This would continue until each trail dead ends - presumably at a board member who only belongs to one board.
Anyone have advice on how to create this array and avoid duplicates?
$board = $center->board();
$top['id'] = $center->ID;
$top['name'] = $center->Org;
$top['children'] = array();
if ($board) {
foreach ($board as $b) {
$child['id'] = $b->ID;
$child['name'] = (strlen(trim($b->Last)) > 0) ? $b->First . ' ' . $b->Last : 'Unknown';
$child['data']['orgname'] = $center->Org;
$child['data']['relation'] = $b->Role;
$child['data']['occupation'] = $b->Occupation;
$child['children'] = array();
$childboards = $b->boards();
if ($childboards) { foreach ($childboards as $cb) {
$gchild['id'] = $cb->ID;
$gchild['name'] = $cb->Org;
$gchild['data']['orgname'] = (strlen(trim($b->Last)) > 0) ? $b->First . ' ' . $b->Last : 'Unknown';
$gchild['children'] = array();
$childboardmembers = $cb->board();
if ($childboardmembers) { foreach ($childboardmembers as $cbm) {
$ggchild['id'] = $cbm->ID;
$ggchild['name'] = (strlen(trim($cbm->Last)) > 0) ? $cbm->First . ' ' . $cbm->Last : 'Unknown';
$ggchild['data']['orgname'] = $cb->Org;
$ggchild['data']['relation'] = $cbm->Role;
$ggchild['data']['occupation'] = $cbm->Occupation;
$ggchild['children'] = array();
$gchild['children'][]= $ggchild;
}}
$child['children'][]= $gchild;
}}
$top['children'][] = $child;
}
}
$top['data'] = array();
$top['data']['description'] = $center->Desc;
echo json_encode($top);
// Edit 2011.10.24 In Re hakre response
My data structure is a table of Organizations with unique IDs, a table of People with Unique IDs, and then a bridging table for the two specifying Organization (Entity) and Person and the Role the Person is playing in the Entity. A typical many-to-many. No sub-boards at all. I made an image of it which now seems kind of pointless but I'll add it at the bottom.
The JIT library data structure is a little nuts (to me) in that it goes like this in their band example:
Top: Nine Inch Nails
Child: Jerome Dillon
Child: Howlin Maggie (another band)
{all the bands' members and then all of their bands...}
So the organization (band) is treated as though it is a Person even though it is comprised of a number of Persons. And when I recurse using the code above I get (I think) terrible bloat but the JSON it makes works correctly despite bloat.
Example JSON and Example Visualization
// End Edit
Your question is hard to answer in the sense that your data-structure is mainly unknown.
For the graphical represenation you only need to provide simple relationships if I understand that correctly:
*- Parent
+- Child
+- Child
...
`- Child
Your data structure has a different format, I don't know specifically but it's something like:
Org <-- 1:n --> Board
Board <-- n:n --> Board # If you have sub-boards
Board <-- n:n --> Member
Whichever your data is represented, to map or transpose your data onto the required structure for the graphical representation, you need some functions that take care of that.
To do that you need to share classification/type between both and specific keys, so that you can look-up the needed data from the event to return the data. For example:
if (request_parent_is_org())
{
$id = request_parent_id();
$parent = data_get_board($id);
$parent->addChildrent(data_get_board_children($id));
}
else
{
... # All the other decisions you need to take based on request type
}
view_response_to_json($parent);
What you have with your many-to-many data model is a graph. JIT is designed for trees.
To put it another way, JIT will not correctly show the crossing lines that are represented in the data whenever a single person is connected to multiple organizations.
I'd recommend a proper network graph visualization - D3.js has a great implementation for modern browsers.
The JSON data format it uses is actually easier to implement given your table structure - for all the organizations and people, you define objects:
{
"name": "Mme.Hucheloup",
"group": 1
},
{
"name": "Afton School Board",
"group": 2
}
And for each association in your association table you define connection objects that wire them together:
{
"source": 1,
"target": 2
},
The fancy coding in D3 takes care of the rest. Good luck!
You can use function below:
function get_orgs_and_childs ($child_id, $found = array())
{
array_push ($found, $child['name']);
if($child['children'])){
$found[] = get_parents($child['id'], $found);
}
return $found;
}
Call it using:
$orgs = get_orgs_and_childs($child['id']);

Categories