mysql delete with inner joins and limit - mysql

This is the only way I could think of doing this and I am getting an error about the limit. I am trying to delete in chunks within the while loop as the result total might be quite large.
The first count statement works fine. It is the second, delete statement, which does not. I am guessing because of the joins and/or using limit. How can I limit the delete while still joining?
//find total count to delete
$stmt = $db->prepare("
SELECT
COUNT(*)
FROM app_logs
INNER JOIN users
ON users.user_id = app_logs.user_id
INNER JOIN computers
ON computers.computer_id = users.computer_id AND computers.account_id != :account
WHERE app_logs.timestamp < :cutoff_time
");
$binding = array(
'account' => 2,
'cutoff_time' => strtotime('-3 months')
);
$stmt->execute($binding);
//get total results count from above
$found_count = $stmt->fetch(PDO::FETCH_COLUMN, 0);
echo $found_count; //ex. 15324
//delete rows
$stmt = $db->prepare("
DELETE
FROM app_logs
INNER JOIN users
ON users.user_id = spc_app_logs.user_id
INNER JOIN computers
ON computers.computer_id = users.computer_id AND computers.account_id != :account
WHERE app_logs.timestamp < :cutoff_time
LIMIT :limit
");
$binding = array(
'account' => 2,
'cutoff_time' => strtotime('-3 months'),
'limit' => 2000
);
while($found_count > 0)
{
$stmt->execute($binding);
$found_count = $found_count - $binding['limit'];
}

As mentioned in the docs and also in this answer, LIMIT cannot be used along DELETE + JOIN.
If you need to somehow limit deleting, just create conditions for the DELETE statement, that will emulate your LIMIT section. For example you could follow these steps:
Take minimum and maximum rows' ids from the query you want to delete values from (lets say: 10 and 200)
Then choose bordering ids imitating your limit (so for limit 50, the bordering ids could be 50, 100, 150, 200)
Then, for each bordering id, query a DELETE statement with WHERE having AND id <= ?, and put every bordering id in the place of ?.
So you end up with 4 similar queries, each of them deleting ids from a certain range (10-50], (50, 100] etc..
Hope this helps.

Related

codeigniter on and where combination

Table user left join table personal_record then join table activity.
user table:id ...
personal_record table: user.id, activity.id, confirm_time...
activity: id, time, delete_time...
note: time means hours spent on this activity, 8 etc.
Before user join personal_record, some of persanoal_record rows need to be filtered as personal_record only valid when confirm_time is set.personal_reocrd only contains activity_id, activity which contains activities' time need to join to sum (time) a person spend.
As a activity is deleted by updating activity.delete_time column.
Here is my code.
Update(Almost solved): ($data_begin filter the range of records)
$result = $this->db->select('user.*, ifnull(sum(ac.time), 0) as time')
->from('user')
->join('(select * from personal_record where personal_record.confirm_time is not null) as pr','user.id = pr.person', 'left')
->join('(select * from activity where activity.delete_time is null and activity.create_time >"'.$date_begin.'")as ac','ac.id = pr.activity', 'left')
->group_by('user.id')
->order_by('time', 'desc')
->get()
->result();
$this->db->select('user.*,personal_record.*,ifnull(sum(activity.time), 0) as time');
$this->db->from('user');
$this->db->join('personal_record','personal_record.person = user.id');
$this->db->join('activity','activity.id = personal_record.activity');
$this->db->where('activity.delete_time',null);
$this->db->or_where('personal_record.confirm_time !=', null);
$this->db->order_by('time', 'asc');
$this->db->group_by('user.id');
$data = $this->db->get()->result();
return $data;

sql update statement with from clause for validation?

I have an update operation that I perform on multiple users at once (the value stays the same). Is it possible to join tables to an update statement for reasons of validation?
For instance, here is my statement at the moment :
$user_set = array(1, 5, 10, 15, .....)
//update 'changed' status for ALL selected users at once
$stmt = $db->prepare("
UPDATE users
SET changed = ?
WHERE user_id IN(". implode(', ', array_fill(1,count($user_set),'?')) .")
");
array_unshift($user_set, 1);
$stmt->execute($user_set);
In a perfect scenario I would like to join one table (computers) to the users table to validate account ownership and if this update is 'valid' and should occur or not.
I found out earlier I can do EXACTLY that with DELETE, but can it be done with UPDATE as well? Example delete using validation I want :
$selected = array(1, 5, 10, 15, .....)
$stmt = $db->prepare("
DELETE del_table.*
FROM some_table as del_table
LEFT JOIN
users
on users.user_id = del_table.user_id
LEFT JOIN
computers
on computers.computer_id = users.computer_id
WHERE computers.account_id = ? AND del_table.activity_id IN(". implode(', ', array_fill(1,count($selected),'?')) .")
");
// use selected array and prepend other data into the array so binding and execute work in order
array_unshift($selected, $_SESSION['user']['account_id']);
$stmt->execute($selected);
EDIT (SOLUTION) :
Thanks Alex... it works!
$selected = array(5,10,12,13);
$stmt = $db->prepare("
UPDATE users
INNER JOIN computers
on computers.computer_id = users.computer_id
SET changed = ?
WHERE computers.account_id = ? AND users.user_id IN(". implode(', ', array_fill(1,count($selected),'?')) .")
");
array_unshift($selected, 1, $_SESSION['user']['account_id']);
$stmt->execute($selected);
Yes, you can, as documented here under the multi-table syntax section.
UPDATE [LOW_PRIORITY] [IGNORE] table_references
SET col_name1={expr1|DEFAULT} [, col_name2={expr2|DEFAULT}] ...
[WHERE where_condition]
You just need to make sure you order the statements correctly.
UPDATE my_table
INNER JOIN other_table
ON my_table.col2 = othertable.col2
SET my_table.col = 'foo'
WHERE other_table.col = 'bar'
try this
$stmt = $db->prepare("
UPDATE users
SET changed = ?
from users
JOIN computers on computers.computer_id = users.computer_id
WHERE user_id IN(". implode(', ', array_fill(1,count($user_set),'?')) .")
");

Optimizing MySQL operation to get counts

I have this lovely piece of code that copies out different counts of rows so I can build an associative list of how many of each good a citizen possesses.
$citizen_id = 1;
$goods = array();
$goods_result = mysql_query("SELECT id,name FROM goods");
while(($goods_row = mysql_fetch_assoc($goods_result))) {
$good_id = $goods_row['id'];
$query = "SELECT count(*) FROM possessions where citizen_id=$citizen_id and good_id=$good_id";
$possessed_result = mysql_query($query);
$possessed_row = mysql_fetch_row($possessed_result);
if($possessed_row) {
$possessed = $possessed_row[0];
$goods[$goods_row['name']] = $possessed;
}
}
echo json_encode ($goods);
It works, but it runs too slow. It seems to me that there must be a way to get MySQL to build a table of counts and return it to me in a single query, but I have no idea how to figure out how to do that. Any ideas on how to make this operation faster?
How about using a query like
SELECT g.id good_id,
count(*) cnt_of_goods
FROM possessions p INNER JOIN
goods g ON p.good_id = g.id
where citizen_id=$citizen_id
GROUP BY g.id

Writing a custom mysql query to order wordpress users by a custom user meta field

Basically, I need to order a list of WordPress users by the city they live in. I've got a custom user meta field for city, and I've got the query working properly, but the query lists everyone who hasn't filled out a city at the beginning since it places blank fields at the beginning of the order.
What I need is to figure out how to only select and display users who have given a value other than blank in the city field. Unfortunately, I've found myself stumped.
Any thoughts on how to do this? Also, if anyone knows a way to orderby a custom user meta field using wp_user_query as opposed to this mess, I'm all ears.
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$limit = 10;
$offset = ($paged - 1) * $limit;
$key = 'city';
$sql = "SELECT SQL_CALC_FOUND_ROWS {$wpdb->users}.* FROM {$wpdb->users}
INNER JOIN {$wpdb->usermeta} wp_usermeta ON ({$wpdb->users}.ID = wp_usermeta.user_id)
INNER JOIN {$wpdb->usermeta} wp_usermeta2 ON ({$wpdb->users}.ID = wp_usermeta2.user_id)
WHERE 1=1
AND wp_usermeta.meta_key = 'wp_capabilities'
AND CAST(wp_usermeta.meta_value AS CHAR) LIKE '%\"subscriber\"%'
AND wp_usermeta2.meta_key = '$key'
ORDER BY wp_usermeta2.meta_value ASC
LIMIT $offset, $limit";
$site_users = $wpdb->get_results($sql);
$found_rows = $wpdb->get_var("SELECT FOUND_ROWS();");
foreach ($site_users as $site_user) {
// user info here
}
Try something like
...
WHERE 1=1
AND wp_whatever.name_of_city IS NOT NULL
AND LENGTH(wp_whatever.name_of_city) > 0
AND wp_usermeta.meta_key = 'wp_capabilities'
...

how can I connect these two tables in sql?

i need to take only the items from the table "__sobi2_item" that are in the same country of the user.And use this results for the rest of the function Showupdatelisting. This is my php script:
<?php
function showUpdatedListing()
{
//i found the user country field value...
global $database;
$user =& JFactory::getUser();
$userId = $user->get( 'id' );
$sql = "SELECT (id) FROM #__community_fields WHERE fieldcode= 'FIELD_COUNTRY'";
$database->setQuery( $sql );
$fieldID = $database->loadResult();
$sql = "SELECT (value) FROM #__community_fields_values WHERE field_id= {$fieldID} && user_id= {$userId}";
$database->setQuery( $sql );
$usercountry = $database->loadResult();
// From all the entries i take only ones that have country field like the user has...
$query = "SELECT `data_txt`, `itemid`, `fieldid` FROM `#__sobi2_fields_data` WHERE (`fieldid` = 6) AND ('data_txt' = {$usercountry})";
$database->setQuery($query);
$ResultsArray = $database->loadObjectList();
// We need something here like a Query to load only the entries from $ResultsArray... ??
//....instead of this...
$config =& sobi2Config::getInstance();
$database = $config->getDb();
$now = $config->getTimeAndDate();
$query = "SELECT itemid FROM #__sobi2_item WHERE (published = 1 AND publish_down > '{$now}' OR publish_down = '{$config->nullDate}') ORDER BY last_update DESC LIMIT 0, 30";
$database->setQuery($query);
$sids = $database->loadResultArray();
// ......... show update function goes on...
?>
can anyone help me to connect and adjust these query? thanks.
NB:with the last query (4) i need to filter items of the $ResultsArray taking only ones published and ordering them by last_update. i know it is wrong and now there is no connection with the query before. This is how i have tables in mysql:
_sobi2_fields_data:
itemid
fieldid
data_txt --->(is a description column for each field)
_sobi2_item:
itemid
published --->( 1 if true, 0 if false )
last_update --->(date of the last update for the item, also equal to the publication date if there are no changes)
thanks.
I don't know what you are trying to ask as well. Your last query (number 4) doesn't make sense to me, how is it linked to the above queries?
[EDIT] I've linked your 4th table above assuming itemid is the primary key for the items in sobi2_item table and that the value is linked to the sobi_fields_data table via itemid.
SELECT
cf.id,
sfd.data_txt,
sfd.itemid,
sfd.fieldid
FROM #__community_fields cf
INNER JOIN #__community_fields_values cfv ON cf.id=cfv.field_id
INNER JOIN #__sobi2_fields_data sfd ON cfv.value=sfd.data_txt
INNER JOIN #__sobi2_item si ON sfd.itemid=si.itemid
WHERE cf.fieldcode='FIELD_COUNTRY'
AND cfv.user_id=$userId
AND sfd.fieldid=6
AND si.published=1
AND (si.publish_down > '{$now}' OR si.publish_down = '{$config->nullDate}')
ORDER BY si.last_update DESC LIMIT 0, 30
Good luck!