4

I have quite a complicated situation here. I can't find a better way to solve this without putting a SELECT query inside a loop that rolls over 70000 times when I enter that page (don't worry, I use array_chunk to split the array into pages). I guess this would be a resource killer if I use a query here. Because of this, here I am, asking a question.

I have this big array I need to loop on:

$images = scandir($imgit_root_path . '/' . IMAGES_PATH);

$indexhtm = array_search('index.htm', $images);
unset($images[0], $images[1], $images[$indexhtm]);

Now I have an array with all file names of the files (images) in my IMAGES_PATH. Now the problem comes here:

Some of these images are registered on the database, because registered users have their images listed on my database. Now I need to retrieve the user_id based on the image name that the array above gives me.

Inside a loop I simply did this:

foreach ($images as $image_name)
{
    $query = $db->prepare('SELECT user_id FROM imgit_images WHERE image_name = :name');
    $query->bindValue(':name', $image_name, PDO::PARAM_STR);
    $query->execute();
    $row = $query->fetch(PDO::FETCH_ASSOC);
    $user_id = $row['user_id'];

    echo $user_id;
}

This works just fine, but the efficiency equals to 0. Using that user_id I plan on getting other stuff from the imgit_users table, such as username, which would require another query inside that loop.

This is too much and I need a simpler way to deal with this.

Is there a way to get those user_ids before going inside the loop and use them IN the loop?

This is the table structure from imgit_images: imgit_images table schema

While this is the schema for imgit_users: imgit_users table schema

aborted
  • 4,481
  • 14
  • 69
  • 132
  • 2
    You should check `WHERE x IN(...)` http://stackoverflow.com/questions/9736284/mysql-where-in – mbinette Dec 31 '12 at 14:44
  • To be honest, I thought about that, but then again I had no idea how to use the array with user_ids inside the loop and then get the usernames as well. How would I know which `image_name` fits to `user_id` with two different arrays? – aborted Dec 31 '12 at 14:45
  • 1
    You can select `user_id` and `image_name` so inside the loop, you've got the associations between the two. – mbinette Dec 31 '12 at 14:49
  • Select `image_name, user_id, username...` for *all* the records from `imgit_images` , turn it into an associative array indexed on `image_name`, shove this array into some faster storage (e.g. memcached). Fetch it from memcached on page load. Only query MySQL to refresh the results stored in memcached when some image is changed/added. – DCoder Dec 31 '12 at 14:53
  • Check my answer based on these comments. – mbinette Dec 31 '12 at 14:54
  • @Aborted Added the user table/username stuff to my answer. Did that fix your problem? If not, tell me, I'll assist you. – mbinette Dec 31 '12 at 15:05
  • @mbinette Right now I'm trying to get it to work using your answer. Thanks a lot. – aborted Dec 31 '12 at 15:08

4 Answers4

3

Something like this would work (I'm not sure if it's possible to prepare the WHERE IN query since the # of values is unknown... Else, make sure you sanatize $images):

$image_names = "'".implode("', '", $images)."'";
$query = $db->prepare("SELECT img.user_id, image_name, username 
                         FROM imgit_images img
                         INNER JOIN imgit_users u ON u.user_id = img.user_id 
                         WHERE image_name IN(".$image_names.")");
$query->execute();
while($row = $query->fetch(PDO::FETCH_ASSOC))
{
    echo $row['user_id']."'s image is ".$row['image_name'];
}

You might need to tweak it a little (haven't tested it), but you seem to be able to, so I'm not worried!

mbinette
  • 5,094
  • 3
  • 24
  • 32
  • Hey. It kinda works, but my logic is still blocked and I can't figure out how to check inside the main loop (where I loop the `scandir()`) if the image_name from the loop equals the image_name we got from the query, so we can add the username to that image. This is an example how the method I created (based on your query) returns an array: `Array ( [user_id] => 4 [image_name] => 7E4c1i2.jpg [username] => DEATHMETALGORE )` – aborted Dec 31 '12 at 15:29
  • What do you mean? If the `image_names` were not equals, it wouldn't end up in the query results! Or do you mean that you want to make sure the order in the query result is the same as the one in `$images`? – mbinette Dec 31 '12 at 15:31
  • I got it to work. I'll be adding a pastebin.com link to show how I did it in a second.. Thanks a lot *mbinette*! – aborted Dec 31 '12 at 15:42
  • Great! Glad to know you've got it working! How did you do it? :-) – mbinette Dec 31 '12 at 15:48
  • Here it is how I did it: http://pastebin.com/LbwzHMXg - Thanks a lot, I appreciate your help! :) – aborted Dec 31 '12 at 15:52
  • If you aren't going to bother writing a secure prepared statement with placeholders and bound variables, then just use `query` because the query will be just as insecure. Researchers should not use this answer. – mickmackusa Jun 22 '19 at 12:33
1

Can you not just use an INNER JOIN in your query, this way each iteration of the loop will return details of the corresponding user with it. Change your query to something like (i'm making assumptions as to the structure of your tables here):

SELECT imgit_users.user_id 
,imgit_users.username
,imgit_users.other_column_and_so_on
FROM imgit_images 
INNER JOIN imgit_users ON imgit_users.user_id = imgit_images.user_id
WHERE imgit_images.image_name = :name

This obviously doesn't avoid the need for a loop (you could probably use string concatenation to build up the IN part of your where clause, but you'd probably use a join here anyway) but it would return the user's information on each iteration and prevent the need for further iterations to get the user's info.

Paul Aldred-Bann
  • 5,840
  • 4
  • 36
  • 55
1

Not sure if it is going to help, but I see a couple of optimizations that may be possible:

  1. Prepare the query outside the loop, and rebound/execute/get result within the loop. If query preparation is expensive, you may be saving quite a bit of time.

  2. You can pass an array as in Passing an array to a query using a WHERE clause and obtain the image and user id, that way you may be able to fragment your query into a smaller number of queries.

Community
  • 1
  • 1
Luis
  • 1,294
  • 7
  • 9
0

PDO makes writing your query securely a cinch.

$placeholders = implode(',', array_fill(0, count($images), '?'));
$sql = "SELECT u.username 
        FROM imgit_images i
        INNER JOIN imgit_users u ON i.user_id = u.id
        WHERE i.image_name IN ({$placeholders})";
$stmt = $db->prepare($sql);
$stmt->execute($images);

while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    // use $row['username']
}

Create a string of comma separated ?s and write them into IN's parentheses. Then pass the array of images to execute(). Easily done, and now all of your desired data is available in a single resultset from a single query. (Add additional columns to your query's SELECT clause as needed.)

mickmackusa
  • 43,625
  • 12
  • 83
  • 136