多列约束查询

Posted

技术标签:

【中文标题】多列约束查询【英文标题】:Constrain query by multiple columns 【发布时间】:2018-06-21 03:41:53 【问题描述】:

我的查询:

    $units = DB::table('units')
    ->join('locations', 'locations.id', '=', 'units.location_id')
    ->join('castles', 'castles.id', '=', 'units.castle_id')
    ->join('unit_types', 'unit_types.id', '=', 'units.unit_type_id')
    ->select('units.location_id',
        'units.previous_location_id',
        'units.id as unit_id',
        'units.castle_id',
        'castles.guild_id',
        'units.unit_type_id',    
        'units.current_health',
        'unit_types.damage',
        'unit_types.range')
    ->get()
    ->groupBy('location_id', 'castle_id');

产生一些类似这样的测试数据:


    "1": [
        
            "location_id": 1,
            "previous_location_id": 1,
            "unit_id": 2,
            "castle_id": 1,
            "guild_id": null,
            "unit_type_id": 3,
            "current_health": 90,
            "damage": 10,
            "range": 3
        
    ],
    "2": 
        "1": 
            "location_id": 2,
            "previous_location_id": 2,
            "unit_id": 3,
            "castle_id": 2,
            "guild_id": null,
            "unit_type_id": 5,
            "current_health": 100,
            "damage": 20,
            "range": 2
        ,
        "6": 
            "location_id": 2,
            "previous_location_id": 5,
            "unit_id": 7,
            "castle_id": 5,
            "guild_id": null,
            "unit_type_id": 15,
            "current_health": 180,
            "damage": 20,
            "range": 3
        
    ,
    "3": 
        "2": 
            "location_id": 3,
            "previous_location_id": 3,
            "unit_id": 4,
            "castle_id": 3,
            "guild_id": null,
            "unit_type_id": 1,
            "current_health": 100,
            "damage": 10,
            "range": 1
        ,
        "3": 
            "location_id": 3,
            "previous_location_id": 3,
            "unit_id": 5,
            "castle_id": 3,
            "guild_id": null,
            "unit_type_id": 1,
            "current_health": 100,
            "damage": 10,
            "range": 1
        
    ,
    "4": 
        "5": 
            "location_id": 4,
            "previous_location_id": 4,
            "unit_id": 8,
            "castle_id": 4,
            "guild_id": null,
            "unit_type_id": 20,
            "current_health": 300,
            "damage": 40,
            "range": 2
        ,
        "7": 
            "location_id": 4,
            "previous_location_id": 1,
            "unit_id": 1,
            "castle_id": 1,
            "guild_id": null,
            "unit_type_id": 1,
            "current_health": 100,
            "damage": 10,
            "range": 1
        
    ,
    "5": 
        "4": 
            "location_id": 5,
            "previous_location_id": 5,
            "unit_id": 6,
            "castle_id": 5,
            "guild_id": null,
            "unit_type_id": 15,
            "current_health": 180,
            "damage": 20,
            "range": 3
        
    

我正在尝试找到一种方法来限制查询,以便我只得到location_id 相同但castle_id 不同的结果,如下所示:


  "2": 
        "1": 
            "location_id": 2,
            "previous_location_id": 2,
            "unit_id": 3,
            "castle_id": 2,
            "guild_id": null,
            "unit_type_id": 5,
            "current_health": 100,
            "damage": 20,
            "range": 2
        ,
        "6": 
            "location_id": 2,
            "previous_location_id": 5,
            "unit_id": 7,
            "castle_id": 5,
            "guild_id": null,
            "unit_type_id": 15,
            "current_health": 180,
            "damage": 20,
            "range": 3
        
    ,
    "4": 
        "5": 
            "location_id": 4,
            "previous_location_id": 4,
            "unit_id": 8,
            "castle_id": 4,
            "guild_id": null,
            "unit_type_id": 20,
            "current_health": 300,
            "damage": 40,
            "range": 2
        ,
        "7": 
            "location_id": 4,
            "previous_location_id": 1,
            "unit_id": 1,
            "castle_id": 1,
            "guild_id": null,
            "unit_type_id": 1,
            "current_health": 100,
            "damage": 10,
            "range": 1
        
    ,

我尝试了类似 ->whereColumn('units.location_id', '<>', 'units.castle_id') 的方法,但在 castle_id 碰巧有类似密钥的情况下不起作用。


编辑 当我在给定位置有超过 1 个 castle_id 时,这个原始 SQL 会告诉我:

SELECT location_id, 
COUNT(DISTINCT castle_id)
FROM units
GROUP BY location_id
ORDER BY COUNT DESC;

但我只想像这样直接选择那些行:

SELECT location_id, 
FROM units
WHERE (DISTINCT castle_id);

编辑#2

这是一个假设:

units 有 100 行 10行有location_id: 1 20行有location_id: 3 等等……

在有 location_id: 1 的 10 行中,所有 10 行也有 castle_id: 76

在有location_id: 3的20行中,有13行有castle_id: 99,其中7行有castle_id: 42

我正在尝试确定行何时具有相同的 location_idcastle_id 不相同。

在这种情况下,我会跳过 10 行,保留符合我的条件的 20 行,然后检查剩余的 70 行。


编辑 #3 这是一个按要求提供的 SQL Fiddle:http://sqlfiddle.com/#!9/fad08


编辑 #4 这是我的新查询:

    $units = DB::table('units AS u1')
    ->select('u1.location_id',
        'u1.previous_location_id', 
        'u1.id as unit_id',
        'u1.castle_id',
        'c1.guild_id',
        'u1.unit_type_id',
        'u1.current_health',
        'ut.damage',
        'ut.range')
    ->distinct()
    ->join('units AS u2', 'u1.location_id', '=', 'u2.location_id')
    ->join('castles AS c1', 'c1.id', '=', 'u1.castle_id')
    ->join('castles AS c2', 'c2.id', '=', 'u2.castle_id')
    ->join('unit_types AS ut', 'ut.id', '=', 'u1.unit_type_id')
    ->whereColumn([
        ['u1.castle_id', '<>', 'u2.castle_id'],
        ['c1.guild_id', '<>', 'c2.guild_id']
        ])
    ->orderBy('location_id')
    ->get();

    echo $units;

基于 fubar 的回答,在 guild_id 相同时添加了另一个约束。

【问题讨论】:

请详细说明您要达到的目标 @AndréMarcondesTeixeira 我用一个假设编辑了我的原始帖子,希望能阐明我在查询中想要实现的目标。感谢您的帮助! 你能用种子数据创建一个 SQL 小提琴吗?这可能有助于可视化数据,并且肯定有助于测试查询。 @fubar 我在原始帖子中添加了一个 SQL 小提琴。我正在使用 laravel 迁移来建立我的数据库,但我没有太多的 SQL 经验,所以我让它有点简单......你需要我添加更多表或定义主键吗? @deja 真棒。澄清一下,get the results where location_id is the same but castle_id is different - 根据您刚刚创建的 SQL 小提琴,您希望返回哪些行?对于第 4 行和第 5 行,位置和城堡 id 相同,那么是返回第 4 行、第 5 行还是两者都不返回? 【参考方案1】:

这个查询应该可以解决问题

SELECT id, location_id, castle_id FROM Units WHERE location_id IN (
    SELECT a.location_id FROM Units a
    JOIN Units b on (
       b.location_id = a.location_id AND 
       b.castle_id <> a.castle_id
    )
) ORDER BY location_id

【讨论】:

【参考方案2】:

您可以通过以下查询实现您想要的:

SELECT DISTINCT u1.location_id, u1.previous_location_id, u1.id as unit_id, u1.castle_id, c.guild_id, u1.unit_type_id, u1.current_health, ut.damage, ut.srange
FROM units u1
INNER JOIN units u2 ON (u1.location_id = u2.location_id)
INNER JOIN castles c ON (c.id = u1.castle_id)
INNER JOIN unit_types ut ON (ut.id = u1.unit_type_id)
WHERE u1.castle_id <> u2.castle_id
ORDER BY u1.id

还可以查看有效的 SQL Fiddle - http://sqlfiddle.com/#!9/7c9cb8/3

至于使用 Laravel Query Builder 编写这个,它看起来像:

$units = DB::table('units AS u1')
    ->select('u1.location_id', 'u1.previous_location_id', 'u1.id as unit_id', 'u1.castle_id', 'c.guild_id', 'u1.unit_type_id', 'u1.current_health', 'ut.damage', 'ut.range')
    ->distinct()
    ->join('units AS u2', 'u1.location_id', '=', 'u2.location_id')
    ->join('castles AS c', 'c.id', '=', 'u1.castle_id')
    ->join('unit_types AS ut', 'ut.id', '=', 'u1.unit_type_id')
    ->whereColumn('u1.castle_id', '<>', 'u2.castle_id')
    ->groupBy('u1.id')
    ->orderBy('u1.id')
    ->get();

【讨论】:

非常感谢@fubar!这正是我一直在寻找的解决方案。我不确定自己要花多长时间才能解决这个问题,我已经挣扎了好几天了! 每当我在某个位置的单位数量不均时,它就会崩溃。当一个 castle_id 有 5 个单位而另一个 castle_id 有 1 个单位时,它会将 1 个单位再复制四次(我认为是通过加入?),以便它们的数量相同。我注意到,因为有多个单元具有相同的主键 id,这永远不会发生。我可以添加另一个约束以仅显示唯一的user_id,但我担心它仍会在每个查询中重复这些单元,这将花费大量时间和内存。 你能用重现问题的示例数据更新 SQL 小提琴吗? 我刚刚做了@fubar 看起来使用GROUP BY u1.id 可以解决这个问题。我已经更新了我的答案和 SQL 小提琴。可以查一下确认吗?

以上是关于多列约束查询的主要内容,如果未能解决你的问题,请参考以下文章

sql查询 表中一列不重复 显示多列

HSQLDB 索引和多列约束

Sql Server 主键 外键约束

多列的唯一约束

CoreData(IOS)多列的唯一约束?

在多列上检查约束