问题
I have have the following tables
Meta table
id | recording_id | meta_key | meta_value
Recording Table
id | recording
The recording_id
on the meta_table
is a foreign key that points towards a recording on the recording table.
Now I have a associative array $metas
from my $_GET
with meta keys and values and I want to SELECT
the recordings that match all the meta keys and values. How would I do that?
This is what I have so far. How do I add an array into my bind param? And am I on the right track?
I just cannot wrap my head around this.
function retrieveRecordingsByMetaData($connection, $config, $metas, $limit)
{
$where = "";
for ($i = 0; $i < count($metas); $i++) {
$where .= "meta_key=? AND meta_value=? AND ";
}
$where = preg_replace('/ AND $/', '', $where);
$sql = "SELECT recording_id
FROM $config->meta_table
WHERE " . $where . "
INNER JOIN $config->recording_table
ON $config->meta_table.id=$config->recording_table.id
LIMIT ?";
$stmt = $connection->prepare($sql);
foreach ($metas as $key => $value) {
$stmt->bind_param("s", $key);
$stmt->bind_param("s", $value);
}
$stmt->bind_param("i", $limit);
if (!$stmt->execute()) {
echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error . " \r\n";
die();
}
$result = $stmt->get_result();
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
echo "recording found";
//$recording = $row["recording"];
//$hex = bin2hex($recording);
//echo ("response=recording" . $id . "=" . $hex . "\r\n");
}
} else {
echo "0 results \r\n";
}
}
回答1:
To write this query just in SQL, you would write something like
SELECT r.id
FROM recording r
JOIN meta m ON m.recording_id = r.id
AND (m.meta_key = 'key1' AND m.meta_value = 'value1'
OR m.meta_key = 'key2' AND m.meta_value = 'value2'
OR m.meta_key = 'key3' AND m.meta_value = 'value3'
...)
GROUP BY r.id
HAVING COUNT(*) = <count of all key/value pairs>
LIMIT 10
The HAVING
clause is what asserts that a recording has all of the specified meta key and value pairs.
To translate that into your PHP code, you need to build up your $where
clause in a similar manner; I prefer using an array and imploding to save worrying about trailing AND
and the like. At the same time as we are building that clause, we can build the inputs to bind_param
:
$join = array();
$params = array();
$types = '';
foreach ($metas as $key => $value) {
$join[] = 'm.meta_key=? AND m.meta_value=?';
$params[] = $key;
$params[] = $value;
$types .= 'ss';
}
// add the parameter for the `HAVING` check
$params[] = count($metas);
$types .= 'i';
// add the limit
$params[] = $limit;
$types .= 'i';
// make the query string
$sql = "SELECT recording_id
FROM {$config->recording_table} r
JOIN {$config->meta_table} m ON m.recording_id = r.id
AND (" . implode(' OR ', $join) . ")
GROUP BY r.id
HAVING COUNT(*) = ?
LIMIT ?";
$stmt = $connection->prepare($sql);
if (!$stmt) {
echo "Prepare failed: " . $conn->error . "\n";
die();
}
$stmt->bind_param($types, ...$params);
if (!$stmt->execute()) {
echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error . " \r\n";
die();
}
A demo of the query formation and params generation can be found here.
来源:https://stackoverflow.com/questions/59186340/select-a-recording-based-on-the-meta