【问题标题】:How to select items from a table based on one value if another value does not exists? (eloquent/sql)如果另一个值不存在,如何根据一个值从表中选择项目? (雄辩的/sql)
【发布时间】:2016-09-10 07:41:21
【问题描述】:

我有一张表格,用于为我的网站存储不同的文本块。我目前正在使用页面的 slug 和首选语言为每个页面选择正确的块。当首选语言不可用时,我想在备用语言中选择相同的文本块(具有相同的标题)。

方块表

columns:
| id | slug | title | language | content |

entries:
| 1 | home | first | en | the first block |
| 2 | home | first | nl | het eerste blok |
| 3 | home | second | en | the second block |
    --> block "second" not available for the 'nl' language

所需输出

fallback language = en

selected prefered language = en, output:
| 1 | home | first | en | the first block |
| 3 | home | second | en | the second block |

selected prefered language = nl, output:
| 2 | home | first | nl | het eerste blok |
| 3 | home | second | en | the second block |
     --> select this one, because the 'nl' version is not available

目前我只选择具有所选首选语言的块,因为如果“首选语言”块不可用,我不知道如何通过选择“备用语言”块来解决此问题。我可以尝试对两种语言运行两个查询,然后以某种方式合并它们,并且仅在此块的标题计数低于 1 时插入“后备语言”块,但这似乎相当复杂且不是很优雅?

对于我的应用,我使用的是 eloquent:

$blocks = Block::->where('slug', '=', 'home')
                ->whereIn('language', $selectedLanguage)
                ->get();

我怎么能用雄辩的方式做到这一点? (或者在原始 SQL 中,就此而言?)

【问题讨论】:

    标签: mysql sql eloquent


    【解决方案1】:

    如果我对您的理解正确,如果已经有一行具有相同 slugtitle 的首选语言,您想“删除”具有备用语言的行。

    您可以使用 LEFT JOIN 作为备用语言来检查是否存在具有首选语言的条目。例如,如果您的首选语言是“nl”并且备用语言是“en”,那么您的查询可能如下所示:

    select blocks.*
    from blocks
    left join blocks b1
        on  b1.slug  = blocks.slug
        and b1.title = blocks.title
        and b1.language = 'nl'
        and blocks.language <> 'nl'
    where blocks.slug = 'home'
      and blocks.language in ('nl', 'en')
      and b1.id is null
    

    sqlfiddle

    单词中的连接可能类似​​于:为相同的slugtitle 寻找更好的翻译。如果语言是首选语言,则不会因为blocks.language &lt;&gt; 'nl' 而匹配。否则,连接将“搜索”首选翻译 ('b1.language = 'nl')。

    在 WHERE 子句中,我们告诉仅在没有找到更好的翻译时返回行 (b1.id is null)。

    将查询转换为 eloquent 的最佳方法是:

    $prefered = 'nl';
    $fallback = 'en';
    
    $blocks = App\Block::where('blocks.slug', '=', 'home')
        ->whereIn('blocks.language', [$prefered, $fallback])
        ->leftJoin('blocks as b1', function($join) {
            $join->on('b1.slug', '=', 'blocks.slug')
                 ->on('b1.title', '=', 'blocks.title')
                 ->on('b1.language', '=', DB::raw('?'))
                 ->on('blocks.language', '<>', DB::raw('?'))
            ;
        })
        ->whereNull('b1.id')
        ->addBinding([$prefered, $prefered], 'join')
        ->select(DB::raw('blocks.*'))
        ->get()
    ;
    

    注意:我假设 title 对于所有语言的块都是相同的。否则,您将需要另一列(如block_id)来识别块。

    【讨论】:

    • 我喜欢这种左连接方式。不要检查它是否存在,如果不存在就插入块。谢谢!
    【解决方案2】:

    您可以使用GROUP_CONCAT 来执行此操作。

    1. title 分组,将所有相关字符串放在一行中。
    2. 使用GROUP_CONCATORDER BY 将所需语言放在首位。
    3. 使用SUBSTRING_INDEX 仅提取第一个字符串。

    查询示例:

    SELECT SUBSTRING_INDEX(GROUP_CONCAT(content ORDER BY IF(language='nl',1,IF(language='en',2,3))),',',1)
    FROM block
    GROUP BY title;
    

    【讨论】:

    • 我也测试过这个答案。它完美运行,代码非常简单,非常优雅!我确实更喜欢@Paul 的回答;这个答案更容易被翻译成 eloquent 使用。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-21
    • 2018-05-31
    • 1970-01-01
    • 2015-07-02
    相关资源
    最近更新 更多