如何在laravel视图或控制器中格式化连接查询结果?

时间:2017-07-19 12:17:10

标签: php mysql laravel join

我有五个表studentsgradessubjectstermsscores。我正在thes表上执行内部联接以返回结果。这是我的架构的样子:

学生表:

students
--------
id *
name
class_id (fk)

主题表:

subjects
--------
id *
name

班级表:

classes
--------
id *
name

条款表:

terms
--------
id *
name

得分表:

scores
---------------
id *
student_id (fk)
subject_id (fk)
class_id (fk)
term_id (fk)
score

我的laravel查询:

$scores = \DB::table('scores')
        ->join('students', 'students.id', '=', 'scores.student_id')
        ->join('subjects', 'subjects.id', '=', 'scores.subject_id')
        ->join('grades', 'grades.id', '=', 'scores.grade_id')
        ->join('terms', 'terms.id', '=', 'scores.term_id')
        ->select('students.first_name', 'students.surname', 'subjects.name as subject', 'grades.name as grade', 'terms.name as term', 'score')
        ->where('students.id', 1)
        ->whereBetween('scores.term_id', [1, 3])
        ->get();
当我死掉并转储它时,

查询返回结果:

enter image description here

毫无疑问,查询返回了正确的结果,但问题是我希望结果出现在我的html表中,如下所示:

enter image description here

这是基于我现在在控制器和视图中的代码显示的方式。

控制器:

public function index()
{
    //
    $scores = \DB::table('scores')
        ->join('students', 'students.id', '=', 'scores.student_id')
        ->join('subjects', 'subjects.id', '=', 'scores.subject_id')
        ->join('grades', 'grades.id', '=', 'scores.grade_id')
        ->join('terms', 'terms.id', '=', 'scores.term_id')
        ->select('students.first_name', 'students.surname', 'subjects.name as subject', 'grades.name as grade', 'terms.name as term', 'score')
        ->where('students.id', 1)
        ->whereBetween('scores.term_id', [1, 3])
        ->get();


    // finding details of the student based on id pased
    $student = Student::findOrFail(1);

    // getting the name of the student
    $name = $student->first_name.' '.$student->surname;

    // getting the class or grade of the student (grade 12 or grade 11)
    $grade = $student->grade->name;

    // getting the current date
    $date = Score::date();

    return view('scores.home', compact('scores', 'name', 'date', 'grade'));

视图:

<table class="table table-bordered table-condensed table-striped">
    <thead>
        <tr>
            <th scope="row">Name</th>
            <td colspan="4">{{$name}}</td>
        </tr>
        <tr>
            <th scope="row">Class</th>
            <td colspan="2">{{$grade}}</td>

            <th scope="row">Date</th>
            <td>{{$date->toFormattedDateString()}}</td>
        </tr>
        <tr>
            <th class="text-center">Subject</th>
            @foreach($scores as $score)
                <th class="text-center">{{$score->term}}</th>
            @endforeach
        </tr>
    </thead>
    <tbody>
        @foreach($scores as $score)
            <tr>
                <td>{{$score->subject}}</td>
                <td>{{$score->score}}</td>
            </tr>
        @endforeach
    </tbody>
</table>

结果:

enter image description here

如上面的结果所示,术语名称1st Period正在重复,我该如何避免?如何在视图或控制器中重构我的查询或代码以获得我想要的结果?

3 个答案:

答案 0 :(得分:4)

首先,我会稍微简化一下查询,只选择你还不知道的数据。 没有必要在每一行返回学生姓名和成绩,因为它们总是一样的。

$student = Student::findOrFail(1);

$scores = \DB::table('scores')
    ->join('subjects', 'subjects.id', '=', 'scores.subject_id')
    ->join('terms', 'terms.id', '=', 'scores.term_id')
    ->select('subjects.name as subject', 'terms.name as term', 'score')
    ->where('scores.student_id', $student->id)
    ->whereBetween('scores.term_id', [1, 3])
    ->get();

您将获得与您的结果类似的以下集合:

[
    0 => (object)[
        'subject' => 'Mathematics',
        'term' => '1st Period',
        'score' => 99
    ],
    1 => (object)[
        'subject' => 'Biology',
        'term' => '2nd Period',
        'score' => 99
    ],
    2 => (object)[
        'subject' => 'Biology',
        'term' => '3rd Period',
        'score' => 79
    ]
]

现在将其转换为嵌套结构:

$scores = $scores->groupBy('subject')->map(function($item){
    return $item->keyBy('term')->map(function($item){
        return $item->score;
    });
});

您将获得以下收藏:

    [
        'Mathematics' => [
            '1st Period' => 99,
        ],
        'Biology' => [
            '2nd Period' => 99,
            '3nd Period' => 79
        ]
    ]

但这不是表结构 - 缺少一些术语。 所以你需要填写缺少的条款,因为你不想在你的视图中这样做。 我会创建一个空表结构并将数据填入其中:

$terms = Term::whereBetween('id', [1, 3])->pluck('name');
// returns: ['1st Period', '2nd Period', '3rd Period']

使用空分数初始化表格:

$scoreTable = [];
foreach ($scores->keys() as $subject){
    $scoreTable[$subject] = [];
    foreach ($terms as $term){
        $scoreTable[$subject][$term] = '';
    }
}

在表格中填写给定分数:

foreach ($scores as $subject => $row){
    foreach($row as $term => $score){
        $scoreTable[$subject][$term] = $score;
    }
}

现在“表格”将如下所示:

    [
        'Mathematics' => [
            '1st Period' => 99,
            '2nd Period' => '',
            '3nd Period' => '',
        ],
        'Biology' => [
            '1st Period' => '',
            '2nd Period' => 99,
            '3nd Period' => 79,
        ],
    ]

将其传递给您的视图,然后像这样渲染表格:

<table>
    <tr>
        <th>Subject</th>
        @foreach($terms as $term)
            <th>{{$term}}</th>
        @endforeach
    </tr>
    @foreach($scoreTable as $subject => $scores)
        <tr>
            <td>{{$subject}}</td>
            @foreach($terms as $term)
                <td>{{$scores[$term]}}</td>
            @endforeach
        </tr>
    @endforeach
</table>

您将获得以下HTML代码:

<table>
    <tr>
        <th>Subject</th>
        <th>1st Period</th>
        <th>2nd Period</th>
        <th>3rd Period</th>
    </tr>
    <tr>
        <td>Mathematics</td>
        <td>99</td>
        <td></td>
        <td></td>
    </tr>
    <tr>
        <td>Biology</td>
        <td></td>
        <td>99</td>
        <td>79</td>
    </tr>
</table>

更新

获取$scoreTable的较短方式可能是

$terms = Term::whereBetween('id', [1, 3])->pluck('name');

$initRow = $terms
    ->keyBy(function($term){ return $term; })
    ->map(function(){ return ''; });

$scoreTable = $scores
    ->groupBy('subject')
    ->map(function($subject) use($initRow){
        $row = $subject
            ->keyBy('term')
            ->map(function($term) use($initRow){
                return $term->score;
            });
        return $initRow->merge($row);
    });

但它似乎不太可读。也可能有一个或另一个collection函数将替换map函数并使事情变得更容易。但我不知道所有这些。

更新2:

这是另一种更短的方式,但在较大的数据集上可能会很慢,因为每个表格单元都会调用两次昂贵的where()函数。

$scoreTable = [];
foreach ($scores->pluck('subject')->unique() as $subject){
    foreach ($scores->pluck('term')->unique() as $term) {
        $scoreTable[$subject][$term] = $scores
            ->where('subject', $subject)
            ->where('term', $term)
            ->pluck('score')
            ->first();
    }
}

更新3:

最后,我提出了以下解决方案,其中(我认为)是最易读/最简单快速的解决方案。

$subjects = $scores->pluck('subject')->unique(); // ['Mathematics', 'Biology']
$terms    = $scores->pluck('term')->unique(); // ['1st Period', '2nd Period', '3rd Period']

$scoreTable = [];
foreach ($subjects as $subject) {
    foreach ($terms as $term) {
        $scoreTable[$subject][$term] = '';
    }
}

foreach ($scores as $row) {
    $scoreTable[$row->subject][$row->term] = $row->score;
}

return view('scores.home', compact('scoreTable', 'terms', 'name', 'date', 'grade'));

前两行将从查询结果中提取唯一的主题和术语。 (有关您在pluck()中找到的unique()laravel docs方法的更多信息。)然后在以下嵌套循环中使用它们来生成一个表空结构(主题✕条款)值。在下一个循环中,查询结果中的分数将填入“表”中。

答案 1 :(得分:0)

在Laravel,我们有Laravel雄辩,你只需要定义关系,你就可以了。 在你的分数模型中,假设你有一个,只需定义关系

public function student(){
   return $this->belongsTo(student::class, 'student_id', 'id');
}

在控制器或路线中,根据您的工作地点,只需拨打分数

$scores = Score::all();
return view('scores.home', compact('scores'));

当你用刀片呼叫学生时,你只需写{{$score->student->name}}

然后对所有其他项目(即成绩,条款等)执行相同操作。

答案 2 :(得分:0)

我会通过这样的方式收集与他的主题配对的分数:

$scores_paired = collect();

foreach($student->subjects as $subject){
   $scores_paired->push($scores->where('subject', $subject->name)->sortBy('term','ASC'));
}

然后将此新集合传递给视图,以便您可以在视图中循环$scores_paired