Prevent Duplicate

Robust

Well-known member
Hey,

So I'm using deferred to remove a string from a varbinary storing a stringlist of "tasks completed" when a "task" is deleted, similar to XenForo's secondary user groups system.

I'm fetching users and it appears as if the same user is constantly fetched again and won't stop until application error. How do I prevent duplicate entries?
 
could you provide your deferred class? Sounds like you aren't getting the list of user IDs first then getting X users from that list per batch, but instead are just selecting X users on each batch so it keeps hitting the same users, but a look at your Deferred class will help :)
 
could you provide your deferred class? Sounds like you aren't getting the list of user IDs first then getting X users from that list per batch, but instead are just selecting X users on each batch so it keeps hitting the same users, but a look at your Deferred class will help :)

Called in DataWriter:
Code:
    protected function _postDelete()
    {
        $taskId = $this->get('task_id');
        $points = $this->get('added_points');

        $data = array('taskId' => $taskId, 'points' => $points);
        XenForo_Application::defer('Apantic_Tasks_Deferred_TaskDelete', $data, "taskDelete_$taskId", true);
    }

Deferred:
Code:
class Apantic_Tasks_Deferred_TaskDelete extends XenForo_Deferred_Abstract
{
    public function canTriggerManually()
    {
        return false;
    }

    public function execute(array $deferred, array $data, $targetRunTime, &$status)
    {
        $data = array_merge(array(
            'taskId' => 0,
            'count' => 0
        ), $data);

        if (!$data['taskId'])
        {
            return false;
        }

        $s = microtime(true);

        /* @var $progressModel Apantic_Tasks_Model_Tasks */
        $progressModel = XenForo_Model::create('Apantic_Tasks_Model_Tasks');

        $limit = 100;

        do
        {
            $results = $progressModel->removeTaskFromUserIdsWithTask($data['taskId'], $limit, $data['count']);
            if(empty($results))
            {
                return false;
            }

            $data['count'] += count($results);
        }
        while ($targetRunTime && microtime(true) - $s < $targetRunTime);

        $actionPhrase = new XenForo_Phrase('deleting');
        $typePhrase = new XenForo_Phrase('user_group');
        $status = sprintf('%s... %s (%s)', $actionPhrase, $typePhrase, XenForo_Locale::numberFormat($data['count']));

        return $data;
    }
}

Model functions:

Code:
    public function setCompletedTasks($user, array $taskIds)
    {
        $db = $this->_getDb();
        $tasks = (!empty($user['aupp_completed_tasks']) ? implode(',', array_merge(explode(',', $user['aupp_completed_tasks']), $taskIds)) : implode(',', $taskIds));
        $db->query('
            UPDATE xf_user
            SET aupp_completed_tasks = ?
            WHERE user_id = ?
        ', array($tasks, $user['user_id']));
    }

    public function getCompletedTasks($user)
    {
        return explode(',', $user['aupp_completed_tasks']);
    }

    public function removeTaskFromUserIdsWithTask($taskId, $limit = 0, $offset = 0)
    {
        $affectedUserIds = array();

        $data = $this->_getDb()->fetchAll($this->limitQueryResults(
            '
                SELECT user_id, aupp_completed_tasks
                FROM xf_user
                WHERE aupp_completed_tasks <> \'\'
                ORDER BY user_id
            ', $limit, $offset
        ));

        foreach($data as $user)
        {
            $userTasks = $this->getCompletedTasks($user);

            foreach($userTasks AS $key => $value)
            {
                if($value == $taskId)
                {
                    unset($userTasks[$key]);
                    $this->setCompletedTasks($user, $userTasks);
                    $affectedUserIds[] = $user['user_id'];
                }
            }
        }

        return $affectedUserIds;
    }

aupp_completed_tasks table, varbinary(255) not null

As it looks, I really have no idea what I'm doing here.
 
I suspect it is your offset approach on the user fetching.

Rather than using an offset instead keep track of the last user ID you worked on. Then your query can be equivalent to "WHERE user_id > $lastId". That way you can avoid passing in an offset, which I must add, doesn't scale well in queries and should be avoided where possible.
 
I suspect it is your offset approach on the user fetching.

Rather than using an offset instead keep track of the last user ID you worked on. Then your query can be equivalent to "WHERE user_id > $lastId". That way you can avoid passing in an offset, which I must add, doesn't scale well in queries and should be avoided where possible.
I see - is my method of the deferred a bit clumsy by any chance?
 
Top Bottom