1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Caching Data

Discussion in 'XenForo Development Discussions' started by Robust, May 25, 2015.

  1. Robust

    Robust Well-Known Member

    Alright, I'm really confused here. So I'm making a Best Answer/Q&A manager. I've got a few problems, and have tried resolving them for 2 weeks but found no solution. I'd really appreciate some help.

    Problem 1: Showing phrase ba_vote or ba_unvote
    So, this function almost identically represents the one of XenForo's Like function. The way I currently do it:

    If {xen:helper checkVoteStatus, $user} is true, show ba_unvote, else show ba_vote.
    checkVoteStatus calls this method:

         * Gets a best answer content record for a user that has voted on a piece of content.
         * @param integer $postId
         * @param integer $userId
         * @return array|false
    public function getCurrentBestAnswerStatusOnPost($postId$userId)
                SELECT *
                FROM ba_votes
                WHERE post_id = ?
                    AND vote_user_id = ?
    , array($postId$userId));
    If it's an array (true), it means a vote already exists, so it shows ba_unvote, and if it returns false then no vote already exists, and it shows vote.

    This works all fine, but damn that's a lot of queries. That's an extra query per post. I could always make one query per page, using WHERE thread_id = ? instead and then filter based on post IDs, but even then that's poorly inefficient. What's the best way of showing vote or unvote based on if they have already voted?

    Problem 2: Submitting Votes / Unvotes
    I previously posted a thread on too many DB queries (https://xenforo.com/community/threads/optimising-code-too-many-db-queries.97853/). I got given the following, and it sounds like a good idea, but I'm not completely sure on how to execute it.

    I think it means to have an array with the data I'd be inputting into the database, and serialise it (the serialize() function), then insert it into the DataRegistry perhaps? Not really sure on how to execute this.

    Thanks for any help you can give :)
  2. Chris D

    Chris D XenForo Developer Staff Member

    For Problem 1:

    The way that XF handles this, is as follows:

    When the posts for a thread are fetched, we do a join on the xf_liked_content table - we fetch the "like_date" from that table... a severely cut down version of that query would look something like:

    SELECT post.*, liked_content.like_date
    FROM xf_post AS post
    LEFT JOIN xf_liked_content AS liked_content
        ON (liked_content.content_type = \'post\'
            AND liked_content.content_id = post.post_id
            AND liked_content.like_user_id = ?
    That's just a basic concept, the actual practice of doing it is actually quite difficult in XF. It's quite difficult to add additional fetch and join options. But, you get the idea, if the like_date exists for that user and that post in the like table, then the user has liked that post.

    There is, however, another way you may want to look at.

    What about doing a single query on each page load. You need to know the post IDs which is easy to get if you're extending the controller.

    SELECT *
    FROM your_table
    WHERE post_id IN (?)
    AND user_id = ?
    You then have a list of all the posts that user has voted on which with a bit more work should be easy to pass to your template and easy to interrogate to see which posts have or haven't been voted for.

    Another option is basically problem 2.

    The idea here is you have a new field in the post table, e.g. 'votes'.

    Each time a vote is added, get a list of all votes for that post into an array, serialize that array and then write it to the votes field. Then, to access that, all you need to do is unserialize it, and it's there, inside every post record, automatically, so you can then use it as you need it without any additional query overhead.
  3. Robust

    Robust Well-Known Member

    @Chris D
    I took a look at XenForo's query that it does:

    SELECT post.*
    user.*, IF(user.username IS NULL, post.username, user.username) AS username,
    deletion_log.delete_date, deletion_log.delete_reason,
    deletion_log.delete_user_id, deletion_log.delete_username,
    session_activity.view_date AS last_view_date,
    FROM xf_post AS post

    LEFT JOIN xf_user AS user ON
    (user.user_id = post.user_id)
    LEFT JOIN xf_user_profile AS user_profile ON
    (user_profile.user_id = post.user_id)
    LEFT JOIN xf_user_privacy AS user_privacy ON
    (user_privacy.user_id = post.user_id)
    LEFT JOIN xf_deletion_log AS deletion_log ON
    (deletion_log.content_type = 'post' AND deletion_log.content_id = post.post_id)
    LEFT JOIN xf_session_activity AS session_activity ON
    (post.user_id > 0 AND session_activity.user_id = post.user_id)
    LEFT JOIN xf_liked_content AS liked_content
    ON (liked_content.content_type = 'post'
    AND liked_content.content_id = post.post_id
    AND liked_content.like_user_id = 1)
    WHERE post.thread_id = ?
    AND (post.position >= 0 AND post.position < 20)
    AND (post.message_state IN ('visible','deleted','moderated'))
    ORDER BY post.position ASC, post.post_date ASC

    Quite complex but if I wanted to add to that I could do:

    LEFT JOIN ba_votes AS voted_content
    (ON voted_content.post_id = post.post_id
    AND voted_content.vote_user_id = ?)

    Or would that not work? I'd also need to figure out how to modify the existing query and add this on without causing conflict with another add-on that's also wanting to add to the query.
  4. Chris D

    Chris D XenForo Developer Staff Member

    Hence my further comments suggesting other ways. Don't do it like that, I just wanted to show you how XF does it. It's not impossible, but I think the other ways are likely going to be easier, and, over time, less painful.

Share This Page