【问题标题】:Laravel where, orWhereHas and whereNotInLaravel where, orWhereHas 和 whereNotIn
【发布时间】:2021-06-05 09:15:19
【问题描述】:

你好 SO 的伟大人物!

希望大家天天开心,身体健康

注意:我不擅长 SQL

抱歉英语不好,但我会尽力解释我的问题

我在我的应用程序中使用 Laravel v8.x,在设置模型关系、事件、队列等之后,现在我正在为 SQL 工作

ATM,我有 2 个型号,

  • User
  • Post

关系:

  • User hasMany Post
  • UserbelongsToMany User(块)
  • UserbelongsToMany User(关注)
  • Post 属于User

数据库: User 的 5 条记录 Block 的 2 条记录 Post 的 3 条记录

表格:(使用faker)

users

[
    { id: 1, name: 'Jonathan Beatrice', username: 'kiana.fay', ... },
    { id: 2, name: 'Lacey Kirlin', username: 'kenna.turner', ... },
    { id: 3, name: 'Alexander Schiller', username: 'cassandra95', ... },
    { id: 4, name: 'Daniel Wickozky', username: 'nkoepp', ... },
    { id: 5, name: 'Maymie Lehner', username: 'frami.felton', ... }
]

block

[
    { id: 1, by_id: 1, to_id: 2 }, // User #1 block user #2
    { id: 2, by_id: 4, to_id: 1 } // User #4 block user #1
]

posts

[
    { id: 1, user_id: 2, body: 'Test post', ... },
    { id: 2, user_id: 5, body: 'Lorem ipsum dolor sit amet ...', ... },
    { id: 3, user_id: 4, body: 'ABCD festival soon! ...', ... },
]

一切顺利

现在我想实现搜索系统,我有一个问题,因为我不擅长SQL

这是我的代码

SearchController.php

use ...;
use ...;
...

public function posts(Request $request)
{
    // For testing purpose
    $user = User::with(['userBlocks', 'blocksUser'])->find(1);

    // Get all id of user that $user block
    // return [2]
    $user_blocks = $user->userBlocks->pluck('pivot')->pluck('to_id')->toArray();

    // Get all id of user that block $user
    // return [4]
    $blocks_user = $user->blocksUser->pluck('pivot')->pluck('by_id')->toArray();

    // Merge all ids above (must be unique())
    // return [2, 4]
    $blocks = array_merge($user_blocks, $blocks_user);

    // .../search?q=xxx
    $query = $request->query('q');

    $sql = Post::query();

    // Search for posts that has `posts`.`body` LIKE ? ($query)
    $sql->where('body', 'LIKE', "%$query%");

    // This is where I got confused
    $sql->orWhereHas('user', function ($post_user) use ($blocks, $query) {
        $post_user
            ->whereNotIn('id', $blocks) // Exclude posts that has user and their id not in (x, x, x, x, ... ; $block variable above)
            ->where('name', 'LIKE', "%$query%") // Find user that has name LIKE ? ($query)
            ->orWhere('username', 'LIKE', "%$query%"); // or Find user that has username LIKE ? ($query)
    });

    $sql->orderBy('created_at', 'DESC');
    $sql->with(['user']);

    $posts = $sql->simplePaginate(10, ['*'], 'p');

    return $posts;
}

我运行代码,.../search?q=e

注意:

  • 所有users 的名称中都有字母E
  • 而且所有posts 的正文中都有字母E
  • 我们(作为用户 #1)屏蔽用户 #2,用户 #4 屏蔽我们(用户 #1)

结果:控制器返回所有帖子

这是我使用DB::enableQueryLog()DB::getQueryLog()时的查询

SELECT
  *
FROM
  `posts`
WHERE `body` LIKE ?
  AND EXISTS
  (SELECT
    *
  FROM
    `users`
  WHERE `posts`.`user_id` = `users`.`id`
    AND (
      `id` NOT IN (?)
      AND `username` LIKE ?
      OR `name` LIKE ?
    ))
ORDER BY `created_at` ASC
LIMIT 11 OFFSET 0

目标:搜索所有有 body LIKE 的帖子,或者有用户的帖子;用户名 LIKE ?或名称 LIKE ? (但也要排除我们屏蔽的用户和屏蔽我们的用户

提前致谢

如果有任何不清楚的解释,我会尽快编辑它

【问题讨论】:

  • 您也许可以使用whereDoesntHave (docs),但看到它是如何自加入可能会很棘手
  • 如果我尝试理解whereDoesntHave 方法,它将获取所有没有用户的帖子?同时,帖子必须是belongsTo用户

标签: sql laravel eloquent


【解决方案1】:

如果我在最近安装的 laravel 上运行,并针对您的一个问题提出更改,版本 7.19.1,我会收到以下查询:

SELECT
  *
FROM
  `posts`
WHERE `body` LIKE ?
  OR EXISTS <- line of interest
  (SELECT
    *
  FROM
    `users`
  WHERE `posts`.`user_id` = `users`.`id`
    AND (
      `id` NOT IN (?)
      AND (`username` LIKE ?
      OR `name` LIKE ?) <- extra brackets ive added
    ))
ORDER BY `created_at` ASC
LIMIT 11 OFFSET 0

查看感兴趣的行,并将其与您的 laravel 版本正在运行的查询进行比较。 laravel 错误地生成了 AND EXISTS 行。 OrWhereHas 在您的版本中表现不正确,我找不到版本号来查看它的修复位置。

如果可能,我建议升级到最新版本,但这并不总是一种选择。我已经挖掘了一下,看起来这个问题中的用户在这里遇到了类似的问题:

WhereHas() / orWhereHas not constraining the query as expected

您可以尝试将 $sql-&gt;with(['user']); 移到 OrWhereHas 子句之前。我不确定这是否会将其更改为 OR,但值得一试。 第二件事,我在您的 OR 子句中添加了whereNested 以确保优先级正确,这会在上面的查询中添加额外的括号,就像您不想要的那样:

(`id` NOT IN (1, 2, 3)
      AND `name` LIKE % test %)
      OR `username` LIKE % test %

从那时起,它会将您被屏蔽的帖子包含在存在子句中。

所以最终的更改看起来像这样,我认为这符合您的描述:

$sql->with(['user']); //deleted from original position and move here
$sql->where('body', 'LIKE', "%$query%")->whereNotIn('id', $blocks); //additional line 
$sql->orWhereHas('ambience', function ($post_user) use ($blocks, $query) {
    $post_user
        ->whereNotIn('id', $blocks); 
            $post_user->whereNested(function($post_user) use ($query) { //new bit
                $post_user->where('name', 'LIKE', "%$query%") 
                ->orWhere('username', 'LIKE', "%$query%"); 
            });
});

【讨论】:

  • whereNested()这样的方法吗? Idk 直到你提到它,我会尝试你的答案,但是我在我的城市之外
  • 我做了一个快速搜索,其中Nested 自 2015 年以来一直在 laravel 中。我还运行了上面的代码来获取上面的查询。
  • 我在上面复制了你的代码,它仍然显示来自所有人的所有帖子(包括人员用户阻止和阻止用户),我正在使用 Laravel 8.40.0
  • 这是我的控制器 rn 中的代码; $sql = Post::query(); $sql-&gt;with(['user']); $sql-&gt;where('body', 'LIKE', "%$query%"); $sql-&gt;orWhereHas('user', function ($post_user) use ($blocks, $query) { $post_user-&gt;whereNotIn('id', $blocks); $post_user-&gt;whereNested( function ($post_user) use ($query) { $post_user-&gt;where('name', 'LIKE', "%$query%") -&gt;orWhere('username', 'LIKE', "%$query%"); }); });
  • AND EXISTS 从我的查询中消失了,多亏了你,我尝试用whereHas() 切换orWhereHas(),使用orWhereHas() 返回所有帖子,使用whereHas() 返回0 个帖子,只有数组正方形括号[],我真的很困惑@_@
猜你喜欢
  • 2019-03-14
  • 2016-10-26
  • 1970-01-01
  • 2020-05-02
  • 2018-05-17
  • 2021-06-14
  • 2020-05-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多