MySQL + PHP:避免由于连接表有两个结果而导致重复结果

时间:2019-05-01 07:17:37

标签: php mysql

我一直在努力处理以下情况:

第一种方法:

SELECT * FROM vinyl_tracks vt
INNER JOIN vinyl_keys vk ON vt.mKeyID = vk.mKeyID
INNER JOIN vinyl_tracks_styles vts ON vt.trackID = vts.trackID
INNER JOIN vinyl_styles vs ON vts.styleID = vs.styleID
WHERE vinylID = :vinylID

对于某些曲目,表vts将包含两种或更多种样式(表仅在单独的行上包含trackIDstyleID。这将导致整个结果重复。我已经尝试过DISTINCT和各种GROUP BY,包括DISTINCTGROUP BY,这确实会为每个轨道返回一个结果,但是第二个styleID是丢失。

这里是此查询的var_dump,没有任何GROUP BY

array (size=4)
0 => 
  array (size=10)
    'trackID' => string '868' (length=3)
    'vinylID' => string '249' (length=3)
    'side' => string 'A' (length=1)
    'trackArtist' => string '' (length=0)
    'trackTitle' => string 'Waiting (John Creamer & Stephane K Remix)' (length=41)
    'notes' => string '130 bpm' (length=7)
    'mKeyID' => string '1' (length=1)
    'mKey' => string 'A major' (length=7)
    'Camelot' => string '11B' (length=3)
    'styleID' => string '8' (length=1)
1 => 
  array (size=10)
    'trackID' => string '868' (length=3)
    'vinylID' => string '249' (length=3)
    'side' => string 'A' (length=1)
    'trackArtist' => string '' (length=0)
    'trackTitle' => string 'Waiting (John Creamer & Stephane K Remix)' (length=41)
    'notes' => string '130 bpm' (length=7)
    'mKeyID' => string '1' (length=1)
    'mKey' => string 'A major' (length=7)
    'Camelot' => string '11B' (length=3)
    'styleID' => string '17' (length=2)
2 => 
  array (size=10)
    'trackID' => string '869' (length=3)
    'vinylID' => string '249' (length=3)
    'side' => string 'B' (length=1)
    'trackArtist' => string '' (length=0)
    'trackTitle' => string 'Waiting (Jay Welsh (Black Ice) Remix)' (length=37)
    'notes' => string '135 bpm' (length=7)
    'mKeyID' => string '1' (length=1)
    'mKey' => string 'A major' (length=7)
    'Camelot' => string '11B' (length=3)
    'styleID' => string '17' (length=2)
3 => 
  array (size=10)
    'trackID' => string '869' (length=3)
    'vinylID' => string '249' (length=3)
    'side' => string 'B' (length=1)
    'trackArtist' => string '' (length=0)
    'trackTitle' => string 'Waiting (Jay Welsh (Black Ice) Remix)' (length=37)
    'notes' => string '135 bpm' (length=7)
    'mKeyID' => string '1' (length=1)
    'mKey' => string 'A major' (length=7)
    'Camelot' => string '11B' (length=3)
    'styleID' => string '8' (length=1)

然后我运行另一个查询以获取所有样式,以便填充多个选择下拉列表。用foreach循环创建下拉菜单和截图的样子是这样的-两个轨道(分别具有两种样式)显示为四个轨道:

$allStylesQuery = $db->prepare("SELECT * FROM vinyl_styles ORDER BY styleID ASC");
$allStylesQuery->execute();
$styles = $allStylesQuery->fetchAll(PDO::FETCH_ASSOC);
$stylesSelector = "<select multiple=\"multiple\" class=\"form-control form-control-sm stylesSelector\" name=\"styleID[".$trow['trackID']."][]\"><option> &ndash; </option>";
foreach($styles as $style) {
    if(isset($trow['styleID']) && ($trow['styleID'] == $style['styleID'])) {
        $stylesSelector .= "<option selected=\"selected\" value=\"".$style['styleID']."\">".$style['styleName']."</option>";
    }
    else {
        $stylesSelector .= "<option value=\"".$style['styleID']."\">".$style['styleName']."</option>";
    }
}
$stylesSelector .= "</select>";

Results from first approach – two tracks shown as four

替代方法: 我还尝试使用两个查询来实现相同目的:

SELECT * FROM vinyl_tracks vt
INNER JOIN vinyl_keys vk ON vt.mKeyID = vk.mKeyID
WHERE vinylID = :vinylID

然后,在foreach循环中,从第一个查询获得以下查询的结果:

SELECT * FROM vinyl_tracks_styles vts
RIGHT JOIN vinyl_styles vs ON vts.styleID = vs.styleID
WHERE vts.trackID = :trackID

这种替代方法可以产生我想要的结果,例如:

array (size=2)
  0 => 
    array (size=3)
    'trackID' => string '868' (length=3)
    'styleID' => string '8' (length=1)
    'styleName' => string 'progressive house' (length=17)
 1 => 
   array (size=3)
    'trackID' => string '868' (length=3)
    'styleID' => string '17' (length=2)
    'styleName' => string 'progressive trance' (length=18)

但是随后我无法按我需要的方式显示结果,即: one row per track, all styles shown in a multiple select

为了在选择下拉列表中列出所有可能的样式,我需要再运行一个查询(我知道WHERE的上述条件会阻止显示所有样式名称和ID,尽管RIGHT JOIN是这样) :

SELECT * FROM vinyl_styles ORDER BY styleID ASC

突出显示我发现的选择选项(样式)的唯一方法是将两个foreach循环相互嵌套,这当然会导致重复的结果:

$trackStylesQuery = $db->prepare("SELECT * FROM vinyl_tracks_styles vts
                        RIGHT JOIN vinyl_styles vs ON vts.styleID = vs.styleID
                        WHERE vts.trackID = :trackID");
$trackStylesQuery->bindParam(':trackID', $trow['trackID'], PDO::PARAM_INT);
$trackStylesQuery->execute();
$trackStyles = $trackStylesQuery->fetchAll(PDO::FETCH_ASSOC);
//var_dump($trackStyles);

$allStylesQuery = $db->prepare("SELECT * FROM vinyl_styles ORDER BY styleID ASC");
$allStylesQuery->execute();
$styles = $allStylesQuery->fetchAll(PDO::FETCH_ASSOC);
$stylesSelector = "<select multiple=\"multiple\" class=\"form-control form-control-sm stylesSelector\" name=\"styleID[".$trow['trackID']."][]\"><option> &ndash; </option>";
foreach($styles as $style) {
    foreach($trackStyles as $trackStyle) {
        if(isset($trackStyle['styleID']) && ($trackStyle['styleID'] == $style['styleID'])) {
            $stylesSelector .= "<option selected=\"selected\" value=\"".$style['styleID']."\">".$style['styleName']."</option>";
        }
        else {
            $stylesSelector .= "<option value=\"".$style['styleID']."\">".$style['styleName']."</option>";
        }
    }
}
$stylesSelector .= "</select>";

Styles repeated twice in select dropdown, the second style is highlighted too

如果我坚持上面的第一个查询变量,那么整行输入将重复两次,因此对于这两条轨迹,我将获得四行。

我真的没有主意,请帮忙。

更新

使用@Ultimater建议的较少查询的方法,现在我已经有了我需要的所有细节的情况,唯一的问题是样式列表在轨道的每个样式的样式下拉列表中重复出现已。如果为轨道仅添加一种样式或没有添加任何样式,则没有问题。在下面发布我的完整代码:

$tracksQuery = $db->prepare("SELECT vt.trackID, vt.vinylID, vt.side, vt.trackArtist, vt.trackTitle, vt.notes, vt.mKeyID, vk.mKey, vk.Camelot, 
                            GROUP_CONCAT(vts.styleID SEPARATOR ',') AS 'styleIDs',
                            GROUP_CONCAT(vs.styleName SEPARATOR ',') AS 'styleNames'
                            FROM vinyl_tracks vt
                            INNER JOIN vinyl_keys vk ON vt.mKeyID = vk.mKeyID
                            LEFT JOIN vinyl_tracks_styles vts ON vt.trackID = vts.trackID
                            LEFT JOIN vinyl_styles vs ON vts.styleID = vs.styleID
                            WHERE vt.vinylID = :vinylID
                            GROUP BY vt.trackID");
$tracksQuery->bindParam(':vinylID', $vinylID);
$tracksQuery->execute();
$tracks = $tracksQuery->fetchAll(\PDO::FETCH_ASSOC);
//var_dump($tracks);

// fetch all styles to use in the dropdown
$allStylesQuery = $db->prepare("SELECT * FROM vinyl_styles ORDER BY styleID ASC");
$allStylesQuery->execute();
$styles = $allStylesQuery->fetchAll(PDO::FETCH_ASSOC);

// build a dropdown menu for each of the tracks
// which contains all styles and where the current track's styles are selected
$stylesSelector = "";

foreach($tracks as $track) {

    // start building the selector
    $stylesSelector .= "<select multiple=\"multiple\" class=\"form-control form-control-sm\" name=\"styleID[".$track['trackID']."][]\"><option> &ndash; </option>";

            // check if any styles have been added for this track
    if(!empty($track['styleIDs'])) {

        // check if StyleIDs and StyleNames results contain a comma
        // which means more than one style has been added for that track
        if((strpos($track['styleIDs'], ',')) && (strpos($track['styleNames'], ','))) {

            // separate trackStyleIDs and trackStyleNames
            $trackStyleIDs = explode(',', $track['styleIDs']);
            $trackStyleNames = explode(',', $track['styleNames']);

            // now combine them in one array where styleID is the key and styleName is the value
            $styleIDs_and_Names = array_combine($trackStyleIDs, $trackStyleNames);

            // for each of the styleID => styleName pairs in the array
            // check against all available styles if selected
            foreach($styleIDs_and_Names as $styleID => $styleName) {

                // iterate over all available styles
                foreach($styles as $style) {

                    // if there's a match, add "selected" to the option
                    if($styleID == $style['styleID']) {
                        $stylesSelector .= "<option selected=\"selected\" value=\"".$style['styleID']."\">".$style['styleName']."</option>";
                    }
                    else {
                        $stylesSelector .= "<option value=\"".$style['styleID']."\">".$style['styleName']."</option>";
                    }
                }
            }
        }
        // only one style has been added for this track
        else {
            // iterate over all available styles
            foreach($styles as $style) {

                // if there's a match, add "selected" to the option
                if($track['styleIDs'] == $style['styleID']) {
                    $stylesSelector .= "<option selected=\"selected\" value=\"".$style['styleID']."\">".$style['styleName']."</option>";
                }
                else {
                    $stylesSelector .= "<option value=\"".$style['styleID']."\">".$style['styleName']."</option>";
                }
            }
        }
    }

    // if no styles have been added for this track
    else {
        // iterate all available styles
        foreach($styles as $style) {
            $stylesSelector .= "<option value=\"".$style['styleID']."\">".$style['styleName']."</option>";
        }
    }
    $stylesSelector .= "</select>";
}

以下屏幕截图显示了以上代码生成的实际页面部分。对于失踪的专家来说,一定是显而易见的事情,而且很容易,但是我仍然看不到。 styles repeated inside selector for each style

2 个答案:

答案 0 :(得分:1)

您可以使用GROUP_CONCAT将结果缩小到两个维度,这样就不会仅因为填充样式下拉列表而获得重复的相册。 取而代之的是,您将样式合并在一起,并使用换行符使其变得漂亮。

您的查询最终看起来像这样:

SELECT
  trackID,
  vinylID,
  side,
  trackArtist,
  trackTitle,
  notes,
  GROUP_CONCAT(mKeyID SEPARATOR '\n') as 'mKeyIDs',
  GROUP_CONCAT(mKey SEPARATOR '\n') as 'mKeys',
  Camelot,
  GROUP_CONCAT(styleID SEPARATOR '\n') AS 'styleIDs',
  GROUP_CONCAT(styleName SEPARATOR '\n') AS 'styleNames'
FROM
  vinyl_tracks vt
INNER JOIN
  vinyl_keys vk ON vt.mKeyID = vk.mKeyID
INNER JOIN
  vinyl_tracks_styles vts ON vt.trackID = vts.trackID
INNER JOIN
  vinyl_styles vs ON vts.styleID = vs.styleID
GROUP BY
  vt.trackID
WHERE
  vinylID = :vinylID

我在这里按轨道ID进行分组,因为这似乎是您用来确定某件商品是否重复的标准。从那里开始,我使用带有新行分隔符的GROUP_CONCAT在同一单元格中显示受此组影响的所有样式。您的PHP可以引用此单元格,并在换行符上爆炸以获取填充下拉列表所需的内容。

确保此查询首先直接在数据库上运行,以查看是否需要对其进行调整。

JSON方法:

或者,如果您的MySQL版本支持JSON_ARRAYAGG,则可以使用它代替GROUP_CONCAT来完成功能上等效的行为,尽管更为简洁:

SELECT
  trackID,
  vinylID,
  side,
  trackArtist,
  trackTitle,
  notes,
  JSON_ARRAYAGG(mKeyID) AS 'mKeyIDs',
  JSON_ARRAYAGG(mKey) AS 'mKeys',
  Camelot,
  JSON_ARRAYAGG(styleID) AS 'styleIDs',
  JSON_ARRAYAGG(styleName) AS 'styleNames'
FROM
  vinyl_tracks vt
INNER JOIN
  vinyl_keys vk ON vt.mKeyID = vk.mKeyID
INNER JOIN
  vinyl_tracks_styles vts ON vt.trackID = vts.trackID
INNER JOIN
  vinyl_styles vs ON vts.styleID = vs.styleID
GROUP BY
  vt.trackID
WHERE
  vinylID = :vinylID

然后在您的PHP中,您只需引用$row['styleIDs']$row['styleNames']即可为“样式”下拉列表中使用的每个选项生成键=>值对。

对于音乐“键”下拉选项,请参考$row['mKeyID']$row['mKeys']为“键”下拉菜单中使用的每个选项生成键=>值对。

更新

我在本地进行了设置,并发现OP遇到了问题。我的查询很好。但是,当试图显示下拉列表时,OP的PHP循环逻辑遇到了一个错误。更具体地说,以下逻辑是错误的:

        foreach($styles as $style) {

            // if there's a match, add "selected" to the option
            if($track['styleIDs'] == $style['styleID']) {
                $stylesSelector .= "<option selected=\"selected\" value=\"".$style['styleID']."\">".$style['styleName']."</option>";
            }
            else {
                $stylesSelector .= "<option value=\"".$style['styleID']."\">".$style['styleName']."</option>";
            }
        }
    }

现在,我了解OP想要做什么,让我们重写整个混乱。我已经重构了代码。我也在本地进行了测试:

$tracksQuery = $db->prepare("SELECT vt.trackID, vt.vinylID, vt.side, vt.trackArtist, vt.trackTitle, vt.notes, vt.mKeyID, vk.mKey, vk.Camelot, 
                            GROUP_CONCAT(vts.styleID SEPARATOR ',') AS 'styleIDs'
                            FROM vinyl_tracks vt
                            INNER JOIN vinyl_keys vk ON vt.mKeyID = vk.mKeyID
                            LEFT JOIN vinyl_tracks_styles vts ON vt.trackID = vts.trackID
                            WHERE vt.vinylID = :vinylID
                            GROUP BY vt.trackID");
$tracksQuery->bindParam(':vinylID', $vinylID);
$tracksQuery->execute();
$tracks = $tracksQuery->fetchAll(\PDO::FETCH_ASSOC);

$allStylesQuery = $db->prepare("SELECT * FROM vinyl_styles ORDER BY styleID ASC");
$allStylesQuery->execute();
$styles = $allStylesQuery->fetchAll(PDO::FETCH_ASSOC);


$stylesSelector = "";

foreach($tracks as $track)
{

    $stylesSelector .= "<select multiple=\"multiple\" class=\"form-control form-control-sm\" name=\"styleID[".$track['trackID']."][]\"><option> &ndash; </option>";
    $trackStyleIDs = explode(',', $track['styleIDs']);
    foreach($styles as $style)
    {
        $optionValue = $style['styleID'];
        $optionText = $style['styleName'];
        $optionSelected = in_array($optionValue, $trackStyleIDs) ? ' selected="selected"' : '';
        $stylesSelector .= sprintf('<option value="%s"%s>%s</option>', $optionValue,$optionSelected,$optionText);
    }

    $stylesSelector .= "</select>";
}

答案 1 :(得分:0)

正如我在评论中所说,您必须分两步细分该过程。

  • 第一步只是获取轨道的基本信息。
  • 第二步是在单独的数组中获取轨道的所有样式

分别存储样式之后,现在将有一个像这样的数组,

[
    track_id_1 => [ style_id_1, style_id_2, ]
    track_id_2 => [ style_id_x, style_id_y, ]
]

意思是如果您知道轨道ID和样式ID,则可以使用in_array测试样式是否附加到当前轨道:

<?php
     if (in_array($style_id, $styles_per_track[$track_id])) {
         echo 'Style '.$style_id.' is attached to track '.$track_id;
     }

以下是该解决方案的模型。
请注意,我没有测试任何代码,这只是为了给您一个分离数据的想法

    <?php
        // Step 1: Fetch all tracks w/out worrying about styles
        $tracks_stmt = $db->prepare('SELECT * FROM vinyl_tracks vt WHERE vinylID=:vinylID');
        $track_stmt->execute([':vinylID' => $vinylID, ]);

        $tracks = $track_stmt->fetchAll(\PDO::FETCH_ASSOC);

        // Step 2: Loop each track and store the styles accordingly
        $styles_per_track = [];
        $styles_per_track_stmt = $db->prepare('SELECT * FROM vinyl_tracks_styles vts WHERE trackId=:trackId')

        foreach($tracks as $track) {
            $vinyl_tracks_styles[$track['trackID']] = [];
            $styles_per_track_stmt->execute($track['trackID']);
            $temp = $styles_per_track_stmt->fetchAll(\PDO::FETCH_ASSOC);
            foreach($temp as $style_per_track) $vinyl_tracks_styles[$track['trackID']] = $style_per_track['styleID'];
        }

        //fetch styles
        /**
        ...
        ...
        ...
        **/
        foreach($tracks as $track) {
    ?>
            <select name="track[<?=$track['trackID'];?>" multiple>
    <?php
            foreach($styles as $style) {
    ?>
                <option value="<?= $style['styleID']; ?>" <?php if(in_array($style['styleID'], $styles_per_track[$track['trackID']])) { echo ' selected'; } ?>><?= $style['styleName']; ?></option>
    <?php
            }
    ?>
        </select>
    <?php
    }
    ?>