0

I need to query one table, and use that information to query another table. Finally, I need to run a query on the first table, displaying the results from both tables and queries.

Everything works except for $summary = mysql_result($y,$j, 'WebDesc'); Conversely, $sql3 is echoing correctly, and when I run the query manually, it pulls data for WebDesc. I see in the manual that mysql_query doesn't support "multiple queries" but I don't really know what that means. The first two queries ($sql and $sql2) work together fine, and I've written other scripts with multiple queries using mysql_query.

$sql = "SELECT * FROM T_AdditionalInfo WHERE Description LIKE '" . $page_title . "%'" . " AND (ProductType='FG8' OR ProductType='FG1') AND Active='Yes'";
$x = mysql_query($sql);
$table_data = "";

while ($result = mysql_fetch_array($x)) {
    $kilnnum = $result['ItemNo'];
} //kilnnum should be set to ItemNo of last matching kiln

$sql2 = "SELECT * FROM T_Accessories WHERE KilnNo='$kilnnum'"; 
$x = mysql_query($sql2);
$rows = mysql_num_rows($x);

for ($j = 0 ; $j < 4 ; ++$j) //increment through 0-3 first 4 rows of data
{
    $item_no = mysql_result($x,$j, 'PartNo'); //get PartNo from T_Accessories
    $sql3 = "SELECT ItemNo,WebDesc FROM T_AdditionalInfo WHERE ItemNo='$item_no'"; //Pull data from T_AdditionalInfo for above PartNo/ItemNo
    $y = mysql_query($sql3);

    $title_w_spaces = mysql_result($x,$j, 'Description'); //Still pulling title from T_Accessories to match image names
    $title = str_replace(" ", "-", $title_w_spaces); //Still using title for heading from T_Accessories
    $summary = mysql_result($y,$j, 'WebDesc'); //Pulling description from T_AdditionalInfo
    if ($title <> "") {
    $table_data .= "
        <div>
            <h6> $title_w_spaces </h6>
            <img src='/images/" . $title . ".jpg' alt='" . $title ."' title='" . $title . "' class='alignnone size-full' width='114' />
            <p>" . $summary . "</p>  
        </div>";
        } //end if
} //end for
Nathaniel Ford
  • 20,545
  • 20
  • 91
  • 102
Mark
  • 3
  • 3
  • 2
    It looks like you need to learn about SQL JOINs as well as how to avoid SQL injection by using parametrized queries. – Andy Lester Jun 21 '13 at 17:52
  • 3
    and how not to use mysql_*. New code should all be using mysqli or PDO – exussum Jun 21 '13 at 17:53
  • 1
    http://stackoverflow.com/questions/12859942/why-shouldnt-i-use-mysql-functions-in-php – element119 Jun 21 '13 at 17:55
  • 1
    This is a very inefficient way of doing things, learn about SQL JOINS, and definitely use PDO instead – Kylie Jun 21 '13 at 17:57
  • Good advice Re: JOIN - thanks. I am a relative newbie as you all can clearly tell. I'll transition the script to mysqli (I guess I'm using an outdated source, even though the book was published in 2012!), and follow the example in the manual to prevent injection (this is in dev mode now). Can anyone provide constructive information of why $summary = mysql_result($y,$j, 'WebDesc'); isn't assigning the WebDesc field value to $summary? – Mark Jun 21 '13 at 18:06
  • @Mark I'm not quite sure what the association is between your tables, but I have posted an answer which would demonstrate how to use that with PDO/mysqli. Using a join can really help eliminate the number of queries. I also showed how you can externalize a lot things to help keep your main code cleaner using the existing functionality of PDO/mysqli. – Kyle Jun 21 '13 at 18:57

1 Answers1

0

As others have suggested, I would also recommend combining your multiple selects into a single JOIN statement. As others have also said, I would recommend using mysqli or PDO. Both of these will effectively help eliminate SQL injections as well as prevent you from using code that has been deprecated and that will be removed in the next release.

Here is an example on how you can rewrite your query and loop logic (example uses PDO as it has been a while since I used mysqli). I think I may have missed part of the join, as I'm not 100% sure what the association is between your tables.

$sql = "
SELECT 
    Description, 
    WebDesc 
FROM T_AdditionalInfo info JOIN T_Accessories acc ON info.ItemNo = acc.PartNo
WHERE info.Description LIKE :title
";
$db = false;
$table_date = '';
try {
    $db = new PDO(...);
}
catch(PDOException $e) {
    $db = false
    //Failed to connect to DB for some reason
}
if($db !== false) { //Did we connect?
    if(($stmt = $db->prepare($sql)) !== false) {  //Prepare the statement
        $stmt->bindParam(':title', '%abc%');  //Bind the parameter
        if($stmt->execute() !== false) {  //Execute the statement
            while($row = $stmt->fetch(PDO::FETCH_ASSOC)) { //Loop through the result set
                $table_w_spaces = $row['Description']; //Get title
                $title = str_replace(' ', '-', $title_w_spaces); //alter title
                $table_date .= "
<div>
  <h6>{$table_w_spaces}</h6>
  <img src="/images/{$title}.jpg" alt="{$title}" title="{$title}" class="alignnone size-full" width="114" />
<p>{$row['WebDesc']}</p>
</div>
";
            }
        }
        else {
            //Execution of statement failed
            print_r($stmt->errorInfo());
        }
    }
    else {
        //An error occurred when preparing SQL statement, probably a SQL error
        print_r($stmt->errorInfo());
    }
}

This might be me getting a little ahead of myself, but I'm guessing you will probably be doing things like this a lot. Once you become familiar with PDO or mysqli, it might be beneficial to create a class to take away a lot of the repetitive tasks. Also if you should ever change to another DB server (from MySql, to PostGre, or MSSQL) or even change your server name, it is all right there in one spot and easy to change. If the example class below seems a bit confusing at first don't worry. See the examples below and it should become clear. It is basically a way of taking away all of the repetitive tasks and integrating them into two specific functions executeQuery and executeNonQuery.

class Database {
    private $db = false;
    private $connected = false;
    public function __construct($db = 'DefaultDBName', $username = 'MyUser', $password = 'MyPassword') {
        $this->connect($db, $username, $password); //Auto connect when you create a new database
    }
    /**
     * Connect to a database
     * 
     * @param $db The database to connect to; default is DefaultDBName
     * @param $username The username to connect as; default is MyUser
     * @param $password The password to use when connecting; default is MyPassword
     * @return True/False if the connection was successfull
     */
    public function connect($db = 'DefaultDBName', $username = 'MyUser', $password = 'MyPassword') {
        try {
            $this->db = new PDO('drivername:Server=MYDBSERVER;Database=' . $db, $username, $password); //Create new connection
            $this->connected = true; //We are connected
        }
        catch(PDOException $e) { //Oh no.  An error
            $this->db = false; //Set DB back to false, just in case
            $this->connected = false; //We couldn't connect
            //You can do something with the error message if you wish
        }
        return $this->connected
    }
    /**
     * Prepares a SQL statement for execution
     * 
     * @param $sql The SQL string to be prepared
     * @params $params An optional array of parameters to bind to the statement
     * @return The prepared statement; false if failed
     */
    public function prepare($sql, $params = array()) {
        $stmt = false;
        if($this->connected) { //Are we connected
            if(($stmt = $this->db->prepare($sql)) !== false) { //Prepare the statement
                foreach($params as $param => $value) { //Bind the parameters
                    $stmt->bindValue($param, $value);
                }
            }
            else {
                //You can do something with $stmt->errorInfo();
            }
        }
        return $stmt;
    }
    /**
     * Execute a prepared statement
     * 
     * @param $stmt A PDO statement
     * @param $params An optional array of parameter values
     * @return True/False
     */
    public function execute($stmt, $params = array()) {
        if($this->connected) { //Are we connected
            if(!empty($params)) { //Can't bind an empty array of parameters
                $result = $stmt->execute($params); //Execute with parameters
            }
            else {
                $result = $stmt->execute(); //Execute without parameters
            }
        }
        return $result;
    }
    /**
     * Gets the results from an executed statement
     * 
     * @param $stmt A reference to a PDO statement
     * @return An array of results from the statement
     */
    public function getResults($stmt) {
        $results = array();
        if($stmt !== false) { //Make sure we have a valid statement
            while($row = $stmt->fetch(PDO::FETCH_ASSOC))) { //Get all of the data for the row
                $results[] = $row; //Add the row to the results array
            }
        }
        return $results; //Return the results
    }
    /**
     * Executes a query and returns the results
     * 
     * @param $sql The SQL query
     * @param $parameters An array of parameters
     * @return An array of results or false if execution failed
     */
    public function executeQuery($sql, $params = array()) {
        $results = false;
        if($this->connected) { //Are we connected
            if(($stmt = $this->prepare($sql, $params)) !== false) { //Prepare the statement
                if($this->execute($stmt) !== false) { //Execute the statement
                    $results = $this->getResults($stmt); //Get the result set
                }
            }
        }
        return $results;
    }
    /**
     * Executes a query, but returns no results
     * 
     * @param $sql The SQL query
     * @param $parameters An optional array of paraters
     * @return True/False
     */
    public function executeNonQuery($sql, $params = array()) {
        $success = false;
        if($this->connected) { //Are we connected
            if(($stmt = $this->prepare($sql, $params)) !== false) { //Prepare the statement
                if($this->execute($stmt) !== false) { //Execute the statement
                    $success = true; //Successfully executed
                }
            }
        }
        return $success;
    }
}

EXAMPLES

In each of the examples, assume the following

$sql = "
SELECT 
    Description, 
    WebDesc 
FROM T_AdditionalInfo info JOIN T_Accessories acc ON info.ItemNo = acc.PartNo
WHERE info.Description LIKE :title
";
$parameters = array(':title' => '%abc%');

1) Putting it all together using the new database class

$db = new Database();
if(($stmt = $db->prepare($sql, $parameters)) !== false) {
    if($db->execute($stmt)) {
        $results = $db->getResults($stmt);
        foreach($results as $result) {
            //Do something with this result
        }
    }
}

2) Now I know I said that creating this class would make everything easier by taking away all of the repetitiveness of preparing the statement, binding parameters, executing the statement, and then retrieving the result set. Below is a quick example of how all of the above can be done in one line.

$db = new Database();
if(($results = $db->executeQuery($sql, $parameters)) !== false) {
    foreach($results as $result) {
        //Do something with this result
    }
}

3) But what about the queries I want to use that don't return a result set? Can I still quickly execute a statement without all of the mess? Yes. You can use the executeNonQuery function. Below is an example of that.

$db = new Database();
$sql = 'update table1 set field = :value where id = :id';
$parameters = array(':value' => 'val1', ':id' => '5');
if($db->executeNonQuery($sql, $parameters)) {
    //Yay!  The update was successfull
}

UPDATE

Per my conversation with the OP (see comments), here is an updated version of his query that should get the desired results. The LIMIT 1 portion of the query ensures that only one ItemNo is used which should give only the 8 accessories being looked for rather than all 32 (8 accessories X 4 ItemNo's).

SELECT 
    info.Description, 
    acc.WebDesc 
FROM T_AdditionalInfo info JOIN T_Accessories acc ON info.ItemNo = acc.PartNo 
WHERE info.ItemNo = (SELECT ItemNo FROM T_AdditionalInfo WHERE Description LIKE %abc% LIMIT 1)

The nested select statement is called a subquery. Since you indicated you are using MySQL, here is a reference page that you might find useful. Subqueries are very useful in SQL for things like this as well as using IN clauses SELECT * FROM t1 WHERE id IN (SELECT t1_id FROM t2).

Kyle
  • 4,421
  • 22
  • 32
  • Thanks so much for the detailed tutorial! I am actually now hung up on how to get the data I need with my query. The deal is there are up to 4 different records in `info` table that will match my title `WHERE` clause w/ wildcard. Each of those records will match up with up to 8 different records in `acc` table. Basically there are 4 models/variations in `info` that each have the same 8 accessories in `acc`, but I only want to display the 8 accessories once. I've tried `SELECT DISTINCT` but this only seems to work if I select only one column. acc.Description, but not with info.WebDesc incl. – Mark Jun 21 '13 at 20:24
  • @Mark You are most welcome. This is the kind of information I wish I had when I started with `PDO`, so I figured I would share it. If I understand you correctly, you are looking to only display the `accessories` items? If that is the case, then yes you are correct. You will need to do a `SELECT DISTINCT acc.Description FROM ...` if all you want are the accessories that match the given criteria. If I have misunderstood anything, feel free to share. – Kyle Jun 21 '13 at 21:00
  • The access. and prods. are all in `info` with `WebDesc` and `Description` fields and have a unique `PartNo`. The `acc` table has relationship between access. and prods. (links `PartNo` of prods. to `PartNo` of access.) but has a record for each of the 4 prod. models from `info` (8 access, total of 32 records per prod.). Need to display `acc.Description` (could change use `info.Description` if easier), and `info.WebDesc`, but only for unique acc.Descriptions (total of 8). My query now is giving me `Description` and `WebDesc` for the prod, and not access. Hitting my head against wall-so thankful – Mark Jun 21 '13 at 21:56
  • @Mark Sorry. I was gone for the last 2 days. I'm not 100% sure I understand. What exactly is the output you are expecting to get? You're looking to get just the 8 accessories that are listed for the one product, right? So you would do `SELECT * FROM T_Accessories WHERE PartNo = 1`. Or you could do `SELECT info.Description, acc.WebDesc FROM T_Accessories acc JOIN T_AdditionalInfo info ON acc.ItemNo = info.ItemNo WHERE info.Description LIKE '%abc%'`. – Kyle Jun 24 '13 at 13:27
  • Need to use page title (pulled from WordPress) to find prods. in `info`. There will be up to 4 matches, but I only want to use one record. I will then use the `info.ItemNo` of the prod. to pull all matching `acc.ItemNo` (each record in `acc` has an ItemNo for a prod. matching to a prod. from `info` and its own acc.PartNo which matches to a record for the acces. in the `info` table info.PartNo=acc.ItemNo). I then need to pull the record from `info` for those acces. and display info.Description and info.WebDesc. I don't see how I can do this with one JOINed query. Thanks for any more advice! – Mark Jun 24 '13 at 16:14
  • @Mark Is there a different `ItemNo` in `info` for each of the 4 matches and how is this field structured (incrementing int, string value, is it unique or the same)? Thanks for the clarification. – Kyle Jun 24 '13 at 16:38
  • Yes, there is a different ItemNo for each match. This is a unique string (VARCHAR). Thanks again so much for helping me figure this out! – Mark Jun 24 '13 at 16:42
  • @Mark I see in your original question that you always want the `last` ItemNo from the select. Does it have to be the last ItemNo or can it be any ItemNo? – Kyle Jun 24 '13 at 17:58
  • Kyle- it actually doesn't matter- it can be any of the record's `ItemNo` - putting it in a while loop and using the last one was just the easiest way to get one `ItemNo`. – Mark Jun 24 '13 at 21:31
  • @Mark See my update at the bottom of the post. If I understand correctly, that should give you the result you are looking for. – Kyle Jun 25 '13 at 13:59
  • Awesome! That works great- had to change a couple of column names in the statement (due to my not explaining it quite right), but it's now working as it should. I didn't realize you could nest a `SELECT` within a `SELECT` , but that works perfectly. Thanks again so much for taking the time to help. Hopefully I'll get to the point where I can do the same for others! – Mark Jun 25 '13 at 17:22
  • @Mark I'm glad I was able to help you and that it worked out. You should consider marking my post as the answer so that it can help others if they find your question. I posted one more update to the answer to give you a helpful reference from MySQL. Those nested `SELECT` statements can be very useful. They will work in other types of SQL as well in case you ever work on a project that doesn't use MySQL. – Kyle Jun 25 '13 at 17:32