I am fairly new to Lithium PHP Framework but I do know how to use the basic database abstraction method. I am using a plugin to build menu trees and it works great, although I want to use Lithium's abstraction layer for the functions. I'm stuck on this:
public function get_menuItems($menu_id) {
// retrieve the left and right value of the $root node
$result = $this->database->query("SELECT * FROM ".$this->tbl_menu_items." WHERE menu_id = '$menu_id'");
if ($this->database->num_rows($result)) {
$right = array();
$result = $this->database->query("SELECT node.title, node.type, node.class_name, node.content, node.id AS id, node.lft AS lft, node.rgt AS rgt, (COUNT(parent.title) - 1) AS depth FROM ".$this->tbl_menu_items." AS node CROSS JOIN ".$this->tbl_menu_items." AS parent WHERE node.menu_id = '$menu_id' AND parent.menu_id = '$menu_id' AND node.lft BETWEEN parent.lft AND parent.rgt GROUP BY node.id ORDER BY node.lft");
$tree = array();
while ($row = mysql_fetch_assoc($result)) {
$tree[] = $row;
}
} else {
$tree = false;
}
return $tree;
}
More so the $result part. I don't know how Lithium could handle all the "AS"'s and whatnot. I tried a few different ways to execute the query as is but no luck.
Any help would be greatly appreciated and I hope I am explaining it well enough.
If your php version is 5.4 you can use li3_tree behavior. Take a look at it, as it is a good example on how to implement such recursive behavior.
Also, I would strongly suggest you to take a look at "Using Models" and "Adding functionality to Lithium models" as your example code could benefit a lot.
Related
I'm updating a site from pear DB to MDB2, I've managed to get quite far but I've come unstuck on a query I'm not sure what they are trying to achieve here.
Can anyone explain.
Here it is
$bookRes = $mdb2->query(("SELECT * FROM book WHERE (".join(' OR ', $sqlParams).") $categorySQL ORDER BY title"), $sqlValues);
while ($row = $bookRes->fetchRow()) {
$row['type'] = 'book';
$booksPossibles[] = $row;
}
I would need to see a bit more of the code to be sure, but this appears to be a simple dynamic generation of a SELECT statement where various criteria, with placeholders, stored in the $sqlParams array are being added to the WHERE clause and the values for those criteria are specified in the $sqlValues variable, again presumably an array.
so $sqlParams and $sqlValues might look like:
$sqlParams = array("val1 > ?", "val2 = ?");
$sqlValues = array(2, 4);
(It helps to know that the join keyword in PHP is just an alias for implode.)
now, if the node type is article. on the term page's left, i want to show the latest 10 articles's title which node type is article. i don't want to use views, how do i do? thank you.
if i want to show the latest 10 articles's title which node type is article on the node page's left . how to write the query. many thanks.
ps:i found EntityFieldQuery maybe can do this, but i don't now how to do out put it.
my code:
$query = new EntityFieldQuery();
$query
->entityCondition('entity_type', 'node')
->entityCondition('bundle', 'article')
->propertyCondition('status', 1)
->propertyOrderBy('created', 'DESC')
->range(0, 10);
$result = $query->execute();
The code can be something like that (using db_select())
$query = db_select("node", "n") // select from the node table
->fields("n", array("nid", "title")) // fields nid, title
->condition("type", "page", "=") // where the node type = page
->orderBy("created", "DESC") // order by the newest
->range(0, 10) // select only 10 records
->execute(); // execute the query
while($record = $query->fetchAssoc()) {
print(l($record['title'], "node/" . $record['nid'])); // print the node title linked to node.
}
Another example using EntityFieldQuery():
$query = new EntityFieldQuery();
$entities = $query->entityCondition('entity_type', 'node')
->entityCondition('bundle', 'club')
->propertyOrderBy("created", "DESC")
->range(0, 10)
->execute();
foreach($entities['node'] as $obj)
{
$node = node_load($obj->nid);
print(l($node->title, "node/" . $node->nid));
}
Performance wise: use the first method.
Another solution I'll mention since if it is good Drupal knowledge. The Views module can create a block like this with very little work. It is a bit tricky to learn but it is very much meant for making these kinds of listings.
In terms of D7 performance you're probably better off using the old db_query command:
$result = db_query("SELECT nid FROM {node} WHERE type = :type AND status = 1 ORDER BY created DESC LIMIT 10", array(':type' => $type));
foreach ($result as $record) {
// Do something with each $record
$node = node_load($record->nid);
}
For a speed comparison of db_query and db_select look here: https://www.drupal.org/node/1067802#comment-8996571
For simple queries, db_query() is 22% faster than db_select()
For simple queries, db_query() is 124% faster than EFQ
For queries with two joins, db_query() is 29% faster than db_select()
This is because db_select and EntityFieldQuery() allow modules to hook in and modify the query. Which may be a good thing for you!
I'm just providing options.
I am trying to figure out a script to take a MySQL query and turn it into individual queries, i.e. denormalizing the query dynamically.
As a test I have built a simple article system that has 4 tables:
articles
article_id
article_format_id
article_title
article_body
article_date
article_categories
article_id
category_id
categories
category_id
category_title
formats
format_id
format_title
An article can be in more than one category but only have one format. I feel this is a good example of a real-life situation.
On the category page which lists all of the articles (pulling in the format_title as well) this could be easily achieved with the following query:
SELECT articles.*, formats.format_title
FROM articles
INNER JOIN formats ON articles.article_format_id = formats.format_id
INNER JOIN article_categories ON articles.article_id = article_categories.article_id
WHERE article_categories.category_id = 2
ORDER BY articles.article_date DESC
However the script I am trying to build would receive this query, parse it and run the queries individually.
So in this category page example the script would effectively run this (worked out dynamically):
// Select article_categories
$sql = "SELECT * FROM article_categories WHERE category_id = 2";
$query = mysql_query($sql);
while ($row_article_categories = mysql_fetch_array($query, MYSQL_ASSOC)) {
// Select articles
$sql2 = "SELECT * FROM articles WHERE article_id = " . $row_article_categories['article_id'];
$query2 = mysql_query($sql2);
while ($row_articles = mysql_fetch_array($query2, MYSQL_ASSOC)) {
// Select formats
$sql3 = "SELECT * FROM formats WHERE format_id = " . $row_articles['article_format_id'];
$query3 = mysql_query($sql3);
$row_formats = mysql_fetch_array($query3, MYSQL_ASSOC);
// Merge articles and formats
$row_articles = array_merge($row_articles, $row_formats);
// Add to array
$out[] = $row_articles;
}
}
// Sort articles by date
foreach ($out as $key => $row) {
$arr[$key] = $row['article_date'];
}
array_multisort($arr, SORT_DESC, $out);
// Output articles - this would not be part of the script obviously it should just return the $out array
foreach ($out as $row) {
echo '<p>'.$row['article_title'].' <i>('.$row['format_title'].')</i><br />'.$row['article_body'].'<br /><span class="date">'.date("F jS Y", strtotime($row['article_date'])).'</span></p>';
}
The challenges of this are working out the correct queries in the right order, as you can put column names for SELECT and JOIN's in any order in the query (this is what MySQL and other SQL databases translate so well) and working out the information logic in PHP.
I am currently parsing the query using SQL_Parser which works well in splitting up the query into a multi-dimensional array, but working out the stuff mentioned above is the headache.
Any help or suggestions would be much appreciated.
From what I gather you're trying to put a layer between a 3rd-party forum application that you can't modify (obfuscated code perhaps?) and MySQL. This layer will intercept queries, re-write them to be executable individually, and generate PHP code to execute them against the database and return the aggregate result. This is a very bad idea.
It seems strange that you imply the impossibility of adding code and simultaneously suggest generating code to be added. Hopefully you're not planning on using something like funcall to inject code. This is a very bad idea.
The calls from others to avoid your initial approach and focus on the database is very sound advice. I'll add my voice to that hopefully growing chorus.
We'll assume some constraints:
You're running MySQL 5.0 or greater.
The queries cannot change.
The database tables cannot be changed.
You already have appropriate indexes in place for the tables the troublesome queries are referencing.
You have triple-checked the slow queries (and run EXPLAIN) hitting your DB and have attempted to setup indexes that would help them run faster.
The load the inner joins are placing on your MySQL install is unacceptable.
Three possible solutions:
You could deal with this problem easily by investing money into your current database by upgrading the hardware it runs on to something with more cores, more (as much as you can afford) RAM, and faster disks. If you've got the money Fusion-io's products come highly recommended for this sort of thing. This is probably the simpler of the three options I'll offer
Setup a second master MySQL database and pair it with the first. Make sure you have the ability to force AUTO_INCREMENT id alternation (one DB uses even id's, the other odd). This doesn't scale forever, but it does offer you some breathing room for the price of the hardware and rack space. Again, beef up the hardware. You may have already done this, but if not it's worth consideration.
Use something like dbShards. You still need to throw more hardware at this, but you have the added benefit of being able to scale beyond two machines and you can buy lower cost hardware over time.
To improve database performance you typically look for ways to:
Reduce the number of database calls
Making each database call as efficient as possible (via good design)
Reduce the amount of data to be transfered
...and you are doing the exact opposite? Deliberately?
On what grounds?
I'm sorry, you are doing this entirely wrong, and every single problem you encounter down this road will all be consequences of that first decision to implement a database engine outside of the database engine. You will be forced to work around work-arounds all the way to delivery date. (if you get there).
Also, we are talking about a forum? I mean, come on! Even on the most "web-scale-awesome-sauce" forums we're talking about less than what, 100 tps on average? You could do that on your laptop!
My advice is to forget about all this and implement things the most simple possible way. Then cache the aggregates (most recent, popular, statistics, whatever) in the application layer. Everything else in a forum is already primary key lookups.
I agree it sounds like a bad choice, but I can think of some situations where splitting a query could be useful.
I would try something similar to this, relying heavily on regular expressions for parsing the query. It would work in a very limited of cases, but it's support could be expanded progressively when needed.
<?php
/**
* That's a weird problem, but an interesting challenge!
* #link http://stackoverflow.com/questions/5019467/problem-writing-a-mysql-parser-to-split-joins-and-run-them-as-individual-query
*/
// Taken from the given example:
$sql = "SELECT articles.*, formats.format_title
FROM articles
INNER JOIN formats ON articles.article_format_id = formats.format_id
INNER JOIN article_categories ON articles.article_id = article_categories.article_id
WHERE article_categories.category_id = 2
ORDER BY articles.article_date DESC";
// Parse query
// (Limited to the clauses that are present in the example...)
// Edit: Made WHERE optional
if(!preg_match('/^\s*'.
'SELECT\s+(?P<select_rows>.*[^\s])'.
'\s+FROM\s+(?P<from>.*[^\s])'.
'(?:\s+WHERE\s+(?P<where>.*[^\s]))?'.
'(?:\s+ORDER\s+BY\s+(?P<order_by>.*[^\s]))?'.
'(?:\s+(?P<desc>DESC))?'.
'(.*)$/is',$sql,$query)
) {
trigger_error('Error parsing SQL!',E_USER_ERROR);
return false;
}
## Dump matches
#foreach($query as $key => $value) if(!is_int($key)) echo "\"$key\" => \"$value\"<br/>\n";
/* We get the following matches:
"select_rows" => "articles.*, formats.format_title"
"from" => "articles INNER JOIN formats ON articles.article_format_id = formats.format_id INNER JOIN article_categories ON articles.article_id = article_categories.article_id"
"where" => "article_categories.category_id = 2"
"order_by" => "articles.article_date"
"desc" => "DESC"
/**/
// Will only support WHERE conditions separated by AND that are to be
// tested on a single individual table.
if(#$query['where']) // Edit: Made WHERE optional
$where_conditions = preg_split('/\s+AND\s+/is',$query['where']);
// Retrieve individual table information & data
$tables = array();
$from_conditions = array();
$from_tables = preg_split('/\s+INNER\s+JOIN\s+/is',$query['from']);
foreach($from_tables as $from_table) {
if(!preg_match('/^(?P<table_name>[^\s]*)'.
'(?P<on_clause>\s+ON\s+(?P<table_a>.*)\.(?P<column_a>.*)\s*'.
'=\s*(?P<table_b>.*)\.(?P<column_b>.*))?$/im',$from_table,$matches)
) {
trigger_error("Error parsing SQL! Unexpected format in FROM clause: $from_table", E_USER_ERROR);
return false;
}
## Dump matches
#foreach($matches as $key => $value) if(!is_int($key)) echo "\"$key\" => \"$value\"<br/>\n";
// Remember on_clause for later jointure
// We do assume each INNER JOIN's ON clause compares left table to
// right table. Forget about parsing more complex conditions in the
// ON clause...
if(#$matches['on_clause'])
$from_conditions[$matches['table_name']] = array(
'column_a' => $matches['column_a'],
'column_b' => $matches['column_b']
);
// Match applicable WHERE conditions
$where = array();
if(#$query['where']) // Edit: Made WHERE optional
foreach($where_conditions as $where_condition)
if(preg_match("/^$matches[table_name]\.(.*)$/",$where_condition,$matched))
$where[] = $matched[1];
$where_clause = empty($where) ? null : implode(' AND ',$where);
// We simply ignore $query[select_rows] and use '*' everywhere...
$query = "SELECT * FROM $matches[table_name]".($where_clause? " WHERE $where_clause" : '');
echo "$query<br/>\n";
// Retrieve table's data
// Fetching the entire table data right away avoids multiplying MySQL
// queries exponentially...
$table = array();
if($results = mysql_query($table))
while($row = mysql_fetch_array($results, MYSQL_ASSOC))
$table[] = $row;
// Sort table if applicable
if(preg_match("/^$matches[table_name]\.(.*)$/",$query['order_by'],$matched)) {
$sort_key = $matched[1];
// #todo Do your bubble sort here!
if(#$query['desc']) array_reverse($table);
}
$tables[$matches['table_name']] = $table;
}
// From here, all data is fetched.
// All left to do is the actual jointure.
/**
* Equijoin/Theta-join.
* Joins relation $R and $S where $a from $R compares to $b from $S.
* #param array $R A relation (set of tuples).
* #param array $S A relation (set of tuples).
* #param string $a Attribute from $R to compare.
* #param string $b Attribute from $S to compare.
* #return array A relation resulting from the equijoin/theta-join.
*/
function equijoin($R,$S,$a,$b) {
$T = array();
if(empty($R) or empty($S)) return $T;
foreach($R as $tupleR) foreach($S as $tupleS)
if($tupleR[$a] == #$tupleS[$b])
$T[] = array_merge($tupleR,$tupleS);
return $T;
}
$jointure = array_shift($tables);
if(!empty($tables)) foreach($tables as $table_name => $table)
$jointure = equijoin($jointure, $table,
$from_conditions[$table_name]['column_a'],
$from_conditions[$table_name]['column_b']);
return $jointure;
?>
Good night, and Good luck!
In instead of the sql rewriting I think you should create a denormalized articles table and change it at each article insert/delete/update. It will be MUCH simpler and cheaper.
Do the create and populate it:
create table articles_denormalized
...
insert into articles_denormalized
SELECT articles.*, formats.format_title
FROM articles
INNER JOIN formats ON articles.article_format_id = formats.format_id
INNER JOIN article_categories ON articles.article_id = article_categories.article_id
Now issue the appropriate article insert/update/delete against it and you will have a denormalized table always ready to be queried.
This is just an example, but most of the queries in my application are like this:
public function getCommentsByPost($postid)
{
$db = Zend_Registry::get('db');
$sql = 'SELECT * FROM comments c
LEFT JOIN users u ON u.user_id = c.comment_userid
WHERE c.comment_postid = ?';
$statement = $db->query($sql, array($postid));
$rows = $statement->fetchAll();
$comment = null;
foreach($rows as $row){
$comment = new Model_Comment();
$comment->populate($row);
$this->list[] = $comment;
}
return $this->getList();
}
I don't use zend_db_select because usually there are a lot of joins involved and the queries are quite complicated. But I need to add pagination for my page listing, and I'm afraid that using the array adapter won't be very effective, because Zend will select every row from my 'posts' table. Are there any ways around this, or should I implement my own pagination functionality?
There isn't a way of getting an SQL string into the Zend Select object. You'd need an SQL parser for that and ZF doesn't have that.
You'd have to refactor the statements into the Zend Select object, this can be very annoying and difficult at times, I didn't do it on my first ZF project. I wish I had done now as it is the best way of doing it and ensures you can take advantage of all the extras, E.g. pagination.
I use something similar to the following with a zend_db_select model and it works with zend pagination
$select = $this->model->select(Zend_Db_Table::SELECT_WITH_FROM_PART)
->setIntegrityCheck(false);
$select->where('table1.field = ?', $field)
->join('table2','table1.field = table2.feild')
->where('table2.field = ?', $field)
->order('field DESC');
$object = $this->model->fetchAll($select);
I want to print a individual comment in drupal based on it's comment ID. How can I do this? Google and other sources have yielded me nothing. Thank you.
Eaton's suggestion is good (except it's {comments}, not {comment}) if you need to display the comment like core does it, including the info coming from the node. Except the default theme_comment implementation in modules/comment/comment.tpl.php makes no use of $node.
However, I'd do it slightly differently, because if you need to extract a single comment, displaying it with the normal content formatting provided by comment.tpl.php is likely to be inappropriate.
function print_comment($cid) {
$sql = "SELECT * FROM {comment} c WHERE c.cid = %d";
if ($comment = db_fetch_object(db_rewrite_sql(db_query($sql, $cid), 'c'))) {
return theme('my_special_comment_formatting', $comment);
}
}
And of course, define this special commment formatting in your module's hook_theme() implementation, inspired by what comment.tpl.php does.
2014-02 UPDATE: note that this is a 2009 question/answer. In Drupal 8, you just don't want to access the hypothetical underlying SQL database (and would not do it like this anyway, but use DBTNG), but just use something like:
if ($comment = entity_load('comment', $cid)) {
return entity_view($comment, $view_mode);
}
function print_comment($cid) {
$sql = "SELECT * FROM {comments} WHERE cid = %d";
if ($comment = db_fetch_object(db_query($sql, $cid))) {
$node = node_load($comment->nid);
return theme('comment', $comment, $node);
}
}
No reason to use any sql to do this, two drupal api function calls is all it takes.
function print_comment($cid)
{
$comment = _comment_load($cid);
return theme('comment',$comment);
}