Codeigniter - db -> join () double results merger - php

I am building a NOTE system.
I am having multiple tables (notes, tags, ...) and I am joining tags to my main notes with $this -> db -> join(); to get everything into one object.
When there are 2 or more tags for one note, then I get two or more rows with only different TAGS. The rest is the same. I need to merge it to have only one note entry.
$this -> db -> where ('user', USER_ID);
$this -> db -> join ('tags', 'tags.note_id = note.id', 'inner');
$query = $this->db->get('notes');
There may be also other tables with same character as TAGS, for example places. There may be more than one place for a note.
How do I proceed from now? I would like to have one object NOTE with parameters such as note_id, note_text, and join TAGS to it and probably if more than ONE tag, then OBJECT PARAMETER = ARRAY containing all the NOTES.
How to achieve that?
Is that actually good idea for further development to have it in one object? Or should I go foreach and list all the tags for each of the rows?
When somebody is filtering according to the tags, where & how should I store one's filtering? I am so far using $this -> session;
Thank you, Jakub

You might want to use MySQL's GROUP_CONCAT()
$this->db->select('n.*, GROUP_CONCAT(t.name)', false)
->from('notes n')->join('tags t', 't.note_id = n.id', 'inner')
->where('n.user', USER_ID)->get();
I used t.name but whatever the field name it is, you get the point.

Related

Laravel Eloquent specific columns error

I have the following in Laravel 4.x using eloquent:
Book table has a dozen fields.
$single_data = Book::
selectRaw("GROUP_CONCAT(DISTINCT CAST(id as char) SEPARATOR ',') as type_id_list")
->first();
Question: the data returned is everything within the table inside Book AND type_id_list as seen from the selectRaw.
I would like to ONLY return what I specified within the selectRaw.
It was suggested that you get add an array of columns within first() or get() in order to retrieve this only - however it is not working with custom selectRaw where you specify an expression or your own wording. It throws an error and when the query is analyzed the array that you put in first/get gets appended as part of the select.
Does anyone have a work around?
first() will return only that particular column but wrapped in a Book object anyway, so use pluck() instead:
$single_data = Book::
selectRaw("GROUP_CONCAT(DISTINCT CAST(id as char) SEPARATOR ',') as type_id_list")
->pluck('type_id_list');

Ridiculous slow Doctrine Query

I have already read some articles on Doctrine performance, but this one query is so slow it seems just wrong:
public function getBetRoundMainDataBuilder(BetRound $betRound){
$qb = $this->createQueryBuilder('br')
->select('br, uG, u, b, gG, g, t1, t2')
->where('br.id = :bID')
->setParameter('bID', $betRound->getId())
->innerJoin('br.userGroup', 'uG')
->innerJoin('uG.users', 'u')
->innerJoin('br.gameGroup', 'gG')
->leftJoin('gG.games', 'g')
->leftJoin('g.team1', 't1')
->leftJoin('g.team2', 't2')
->leftJoin('br.bets', 'b' );
return $qb;
}
I know it has a lot of Joins, but I thought I rather query everything within one query instead of lazy load all dependent Data.
I have profiled the code and although this query does not select too much data it takes endlessly for an array_shift:
Am I missing something? I even changed the Hydration Mode to array, but still have problems.
UPDATE:
I have now tried to select only partially but didn't change too much:
->select('partial br.{id},
partial uG.{id},
partial u.{id, firstName, lastName, nickName, username, imageName},
partial b.{id, data},
partial gG.{id, title},
partial g.{id, data, date},
partial t1.{id, name, shortName, shortCode, logoName},
partial t2.{id, name, shortName, shortCode, logoName}')
Next step is to split the query up.
Update 2
It is getting better, I have several areas in my View where I need different Datasets. I tried to split my content up into those Areas and also Query accordingly:
Data for Main Area:
BetRound
GamGroup (only to get the Games)
Games
Teams
Bets (only for Current User!)
This looks now like this:
$qb = $this->createQueryBuilder('br')
->select(
'partial br.{id},
partial b.{id, data},
partial gG.{id, title},
partial g.{id, data, date},
partial t1.{id, name, shortName, shortCode, logoName},
partial t2.{id, name, shortName, shortCode, logoName}'
)
->where('br.id = :bID')
->setParameter('bID', $betRound->getId())
->innerJoin('br.gameGroup', 'gG')
->leftJoin('gG.games', 'g')
->leftJoin('g.team1', 't1')
->leftJoin('g.team2', 't2')
->leftJoin('g.bets', 'b', 'WITH', 'b.user = :user')
->setParameter('user', $user->getId());
return $qb;
Second Area is the PointTable
$qb = $this->createQueryBuilder('br')
->select('br, b, u, uBs')
->where('br.id = :bID')
->setParameter('bID', $betRound->getId())
->leftJoin('br.bets', 'b')
->innerJoin('b.user', 'u')
->innerJoin('u.betroundStatus', 'uBs', 'WITH', 'uBs.betRound = :bID');
return $qb;
And then I have a third area which has all the stats. It is basically the same as the first Query but includes Bets for all Users. I am now unsure, If I should use only one query which queries all the Users Bets or, than for the stats create one query for each User or somehow different.
UPDATE QUESTION:
Maybe you see that I have a root entity called BetRound that kind of starts all queries (I am now up to 4 compley quersies from my BetRound?
I just don't know how I can "add" the data to my relations after initial load.
As you may see there are several join "paths" going from my BetRound. One of them is the "games path" that looks like:
BetRound -> GameGroup -> Games -> Team1 & Team2
BetRound -> UserGroup -> Users -> UserBetRoundStatus
BetRound -> Bets
I need all this Data, but how do I get all the data in one root Entity with the correct relations? And If I query e.g. do I start with the e.g. GameGroup or always with my root entity(=BetRound)?
I think there's a lot of left joins which may be really slow. You should rebuild this query and remove those left joins to improve some performance. Also please check if every keys and indexes are created properly in database.
In conclusion - it's not Doctrine's fault but the query.
The actual query (PDOStatement::execute()) only takes 3% of the total. Seeing as interacting with a database should be the slowest part of a script, 3% of the total execution time is plenty fast.
The vast majority of the time is taken up by the following 3 method calls:
ArrayHydrator::hydrateRowData() // 17%
AbstractHydrator::gatherRowData() // 39%
DateTimeType::convertToPHPValue() // 16%
If you count that up, it is 72% of the total. This is totally unacceptable by any standard.
Is "hydrating" records really necessary?
Can you eliminate the date/time conversion?
You just need to go after the biggest sinners. Shave off the fat.

Having difficulties sorting data from mysql query using php

This code is used to get a specific list of ID's from one table, then use those ID's to get the information from another table. Once I get all the information from the 2nd table, I am attempting to sort the data alphabetically based on a field in the 2nd table.
Example, I am getting the name based on a correlating ID and then want to display the entire result in alphabetical order by name (artist_name).
Here is the code I have. When I execute this without the sort(), it works fine but is not in alphabetical order. When I add the sort() in the 2nd while statement, the page looks the same but the name and other data do not display. The source code in the browser shows that the results are being accounted for but the sort must be preventing the variables or information from being displayed for some reason.
I haven't used a sort function before and I tried looking at some examples but couldn't really find something specific to my situation. Any and all help would be greatly appreciated. I have already looked at the PHP manual for sort so no need to send me a link to it ;-)
<?php $counter = 0;
$artistInfo = mysql_query("SELECT DISTINCT event_url_tbl.artist_id FROM event_url_tbl WHERE (SELECT cat_id FROM artist_tbl WHERE artist_tbl.artist_id = event_url_tbl.artist_id) = 1");
while ($aID = mysql_fetch_array($artistInfo))
{
$getArtistInfo = mysql_query("SELECT * FROM artist_tbl WHERE artist_id = '" . $aID['artist_id'] . "'");
while($artist = mysql_fetch_array($getArtistInfo))
{
sort($artist);?>
<a class="navlink" href="<?=HOST?><?=$artist['page_slug']?>/index.html">
<?=$artist['artist_name']?>
</a><br />
<?php }
}
?>
Your best bet, as a commenter mentioned, is to use an ORDER BY clause in SQL.
SELECT *
FROM artist_tbl
WHERE artist_id = XXX
ORDER BY artist_name ASC
The other commenter who suggested using PDO or mysqli is also correct, but that's a different issue.
To answer your specific question about sorting, according to the manual,
Blockquote Note: This function assigns new keys to the elements in array. It will remove any existing keys that may have been assigned, rather than just reordering the keys.
This means all of your array keys ('page_slug', 'artist_name', etc) are wiped out. So when you try to refer to them later, there is no data there.
Were you to use this method, you would want to use asort to sort an associative array.
However, you don't want to use sort here. What you're sorting is the variables for one row of data (one individual artists), not all of your artists. So if you think of each artist row as an index card full of data (name, id#, page slug, etc) all you're doing is moving those items around on the card. You're not reorganizing your card catalog.
Using an order by clause in the SQL statement (and rewriting in PDO) is your best bet.
Here is how I would rewrite it. I have to take some guesses at the SQL because I'm not 100% sure of your database structure and what you're specifically trying to accomplish, but I think this would work.
$query_str = "
SELECT
artist.name,
artist.page_slug
FROM
artist_tbl AS artist
INNER JOIN event_tbl AS event
ON event.artist_id = artist.artist_id
WHERE
artist.cat_id = 1
ORDER BY
artist.name ASC";
$db_obj = new PDO (/*Connection stuff*/);
$artists_sql = $db_obj->prepare ($query_str);
$artists_sql->execute ();
while ($artist = $artists_sql->fetch (PDO::FETCH_ASSOC)) {
$return_str .= "<a class='navlink' href='{HOST}
{$artist['page_slug']}/index.html'>
{$artist['artist_name']}</a><br/>";
}
echo $return_str;
In all honesty, I would probably create an artist class with a display_link method and use PDO's fetchObject method to instantiate the artists, but that's getting ahead of ourselves here.
For now I stuck with procedural code. I don't usually like to mix my HTML and PHP so I assign everything to a return string and echo it out at the end. But this is close to what you had, using one SQL query (in PDO - seriously worth starting to use if you're creating new code) that should give you a list of artists sorted by name and their associated page_slugs.
You could do all of this in one query:
SELECT * FROM event_url_tbl AS event
INNER JOIN artist_tbl AS artist ON event.artist_id = artist.id
ORDER BY artist.name DESC;
This cuts out a lot of the complexity/foreaches in your script. You'll end up with
Label1 (Label 1 details..) Artist1 (Artist1 details..)
Label1 (Label 1 details..) Artist2 (Artist1 details..)
Label2 (Label 2 details..) Artist3 (Artist1 details..)
Always good to bear in mind "one query is better than many". Not a concrete rule, just if it's possible to do, try to do it. Each query has overheads, and queries in loops are a warning sign.
Hopefully that helps

Doctrine $record->get() with a JOIN

I'm using the following piece of code to retrieve the tags of a shop:
$tags = $this->getObject()->get('Tag');
$this->getObject() returns a Shop object, and ->get('Tag') returns an array of Tag objects related to this shop.
Here's how my database is arranged: 1 Shop = 1 or more Tag, and 1 Tag = 1 Tag_Translation.
What i'd like to do is to retrieve, instead of an array of Tag objects, and array of Tag objects with their translations (in other words, a kind of JOIN).
How is that possible, keeping that same syntax? Thank you very much, i'm new to Doctrine and ORMs in general, i would have had no problem doing it with MySQL but here ...
You may solve this issue like this
a) You can call Tag Models function, when you need the translation
$tag->getTagTranslation()
b) Or you can overwrite your Shop's getTag() function and build your own Query with DQL as #greg0ire suggested, to fetch translation and tag at once
public function getTag(){
return Doctrine_Query::create()
->from("Tag t")
->leftJoin("t.TagTranslation tt")
->addWhere("t.shop_id = ?", $this->getId())
}
(Of course you can name a new function e.g. getTagsWithTranslation())
This assumes, you have built a schema.yml with proper relations !

Help setting up logic for advanced search parameters in PHP

I would like to create an advanced search form much like a job site would have one that would include criteria such as keyword, job type, min pay, max pay, category,sub category etc...
My problem is deciding on how best to set this up so if I have to add categories to the parameters I'm not having to modify a whole bunch of queries and functions etc...
My best guess would be to create some sort of associative array out of all of the potential parameters and reuse this array but for some reason I feel like it's a lot more complex than this. I am using CodeIgniter as an MVC framework if that makes any difference.
Does anybody have a suggestion as how best to set this up?
Keep in mind I will need to be generating links such as index.php?keyword=designer&job_type=2&min_pay=20&max_pay=30
I hope my question is not to vague.
I don't know if it's what you need, but I usually create some search class.
<?php
$search = new Search('people');
$search->minPay(1000);
$search->maxPay(4000);
$search->jobType('IT');
$results = $search->execute();
foreach ($results as $result)
{
//whatever you want
}
?>
You can have all this methods, or have some mapping at __set() between method name and database field. The parameter passed to the constructor is the table where to do the main query. On the methods or mapping in the __set(), you have to take care of any needed join and the fields to join on.
There are much more 'enterprise-level' ways of doing this, but for a small site this should be OK. There are lots more ActiveRecord methods you can use as necessary. CI will chain them for you to make an efficient SQL request.
if($this->input->get('min_pay')) {
$this->db->where('min_pay <', $this->input->get('min_pay'));
}
if($this->input->get('keyword')) {
$this->db->like($this->input->get('keyword'));
}
$query = $this->db->get('table_name');
foreach ($query->result() as $row) {
echo $row->title;
}
To use Search criterias in a nice way you should use Classes and Interfaces.
Let's say for example you define a ICriteria interface. Then you have different subtypes (implementations) of Criteria, TimeCriteria, DateCriteria, listCriteria, TextSearch Criteria, IntRange Criteria, etc.
What your Criteria Interface should provide is some getter and setter for each criteria, you'll have to handle 3 usages for each criteria:
how to show them
how to fill the query with the results
how to save them (in session or database) for later usage
When showing a criteria you will need:
a label
a list of available operators (in, not in, =, >, >=, <, <=, contains, does not contains) -- and each subtypes can decide which part of this list is implemented
an entry zone (a list, a text input, a date input, etc)
Your main code will only handle ICriteria elements, ask them to build themselves, show them, give them user inputs, ask them to be saved or loop on them to add SQL criteria on a sql query based on their current values.
Some of the Criteria implementations will inherits others, some will only have to define the list of operators available, some will extends simple behaviors to add rich UI (let's say that some Date elements should provide a list like 'in the last day', 'in the last week', 'in the last year', 'custom range').
It can be a very good idea to handle the SQL query as an object and not only a string, the way Zend_Db_Select works for example. As each Criteria will add his part on the final query, and some of them could be adding leftJoins or complex query parts.
Search queries can be a pain sometimes, but not as big of a pain as pagination. Luckily, CodeIgniter helps you out a bit with this with their pagination library.
I think you're on the right track. The basic gist, I would say, is:
Grab your GET variables from the URL.
Create your database query (sanitize the GET values).
Generate the results set.
Do pagination.
Now, CodeIgniter destroys the GET variable by default, so make sure you enable http query strings in your config file.
Good luck!
I don't know anything about CodeIgniter, but for the search application I used to support, we had drop-down combo-boxes with category options stored in a database table and would rely on application and database cacheing to avoid round-trips each time the page was displayed (an opportunity for learning in itself ;-). When you update the table of job_type, location, etc. the new values will be displayed in your combo-box.
It depends on
how many categories you intend to have drop-down lists
how often you anticipate having to update the list
how dynamic you need it to be.
And the size of your web-site and overall activity are factors you will have to consider.
I hope this helps.
P.S. as you appear to be a new user, if you get an answer that helps you please remember to mark it as accepted, or give it a + (or -) as a useful answer
A pagination class is a good foundation. Begin by collecting query string variables.
<?php
// ...in Pagination class
$acceptableVars = array('page', 'delete', 'edit', 'sessionId', 'next', 'etc.');
foreach($_GET as $key => $value) {
if(in_array($key, $acceptableVar)) {
$queryStringVars[] = $key . '=' . $value;
}
}
$queryString = '?' . implode('&', $queryStringVars);
$this->nextLink = $_SEVER['filename'] . $queryString;
?>
Duplicate the searchable information into another table. Convert sets of data into columns having two values only like : a search for color=white OR red can become a search on 10 columns in a table each containing one color with value 1 or 0. The results can be grouped after so you get counters for each search filter.
Convert texts to full text searches and use MATCH and many indexes on this search table. Eventually combine text columns into one searchable column. The results of a seach will be IDs which you can then convert into the records with IN() condition in SQL
Agile Toolkit allows to add filters in the following way (just to do a side-by-side comparison with CodeIgniter, perhaps you can take some concepts over):
$g=$this->add('Grid');
$g->addColumn('text','name');
$g->addColumn('text','surname');
$g->setSource('user');
$conditions=array_intersect($_GET, array_flip(
array('keyword','job_type','min_pay'));
$g->dq->where($conditions);
$g->dq is a dynamic query, where() escapes values passed from the $_GET, so it's safe to use. The rest, pagination, column display, connectivity with MVC is up to the framework.
function maybeQuote($v){
return is_numeric($v) ?: "'$v'";
}
function makePair($kv){
+-- 7 lines: $a = explode('=', $kv);
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
}
function makeSql($get_string, $table){
+-- 10 lines: $data = explode('&', $get_string);
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
}
$test = 'lloyd=alive&age=40&weather=hot';
$table = 'foo';
print_r(makeSql($test, $table));

Categories