Doctrine2 immutable entities and append only data structures - php

I like the technique described by the Marco Pivetta at PHP UK Conference 2016 (https://youtu.be/rzGeNYC3oz0?t=2011), he recommends to favour immutable entities and instead of changing data structures - appending them. History of changes as a bonus is a nice thing to have for many different reasons, so I would like to apply this approach on my projects. Let's have a look at the following use case:
class Task {
protected $id;
/**
* Status[]
*/
protected $statusChanges;
public function __construct()
{
$this->id = Uuid::uuid4();
$this->statusChange = new ArrayCollection();
}
public function changeStatus($status, $user){
$this->statusChange->add(new Status($status, $user, $this);
}
public function getStatus()
{
return $this->statusChange->last();
}
}
class Status {
protected $id;
protected $value;
protected $changedBy;
protected $created;
const DONE = 'Done';
public function __construct($value, User $changedBy, Task $task)
{
$this->id = Uuid::uuid4();
$this->value = $value;
$this->changedBy = $changedBy;
$this->task = $task;
$this->created = new \DateTime();
}
}
$user = $this->getUser();
$task = new Task();
$task->changeStatus(Status::DONE, $user);
$taskRepository->add($task, $persistChanges = true);
All status changes I'm planning to persist in the MySQL database. So the association will be One(Task)-To-Many(Status).
1) What is the recommended way of gettings tasks by current status? Ie. all currently opened, finished, pending tasks.
$taskRepository->getByStatus(Status::DONE);
2) What is your opinion on this technique, are there some disadvantages which may appear in the future, as the project will grow?
3) Where it is more practical to save status changes (as a serialized array in a Task field, or in a separate table?
Thanks for opinions!

I imagine this is going to get closed to some of it being based on opinion, just so you're aware.
That being said, I've been quite interested in the idea of this but I've not really looked into it a huge amount, but here's my thinking...
1. Find By Status
I think you would need to do some sort of sub query in the join to get the latest state for each task and match that. (I would like to point out that this is just guesswork from looking at SO rather than actual knowledge so it could be well off).
SELECT t, s
FROM t Task
LEFT JOIN t.status s WITH s.id = (
SELECT s2.id
FROM Status s2
WHERE s2.created = (
SELECT MAX(s3.created)
FROM Status s3
WHERE s3.task = t
)
)
WHERE s.value = :status
Or maybe just (provided the combined id & created fields are unique)...
SELECT t, s
FROM t Task
LEFT JOIN t.status s WITH s.created = (
SELECT MAX(s2.created)
FROM Status s2
WHERE s2.task = t
)
WHERE s.value = :status
2 Disadvantages
I would imagine that having to use the above type of queries for each repository call would require more work and would, therefore, be easier to get wrong. As you are only ever appending to the database it will only get bigger so storage/cache space may be an issue depending on how much data you have.
3 Where To Save Status
The main benefit of immutable entities is that they can be cached forever as they will never change. If you saved any state changes in a serialized field then the entity would need to be mutable which would defeat the purpose.

Here's what I do:
All the types of tables involved in my business
I organize my database in 4 types of tables:
Log_xxx
Data_xxx
Document_xxx
Cache_xxx
Any data I store falls in one of those 4 types of tables.
Document_xxx and Data_xxx are just for storing binary files (like PDFs of tariffs the providers send to me), and static data or super-slow-changing data (like the airports or countries or currencies in the world). They are not involved in the "main" of this explanation but worth mentioning them.
Log tables
All my "domain events" and also the "application events" go to a Log_xxx table.
Log tables are write-once, never deleteable, and I must do backups of them. This is where the "history of the business" is stored.
For example, for a "task" domain object as you mention in your question, say that the task can be "created" and then altered later, I'd use:
Log_Task_CreatedEvents
Log_Task_ChangedEvents
Also I save all the "application events": Each HTTP request with some contextual data. Each command-run... They go to:
Log_Application_Events
Never the domain can change unless there is an application that changes it (a command line, a cron, a controller attending an HTTP request, etc.). All the "domain events" have a reference to the application event that created them.
All the events, either domain events (like TaskChangedEvent) or the application events are absolutely immutable and carry several standard things like the timestamp at creation.
The "Doctrine Entities" have no setters so they can only be created and read. Never changed.
In the database I only have one relevant field. It is of type TEXT and represents the event in JSON. I have another field: WriteIndex which is autonumeric, is the primary key and is NEVER used by my software as a key. It is only used for backups and database control. When you have GBs of data sometimes you need to dump only "events starting at index XX".
Then for easiness, I have an extra field which I call "cachedEventId" which contains the very same "id" of the event, redundant to the JSON. This is why the field is named after "cached..." as it does not contain original data, it could be rebuilt from the event field. This is only for simplicity.
Although doctrine calls them "entities" those are not domain entities, they are domain value objects.
So, Log tables look like this:
INT writeIndex; // Never used by my program.
TEXT event; // Stores te event as Json
CHAR(40) cachedEventId; // Unique key, it acts really as the primary key from the point of view of my program. Rebuildable from the event field.
Sometimes I opt-in for having more cached fields, like the creation time-stamp. All those are not needed and only set there for convenience. All that should be extractable from the event.
The Cache tables
Then in the Cache_xxx I have the "accumulated data" data that can be "rebuilt" from the logs.
For example if I have a "task" domain object that has a "title" field, and a "creator", and a "due date", and the creator cannot be overwritten, by definition, and the title and due date can be re-set... then I'd have a table that looks like:
Cache_Tasks
* CHAR(40) taskId
* VARCHAR(255) title
* VARCHAR(255) creatorName
* DATE dueDate
Write model
Then, when I create an task, it writes to 2 tables:
* Log_Task_CreatedEvents // Store the creation event here as JSON
* Cache_Tasks // Store the creation event as one field per column
Then, when I modify a task, it also writes to 2 tables:
* Log_Task_ChangedEvents // Store the event of change here as JSON
* Cache_Tasks // Read the entity, change its property, flush.
Read model
To read the tasks, use the Cache_Tasks always.
They always represent the "latest state" of the object.
Deleteability
All the Cache_xxx tables are deleteable and do not need to be backed up. Just replay the events in date order and you'll get the cached entities again.
Sample code
I wrote this answer as "Task" as this was the question, but today for instance I've been working in assigning an "state" to the client's form-submissions. The clients just ask something via web and now I want to be able to "mark" this request as "new" or "processed" or "answered" or "mailValidated", etc...
I just created a new change() method to my FormSubmissionManager. It looks like this:
public function change( Id $formSubmissionId, array $arrayOfPropertiesToSet ) : ChangedEvent
{
$eventId = $this->idGenerator->generateNewId();
$applicationExecutionId = $this->application->getExecutionId();
$timeStamp = $this->systemClock->getNow();
$changedEvent = new ChangedEvent( $eventId, $applicationExecutionId, $timeStamp, $formSubmissionId, $arrayOfPropertiesToSet );
$this->entityManager->persist( $changedEvent );
$this->entityManager->flush();
$this->cacheManager->applyEventToCachedEntity( $changedEvent );
$this->entityManager->flush();
return $changedEvent;
}
Note that I do 2 flushes. This is on purpose. In the case the "write-to-the-cache" fails I don't want to loose the changedEvent.
So I "store" the event, then I cache it to the entity.
The Log_FormSubmission_ChangeEvent.event field looks like this:
{
"id":"5093ecd53d5cca81d477c845973add91e31a1dd9",
"type":"hellotrip.formSubmission.change",
"applicationExecutionId":"ff7ad4bd5ec6cebacc048650c866812ac0127ac2",
"timeStamp":"2018-04-04T02:03:11.637266Z",
"formSubmissionId":"758d3b3cf864d711d330c4e0d5c679cbf9370d9e",
"set":
{
"state":"quotationSent"
}
}
In the "row" of the cache I'll have the "quotationSent" in the column state so it can be queried normally from Doctrine even without the need of any Join.
I sell trips. You can see there many de-normalized data coming from several sources, like for example the number of adults, kids and infants travelling (coming from the creation of the form submission itself), the name of the trip he requests (coming from a repository of trips) and others.
You can also see the latest-added field "state" at the right of the image. There may be like 20 de-mapped fields in the cached row.
Answers to your questions
Q1) What is the recommended way of gettings tasks by current status? Ie. all currently opened, finished, pending tasks.
Query the cached table.
Q2) Are there some disadvantages which may appear in the future, as the project will grow?
When the project grows, intead of rebuilding the cache at the time of write, chich may be slow, you setup a queue system (for example a RabbitMq or AWS-SNS) and you just send to the queue a signal of "hey, this entity needs to be re-cached". Then you can return very quickly as saving a JSON and sending a signal to the queue is effort-less.
Then a listener to the queue will process all and every changes you make, and if re-caching is slow, you don't matter.
Q3) Where it is more practical to save status changes (as a serialized array in a Task field, or in a separate table?
Separate tables: A table for "status changes" (=log =events =value_objects, not entities), and another table for "tasks" (=cache =domain_entities).
When you make backups, place in super-secure place the backups of the logs.
Upon a critical failure, restore the logs=events and replay them to re-build the cache.
In symfony I use to create a hellotrip:cache:rebuild command that accepts as a parameter the cache I need to reconstruct. It truncates the table (deletes all cached data for that table) and re-builds it over again.
This is costly, so you only need to rebuild "all" when necessary. In normal conditions, your app should take care of having the caches up to date when there is a new event.
Documents and Data
At the very beginning I mentioned the Documents and Data tables.
Time for it, now: You can use that information when rebuilding the caches. For example, you can "de-map" the airport name into the cached entity while in the events you may only have the airport code.
You can rather safely change the cache format as your business has more complex queries, having pre-calculated data. Just change the schema, drop it, re-build the cache.
The change-events, instead, will remain "exactly the same" so the code that gets the data and saves the event has not changed, reducing the risk of regression bugs.
Hope to help!

Related

How does Doctrine handle multiple requests?

Let's say I have a script which inserts rows into the database and it looks like this:
/* $start is some GET parameter. Any number between 0 and 9900 */
/* Select all objects with ids between $start and $start+99
and put their ids into $ids array */
$qb->select('object');
$qb->from('AppBundle:Object','object');
$qb->where("object.id >= $start");
$qb->andWhere("object.id < $start+100");
$objects = $qb->getQuery()->getResult();
$ids = array();
foreach($objects AS $object) {
$ids[] = $object->getId();
}
/* Create missing objects and insert them into database */
for($id=$start; $id<$start+100; ++$id) {
if(in_array($id, $ids)) continue;
/* Some calculations */
$createdObject = new Object($id, $some, $data);
$em->persist($createdObject);
}
$em->flush();
Now imagine there are no objects yet (the table is clear) and one user enters the site with start=0. The script takes like 2 seconds to complete. Before it finishes - another user enters the site with start=50.
I'm not sure what exactly would happen in such scenario, but I persume that:
First user enters - the $ids array is empty, the script is generating objects with id 0-99.
Second user enters - the $em->flush form the first entrance is not yet called, which means the $ids array is still empty (I guess?). The script is generating objects with id 50-149
There is a first $em->flush() call which comes from the first user entrance. It insert objects 0-99 into the database.
There is a second $em->flush() call which comes from the second user entrance. It tries to insert objects 50-149 into the database. It fails, because the object with id=50 already exists in the database. As a result it doesnt actually insert anything into the database.
Is that what would really happen? If so - how to prevent it and what is the best way to insert only those objects that are missing into the database?
#edit: This is just an exmaple code, but in the real script the id is actually 3 columns (x, y, z) and the object is a field on a map. The purpose of this script is that I want to have a huge map and it would take too much time to generate it all at once. So I want to generate only a little and then create the missing parts only when some user tries to access them. At some point the whole map will be created, but the process will be staggered.
You have 2 bad practices here:
You sould avoid INSERT or UPDATE operations when users enter your site (because they are slow/costly), especially if it's about adding many objects to the database like in this script. It should run in some kind of cron script, independently from your website users.
You shouldn't assign ID's to your objects beforehand. Leave it as null and Doctrine will handle it for you. Why would you need to set ID in advance?
To answer your question - if you call $em->persist() for an object with a pre-assigned ID, and in case another object exists in the database with the same ID - INSERT won't happen. Instead, the already existing object will be UPDATED with the data from your newer object (when you call em->flush() afterwards). So instead of 2 objects (as expected), you will have only 1 in the database. So that's why I really doubt if you need to pre-assign IDs in advance. You should tell me more about the purpose of this :)

Laravel 5 record editing check-in/check-out functionality (locking)

I am using Laravel 5.1 and trying to implement a record lock when a user is opening a record edit view so that any other user can't open the edit view for the same record until the lock is released.
There are obviously times that a user will never finish the editing so I need to be able to automatically unlock the record (either when the users makes any another transactions, or after a timeout event, or with an ajax background keep-alive call).
I've looked into the lockForUpdate() and read about InnoDB locking feature but I couldn't get real info about it (searched over 20 posts and they all seem to recite each other with no real information). The Docs also don't offer too much info and exploring the Laravel Query Class code got me the following progress:
Headache.
I realized that it is only available in the Query Builder (I need it for Eloquent).
Seems like it is only a transnational locking not a checkin/checkout record locking.
It seems like there's a big confusion both with terminology and with what mysql (innodb) locking actually does, or it could be just me trying to figure this out.
Long story short, the main caveat with InnodDB/Laravel locking (for my needs) is that it is not persistent hence it will reset itself when the php script terminates and the user is still editing the form. It is only for transactions.
I need to achieve the mentioned functionality (editing check-in/check-out) and before I re-invent the wheel I would love to know if there's already a built-in Laravel/PHP functionality for it.
If not, I am contemplating between the following approaches and would love to get inputs as to which one is best.
A. Create a edit_checkins table with record_id, user_id, timestamp.
B. Add a locked_at column to the record table which will hold a timestamp or null (similar to deleted_at column).
My main concerns is performance & 'garbage collection' since there are times when a record is locked but never actively unlocked. With approach A I can remove all users locks from the edit_checkins table in a simple query. However it will be a bit slower when checking if the record is locked since I will have to do a table join (I think it should be negligible since edit event is less often then other events). With Approach B it is faster to check but I don't get all the info (such as user_id) and it is harder-to-almost-impossible to implement a unlock-all event without knowning the user_id (I'd need to probably add a locked_by column as well to the record).
I hope I made the question clear enough. Thank you for your time.
What you're trying to do is an application level lock on a record. You have a business level requirement that only one user can be looking at the edit view of the record at a time. This "lock" can last seconds, minutes, hours, or whatever max timeout you wish to allow.
This is completely different then a database level lock on the record. The database level lock is required to make sure two update statements don't run on the same record at the same time. This lock will only last as long as the transaction takes, which is typically just milliseconds. Long running or open ended database transactions are not a good idea.
You're going to need to design your own application logic to do what you want. I did not see any existing Laravel packages for this. There is one called laravel-record-lock, but it does not do what you want to do and it will not persist the lock across multiple requests.
I think the most flexible design would be to create a record_locks table, and then create a polymorphic relationship with any model that you would like to be lockable. Polymorphic relationship documentation. To get you started:
DB table:
record_locks
- id
- timestamps (if you want)
- lockable_id - integer
- lockable_type - string
- user_id - integer
- locked_at - datetime/timestamp
Model
class RecordLock extends Model
{
/**
* Polymorphic relationship. Name of the relationship should be
* the same as the prefix for the *_id/*_type fields.
*/
public function lockable()
{
return $this->morphTo();
}
/**
* Relationship to user.
*/
public function user()
{
return $this->belongsTo('App\User');
}
// additional functionality
}
Now you can add the polymorphic relationship to any Model you want to be lockable:
class Book extends Model
{
/**
* Polymorphic relationship. Second parameter to morphOne/morphMany
* should be the same as the prefix for the *_id/*_type fields.
*/
public function recordLock()
{
return $this->morphOne('App\RecordLock', 'lockable');
}
}
class Car extends Model
{
/**
* Polymorphic relationship. Second parameter to morphOne/morphMany
* should be the same as the prefix for the *_id/*_type fields.
*/
public function recordLock()
{
return $this->morphOne('App\RecordLock', 'lockable');
}
}
Finally, use as regular relationships:
$book = \App\Book::first();
$lock = $book->recordLock; // RecordLock object or null
$car = \App\Car::first();
$lock = $car->recordLock; // RecordLock object or null
/**
* Accessing the relationship from the RecordLock object will
* dynamically return the type of object that was locked.
*/
$lock = \App\RecordLock::find(1);
$lockedObject = $lock->lockable; // \App\Book object
$lock = \App\RecordLock::find(2);
$lockedObject = $lock->lockable; // \App\Car object
One final sidenote to address your concern regarding the Query Builder vs Eloquent: the Eloquent Model falls back to the Eloquent Query Builder, and the Eloquent Query Builder falls back to the plain Query Builder. If you call a method on an Eloquent Model, it attempts to call it on the Eloquent Query Builder. If it doesn't exist there, it attempts to call it on the plain Query Builder. If it doesn't exist there, you'll get an error.
So, if you were to do \App\User::lockForUpdate(), the method call would eventually filter down to the plain Query Builder (since it doesn't exist on the Eloquent Model or the Eloquent Query Builder).

Best way to create nested array from tables: multiple queries/loops VS single query/loop style

Say I have 2 tables,
which I can "merge" and represent in a single nested array.
I'm wandering what would be the best way to do that, considering:
efficiency
best-practice
DB/server-side usage trade-off
what you should do in real life
same case for 3, 4 or more tables that can be "merged" that way
The question is about ANY server-side/relational-db.
2 simple ways I was thinking about
(if you have others, please suggest!
notice I'm asking for a simple SERVER-SIDE and RELATIONAL-DB,
so please don't waste your time explaining why I shouldn't
use this kind of DB, use MVC design, etc., etc. ...):
2 loops, 5 simple "SELECT" queries
1 loop, 1 "JOIN" query
I've tried to give a simple and detailed example,
in order to explain myself & understand better your answers
(though how to write the code and/or
finding possible mistakes is not the issue here,
so try not to focus on that...)
SQL SCRIPTS FOR CREATING AND INSERTING DATA TO TABLES
CREATE TABLE persons
(
id int NOT NULL AUTO_INCREMENT,
fullName varchar(255),
PRIMARY KEY (id)
);
INSERT INTO persons (fullName) VALUES ('Alice'), ('Bob'), ('Carl'), ('Dan');
CREATE TABLE phoneNumbers
(
id int NOT NULL AUTO_INCREMENT,
personId int,
phoneNumber varchar(255),
PRIMARY KEY (id)
);
INSERT INTO phoneNumbers (personId, phoneNumber) VALUES ( 1, '123-456'), ( 1, '234-567'), (1, '345-678'), (2, '456-789'), (2, '567-890'), (3, '678-901'), (4, '789-012');
A JSON REPRESENTATION OF THE TABLES AFTER I "MERGED" THEM:
[
{
"id": 1,
"fullName": "Alice",
"phoneNumbers": [
"123-456",
"234-567",
"345-678"
]
},
{
"id": 2,
"fullName": "Bob",
"phoneNumbers": [
"456-789",
"567-890"
]
},
{
"id": 3,
"fullName": "Carl",
"phoneNumbers": [
"678-901"
]
},
{
"id": 4,
"fullName": "Dan",
"phoneNumbers": [
"789-012"
]
}
]
PSEUDO CODE FOR 2 WAYS:
1.
query: "SELECT id, fullName FROM persons"
personList = new List<Person>()
foreach row x in query result:
current = new Person(x.fullName)
"SELECT phoneNumber FROM phoneNumbers WHERE personId = x.id"
foreach row y in query result:
current.phoneNumbers.Push(y.phoneNumber)
personList.Push(current)
print personList
2.
query: "SELECT persons.id, fullName, phoneNumber FROM persons
LEFT JOIN phoneNumbers ON persons.id = phoneNumbers.personId"
personList = new List<Person>()
current = null
previouseId = null
foreach row x in query result:
if ( x.id != previouseId )
if ( current != null )
personList.Push(current)
current = null
current = new Person(x.fullName)
current.phoneNumbers.Push(x.phoneNumber)
print personList
CODE IMPLEMENTATION IN PHP/MYSQL:
1.
/* get all persons */
$result = mysql_query("SELECT id, fullName FROM persons");
$personsArray = array(); //Create an array
//loop all persons
while ($row = mysql_fetch_assoc($result))
{
//add new person
$current = array();
$current['id'] = $row['id'];
$current['fullName'] = $row['fullName'];
/* add all person phone-numbers */
$id = $current['id'];
$sub_result = mysql_query("SELECT phoneNumber FROM phoneNumbers WHERE personId = {$id}");
$phoneNumbers = array();
while ($sub_row = mysql_fetch_assoc($sub_result))
{
$phoneNumbers[] = $sub_row['phoneNumber']);
}
//add phoneNumbers array to person
$current['phoneNumbers'] = $phoneNumbers;
//add person to final result array
$personsArray[] = $current;
}
echo json_encode($personsArray);
2.
/* get all persons and their phone-numbers in a single query */
$sql = "SELECT persons.id, fullName, phoneNumber FROM persons
LEFT JOIN phoneNumbers ON persons.id = phoneNumbers.personId";
$result = mysql_query($sql);
$personsArray = array();
/* init temp vars to save current person's data */
$current = null;
$previouseId = null;
$phoneNumbers = array();
while ($row = mysql_fetch_assoc($result))
{
/*
if the current id is different from the previous id:
you've got to a new person.
save the previous person (if such exists),
and create a new one
*/
if ($row['id'] != $previouseId )
{
// in the first iteration,
// current (previous person) is null,
// don't add it
if ( !is_null($current) )
{
$current['phoneNumbers'] = $phoneNumbers;
$personsArray[] = $current;
$current = null;
$previouseId = null;
$phoneNumbers = array();
}
// create a new person
$current = array();
$current['id'] = $row['id'];
$current['fullName'] = $row['fullName'];
// set current as previous id
$previouseId = $current['id'];
}
// you always add the phone-number
// to the current phone-number list
$phoneNumbers[] = $row['phoneNumber'];
}
}
// don't forget to add the last person (saved in "current")
if (!is_null($current))
$personsArray[] = $current);
echo json_encode($personsArray);
P.S.
this link is an example of a different question here, where i tried to suggest the second way: tables to single json
Preliminary
First, thank you for putting that much effort into explaining the problem, and for the formatting. It is great to see someone who is clear about what they are doing, and what they are asking.
But it must be noted that that, in itself, forms a limitation: you are fixed on the notion that this is the correct solution, and that with some small correction or guidance, this will work. That is incorrect. So I must ask you to give that notion up, to take a big step back, and to view (a) the whole problem and (b) my answer without that notion.
The context of this answer is:
all the explicit considerations you have given, which are very important, which I will not repeat
the two most important of which is, what best practice and what I would do in real life
This answer is rooted in Standards, the higher order of, or frame of reference for, best practice. This is what the commercial Client/Server world does, or should be doing.
This issue, this whole problem space, is becoming a common problem. I will give a full consideration here, and thus answer another SO question as well. Therefore it might contain a tiny bit more detail that you require. If it does, please forgive this.
Consideration
The database is a server-based resource, shared by many users. In an online system, the database is constantly changing. It contains that One Version of the Truth (as distinct from One Fact in One Place, which is a separate, Normalisation issue) of each Fact.
the fact that some database systems do not have a server architecture, and that therefore the notion of server in such software is false and misleading, are separate but noted points.
As I understand it, JSON and JSON-like structures are required for "performance reasons", precisely because the "server" doesn't, cannot, perform as a server. The concept is to cache the data on each (every) client, such that you are not fetching it from the "server" all the time.
This opens up a can of worms. If you do not design and implement this properly, the worms will overrun the app.
Such an implementation is a gross violation of the Client/Server Architecture, which allows simple code on both sides, and appropriate deployment of software and data components, such that implementation times are small, and efficiency is high.
Further, such an implementation requires a substantial implementation effort, and it is complex, consisting of many parts. Each of those parts must be appropriately designed.
The web, and the many books written in this subject area, provide a confusing mix of methods, marketed on the basis of supposed simplicity; ease; anyone-can-do-anything; freeware-can-do-anything; etc. There is not scientific basis for any of those proposals.
Non-architecture & Sub-standard
As evidenced, you have learned that that some approaches to database design are incorrect. You have encountered one problem, one instance that that advice is false. As soon as you solve this one problem, the next problem, which is not apparent to you right now, will be exposed. The notions are a never-ending set of problems.
I will not enumerate all the false notions that are sometimes advocated. I trust that as you progress through my answer, you will notice that one after the other marketed notion is false.
The two bottom lines are:
The notions violate Architecture and Design Standards, namely Client/Server Architecture; Open Architecture; Engineering Principles; and to a lesser in this particular problem, Database Design Principles.
Which leads to people like you, who are trying to do an honest job, being tricked into implementing simple notions, which turn into massive implementations. Implementations that will never quite work, so they require substantial ongoing maintenance, and will eventually be replaced, wholesale.
Architecture
The central principle being violated is, never duplicate anything. The moment you have a location where data is duplicated (due to caching or replication or two separate monolithic apps, etc), you create a duplicate that will go out of synch in an online situation. So the principle is to avoid doing that.
Sure, for serious third-party software, such as a gruntly report tool, by design, they may well cache server-based data in the client. But note that they have put hundreds of man-years into implementing it correctly, with due consideration to the above. Yours is not such a piece of software.
Rather than providing a lecture on the principles that must be understood, or the evils and costs of each error, the rest of this answer provides the requested what would you do in real life, using the correct architectural method (a step above best practice).
Architecture 1
Do not confuse
the data which must be Normalised
with
the result set, which, by definition, is the flattened ("de-normalised" is not quite correct) view of the data.
The data, given that it is Normalised, will not contain duplicate values; repeating groups. The result set will contain duplicate values; repeating groups. That is pedestrian.
Note that the notion of Nested Sets (or Nested Relations), which is in my view not good advice, is based on precisely this confusion.
For forty-five years since the advent of the RM, they have been unable to differentiate base relations (for which Normalisation does apply) from derived relations (for which Normalisation does not apply).
Two of these proponents are currently questioning the definition of First Normal Form. 1NF is the foundation of the other NFs, if the new definition is accepted, all the NFs will be rendered value-less. The result would be that Normalisation itself (sparsely defined in mathematical terms, but clearly understood as a science by professionals) will be severely damaged, if not destroyed.
Architecture 2
There is a centuries-old scientific or engineering principle, that content (data) must be separated from control (program elements). This is because the analysis, design, and implementation of the two are completely different. This principle is no less important in the software sciences, where it has specific articulation.
In order to keep this brief (ha ha), instead of a discourse, I will assume that you understand:
That there is a scientifically demanded boundary between data and program elements. Mixing them up results in complex objects that are error-prone and hard to maintain.
The confusion of this principle has reached epidemic proportions in the OO/ORM world, the consequences reach far and wide.
Only professionals avoid this. For the rest, the great majority, they accept the new definition as "normal", and they spend their lives fixing problems that we simply do not have.
The architectural superiority, the great value, of data being both stored and presented in Tabular Form per Dr E F Codd's Relational Model. That there are specific rules for Normalisation of data.
And importantly, you can determine when the people, who write and market books, advise non-relational or anti-relational methods.
Architecture 3
If you cache data on the client:
Cache the absolute minimum.
That means cache only the data that does not change in the online environment. That means Reference and Lookup tables only, the tables that populate the higher level classifiers, the drop-downs, etc.
Currency
For every table that you do cache, you must have a method of (a) determining that the cached data has become stale, compared to the One Version of the Truth which exists on the server, and (b) refreshing it from the server, (c) on a table-by-table basis.
Typically, this involves a background process that executes every (e) five minutes, that queries the MAX updated DateTime for each cached table on the client vs the DateTime on the server, and if changed, refreshes the table, and all its child tables, those that dependent on the changed table.
That, of course, requires that you have an UpdatedDateTime column on every table. That is not a burden, because you need that for OLTP ACID Transactions anyway (if you have a real database, instead of a bunch of sub-standard files).
Which really means, never replicate, the coding burden is prohibitive.
Architecture 4
In the sub-commercial, non-server world, I understand that some people advise the reverse caching of "everything".
That is the only way the programs like PostgreSQL, can to the used in a multi-user system.
You always get what you pay for: you pay peanuts, you get monkeys; you pay zero, you get zero.
The corollary to Architecture 3 is, if you do cache data on the client, do not cache tables that change frequently. These are the transaction and history tables. The notion of caching such tables, or all tables, on the client is completely bankrupt.
In a genuine Client/Server deployment, due to use of applicable standards, for each data window, the app should query only the rows that are required, for that particular need, at that particular time, based on context or filter values, etc. The app should never load the entire table.
If the same user using the same window inspected its contents, 15 minutes after the first inspection, the data would be 15 mins out of date.
For freeware/shareware/vapourware platforms, which define themselves by the absence of a server architecture, and thus by the result, that performance is non-existent, sure, you have to cache more than the minimum tables on the client.
If you do that, you must take all the above into account, and implement it correctly, otherwise your app will be broken, and the ramifications will drive the users to seek your termination. If there is more than one user, they will have the same cause, and soon form an army.
Architecture 5
Now we get to how you cache those carefully chosen tables on the client.
Note that databases grow, they are extended.
If the system is broken, a failure, it will grow in small increments, and require a lot of effort.
If the system is even a small success, it will grow exponentially.
If the system (each of the database, and the app, separately) is designed and implemented well, the changes will be easy, the bugs will be few.
Therefore, all the components in the app must be designed properly, to comply with applicable standards, and the database must be fully Normalised. This in turn minimises the effect of changes in the database, on the app, and vice versa.
The app will consist of simple, not complex, objects, which are easy to maintain and change.
For the data that you do cache on the client, you will use arrays of some form: multiple instances of a class in an OO platform; DataWindows (TM, google for it) or similar in a 4GL; simple arrays in PHP.
(Aside. Note that what people in situations such as yours produce in one year, professional providers using a commercial SQL platform, a commercial 4GL, and complying with Architecture and Standards.)
Architecture 6
So let's assume that you understand all the above, and appreciate its value, particularly Architecture 1 & 2.
If you don't, please stop here and ask questions, do not proceed to the below.
Now that we have established the full context, we can address the crux of your problem.
In those arrays in the app, why on Earth would you store flattened views of data ?
and consequently mess with, and agonise over, the problems
instead of storing copies of the Normalised tables ?
Answer
Never duplicate anything that can be derived. That is an Architectural Principle, not limited to Normalisation in a database.
Never merge anything.
If you do, you will be creating:
data duplication, and masses of it, on the client. The client will not only be fat and slow, it will be anchored to the floor with the ballast of duplicated data.
additional code, which is completely unnecessary
complexity in that code
code that is fragile, that will constantly have to change.
That is the precise problem you are suffering, a consequence of the method, which you know intuitively is wrong, that there must be a better way. You know it is a generic and common problem.
Note also that method, that code, constitutes a mental anchor for you. Look at the way that you have formatted it and presented it so beautifully: it is of importance to you. I am reluctant to inform you of all this.
Which reluctance is easily overcome, due to your earnest and forthright attitude, and the knowledge that you did not invent this method
In each code segment, at presentation time, as and when required:
a. In the commercial Client/Server context
Execute a query that joins the simple, Normalised, unduplicated tables, and retrieves only the qualifying rows. Thereby obtaining current data values. The user never sees stale data. Here, Views (flattened views of Normalised data) are often used.
b. In the sub-commercial non-server context
Create a temporary result-set array, and join the simple, unduplicated, arrays (copies of tables that are cached), and populate it with only the qualifying rows, from the source arrays. The currency of which is maintained by the background process.
Use the Keys to form the joins between the arrays, in exactly the same way that Keys are used to form the joins in the Relational tables in the database.
Destroy those components when the user closes the window.
A clever version would eliminate the result-set array, and join the source arrays via the Keys, and limit the result to the qualifying rows.
Separate to being architecturally incorrect, Nested Arrays or Nested Sets or JSON or JSON-like structures are simply not required. This is the consequence of confusing the Architecture 1 Principle.
If you do choose to use such structures, then use them only for the temporary result-set arrays.
Last, I trust this discourse demonstrates that n tables is a non-issue. More important, that m levels deep in the data hierarchy, the "nesting", is a non-issue.
Answer 2
Now that I have given the full context (and not before), which removes the implications in your question, and makes it a generic, kernel one.
The question is about ANY server-side/relational-db. [Which is better]:
2 loops, 5 simple "SELECT" queries
1 loop, 1 "JOIN" query
The detailed examples you have given are not accurately described above. The accurate descriptions is:
Your Option 1
2 loops, each loop for loading each array
1 single-table SELECT query per loop
(executed n x m times ... the outermost loop, only, is a single execution)
Your Option 2
1 Joined SELECT query executed once
followed by 2 loops, each loop for loading each array
For the commercial SQL platforms, neither, because it does not apply.
The commercial SQL server is a set-processing engine. Use one query with whatever joins are required, that returns a result set. Never step through the rows using a loop, that reduces the set-processing engine to a pre-1970's ISAM system. Use a View, in the server, since it affords the highest performance and the code is in one place.
However, for the non-commercial, non-server platforms, where:
your "server" is not a set-processing engine ie. it returns single rows, therefore you have to fetch each row and fill the array, manually or
your "server" does not provide Client/Server binding, ie. it does not provide facilities on the client to bind the incoming result set to a receiving array, and therefore you have to step through the returned result set, row by row, and fill the array, manually,
as per your example then, the answer is, by a large margin, your option 2.
Please consider carefully, and comment or ask questions.
Response to Comment
Say I need to print this json (or other html page) to some STOUT (example: an http response to: GET /allUsersPhoneNumbers. It's just an example to clarify what I'm expecting to get), should return this json. I have a php function that got this 2 result sets (1). now it should print this json - how should I do that? this report could be an employee month salary for a whole year, and so one. one way or anther, I need to gather this information and represent it in a "JOIN"ed representation
Perhaps I was not clear enough.
Basically, do not use JSON unless you absolutely have to. Which means sending to some system that requires it, which means that receiving system, and that demand is stupid.
Make sure that your system doesn't make such demands on others.
Keep your data Normalised. Both in the database, and in whatever program elements that you write. That means (in this example) use one SELECT per table or array. That is for loading purposes, so that you can refer to and inspect them at any point in the program.
When you need a join, understand that it is:
a result-set; a derived relation; a view
therefore temporary, it exists for the duration of the execution of that element, only
a. For tables, join them in the usual manner, via Keys. One query, joining two (or more) tables.
b. For arrays, join arrays in the program, the same way you join tables in the database, via Keys.
For the example you have given, which is a response to some request, first understand that it is the category [4], and then fulfil it.
Why even consider JSON?
What has JSON got to do with this?
JSON is misunderstood and people are interested in the wow factor. It is a solution looking for a problem. Unless you have that problem it has no value.
Check these two links:
Copter - What is JSON
Stack Overflow - What is JSON
Now if you understand that, it is mostly for incoming feeds. Never for outgoing. Further, it requires parsing, deconstructing, etc, before the can be used.
Recall:
I need to gather this information and represent it in a "JOIN"ed representation
Yes. That is pedestrian. Joined does not mean JSONed.
In your example, the receiver is expecting a flattened view (eg. spreadsheet), with all the cells filled, and yes, for Users with more than one PhoneNumber, their User details will be repeated on the second nad subsequent result-set row. For any kind of print, eg. for debugging, I want a flattened view. It is just a:
SELECT ... FROM Person JOIN PhoneNumber
And return that. Or if you fulfil the request from arrays, join the Person and PhoneNumber Arrays, which may require a temporary result-set array, and return that.
please don't tell me you should get only 1 user at a time, etc. etc.
Correct. If someone tells you to regress to procedural processing (ie. row by row, in a WHILE loop), where the engine or your program has set processing (ie. processes an entire set in one command), that marks them as someone who should not be listened to.
I have already stated, your Option 2 is correct, Option 1 is incorrect. That is as far as the GET or SELECT is concerned.
On the other hand, for programming languages that do not have set-processing capability (ie. cannot print/set/inspect an array in a single command), or "servers" that do not provide client-side array binding, you do have to write loops, one loop per depth of the data hierarchy (in your example, two loops, one for Person, and one for PhoneNumber per User).
You have to do that to parse an incoming JSON object.
You have to do that to load each array from the result set that is returned in your Option 2.
You have to do that to print each array from the result set that is returned in your Option 2.
Response to Comment 2
I've ment I have to return a result represented in a nested version (let's say I'm printing the report to the page), json was just an example for such representation.
I don't think you understand the reasoning and the conclusions I have provided in this answer.
For printing and displaying, never nest. Print a flattened view, the rows returned from the SELECT per Option 2. That is what we have been doing, when printing or displaying data Relationally, for 31 years. It is easier to read, debug, search, find, fold, staple, mutilate. You cannot do anything with a nested array, except look at it, and say gee that is interesting.
Code
Caveat
I would prefer to take your code and modify it, but actually, looking at your code, it is not well written or structured, it cannot be reasonably modified. Second, if I use that, it would be a bad teaching tool. So I will have to give you fresh, clean code, otherwise you will not learn the correct methods.
This code examples follow my advice, so I am not going to repeat. And this is way beyond the original question.
Query & Print
Your request, using your Option 2. One SELECT executed once. Followed by one loop. Which you can "pretty up" if you like.
In general it is a best practice to grab the data you need in as few trips to the database as possible then map the data into the appropriate objects. (Option 2)
But, to answer your question I would ask yourself what the use case for your data is. If you know for sure that you will be needing your person and your phone number data then I would say the second method is your best option.
However, option one can also have its use case when the joined data is optional.One example of this could be that on the UI you have a table of all your people and if a user wants to see the phone number for a particular person they have to click on that person. Then it would be acceptable to "lazy-load" all of the phone numbers.
This is the common problem, especially if you are creating a WebAPIs, converting those table sets to nested arrays is a big deal..
I always go for you the second option(in slightly different method though), because the first is worst possible way to do it... One thing I learned with my experience is never query inside a loop, that is a waste of DB calls, well you know what I trying to say.
Although I don't accept all the things PerformanceDBA said, there are two major things I need the address,
1. Don't have duplicate data
2. Fetch only data you want
The Only problem I see in Joining the table is, we end up duplicating data lots of them, take you data for example, Joining Person ans phoneNumber tables we end up duplicating Every person for each of his phone number, for two table with few hundred rows its fine, imagine we need to merge 5 tables with thousands of rows its huge...
So here's my solution:
Query:
SELECT id, fullName From Person;
SELECT personId, phoneNumber FROM phoneNumbers
WHERE personId IN (SELECT id From Person);
So I get to tables in my result set, now I assign Table[0] to my Person list,
and use a 2 loops to place right phoneNumbers in to right person...
Code:
personList = ConvertToEntity<List<Person>>(dataset.Table[0]);
pnoList = ConvertToEntity<List<PhoneNumber>>(dataset.Table[1]);
foreach (person in personList) {
foreach (pno in pnoList) {
if(pno.PersonId = person.Id)
person.PhoneNumer.Add(pno)
}
}
I think above method reduce lots of duplication and only get me what I wanted, if there is any downside to the above method please let me know... and thanks for asking these kind of questions...

Struggling With OOP Concept

I'm really struggling with a recurring OOP / database concept.
Please allow me to explain the issue with pseudo-PHP-code.
Say you have a "user" class, which loads its data from the users table in its constructor:
class User {
public $name;
public $height;
public function __construct($user_id) {
$result = Query the database where the `users` table has `user_id` of $user_id
$this->name= $result['name'];
$this->height = $result['height'];
}
}
Simple, awesome.
Now, we have a "group" class, which loads its data from the groups table joined with the groups_users table and creates user objects from the returned user_ids:
class Group {
public $type;
public $schedule;
public $users;
public function __construct($group_id) {
$result = Query the `groups` table, joining the `groups_users` table,
where `group_id` = $group_id
$this->type = $result['type'];
$this->schedule = $result['schedule'];
foreach ($result['user_ids'] as $user_id) {
// Make the user objects
$users[] = new User($user_id);
}
}
}
A group can have any number of users.
Beautiful, elegant, amazing... on paper. In reality, however, making a new group object...
$group = new Group(21); // Get the 21st group, which happens to have 4 users
...performs 5 queries instead of 1. (1 for the group and 1 for each user.) And worse, if I make a community class, which has many groups in it that each have many users within them, an ungodly number of queries are ran!
The Solution, Which Doesn't Sit Right To Me
For years, the way I've got around this, is to not code in the above fashion, but instead, when making a group for instance, I would join the groups table to the groups_users table to the users table as well and create an array of user-object-like arrays within the group object (never using/touching the user class):
class Group {
public $type;
public $schedule;
public $users;
public function __construct($group_id) {
$result = Query the `groups` table, joining the `groups_users` table,
**and also joining the `users` table,**
where `group_id` = $group_id
$this->type = $result['type'];
$this->schedule = $result['schedule'];
foreach ($result['users'] as $user) {
// Make user arrays
$users[] = array_of_user_data_crafted_from_the_query_result;
}
}
}
...but then, of course, if I make a "community" class, in its constructor I'll need to join the communities table with the communities_groups table with the groups table with the groups_users table with the users table.
...and if I make a "city" class, in its constructor I'll need to join the cities table with the cities_communities table with the communities table with the communities_groups table with the groups table with the groups_users table with the users table.
What an unmitigated disaster!
Do I have to choose between beautiful OOP code with a million queries VS. 1 query and writing these joins by hand for every single superset? Is there no system that automates this?
I'm using CodeIgniter, and looking into countless other MVC's, and projects that were built in them, and cannot find a single good example of anyone using models without resorting to one of the two flawed methods I've outlined.
It appears this has never been done before.
One of my coworkers is writing a framework that does exactly this - you create a class that includes a model of your data. Other, higher models can include that single model, and it crafts and automates the table joins to create the higher model that includes object instantiations of the lower model, all in a single query. He claims he's never seen a framework or system for doing this before, either.
Please Note:
I do indeed always use separate classes for logic and persistence. (VOs and DAOs - this is the entire point of MVCs). I have merely combined the two in this thought-experiment, outside of an MVC-like architecture, for simplicity's sake. Rest assured that this issue persists regardless of the separation of logic and persistence. I believe this article, introduced to me by James in the comments below this question, seems to indicate that my proposed solution (which I've been following for years) is, in fact, what developers currently do to solve this issue. This question is, however, attempting to find ways of automating that exact solution, so it doesn't always need to be coded by hand for every superset. From what I can see, this has never been done in PHP before, and my coworker's framework will be the first to do so, unless someone can point me towards one that does.
And, also, of course I never load data in constructors, and I only call the load() methods that I create when I actually need the data. However, that is unrelated to this issue, as in this thought experiment (and in the real-life situations where I need to automate this), I always need to eager-load the data of all subsets of children as far down the line as it goes, and not lazy-load them at some future point in time as needed. The thought experiment is concise -- that it doesn't follow best practices is a moot point, and answers that attempt to address its layout are likewise missing the point.
EDIT : Here is a database schema, for clarity.
CREATE TABLE `groups` (
`group_id` int(11) NOT NULL, <-- Auto increment
`make` varchar(20) NOT NULL,
`model` varchar(20) NOT NULL
)
CREATE TABLE `groups_users` ( <-- Relational table (many users to one group)
`group_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL
)
CREATE TABLE `users` (
`user_id` int(11) NOT NULL, <-- Auto increment
`name` varchar(20) NOT NULL,
`height` int(11) NOT NULL,
)
(Also note that I originally used the concepts of wheels and cars, but that was foolish, and this example is much clearer.)
SOLUTION:
I ended up finding a PHP ORM that does exactly this. It is Laravel's Eloquent. You can specify the relationships between your models, and it intelligently builds optimized queries for eager loading using syntax like this:
Group::with('users')->get();
It is an absolute life saver. I haven't had to write a single query. It also doesn't work using joins, it intelligently compiles and selects based on foreign keys.
Say you have a "wheel" class, which loads its data from the wheels table in its constructor
Constructors should not be doing any work. Instead they should contain only assignments. Otherwise you make it very hard to test the behavior of the instance.
Now, we have a "car" class, which loads its data from the cars table joined with the cars_wheels table and creates wheel objects from the returned wheel_ids:
No. There are two problems with this.
Your Car class should not contain both code for implementing "car logic" and "persistence logic". Otherwise you are breaking SRP. And wheels are a dependency for the class, which means that the wheels should be injected as parameter for the constructor (most likely - as a collection of wheels, or maybe an array).
Instead you should have a mapper class, which can retrieve data from database and store it in the WheelCollection instance. And a mapper for car, which will store data in Car instance.
$car = new Car;
$car->setId( 42 );
$mapper = new CarMapper( $pdo );
if ( $mapper->fetch($car) ) //if there was a car in DB
{
$wheels = new WheelCollection;
$otherMapper = new WheelMapper( $pdo );
$car->addWheels( $wheels );
$wheels->setType($car->getWheelType());
// I am not a mechanic. There is probably some name for describing
// wheels that a car can use
$otherMapper->fetch( $wheels );
}
Something like this. The mapper in this case are responsible for performing the queries. And you can have several source for them, for example: have one mapper that checks the cache and only, if that fails, pull data from SQL.
Do I really have to choose between beautiful OOP code with a million queries VS. 1 query and disgusting, un-OOP code?
No, the ugliness comes from fact that active record pattern is only meant for the simplest of usecases (where there is almost no logic associated, glorified value-objects with persistence). For any non-trivial situation it is preferable to apply data mapper pattern.
..and if I make a "city" class, in its constructor I'll need to join the cities table with the cities_dealerships table with the dealerships table with the dealerships_cars table with the cars table with the cars_wheels table with the wheels table.
Jut because you need data about "available cares per dealership in Moscow" does not mean that you need to create Car instances, and you definitely will not care about wheels there. Different parts of site will have different scale at which they operate.
The other thing is that you should stop thinking of classes as table abstractions. There is no rule that says "you must have 1:1 relation between classes and tables".
Take the Car example again. If you look at it, having separate Wheel (or even WheelSet) class is just stupid. Instead you should just have a Car class which already contains all it's parts.
$car = new Car;
$car->setId( 616 );
$mapper = new CarMapper( $cache );
$mapper->fetch( $car );
The mapper can easily fetch data not only from "Cars" table but also from "Wheel" and "Engines" and other tables and populate the $car object.
Bottom line: stop using active record.
P.S.: also, if you care about code quality, you should start reading PoEAA book. Or at least start watching lectures listed here.
my 2 cents
ActiveRecord in Rails implements the concept of lazy loading, that is deferring database queries until you actually need the data. So if you instantiate a my_car = Car.find(12) object, it only queries the cars table for that one row. If later you want my_car.wheels then it queries the wheels table.
My suggestion for your pseudo code above is to not load every associated object in the constructor. The car constructor should query for the car only, and should have a method to query for all of it's wheels, and another to query it's dealership, which only queries for the dealership and defers collecting all of the other dealership's cars until you specifically say something like my_car.dealership.cars
Postscript
ORMs are database abstraction layers, and thus they must be tuned for ease of querying and not fine tuning. They allow you to rapidly build queries. If later you decide that you need to fine tune your queries, then you can switch to issuing raw sql commands or trying to otherwise optimize how many objects you're fetching. This is standard practice in Rails when you start doing performance tuning - look for queries that would be more efficient when issued with raw sql, and also look for ways to avoid eager loading (the opposite of lazy loading) of objects before you need them.
In general, I'd recommend having a constructor that takes effectively a query row, or a part of a larger query. How do do this will depend on your ORM. That way, you can get efficient queries but you can construct the other model objects after the fact.
Some ORMs (django's models, and I believe some of the ruby ORMs) try to be clever about how they construct queries and may be able to automate this for you. The trick is to figure out when the automation is going to be required. I do not have personal familiarity with PHP ORMs.

Object oriented representation of multi-table query

Suppose we have two related tables, for example one representing a person:
PERSON
name
age
...
current_status_id
and one representing a status update at a specific time for this person:
STATUS_HISTORY
recorded_on
status_id
blood_pressure
length
...
I have built an application in PHP using Zend Framework, and tried to retain 'object orientedness' by using a class for representing a person and a class for representing the status of a person. I also tried to use ORM principles where possible, such as using the data mapper for separating the domain model from the data layer.
What would be a nice (and object oriented) way of returning a list of persons from a data mapper, where in the list I sometimes want to know the last measured blood_pressure of the person, and sometimes not (depending on the requirements of the report/view in which the list is used). The same holds for different fields, e.g. values computed at the data layer (sum's, count's, etc.).
My first thought was using a rowset (e.g. Zend_Db_Rowset) but this introduces high coupling between my view and data layer. Another way might be to return a list of persons, and then querying for each person the latest status using a data mapper for requesting the status of a specific person. However, this will result in (at least) one additional query for each person record, and does not allow me to use JOINS at the data layer.
Any suggestions?
We have this same issue because of our ORM where I work. If you are worried enough about the performance hit of having to first get a list of your persons, then query for their statuses individually, you really have no other choice but to couple your data a little bit.
In my opinion, this is okay. You can either create a class that will hold the single "person" data and an array containing "status_history" records or suffer the performance hit of making another query per "person". You COULD reduce your query overhead by doing data caching locally (your controller would have to decide that if a request for a set of data is made before a certain time threshold, it just returns its own data instead of querying the db server)
Having a pure OO view is nice, but sometimes impractical.
Try to use "stdclass" class which is PHP's inbuild class, You can get the object of stdclass which will be created automatically by PHP and its member variable will be column name. So u can get object and get the values by column name. For example.
Query is
SELECT a.dept_id,a.dept_name,a.e_id,b.emp_name,b.emp_id from DEPT a,EMP b where b.emp_id=a.e_id;
Result will be array of stdclass objects. Each row represents one stdclass object.
Object
STDCLASS
{
dept_id;
dept_name;
e_id;
emp_id;
emp_name;
}
You can access like
foreach($resultset as $row)
{
$d_id = $row->dept_id;
$d_nam= $row->dept_name;
$e_id = $row->e_id;
$em_id= $row->emp_id;
$e_nam= $row->emp_name;
}
But
Blockquote
I am not sure about performance.

Categories