CakePHP HAVING SUM Query superslow - where is room for improvement? - mysql

This query kills the whole page (90% of the request time):
/**
* Checks if a conversation exists containing these users (at least two!)
* //TODO: fixme! SUPER-SLOW! 5s on a 6s page load total
*
* #param array $users Users to check on
* #param int $limit Limit - needs at least 2 users
* #return array Results
*/
public function partOfConversations($users, $limit = 5) {
$options = array(
'conditions' => array('ConversationUser.status <' => ConversationUser::STATUS_REMOVED),
'group' => array('ConversationUser.conversation_id HAVING SUM(CASE WHEN ConversationUser.`user_id` in (\'' . implode('\', \'', $users) . '\') THEN 1 ELSE 0 END) = ' . count($users) . ''),
'contain' => array('Conversation' => array('LastMessage')),
'limit' => $limit,
'order' => array('Conversation.last_message_id' => 'DESC')
);
return $this->ConversationUser->find('all', $options);
}
The resulting query is
SELECT `ConversationUser`.`id`, `ConversationUser`.`conversation_id`,
`ConversationUser`.`user_id`, `ConversationUser`.`status`, `ConversationUser`.`created`,
`Conversation`.`id`, `Conversation`.`user_id`, `Conversation`.`title`,
`Conversation`.`created`, `Conversation`.`last_message_id`, `Conversation`.`count`
FROM `comm_conversation_users` AS `ConversationUser`
LEFT JOIN `comm_conversations` AS `Conversation`
ON (`ConversationUser`.`conversation_id` = `Conversation`.`id`)
WHERE `ConversationUser`.`status` < 7 GROUP BY `ConversationUser`.`conversation_id`
HAVING SUM(CASE WHEN `ConversationUser`.`user_id` in
('2ed23d7c-dcc8-4d3b-8e7b-0fe018b0f9bf', '297e0fcc-8880-4bc7-9b57-0ba418b0f9bf')
THEN 1 ELSE 0 END) = 2
ORDER BY `Conversation`.`last_message_id` DESC
LIMIT 5
What it tries to do is to find out whether in a 1..x conversation two or more users are part of it (passed as $users).
Is there any way to speed that up?
Conversation 1:N ConversationUser N:1 User
Records are not too many: 70k Conversation, 130k ConversationUser
The fact that this also uses UUIds instead of normal AIID integers is probably making it worse.
But it should still not be 5s.

Besides ensuring your sql tables are optimized via indexes for that query, I think you should add another condition- only those records with acceptable user_id's.
Here is the appropriate WHERE clause
WHERE `ConversationUser`.`status` < 7
AND `ConversationUser`.`user_id` in
('2ed23d7c-dcc8-4d3b-8e7b-0fe018b0f9bf', '297e0fcc-8880-4bc7-9b57-0ba418b0f9bf')
Changing the PHP code to:
public function partOfConversations($users, $limit = 5) {
$options = array(
'conditions' => array('ConversationUser.status <' => ConversationUser::STATUS_REMOVED
,'ConversationUser.user_id' => $users),
'group' => array('ConversationUser.conversation_id HAVING SUM(CASE WHEN ConversationUser.`user_id` in (\'' . implode('\', \'', $users) . '\') THEN 1 ELSE 0 END) = ' . count($users) . ''),
'contain' => array('Conversation' => array('LastMessage')),
'limit' => $limit,
'order' => array('Conversation.last_message_id' => 'DESC')
);
return $this->ConversationUser->find('all', $options);
}

Related

Wordpresss - WP_query count

I need to count how many times my date recorded in the meta key:metakey_AMC_data, in format (d-m-Y) it is contained in the database by comparing it with the current date
$mostra_data_corrente = date('d-m-Y');
$query = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}postmeta
WHERE (meta_key = 'metakey_AMC_data'
AND meta_value = '$mostra_data_corrente')");
$conta_risultati = count($query);
and this I can do perfectly.but now my need is to execute the first query by linking another AND, and specify when the term slug is equal to the category of the event (terms taxonomy), obviously the query is incorrect
SELECT * FROM {$wpdb->prefix}postmeta
WHERE (meta_key = 'metakey_AMC_data'
AND meta_value = '$mostra_data_corrente')
AND(slug = 'aperitivi') "
how can i do this?
You can get that count as well. You need to modify query (code) like follow:
$qry = array(
'post_type' => 'post', // mention your post type to narrow down searching through postmeta table
'meta_query' => array(
array(
'meta_key' => 'metakey_AMC_data',
'meta_value' => $mostra_data_corrente,
'compare' => '='
)
),
'tax_query' => array(
array(
'taxonomy' => 'nameoftaxonomy', // Write the name of taxonomy that you have assinged while you created a CPT (custom post type)
'field' => 'slug',
'terms' => 'aperitivi',
)
)
)
$the_query = WP_Query($qry);
echo $the_query->post_count;
You have to make some necessary changes in above code to suite your requirements. I've added comment where you have to do changes.

yii1 CGridview with custom SQL

i need to use yii1 CGridView with the custom SQL.
i have followed this article
inside model I've created
public function searchUserStats()
{
$count = Yii::app()->db->createCommand()
->select('count(DISTINCT user_id)')
->from('casino_sg_sessions')
->queryScalar();
$sql = '
SELECT
SUM(bet) bet_sum,
SUM(win) win_sum,
SUM(bet_count) bet_count,
user_id,
userName,
modified
FROM
(SELECT
(SELECT IFNULL(SUM(tr1.amount), 0) FROM casino_sg_transactions tr1 WHERE t.id = tr1.session_id AND tr1.action = \'bet\' ) AS bet,
(SELECT IFNULL(SUM(tr2.amount), 0) FROM casino_sg_transactions tr2 WHERE t.id = tr2.session_id AND tr2.action = \'win\' ) AS win,
(SELECT count(0) FROM casino_sg_transactions tr3 WHERE t.id = tr3.session_id) AS bet_count,
t.user_id,
u.userName,
t.modified
FROM `casino_sg_sessions` t
INNER JOIN user u ON u.id = t.user_id
ORDER BY modified DESC
) user_stats
GROUP BY user_id
ORDER BY modified DESC
';
$command = Yii::app()->db->createCommand($sql);
$dataProvider = new CSqlDataProvider($command, array(
'totalItemCount'=>$count,
'pagination'=>array(
'pageSize'=>15,
),
));
return $dataProvider;
}
in the controller:
$model = new CasinoSgSessions('searchUserStats');
$this->render('index', array(
'model' => $model,
));
inside the view i call it this way:
$this->widget('application.widgets.grid.CGridView', array(
'dataProvider' => $model->searchUserStats(),
'filter' => $model,
'columns' => array(
[
'header' => 'User ID',
'name' => 'user_id',
'value' => '$data->user_id',
'htmlOptions' => array('width'=>'150px'),
],
.....
.....
i receive paginated grid without data.
spent huge amount of time and now gave up, what i did wrong ?
sql query itself works good.
PS: i cant use mysql views and create model for the view.
PPS: query will be a bit more complex, so the only way i see is using CGridView through the sql query.
in the template instead of
$data->user_id
i had to use
$data['user_id']
absolutely not expected behavior. Seems to me they didn't hear about SOLID

Zend Framework 2 SQL Join Issue

I am trying to use two left outer joins with Zend Framework 2's SQL classes but for some reason it is not returning one result but the other one is working fine. I've ran the actual SQL in MySQL Workbench and it returns just like I want but it is not doing it with Zend Framework. Here is my code:
Pure SQL:
SELECT groups.group_name, members.username, groups.id FROM groups
LEFT OUTER JOIN group_admins ON groups.id = group_admins.group_id
LEFT OUTER JOIN members ON group_admins.user_id = members.id
WHERE group_admins.user_id = " . parent::getUserId()['id']
This returns the result I wish, (which can be seen here: http://imgur.com/8ydmn4f)
Now, here is the Zend Framework 2 code I have in place:
$select_admins = new Select();
$select_admins->from(array(
'g' => 'groups',
))
->join(array(
'ga' => 'group_admins'
), 'g.id = ga.group_id')
->join(array(
'm' => 'members'
), 'ga.user_id = m.id', array('username'))
->where(array('ga.user_id' => parent::getUserId()['id']));
$query_group_admin = parent::$sql->getAdapter()->query(parent::$sql->buildSqlString($select_admins), Adapter::QUERY_MODE_EXECUTE);
$group_admins = array();
foreach ($query_group_admin as $group_admin) {
$group_admins[] = $group_admin;
}
// get the group members
$select = new Select();
$select->from(array(
'g' => 'group_members'
))
->join(array(
'm' => 'members'
), 'g.member_id = m.id')
->join(array(
'grp' => 'groups'
), 'g.group_id = grp.id')
->where(array(
'g.group_id' => $group_id
));
$query = parent::$sql->getAdapter()->query(parent::$sql->buildSqlString($select), Adapter::QUERY_MODE_EXECUTE);
$member_username = array();
foreach ($query as $member) {
$member_username[] = $member['username'];
}
// get the rest of the group info
$fetch = $this->gateway->select(array(
'id' => $group_id
));
$row = $fetch->current();
if (!$row) {
return false;
}
return array(
'admins' => implode(", ", $group_admins),
'members' => implode(", ", $member_username),
'info' => $row
);
Controller:
public function grouphomeAction()
{
$id = $this->params()->fromRoute('id', 0);
if (0 === $id) {
return $this->redirect()->toRoute('members/groups', array('action' => 'index'));
}
if (!$this->getGroupsService()->getGroupInformation($id)) {
return $this->redirect()->toRoute('members/groups', array('action' => 'index'));
}
return new ViewModel(array('group_info' => $this->getGroupsService()->getGroupInformation($id)));
}
However, this only shows the group name, group creator and group members but leave the group admins field empty.
Here is the print_r result of the array returned:
Array ( [admins] => [members] => jimmysole, fooboy [info] => ArrayObject Object ( [storage:ArrayObject:private] => Array ( [id] => 2 [group_name] => Tim's Group [group_creator] => timlinden [group_created_date] => 2017-01-16 17:39:56 ) ) )
If it helps, here is a screenshot as well of the page - http://imgur.com/xUQMaUu
Any help would be appreciated!
Thanks.
Basically your joins are INNER JOINS...I know....you must hate Zend right now :p . By default they are INNER JOINS so i assume that is what is wrong. SO try to specify the type of join and you should be fine. You can find more examples here: examples
$select12->from('foo')->join('zac', 'm = n', array('bar', 'baz'), Select::JOIN_OUTER);

CakePHP Database Query Count(Case When)

I'm using CakePHP and cannot seem to translate this SQL query I did on the workbench. It's exactly what I want to do but I cannot seem to make it work.
This is my query on MySQL:
select
docs.id, docs.schoolId, schools.name, docs.datesubmitted, docs.status,
uploads.iddocs,
COUNT(*),
COUNT(case uploads.staff_checked when 1 then 1 else null end)
FROM
docs as docs, schools as schools,
uploads as uploads
where
docs.schoolId = schools.schoolId AND
docs.id = uploads.iddocs AND
docs.status ="Submitted"
group by
docs.id;
What I need to do is combine three tables: Docs, Schools, and Uploads.
The two counts: COUNT(case when) and COUNT(*) indicate the number of documents reviewed (COUNT(case when)) and total documents needed to be reviewed (COUNT(*)).
So far MySQL is showing what I need. Can someone figure out how to do it in Cake?
Try the following query
$this->virtualFields = array(
'count_case_uploads_staff_checked_when_1_then_1_else_null_end' => 'COUNT(case uploads.staff_checked when 1 then 1 else null end)',
'count' => 'COUNT(*)',
);
$options = array(
'fields' => array(
'Doc.id',
'Doc.schoolId',
'schools.name',
'Doc.datesubmitted',
'Doc.status',
'uploads.iddocs',
'Doc.count_case_uploads_staff_checked_when_1_then_1_else_null_end',
'Doc.count',
),
'joins' => array(
array(
),
),
'conditions' => array(
'Doc.schoolId = schools.schoolId',
'Doc.id = uploads.iddocs',
'Doc.status' => 'Submitted',
),
'group' => array(
'Doc.id',
),
);
$data = $this->find('all', $options);

Getting total rows using Drupal 7 Database API when using limit

I am using the Drupal 7 Database API to search my table. I am also using the paging and sorting extenders. So the problem is, how do I display the total number of records found when my query is using limit because of the pagination? Do I need to run my query that has all of the conditions TWICE? Once to get the count and another one with the limit? That seems inefficient. Here is my code for reference. I am new to the Database API so feel free to adjust my code or point me in the right direction if I'm doing something wrong. Also I'm not done with this yet and only have one condition in place, but I will end up having 3. THANKS:
function job_search() {
// Initialising output
$output = 'SOME STUFF';
// Table header
$header = array(
array('data' => 'Description'),
array('data' => 'Location', 'field' => 'job_location_display'),
array('data' => 'Specialty', 'field' => 'specialty_description'),
array('data' => 'Job Type', 'field' => 'job_board_type'),
array('data' => 'Job Number', 'field' => 'job_number'),
);
// Setting the sort conditions
if(isset($_GET['sort']) && isset($_GET['order'])) {
// Sort it Ascending or Descending?
if($_GET['sort'] == 'asc')
$sort = 'ASC';
else
$sort = 'DESC';
// Which column will be sorted
switch($_GET['order']) {
case 'Location':
$order = 'job_location_display';
break;
case 'Specialty':
$order = 'specialty_description';
break;
case 'Job Number':
$order = 'job_number';
break;
case 'Job Type':
$order = 'job_board_type';
break;
default:
$order = 'job_number';
}
}
else {
$sort = 'ASC';
$order = 'job_number';
}
// Query object
$query = db_select("jobs", "j");
// Adding fields
$query->fields('j');
if(isset($_GET['location'])) {
$query->condition('j.job_state_code', $_GET['location'], '=');
}
// Set order by
$query->orderBy($order, $sort);
// Pagination
$query = $query->extend('TableSort')->extend('PagerDefault')->limit(20);
// Executing query
$result = $query->execute();
// Looping for filling the table rows
while($data = $result->fetchObject()) {
$description = '<div class="thumbnail"><img src="/sites/all/themes/zen/vista_assets/images/job_headers/' . $data->job_image_file . '"/></div>';
$description .= '<div class="title">' . $data->job_board_subtitle . '</div>';
// Adding the rows
$rows[] = array($description, $data->job_location_display, $data->specialty_description, $data->job_board_type, $data->job_number);
}
$output .= theme('pager');
// Setting the output of the field
$output .= theme_table(
array(
'header' => $header,
'rows' => $rows,
'attributes' => array('id' => array('job-listing')),
'sticky' => true,
'caption' => '',
'colgroups' => array(),
'empty' => t("No records found.")
)
).theme('pager');
// Returning the output
return $output;
}
This ended up working:
//get total records
$num_rows = $query->countQuery()->execute()->fetchField();
// add paging and sorting
$query = $query->extend('TableSort')->extend('PagerDefault')->limit(20);
//execute again
$result = $query->execute();
According to the documentation:https://api.drupal.org/api/drupal/includes!database!database.inc/function/db_query/7
in order to get the total number of rows it is better to use db_query() function, cause it has the method rowCount() which returns total number of query rows:
<?php
// Using the same query from above...
$uid = 1;
$result = db_query('SELECT n.nid, n.title, n.created
FROM {node} n WHERE n.uid = :uid', array(':uid' => $uid));
// Fetch next row as a stdClass object.
$record = $result->fetchObject();
// Fetch next row as an associative array.
$record = $result->fetchAssoc();
// Fetch data from specific column from next row
// Defaults to first column if not specified as argument
$data = $result->fetchColumn(1); // Grabs the title from the next row
// Retrieve all records into an indexed array of stdClass objects.
$result->fetchAll();
// Retrieve all records as stdObjects into an associative array
// keyed by the field in the result specified.
// (in this example, the title of the node)
$result->fetchAllAssoc('title');
// Retrieve a 2-column result set as an associative array of field 1 => field 2.
$result->fetchAllKeyed();
// Also good to note that you can specify which two fields to use
// by specifying the column numbers for each field
$result->fetchAllKeyed(0,2); // would be nid => created
$result->fetchAllKeyed(1,0); // would be title => nid
// Retrieve a 1-column result set as one single array.
$result->fetchCol();
// Column number can be specified otherwise defaults to first column
$result->fetchCol($db_column_number);
// Count the number of rows
$result->rowCount();
?>