Laravel: 使用 eloquent 连接从第 2 个 table 开始仅限制 1 条记录

Laravel: Limit only 1 record from the 2nd table using eloquent join

我有两个 table colorscolor_translations 结构如下:

颜色

id created_at updated_at
1 2021-08-25 NULL
2 2021-09-01 NULL

color_translations

id color_id ​ locale title url
​1 1 en blue blue-cat
​2 1 fr bleu bleu-cat
​3 1 de blau blau-cat
​4 2 de rot rot-cat
​5 2 fr rouge rouge-cat

我只想将 colors table 与 color_translations table 中的一条记录合并,该记录将基于 locale 列,它会先查看 en locale 记录,如果不存在则从 fr locale ,然后 de locale

id created_at updated_at locale title url
1 2021-08-25 NULL en blue blue-cat
2 2021-09-01 NULL fr rouge rouge-cat

我尝试使用我的 eloquent 模型这样做:

 $this->colorsModel
    ->select( 
        [
            'colors.*',
            'color_translations.locale as locale', 
            'color_translations.title as title',
            'color_translations.url as url'
        ]   
    )
    ->leftJoin ('color_translations', function ($query) {
        $query->on('colors.id', '=', 'color_translations.color_id')
         ->orderByRaw('FIELD(color_translations.locale, "en", "fr", "de)')
         ->limit(1);
    })->get();

使用上面的代码而不是 2 条记录我从 color_translations table

中获取所有 5 条记录

我不太了解laravel但查询应该如下所示:

(使用@Bill Karwin here 描述的最佳性能技术)

SELECT t1.*, t2.locale, t2.title, t2.url
FROM colors t1
         LEFT OUTER JOIN color_translation t2 ON t1.id = t2.color_id
         LEFT OUTER JOIN color_translation t3 ON t2.color_id = t3.color_id
                             AND FIELD(t2.locale, 'en', 'fr', 'de') > FIELD(t3.locale, 'en', 'fr', 'de')
WHERE t3.id IS NULL;

注意如果您以后添加任何新的语言环境,该语言环境将具有最高优先级,如 FIELD() returns 0 对于未指定的值。我建议您确保每次在您的应用程序中 运行 此查询。


我努力写在 laravel:

$this->colorsModel
    ->select( 
        [
            'colors.*',
            't2.locale as locale', 
            't2.title as title',
            't2.url as url'
        ]   
    )
    ->leftJoin ('color_translations AS t2', function ($query) {
        $query->on('colors.id', '=', 't2.color_id')
    })->leftJoin ('color_translations AS t3', function ($query) {
        $query->on('t2.color_id', '=', 't3.color_id');
        $query->on(DB::raw('FIELD(t2.locale, \'en\', \'fr\', \'de\')'), '>', DB::raw('FIELD(t3.locale, \'en\', \'fr\', \'de\')'));
    })->whereNull('t3.id')->get();

您可以按原始查询顺序使用案例。这将从选项中选择任何一个。

$this->colorsModel
                    ->select(
                        [
                            'colors.*',
                            'color_translations.locale as locale',
                            'color_translations.title as title',
                            'color_translations.url as url'
                        ]
                    )
                    ->leftJoin ('color_translations', function ($query) {
                        $query->on('colors.id', '=', 'color_translations.color_id')
                            ->orderByRaw('FIELD(color_translations.locale, CASE  WHEN (color_translations.locale  = "en") THEN en 
                            WHEN (color_translations.locale  = "fr")  THEN fr 
                            ELSE de )')
                            ->limit(1);
                    })->get();

如果你想用 SQL/Eloquent 做到这一点而不影响性能,你确实可以通过查询所有翻译来使用 orderBy:

$locales_ordered = ['en', 'fr', 'nl'];
array_walk($locales_ordered, function(&$x) {$x = "'$x'";});
$query = Colors::with([ 'translations' => function ($q) use ($locales_ordered) {
     $q->orderByRaw('FIELD(colors_translations.locale,' . implode(',', $locales_ordered) . ') ASC')->first();
        } ])
  ->first();
$translation = $query->first()->translations->first();

这将获得第一个关联的翻译,按您的自定义语言环境列表顺序排序,因此语言环境回退。假设您有适当的模型,并且您的模型和翻译后的模型之间存在 translations 关系 (color hasMany color_translations)。

array_walk 是使用字符串将 orderByRawFIELD 的值用双引号括起来。

您可以将其包装在 Color 模型的作用域函数中以便轻松查询:

public function scopeWithFallbackTranslation($query)
{
    $locales_ordered = ['en', 'fr', 'nl']; //todo get from helper class, headers, ssession etc..
    array_walk($locales_ordered, function(&$x) {$x = "'$x'";});
    return $query
        ->with([ 'translations' => function ($q) use ($locales_ordered) {
            $q
                ->orderByRaw('FIELD(email_notification_senders_translations.locale,' . implode(',', $locales_ordered) . ') ASC')
                ->first();
        } ]);
}

然后调用它:Color::withFallbackTranslation()->where(stuff)->get()

请注意,->translations 仍将是一个集合,因此需要 ->first()(和空检查)。例如,您可以使用 Color 模型上的自定义属性获取单个对象。