Mysql joining 1 row with multiple rows - group concat or php? - php

Consider the following example:
+----------+--------+-------------+----------+
| Person_id| Person | Language_id | Language |
+----------+--------+-------------+----------+
| 1 | Bob | 5 | English |
| 1 | Bob | 3 | Italiano |
| 1 | Bob | 8 | Deutsch |
+----------+--------+-------------+----------+
and the query is (not that important, just scripting to show you the table structure):
SELECT pl.Person_id, Person, Language_id, Language FROM people as p
LEFT JOIN people_languages as pl ON p.Person_id = pl.Person_id
LEFT JOIN languages as l ON pl.language_id = l.language_id
WHERE pl.Person = 1;
So basically, if the tables are constructed in this way, is it better to retrieve all results as shown above and then create a php function that creates a Person Model with languages_id and languages in an array, or using group_concat to retrieve a single row and then explode the languages and languages_id into an array?
By the way, no matter what I do, at the end I'd like to have a Person Model as the following:
class Person {
public $person_id; // 1
public $person; // Bob
public $language_id; // Array(5, 3, 8)
public $language; // Array(English, Italiano, Deutsch);
.
. // Functions
.
}

I think you should separate the queries into their separate model
There should be a Language model and will keep this simple
class Language
{
function getId() { return $id; }
function getDescription { return $description; }
}
class Person {
public $person_id; // 1
public $person; // Bob
public $languages; //this will store array of Language object
}
//From DataAccess
function getPerson($person_id)
{
$person = new Person();
//select only from Person table
//fill $person properties from records
//$person.person_id = $row['person_id']; etc
//select from people_languages joined to language where person_id=$person_id
$person->languages = getLanguagesByPerson($person->person_id); //returns array of languages
return $person;
}
You can now have
$person = getPerson(123);
$person->langauges[0]->getId(); //language id
$person->langauges[0]->getDescription(); //language id
$person->langauges[1]->getId(); //language id
$person->langauges[1]->getDescription(); //language id
Or loop through the languages
foreach($person->languages as $lang)
{
//use($lang->getId());
//use($lang->getDescription();
}

Here is the answer. You can use both ways but in my opinion it is much much better to use group concat. The reason is that this will increase performance as well as reduce the php code. If you go on with the example you gave you will have to do much coding on the php end. And sometimes it becomes difficult to handle on php end. I had this experience a couple of months ago. Instead using group concat will fetch you single row having everything you need for each person. On the php end simple extract the Group Concated cell and make another loop or put it in array. That is easy to handle.

Consider using a Dictionary
class Person {
public $person_id; // 1
public $person; // Bob
//I don't know php but for your idea
public Dictionary<int,string> languageList; // KeyValuePairs {(5,English),(3,Italiano),(8,Deutsch)}
.
. // Functions
.
}

Related

Doctrine search a string in multiple columns

I hope you can help :)
This is how the table looks:
+------------+----------------+------------------+---------+
| firstName | lastName | email | etc... |
+------------+----------------+------------------+---------+
| John | Doe | john#doe.com | etc... |
+------------+----------------+------------------+---------+
| John | Michaels | john#michaels.es | etc... |
+------------+----------------+------------------+---------+
This is how the code looks:
if($_GET['search-customers'] != '') {
$busqueda = $_GET['search-customers'];
$query->andWhere("(c.firstName LIKE '%$busqueda%' OR c.lastName LIKE '%$busqueda%' OR c.email LIKE '%$busqueda%')");
}
With that QUERY:
If I input: John in the search box, it gives me the 2 results.
OK
If I input: John D in the search box, it doesn't give me any result. FAIL
All right, I understand, When I type "John D", it try to find first in firstName (doesn't match) and also it doesn't match lastName or email.
How can I combine them?
The idea its to find the complete string in all possibilities.
Thanks!
I will provide you a different alternative using MySQL's Full-Text Search Functions. Lets begin to prepare the table:
ALTER TABLE persons ADD FULLTEXT (`firstname`, `lastname`);
Now, firstname and lastname are columns to be used by full-text in order to search for matches:
SELECT * FROM persons
WHERE MATCH (firstname,lastname)
AGAINST ('John D' IN NATURAL LANGUAGE MODE);
The result will be:
+------------+----------------+------------------+---------+
| firstName | lastName | email | etc... |
+------------+----------------+------------------+---------+
| John | Doe | john#doe.com | etc... |
+------------+----------------+------------------+---------+
| John | Michaels | john#michaels.es | etc... |
+------------+----------------+------------------+---------+
Why both? Because John (as a word) was found, however John Doe is in the first row because has much similitude with the term of search.
Say, that lets apply this tool with Doctrine. I will assume that your model looks like this:
class Person{
/** #column(type="string", name="firstname")*/
protected $firstName;
/** #column(type="string", name="lastname")*/
protected $lastName;
/** #column(type="string")*/
protected $email;
}
Lets create the search function:
public function search($term){
$rsm = new ResultSetMapping();
// Specify the object type to be returned in results
$rsm->addEntityResult('Models\Person', 'p');
// references each attribute with table's columns
$rsm->addFieldResult('p', 'firstName', 'firstName');
$rsm->addFieldResult('p', 'lastName', 'lastname');
$rsm->addFieldResult('p', 'email', 'email');
// create a native query
$sql = 'select p.firstName, p.lastname, p.email from persons p
where match(p.firstname, p.lastname) against(?)';
// execute the query
$query = $em->createNativeQuery($sql, $rsm);
$query->setParameter(1, $term);
// getting the results
return $query->getResult();
}
Finnally, and example:
$term = 'John D';
$results = search($term);
// two results
echo count($results);
Additional notes:
Before mysql 5.7, Full-Text only can be added just to MyISAM tables.
Only can be indexed CHAR, VARCHAR, or TEXT columns.
When using IN NATURAL LANGUAGE MODE in a search, mysql returns an empty resultse when the results represent < 50% of the records.
Maybe you could use the explode function like this:
$busqueda = $_GET['search-customers'];
$names = explode(' ',$busqueda);
if(count($names)>1){
$query->andWhere("(c.firstName LIKE '%{$names[0]}%' AND c.lastName LIKE '%{$names[1]}%')");
}else{
$query->andWhere("(c.firstName LIKE '%$busqueda%' OR c.lastName LIKE '%$busqueda%' OR c.email LIKE '%$busqueda%')");
}
but, using like %word% is inefficient, because it can't use index.
Finally, I decided to concat firstName and lastName. I excluded the email then the query looks like that:
$busqueda = $_GET['search-customers'];
$names = explode(' ',$busqueda);
$hasemail = strpos('#', $busqueda);
if ( $hasemail ) {
$query->andWhere("c.email LIKE '%$busqueda%'");
} else {
$query->andWhere("( CONCAT(c.firstName,' ',c.lastName) LIKE '%$busqueda%' OR c.email LIKE '%$busqueda%')");
}
In your repository you could do something like this:
public function findByTerms($terms) : array
{
$alias = "d";
$qb = $this->createQueryBuilder($alias);
foreach (explode(" ", $terms) as $i => $term) {
$qb
->andWhere($qb->expr()->orX( // nested condition
$qb->expr()->like($alias . ".name", ":term" . $i),
$qb->expr()->like($alias . ".description", ":term" . $i)
))
->setParameter("term" . $i, "%" . $term . "%")
;
}
return $qb->getQuery()->getResult();
}
The Query Builder is able to generate complex queries that scale to your needs

Selecting column value dependent on parent ID without need for PHP loop?

I currently have a table that holds among other things:
ID | pagetitle | parent
1 | Page 1 | 0
2 | Page 2 | 1
3 | Page 3 | 2
I am trying to select all the pages, along with their parent's pagetitle.
Currently I am looping through the results in PHP, grabbing the pagetitles for those pages with parents, and it looks pretty ugly:
function showPageParent($pageid) {
do {
$result = mysql_query("SELECT parent FROM pages WHERE id=" . qt($pageid));
while($row = mysql_fetch_array($result)) {
$crumbs[] = $row['parent'];
$pageid1 = $row['parent'];
}
} while($pageid1!=0);
sort($crumbs);
foreach($crumbs as $crumb) {
$result = mysql_query("SELECT id,pagetitle FROM pages WHERE id=" . $crumb);
while($row = mysql_fetch_array($result)) {
$out .= $row['pagetitle'] . " > ";
}
}
if(count($crumbs) < 2) {
return showPageTitle($pageid);
} else {
return $out;
}
}
It works, but it is very old code, I am in the processes of re-writing a LOT of this sytem, and would love to pretty this thing up with one call to the database to return something like this:
ID | pagetitle | parent-pagetitle
Is this possible using inner selects?
edit
I'd like to point out, I am not the original author of the PHP, I am aware it's poorly written :p
select p.id
, p.title
, pp.id
, pp.title parentitle
from pages p
left
outer
join pages pp
on p.parent = pp.id
You can add another join for each level of depth, if needed.
In short, you're trying to manage hierarchical data in a non-hierarchical database. One of the better ways to model this appropriately is the nested set model, which is very nicely explained here. That article also shows you why hierarchical data in a simple adjacent list model (your current model) is virtually impossible to work with. Also look at materialized paths or nested interval tree models.

Fetch Object Only Returning 1 Value

Hi Guys I am having trouble on fetching object please see my code below.
Table Animals
-----------------------
id | type | name
-----------------------
1 Cat Muning
2 Kookaburra Bruce
3 Dog Bruce
-----------------------
Animal.php
class Animal extends DatabaseObject{
static $db_fields;
static $table_name = 'animals';
public function animal_group(){
global $database;
$sql = "SELECT animal_name as ani_name, COUNT(animal_name) as quantity FROM " . static::$table_name . " GROUP BY animal_name";
$stmt = $database->query($sql);
return $result = $database->fetch_object($stmt);
}
}
$animal = new Animal();
index.php
<?php
require_once('includes/database.php');
require_once('includes/animal.php');
$ani = $animal->animal_group();
echo $ani->ani_name . " - " . $ani->quantity;
?>
Result
Bruce - 2
What it should be
Bruce - 2
Muning - 1
I also tried While and Foreach but still didn't work.
fetch_object returns one row as an object. If you want to get all of them you have to call it in a loop, or otherwise use another method that returns all the rows from the result set (if you are using mysqli, that would be fetch_all).
Are you using PDO?.
Use fetchAll() instead of fetch_object.

Help need with formatting returned SQL data

I've created a table which approximates to this:
Fruit | Date Purchased | Amount Purchased
----------------------------------------------
Apples | 01-01-10 | 5
Oranges | 01-01-10 | 7
Apples | 02-01-10 | 3
Oranges | 02-01-10 | 2
etc....
I need to end up with the data in the following format though:
Apples (
(01-01-10, 5),
(02-01-10, 3)
)
Oranges (
(01-01-10, 7),
(02-01-10, 2)
)
etc...
The types of fruit are not fixed - more will be added over time, so this would be need to be taken into account.
I've been stuck on this for quite a while now, so any pointers or tips would be really appreciated.
I to lazy to figure out the correct HTML tag to do the tab, but the following code should help you out.
SELECT CONCAT(
s.Fruit, ' (<br/>', '<tab/>',
GROUP_CONCAT(s.DatePlusAmount SEPARATOR ',<br/><tab>'),
'<br/>)<br/>') as FruitLine FROM
(
SELECT Fruit, CONCAT(PDate, ',', IFNULL(sum(amount),0)) AS DatePlusAmount
FROM table1
GROUP BY DATE
) s
GROUP BY Fruit
No loops in php needed.
You can loop through all all your records and add every row to the appropriate array:
Something like:
$fruits = array();
while ($row = get_new_database_row) /* depends on mysql, mysqli, PDO */
{
$fruits[$row['fruit']][] = array($row['date'], $row['amount']);
}
Edit: Based on your codeigniter comments, you either need result_array() or you need to change $row['fruit'] to $row->fruit, $row['date'] to $row->date, etc.
I would do this in two queries:
$sql = "SELECT * FROM tabledata";
$rs = mysql_query($sql);
while( false !== ($r = mysql_fetch_assoc($rs2)))
{
$arr_fruit[ $r['fruit'] ][ $r['date_purchase'] ] = $r['amout'];
}
at this point, you'll end up having associative array that you can further process.
I leave that as an exercise. Come back again when you have problem. But, be sure to bring some code.

PHP Tree Traversal For Sub Forums Within Sub Forums

I have been developing my own forum for about a week now and I am almost done with all of the code, however, I am stuck on one single issue that I have not been able to figure out.
Well, simply said I have sub forums that can be within any amount of other sub forums.
How would I create a path dynamically to any of those sub forums on the spot with PHP.
After the path is created I would use it within href's and other things.
I am guessing I would somehow need to traverse the database based on a ID column and another column that would link one sub forum to another sub forum.
Let's assume that my database table looks like this:
ID | Name | Link |
---+-------------+-------
1 | Forum-One | Top |
2 | Forum-Two | 1 |
3 | Forum-Three | 2 |
4 | Forum-Four | 2 |
5 | Forum-Five | 3 |
6 | Forum-Six | 3 |
How would I go about doing this - or is there something else that must be done instead?
I hope I was clear enough for everyone to understand.
EDIT:
include("inc/config.php");
function generateBreadcrumb($startingID){
$result = mysql_query("SELECT * FROM temp_table WHERE ID='$startingID'");
while($row = mysql_fetch_array($result))
{
$db_id=$row['ID'];
$db_name=$row['Name'];
}
if($db_id!='Top'){
return generateBreadCrumb($db_id);
} else {
return $db_name;
}
}
$startID='6';
echo generateBreadcrumb($startID);
First you need a terminating condition. So set your top level Forum[link] to null, or 'top', or something. Then its simply a matter of using a recursive function put your bread-crumb together.
So lets assume you wanted to go to display the breadcrumb to Forum-One:Forum-Three:Forum-Six , better known as Forum-Six.
Example code:
<?php
$yourForumId = 6; // replace this dynamically with your forum;
$breadcrumb = generateBreadcrumb($yourForum);
function generateBreadcrumb($startingForumId){
$sql= "SELECT Name ,link FROM Forums WHERE ID = ".$startingForumId;
//run your $sql however you do to get results
//assuming you get associative arrays back
if($res['link'] != 'top'){
return generateBreadCrumb($res['link']).":".$res['Name'];
} else {
return $res['Name'];
}
}
echo $breadcrumb;
?>
It's recursion, which if you're new to it may seem complicated, but I hope that helps!
EDIT: here's your code with the needed edit...
include("inc/config.php");
function generateBreadcrumb($startingID){
$result = mysql_query("SELECT * FROM temp_table WHERE ID='$startingID'");
$row = mysql_fetch_array($result);
$db_id=$row['link'];
$db_name=$row['Name'];
if($db_id!='Top'){
return generateBreadCrumb($db_id).":".$db_name;
} else {
return $db_name;
}
}
$startID='6';
echo generateBreadcrumb($startID);

Categories