How to query employee details and relate their performance metrics? - php

I am fetching the id, first name, and last name of all employees that are approved and not archived. Then I am looping these results and using the ids to query other tables to collect some count data.
I tried the below code, but I am not getting the expected output.
$queryEmp = "
SELECT id, firstname, lastname
FROM tbl_employee as e
WHERE is_archive=0 and is_approved=1
";
$getQuery= $this->db->query($queryEmp);
$result= $getQuery->result();
foreach ($result as $key=> $value) {
//echo "<pre>";
print_r($value);
$day = "MONTH(date_of_created) = DATE(CURRENT_DATE())";
$group = "f_id IN (SELECT MAX(f_id) FROM tbl_fileStatus GROUP BY f_bankid)";
$condiion = "and ba.createdby='" . $value->id . "' and " . $day ." and " . $group;
$query2 = "
select
(SELECT COUNT(c_id)
FROM tbl_lead
WHERE leadstatus='1' AND ".$day.") as confirmCount,
(SELECT COUNT(f_id)
FROM tbl_fileStatus as fs
join tbl_bankdata as ba on ba.bank_id=fs.f_bankid
WHERE fs.f_filestatus=1 " . $condiion . ") as disbursed,
(SELECT COUNT(f_id)
FROM tbl_fileStatus as fs
join tbl_bankdata as ba on ba.bank_id=fs.f_bankid
WHERE fs.f_filestatus=2 ".$condiion.") as filesubmit
";
# code...
$getQuery2= $this->db->query($query2);
$result2[]=$getQuery2->result();
}
echo "<pre>";
print_r(result2);
$result looks like this:
Array (
[0] => stdClass Object (
[id] => 1
[firstname] => xyz
[lastname] => xyz
)
...
)
Second query output:
Array (
[0] => Array (
[0] => stdClass Object (
[fallowCall] => 0
[confirmCount] => 0
[disbursed] => 0
[filesubmit] => 0
)
)
...
)
How can I produce the correct results which relate respective employees with with their performance metrics? Either this structure:
Array (
[0] => stdClass Object (
[id] => 1
[firstname] => xyz
[lastname] => xyz
[somename] => (
[fallowCall] => 0
[confirmCount] => 0
[disbursed] => 0
[filesubmit] => 0
)
)
...
)
Or this structure:
Array (
[0] => stdClass Object (
[id] => 1
[firstname] => xyz
[lastname] => xyz
[fallowCall] => 0
[confirmCount] => 0
[disbursed] => 0
[filesubmit] => 0
)
...
)
I have added the my table structure and some sample data here: https://www.db-fiddle.com/f/8MoWmKPuzTrrC3DQJsiX35/0
some notes here
1) createdby is the id of table tbl_employee
2) lead_id in the bank table is the c_id of the table tbl_lead
3) f_bankid in the tbl_fileStatus is the bank_id of the table tbl_bankdata

There is actually no need to create the additional depth/complexity just to hold the count data. Furthermore, by using a combination of LEFT JOINs to connect the related tables and apply your required conditional rules, you can achieve your desired result by making just one trip to the database. This will without question provide superior efficiency for your application. LEFT JOINs are important to use so that counts can be zero without excluding employees from the result set.
Also, I should point out that your attempted query was mistakenly comparing a MONTH() value against a DATE() value -- that was never going to end well. :) In fact, to ensure that your sql is accurately isolating the current month from the current year, you need to be also checking the YEAR value.
My recommended sql:
SELECT
employees.id,
employees.firstname,
employees.lastname,
COUNT(DISTINCT leads.c_id) AS leadsThisMonth,
SUM(IF(fileStatus.f_filestatus = 1, 1, 0)) AS disbursedThisMonth,
SUM(IF(fileStatus.f_filestatus = 2, 1, 0)) AS filesubmitThisMonth
FROM tbl_employee AS employees
LEFT JOIN tbl_lead AS leads
ON employees.id = leads.createdby
AND leadstatus = 1
AND MONTH(leads.date_of_created) = MONTH(CURRENT_DATE())
AND YEAR(leads.date_of_created) = YEAR(CURRENT_DATE())
LEFT JOIN tbl_bankdata AS bankData
ON employees.id = bankData.createdby
LEFT JOIN tbl_fileStatus AS fileStatus
ON bankData.bank_id = fileStatus.f_bankid
AND MONTH(fileStatus.date_of_created) = MONTH(CURRENT_DATE())
AND YEAR(fileStatus.date_of_created) = YEAR(CURRENT_DATE())
AND fileStatus.f_id = (
SELECT MAX(subFileStatus.f_id)
FROM tbl_fileStatus AS subFileStatus
WHERE subFileStatus.f_bankid = bankData.bank_id
GROUP BY subFileStatus.f_bankid
)
WHERE employees.is_archive = 0
AND employees.is_approved = 1
GROUP BY employees.id, employees.firstname, employees.lastname
The SUM(IF()) expression is a technique used to execute a "conditional count". "Aggregate data" is formed by using GROUP BY and there are specialized "aggregate functions" which must be used to create linear/flat data from these clusters/non-flat collections of data. fileStatus data is effectively piled up upon itself due to the GROUP BY call. If COUNT(fileStatus.f_filestatus) was called, it would count all of the rows in the cluster. Since you wish to differentiate between f_filestatus = 1 and f_filestatus = 2, an IF() statement is used. This is doing the same thing as COUNT() (adding 1 for every qualifying occurrence), but it is different from COUNT() in that it does not count specific rows (within the scope of the cluster) unless the IF() expression is satisfied. Another example.
Here is a db fiddle demo with some adjustments to your supplied sample data: https://www.db-fiddle.com/f/8MoWmKPuzTrrC3DQJsiX35/4 (The result set will only be "good" while the current is June of this year.)
After saving the above string as $sql, you can simply execute it and loop through the array of objects like this:
foreach ($this->db->query($sql)->result() as $object) {
// these are the properties available in each object
// $object->id
// $object->firstname
// $object->lastname
// $object->leadsThisMonth
// $object->disbursedThisMonth
// $object->filesubmitThisMonth
}

Related

Querying a database with a one to many relationship

I am making a database that when users register, they pick 3 games they would like to play. the games are stored in a separate table (gameinfo) from the user information table (personalinformation). I am querying with the first game being shown but I would like all three shown for each user. How would I implement showing all the games?
I have tried to create different variables for each game, but that has seemed to not work as I expected and broke. the games when they are stored on the personalinformation table are stored as numbers like 1 or 2. these are linked to the gameinfo table and are the primary key for each game.
Structure of database
https://imgur.com/a/qee9C1t
$conn = mysqli_connect('localhost', 'root', '', 'esportclub');
$sql = "SELECT user_ID, username, Email, Gender, firstName, lastName, gameName FROM personalinformation, gameinfo WHERE game_id = firstGame";
$result = mysqli_query ($conn, $sql);
if (mysqli_num_rows($result) > 0) {
echo "<table>";
while($row = mysqli_fetch_assoc($result)) {
echo " <tr><td> Name: ". $row{"username"}. " </td><td> Email: ". $row{"Email"}. " </td><td> Gender: ". $row{"Gender"}. "</td>" .
"<td> First Name: ". $row{"firstName"}. " </td><td> First Game: ". $row{"gameName"}. "</td><td> Last Name: ". $row{"lastName"}. "</td>" . "</td></tr>" ;
}
echo "</table>";
}
else{
echo "0 results";
}
$conn->close();
As mentioned in my comment, I would create a table to associate users and games by storing unique pairs of user_ID and game_id values. Then I'd JOIN the tables together accordingly.
However, I see that you are storing three game values for each user in the personalinformation table, in columns named firstGame,secondGame, and thirdGame.
In that case, you can JOIN the game table to each of those columns.
So, with your existing structure:
SELECT
p.*,
game1.`gameName` as `firstGame_name`,
game2.`gameName` as `secondGame_name`,
game3.`gameName` as `thirdGame_name`
FROM `personalinformation` p
LEFT JOIN `games` as game1 ON (game1.`game_id` = p.`firstGame`)
LEFT JOIN `games` as game2 ON (game2.`game_id` = p.`secondGame`)
LEFT JOIN `games` as game3 ON (game3.`game_id` = p.`thirdGame`)
WHERE 1; // or WHERE p.`user_ID` = :user_ID;
EDIT
Since many users can own a game and a user can own many games, it sounds like a "many-to-many" relationship.
Here is my preferred method for that type of relationship. One advantage is that you don't need to limit the number of assigned games. That is, a user can own any number of games.
Create a third table to store unique user/game pairs.
It will tells you which games are assigned to which users.
Something like:
CREATE TABLE `user_game` (
`user_id` MEDIUMINT NOT NULL ,
`game_id` MEDIUMINT NOT NULL
);
ALTER TABLE `user_game`
ADD UNIQUE `unique pair` (`user_id`, `game_id`);
Then join the three tables together:
SELECT
u.*,
g.`game_id`,
g.`gameName`
FROM `personalinformation` u
LEFT JOIN `user_game` as ug ON ( ug.`user_id` = u.`user_ID` )
LEFT JOIN `games` as g ON ( g.`game_id` = ug.`game_id` )
WHERE 1;
You'll get back one row for every user/game relationship.
If one user has three games, that user will have three rows in the result, each row including one gameName.
For example:
Name Game
---- -----------------
Jane League of Legends
Jane Minecraft
Fred Dota 2
Alex Minecraft
Alex War Dragons
Alex Fortnite
More complex display might require some processing:
<?php
$users = array();
while($row= mysqli_fetch_object($result)) {
$uid = $row->user_ID;
// if this user isn't in the array...
if (!array_key_exists($uid,$users)) {
// ... create a user entry ...
$user = new stdClass();
$user->firstname = $row->firstName;
// ... and add it to the user array.
$users[$uid] = $user;
}
// if this row has a valid game ...
if (!empty($row->game_id)) {
// ... create a game entry ...
$game = new stdClass();
$game->id = $row->game_id;
$game->name = $row->gameName;
//.. and add the game to the user's entry
$users[$uid]->games[$game->id]=$game;
}
}
For a structure like this:
Array
(
[1] => stdClass Object
(
[firstname] => Jane
[games] => Array
(
[1] => stdClass Object
(
[id] => 1
[name] => Leage of Legends
)
[2] => stdClass Object
(
[id] => 2
[name] => Minecraft
)
)
)
[2] => stdClass Object
(
[firstname] => Fred
[games] => Array
(
[3] => stdClass Object
(
[id] => 3
[name] => Dota 2
)
)
)
[3] => stdClass Object
(
[firstname] => Alex
[games] => Array
(
[2] => stdClass Object
(
[id] => 2
[name] => Minecraft
)
[4] => stdClass Object
(
[id] => 4
[name] => War Dragons
)
[5] => stdClass Object
(
[id] => 5
[name] => Fortnite
)
)
)
)

Clean result from multiple left joins in mysql

Let's say I have these 3 tables:
Person table
id | name
1 | Sam
Dress table
id | person_id |name
1 | 1 |shorts
2 | 1 |tshirt
Interest table
id | person_id | interest
1 | 1 | football
2 | 1 | basketball
(Above is just a simplified example, in real I have a lot many tables to join)
I need to show all these details on a page, so combined all into 1 left join query mainly for performance. Now the result we get should be messy with repeated results for combinations of dresses and interests for a person. To fix this I will need to manually loop through to arrange into an array that I want to consume. My query looks something like this (am I doing it right?):
select p.id, d.name, i.interest
from person as p
left join dress as d on p.id = d.person_id
left join interest as i on p.id = i.person_id
where p.id = 1;
What is a better way to do this? I am aware that I can also use GROUP_CONCAT to avoid repetition.
UPDATED WITH OUTPUT
I want my final result to look like this (I know I need to loop through to get this format), what would be the best way to query my tables to achieve this?
[
[
'id' => 1,
'dresses' => [
[
'id' => 1,
'name' => 'shorts',
...more columns
],
[
'id' => 2,
'name' => 'tshirt',
..more columns
]
],
'interests' => [
'football',
'basketball'
]
]
]
Amount of data vs. flexibility:
Personally, for your task - let's suppose it's a bit more complex than it is presented, ok? - I wouldn't recommend you to use any sql functions (like group_concat, etc) at all. You may, of course, get a smaller quantity of data by using them. But you would certainly loose the flexibility you need to read and process the fetched results.
Think about running a query with (maybe a lot) more columns. Would you still want to "beautify" the query if some of them would suddenly require you to apply other sql functions or conditions - like another simple, but tricky GROUP BY clauses? What would happen then with your results reading algorithm? It would have to be (maybe hard)-rethought again.
Resources eaters:
Also, please keep in mind, that all these group_concat functions/selections are eating MySQL resources too.
Indexes and EXPLAIN for optimizations:
I'm just also thinking about a situation, in which you would want to apply indexes to some fields - for searching purposes, for example. And that you would want to check their validity/rapidity with an EXPLAIN command. I sincerely don't know if having group_concat's would make this an easy and transparent task.
Display purposes vs. post-processing?
In general, functions like group_concat are used for display purposes, for example in data grids/tables. But your task requires post-processing of the fetched data.
Already sorted:
That said, in your original question you already presented an sql solution. IMHO, your version is the proper and the flexible one. And your sql statement is already correct. You can maybe apply some ORDER BY conditions, in order to directly build a sorted array from the fetched data.
Fetch data and/or post-processing... Alternatives?
You are trying to fetch a lot of data at once AND to post-process it too. This is a sign, that both, the database AND the PHP engine have to work a lot. Maybe it would be better to project your task in another way. E.g. fetch a lot of data without post-processing. Or fetch a smaller amount of data and allow PHP to post-process it. Look what I've found today on the PDOStatement::fetchAll webpage
PDOStatement::fetchAll - Return Values:
Using this method to fetch large result sets will result in a heavy
demand on system and possibly network resources. Rather than
retrieving all of the data and manipulating it in PHP, consider using
the database server to manipulate the result sets. For example, use
the WHERE and ORDER BY clauses in SQL to restrict results before
retrieving and processing them with PHP.
Uniform array structure:
Is there a special reason to build your resulting array to have a not uniform structure (regarding interests)? Wouldn't it be better to uniformize the array structure? See my results in PHP after post-processing, to understand what I mean vs. the structure you requested.
Code version:
I've prepared a php version - not OOP for this problem - of the data fetching and array building steps. I've commented it and also displayed the data source on which I was testing. In the end I'll also present the results. The steps of building the final array ($personDetails) are straightforward: loop through the fetched data and transfer it only (!) if not already.
Mandatory aliases for same columns from different tables:
I tried to fetch all dress and interest data at once (using wild cards) like this:
SELECT d.*, i.* FROM ...
I ran some tests in PHP and tried some coding options, but, in the end, I concluded: it's impossible to process the feched data in a way like this:
$fetchedData = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($fetchedData as $key => $record) {
$dressId = $record['d.id'];
$interestId = $record['i.id'];
//...
}
PHP have not assigned different items in the $record array for the two id columns, whatever I tried. The only one assigned item always corresponds to the last id column in the columns list. So, for a correct output, it's a mandatory task to skip using the wild-cards and to alias all columns having the same name and residing in different tables. Like this:
SELECT d.id AS dress_id, i.id AS interest_id FROM ...
... and the php code:
$fetchedData = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($fetchedData as $key => $record) {
$dressId = $record['dress_id'];
$interestId = $record['interest_id'];
//...
}
I'll be honest: even if this situation is somehow intuitiv, I never tested it. I've always used aliasing for the columns with the same names, but now I have the certitude given by the on-code tests too.
Address array item by key vs. search for array item key:
The resulting array ($personDetails) holds the fetched data as follows: each person's id is the KEY of the corresponding details item. Why I did (and recommend) this? Because you may want to directly read a person from the array by just passing the needed id. It's better to address an array item by its - unique - key than to search for it in the whole array.
Oh, almost forgotten: I ran the example on two persons, with different db entries/record numbers.
Good luck.
The code:
Tested on the following tables:
Results of running the query in db editor:
Fetch and process db data in PHP (read_person_details.php):
<?php
// Db configs.
define('HOST', 'localhost');
define('PORT', 3306);
define('DATABASE', 'db');
define('USERNAME', 'user');
define('PASSWORD', 'pass');
define('CHARSET', 'utf8');
/*
* Error reporting.
* To do: define an error handler, an exception handler and a shutdown
* handler function to handle the raised errors and exceptions.
*
* #link http://php.net/manual/en/function.error-reporting.php
*/
error_reporting(E_ALL);
ini_set('display_errors', 1); // SET IT TO 0 ON A LIVE SERVER!
/*
* Create a PDO instance as db connection to db.
*
* #link http://php.net/manual/en/class.pdo.php
* #link http://php.net/manual/en/pdo.constants.php
* #link http://php.net/manual/en/pdo.error-handling.php
* #link http://php.net/manual/en/pdo.connections.php
*/
$connection = new PDO(
sprintf('mysql:host=%s;port=%s;dbname=%s;charset=%s', HOST, PORT, DATABASE, CHARSET)
, USERNAME
, PASSWORD
, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => FALSE,
PDO::ATTR_PERSISTENT => TRUE,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
// Person ID's to fetch.
$personId1 = 1;
$personId2 = 2;
/*
* The SQL statement to be prepared. Notice the so-called named markers.
* They will be replaced later with the corresponding values from the
* bindings array when using PDOStatement::bindValue.
*
* When using named markers, the bindings array will be an associative
* array, with the key names corresponding to the named markers from
* the sql statement.
*
* You can also use question mark markers. In this case, the bindings
* array will be an indexed array, with keys beginning from 1 (not 0).
* Each array key corresponds to the position of the marker in the sql
* statement.
*
* #link http://php.net/manual/en/mysqli.prepare.php
*/
$sql = 'SELECT
p.id AS person_id,
d.id AS dress_id,
d.name AS dress_name,
d.produced_in AS dress_produced_in,
i.id AS interest_id,
i.interest,
i.priority AS interest_priority
FROM person AS p
LEFT JOIN dress AS d ON d.person_id = p.id
LEFT JOIN interest AS i ON i.person_id = p.id
WHERE
p.id = :personId1 OR
p.id = :personId2
ORDER BY
person_id ASC,
dress_name ASC,
interest ASC';
/*
* The bindings array, mapping the named markers from the sql
* statement to the corresponding values. It will be directly
* passed as argument to the PDOStatement::execute method.
*
* #link http://php.net/manual/en/pdostatement.execute.php
*/
$bindings = [
':personId1' => $personId1,
':personId2' => $personId2,
];
/*
* Prepare the sql statement for execution and return a statement object.
*
* #link http://php.net/manual/en/pdo.prepare.php
*/
$statement = $connection->prepare($sql);
/*
* Execute the prepared statement. Because the bindings array
* is directly passed as argument, there is no need to use any
* binding method for each sql statement's marker (like
* PDOStatement::bindParam or PDOStatement::bindValue).
*
* #link http://php.net/manual/en/pdostatement.execute.php
*/
$executed = $statement->execute($bindings);
/*
* Fetch data (all at once) and save it into $fetchedData array.
*
* #link http://php.net/manual/en/pdostatement.fetchall.php
*/
$fetchedData = $statement->fetchAll(PDO::FETCH_ASSOC);
// Just for testing. Display fetched data.
echo '<pre>' . print_r($fetchedData, TRUE) . '</pre>';
/*
* Close the prepared statement.
*
* #link http://php.net/manual/en/pdo.connections.php Example #3 Closing a connection.
*/
$statement = NULL;
/*
* Close the previously opened database connection.
*
* #link http://php.net/manual/en/pdo.connections.php Example #3 Closing a connection.
*/
$connection = NULL;
// Filter the fetched data.
$personDetails = [];
foreach ($fetchedData as $key => $record) {
$personId = $record['person_id'];
$dressId = $record['dress_id'];
$dressName = $record['dress_name'];
$dressProducedIn = $record['dress_produced_in'];
$interestId = $record['interest_id'];
$interest = $record['interest'];
$interestPriority = $record['interest_priority'];
// Check and add person id as key.
if (!array_key_exists($personId, $personDetails)) {
$personDetails[$personId] = [
'dresses' => [],
'interests' => [],
];
}
// Check and add dress details.
if (!array_key_exists($dressId, $personDetails[$personId]['dresses'])) {
$personDetails[$personId]['dresses'][$dressId] = [
'name' => $dressName,
'producedIn' => $dressProducedIn,
// ... (other fetched dress details)
];
}
// Check and add interest details.
if (!array_key_exists($interestId, $personDetails[$personId]['interests'])) {
$personDetails[$personId]['interests'][$interestId] = [
'interest' => $interest,
'interestPriority' => $interestPriority,
// ... (other fetched interest details)
];
}
}
// Just for testing. Display person details list.
echo '<pre>' . print_r($personDetails, TRUE) . '</pre>';
Fetched results in PHP code:
Fetched data ($fetchedData) of two persons:
Array
(
[0] => Array
(
[person_id] => 1
[dress_id] => 1
[dress_name] => shorts
[dress_produced_in] => Taiwan
[interest_id] => 2
[interest] => basketball
[interest_priority] => 2
)
[1] => Array
(
[person_id] => 1
[dress_id] => 1
[dress_name] => shorts
[dress_produced_in] => Taiwan
[interest_id] => 1
[interest] => football
[interest_priority] => 1
)
[2] => Array
(
[person_id] => 1
[dress_id] => 2
[dress_name] => tshirt
[dress_produced_in] => USA
[interest_id] => 2
[interest] => basketball
[interest_priority] => 2
)
[3] => Array
(
[person_id] => 1
[dress_id] => 2
[dress_name] => tshirt
[dress_produced_in] => USA
[interest_id] => 1
[interest] => football
[interest_priority] => 1
)
[4] => Array
(
[person_id] => 2
[dress_id] => 3
[dress_name] => yellow hat
[dress_produced_in] => England
[interest_id] => 4
[interest] => films
[interest_priority] => 1
)
[5] => Array
(
[person_id] => 2
[dress_id] => 3
[dress_name] => yellow hat
[dress_produced_in] => England
[interest_id] => 5
[interest] => programming
[interest_priority] => 1
)
[6] => Array
(
[person_id] => 2
[dress_id] => 3
[dress_name] => yellow hat
[dress_produced_in] => England
[interest_id] => 3
[interest] => voleyball
[interest_priority] => 3
)
)
Filtered data in PHP, e.g. the final array ($personDetails) holding the infos about two persons:
Array
(
[1] => Array
(
[dresses] => Array
(
[1] => Array
(
[name] => shorts
[producedIn] => Taiwan
)
[2] => Array
(
[name] => tshirt
[producedIn] => USA
)
)
[interests] => Array
(
[2] => Array
(
[interest] => basketball
[interestPriority] => 2
)
[1] => Array
(
[interest] => football
[interestPriority] => 1
)
)
)
[2] => Array
(
[dresses] => Array
(
[3] => Array
(
[name] => yellow hat
[producedIn] => England
)
)
[interests] => Array
(
[4] => Array
(
[interest] => films
[interestPriority] => 1
)
[5] => Array
(
[interest] => programming
[interestPriority] => 1
)
[3] => Array
(
[interest] => voleyball
[interestPriority] => 3
)
)
)
)
MySQL (or any other SQL database) does not return results in the nested array format that you describe. So you're going to have to write application code to process the result of the query one way or another.
Writing multiple joins like you have is bound to create a Cartesian product between the joined tables, and this will multiply the size of the result set, if any of them match multiple rows.
I recommend you run a separate query for each type of dependent information, and combine them in application code. Here's an example:
function get_details($pdo, $person_id) {
$sql = "
select p.id, d.name
from person as p
left join dress as d on p.id = d.person_id
where p.id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$person_id]);
$rows = $stmt->fetchAll();
foreach ($rows as $row) {
if (!isset($data[$row['id']])) {
$data[$row['id']] = [
'id' => $row['id'],
'dress' => []
];
}
$data[$row['id']]['dress'][] = $row['name'];
}
$sql = "
select p.id, i.interest
from person as p
left join interest as i on p.id = i.person_id
where p.id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$person_id]);
$rows = $stmt->fetchAll();
foreach ($rows as $row) {
if (!isset($data[$row['id']])) {
$data[$row['id']] = [
'id' => $row['id'],
'interest' => []
];
}
$data[$row['id']]['interest'][] = $row['interest'];
}
return $data;
}
I tested this by calling it in the following way:
$pdo = new PDO("mysql:host=127.0.0.1;dbname=test", "xxxx", "xxxxxxxx");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$result = get_details($pdo, 1);
print_r($result);
Output:
Array
(
[1] => Array
(
[id] => 1
[dress] => Array
(
[0] => shorts
[1] => tshirt
)
[interest] => Array
(
[0] => football
[1] => basketball
)
)
)
Re your comment:
I can't guarantee which method will have better performance. That depends on several other factors, for example the number of rows you need to query, the speed of creating temp tables needed for GROUP_CONCAT() solutions, the network speed of transferring large result sets containing duplicates, and so on.
As with all performance-related questions, the ultimate answer is that you need to test with your data on your server.
What about using a UNION
(
SELECT p.id, d.id AS type_id, d.name, 'dress' AS `type`
FROM person AS p
LEFT JOIN dress AS d ON p.id = person_id
WHERE p.id = 1
)
UNION
(
SELECT p.id, i.id AS type_id , i.interest AS NAME, 'interest' AS `type`
FROM person AS p
LEFT JOIN interest AS i ON p.id = person_id
WHERE p.id = 1
)
You just use group by person id and group_concat and Add distinct on dress and interest otherwise you will get result with duplicate dress and interest.
Query:
select p.id, p.name, group_concat(distinct i.interest) as interests,group_concat(distinct d.name) as dresses
from person as p left
join dress as d on p.id = d.person_id
left join interest as i on p.id = i.person_id
where p.id = 1 group by p.id;
so you will get comma separated interest and dress
Output:
+----+------+---------------------+---------------+
| id | name | interests | dresses |
+----+------+---------------------+---------------+
| 1 | Sam | football,basketball | shorts,tshirt |
+----+------+---------------------+---------------+
Couple of basic ways to do this:
COLLECT ALL INFORMATION AT ONCE
As suggested by #aendeerei, expanding your query:
SELECT p.id AS p_id,
p.name AS p_name,
d.id AS d_id,
d.name AS d_name,
i.id AS i_id,
i.name AS i_name
FROM person as p
LEFT JOIN dress as d on p.id = d.person_id
LEFT JOIN interest as i on p.id = i.person_id
WHERE p.id = 1;
Then in the application code:
$person = [];
foreach ($rows as $row) {
$person['id'] = $row['p_id'];
$person['name'] = $row['p_name'];
if($row['d_id']){
$person['dresses'][$row['d_id']] = [
'id' => $row['d_id'],
'name' => $row['d_name'],
]
}
if($row['i_id']){
$person['interests'][$row['i_id']] = [
'id' => $row['i_id'],
'name' => $row['i_name'],
]
}
}
When you index the dress and interest arrays by their respective IDs, any duplicate data just overwrites the same index. Overwriting could also be avoided with some if(array_key_exists(...)) conditionals.
This idea could be expanded to multiple persons in a $persons array, by indexing each person by their own id.
The downside here is that when people have large numbers of dresses and interests you return a lot of redundant data.. (5 dresses and 5 interests for a person will return their name 25 times).
COLLECT DEPENDENT DATA SEPARATELY
Or as suggested by #BillKarwin, you could run a separate query for each table. I think I'd even be tempted to go one further and separate the person table as well.
SELECT * FROM person WHERE id = 1;
Build person array from single row returned
SELECT * FROM dress WHERE person_id = 1;
Build person's dress array from returned rows if any.
SELECT * FROM interest WHERE person_id = 1;
Build person's interest array from returned rows if any.
This could be expanded to multiple persons by using WHERE person_id IN (...) on the dependent queries using the ids of persons found in the first.
The downside to this is you are running 3 different queries, which could take longer and adds complexity.. and if someone deletes a person in between, you may have some minor concurrency issues to worry about. It could appear that a deleted person still exists, but with no dresses/interests.

Doctrine Query: Result creates array with index offset

I have a DQL string:
SELECT DISTINCT a,
b,
(
SELECT COUNT(c)
FROM ..\Entity\EntityC c
WHERE c.b = b
),
(
SELECT MAX(c2.date)
FROM ..\Entity\EntityC c2
WHERE c2.b = b
)
FROM ..\Entity\EntityA a
JOIN a.b b
...
I want to retrieve some a's, the count of c's that relate to a.b, and the date of the latest c.
My code DOES generate the results I want, but the resulting arrays have an offset in their indices:
array(size = [rows])
0 => array (size = 3)
0 => Entity(a)
1 => int(COUNT(c))
3 => date(MAX(c2.date))
1 => array (size = 3)
0 => Entity(a)
1 => int(COUNT(c))
3 => date(MAX(c2.date))
...
Why does this offset happen, and is there a way to prevent this?
Maybe it is related to this
"If you fetch multiple entities that are listed in the FROM clause then the hydration will return the rows iterating the different top-level entities." (From http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#fetching-multiple-from-entities )
$dql = "SELECT u, g FROM User u, Group g";
array
[0] => Object (User)
[1] => Object (Group)
[2] => Object (User)
[3] => Object (Group)
Could you verify doing a dump on the next row?
Best regards

Pushing pointers to followers with the metadata (MySQL Query)

I’ve seen the following question on StackOverflow, Intelligent MySQL GROUP BY for Activity Streams posted by Christian Owens 12/12/12.
So I decided to try out the same approach, make two tables similar to those of his. And then I pretty much copied his query which I do understand.
This is what I get out from my sandbox:
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[action] => published_post
[object_id] => 776286559146635
[object_type] => post
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 0
)
)
I am curious, since looking at the results in Owens question, I am not able to fully get something, and does he perform additional queries to grab the actual metadata? And if yes, does this mean that one can do it from that single query or does one need to run different optimized sub-queries and then loop through the arrays of data to render the stream itself.
Thanks a lot in advanced.
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[fullname] => David Anderson
[action] => hearted
[object_id] => array (
[id] => 3438983
[title] => Grand Theft Auto
[Category] => Games
)
[object_type] => product
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 1
)
)
In "pseudo" code you need something like this
$result = $pdo->query('
SELECT stream.*,
object.*,
COUNT(stream.id) AS rows_in_group,
GROUP_CONCAT(stream.id) AS in_collection
FROM stream
INNER JOIN follows ON stream.user_id = follows.following_user
LEFT JOIN object ON stream.object_id = object.id
WHERE follows.user_id = '0'
GROUP BY stream.user_id,
stream.verb,
stream.object_id,
stream.type,
date(stream.stream_date)
ORDER BY stream.stream_date DESC
');
then parse the result and convert it in php
$data = array(); // this will store the end result
while($row = $result->fetch(PDO::FETCH_ASSOC)) {
// here for each row you get the keys and put it in a sub-array
// first copy the selected `object` data into a sub array
$row['object_data']['id'] = $row['object.id'];
$row['object_data']['title'] = $row['object.title'];
// remove the flat selected keys
unset($row['object.id']);
unset($row['object.title']);
...
$data[] = $row; // move to the desired array
}
you should get
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[fullname] => David Anderson
[verb] => hearted
[object_data] => array (
[id] => 3438983
[title] => Grand Theft Auto
[Category] => Games
)
[type] => product
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 1
)
)
It seems that you want a query where you can return the data you're actually able to get plus the user fullname and the data related to the object_id.
I think that the best effort would be to include some subqueries in your query to extract these data:
Fullname: something like (SELECT fullname FROM users WHERE id = stream.user_id) AS fullname... or some modified version using the stream.user_id, as we can't identify in your schema where this fullname comes from;
Object Data: something like (SELECT CONCAT_WS(';', id, title, category_name) FROM objects WHERE id = stream.object_id) AS object_data. Just as the fullname, we can't identify in your schema where these object data comes from, but I'm assuming it's an objects table.
One object may have just one title and may have just one category. In this case, the Object Data subquery works great. I don't think an object can have more than one title, but it's possible to have more than one category. In this case, you should GROUP_CONCAT the category names and take one of the two paths:
Replace the category_name in the CONCAT_WS for the GROUP_CONCAT of all categories names;
Select a new column categories (just a name suggestion) with the subquery which GROUP_CONCAT all categories names;
If your tables were like te first two points of my answer, a query like this may select the data, just needing a proper parse (split) in PHP:
SELECT
MAX(stream.id) as id,
stream.user_id,
(select fullname from users where id = stream.user_id) as fullname,
stream.verb,
stream.object_id,
(select concat_ws(';', id, title, category_name) from objects where id = stream.object_id) as object_data,
stream.type,
date(stream.stream_date) as stream_date,
COUNT(stream.id) AS rows_in_group,
GROUP_CONCAT(stream.id) AS in_collection
FROM stream
INNER JOIN follows ON 1=1
AND stream.user_id = follows.following_user
WHERE 1=1
AND follows.user_id = '0'
GROUP BY
stream.user_id,
stream.verb,
stream.object_id,
stream.type,
date(stream.stream_date)
ORDER BY stream.stream_date DESC;
In ANSI SQL you can't reference columns not listed in your GROUP BY, unless they're in aggregate functions. So, I included the id as an aggregation.

MySQL SELECT, LEFT JOIN and COUNT() in one query returns error

I got stuck with fairly plain query!
Briefly, I have created a search (filter) panel and now I'm adding pagination to it.
I'm having a problem in returning the number of rows that is in current query, mostly dependent on dynamically changing $detailed_search_query variable.
I need to make the following work, with adding COUNT() to it properly, so the new row total would contain the overall number of unique_id's.
Current SQL:
$sql = $db->prepare( "
SELECT
individuals.individual_id,
individuals.unique_id,
individuals.fullname,
individuals.day_of_birth,
TIMESTAMPDIFF(YEAR,individuals.day_of_birth,CURDATE()) AS age,
individuals.gender,
individuals.record_timestamp,
individuals.active,
individuals.deleted,
individuals_dynamics.weight,
individuals_dynamics.degree,
individuals_dynamics.trainer_name
FROM
individuals as individuals
LEFT JOIN
individuals_dynamics AS individuals_dynamics ON individuals.unique_id = individuals_dynamics.individual_id
WHERE
$detailed_search_query $display recognized = 'yes'
GROUP BY
individuals.record_timestamp
ORDER BY $by $how
LIMIT " . $limit);
If I add COUNT() to it, I have PDO error saying Fatal error: Call to a member function execute() on a non-object.
This is how my new query ( just beginning, rest is the same ) looks like, that returns error above:
$sql = $db->prepare( "
SELECT
COUNT(individuals.unique_id),
individuals.individual_id,
individuals.unique_id,
individuals.fullname,
individuals.day_of_birth,
What am I missing here?
EDIT 1:
The example of how I use COUNT() results in plain pre-query that works:
$sql = $db->prepare("SELECT count(unique_id) FROM individuals");
$sql->execute();
$total = $sql->fetchColumn();
$pagination = new Pagination($paginate_number);
$limit = $pagination->getLimit($total);
EDIT 2:
Yes, right, when I add an alias same error returns, example:
$sql = $db->prepare( "
SELECT
COUNT(individuals.unique_id) as total,
individuals.individual_id,
EDIT 3:
It's my bad about the last EDIT, if you add alias, like as total, then query works BUT it only COUNTS current row and returns 1 but I need total row count, example:
Array
(
[0] => Array
(
[total] => 1
[0] => 1
[individual_id] => 51
[1] => 51
[unique_id] => f598edae
[2] => f598edae
EDIT 4:
When the PHP variables are replaced then I have something like this in WHERE clause:
WHERE
individuals.fullname LIKE '%adam%' AND individuals_dynamics.degree BETWEEN '1' AND '3' AND EXTRACT(YEAR FROM (FROM_DAYS(DATEDIFF(NOW(),individuals.day_of_birth))))+0 BETWEEN '7' AND '10' AND individuals_dynamics.weight BETWEEN '20' AND '40' AND individuals_dynamics.degree BETWEEN '7' AND '10' AND deleted != 'yes' AND active != 'no' AND recognized = 'yes'
GROUP BY
individuals.record_timestamp
EDIT 5:
The desired result would be to have in a final array the key total, that would represent the total amount of results that were extracted in the current query based on dynamic PHP variables, as $detailed_search_query and $display:
Now I have always 1 in the total. When it should be 75:
Array
(
[0] => Array
(
[total] => 1
[0] => 1
[individual_id] => 71
[1] => 71
[unique_id] => f598e2ae
[2] => f598e2ae
[fullname] => Name2 Name2 Name2
)
[1] => Array
(
[total] => 1
[0] => 1
[individual_id] => 65
[1] => 65
[unique_id] => b76497ca
[2] => b76497ca
)
The error that you get means that PDO can't prepare the query, and the reason is that there is an error in your SQL query and the database server can't execute it ... So to let understand better the question you should post the error that you get trying to executing the query on the mysql client directly .
To achieve the result set you need, you can insert a subquery that count the records that have the same individual_id as the outer query.
Following the first part of the query :
SELECT
(SELECT COUNT(unique_id) FROM individuals i2 WHERE i2.individual_id = individuals. individual_id) AS total,
individuals.individual_id,
individuals.unique_id,
individuals.fullname,
individuals.day_of_birth,
Bear in mind that to reference a column of the outer query correctly from the subquery you should use two different alias name, even if you are selecting from the same table in both query (outer and sub).

Categories