【问题标题】:How to join multiple rows from same table and select only the column with not NULL values + add an alias to the column如何连接同一表中的多行并仅选择不具有 NULL 值的列 + 为该列添加别名
【发布时间】:2022-01-03 16:49:57
【问题描述】:

我正在使用 SQL 查询来连接同一张表的多行并选择相关列(仅限 NOT NULL 列)。 以下是您必须了解的有关我的表的信息:

我想要实现的是检索所有产品及其所有属性(命名)。 为了说明,如果我有 2 个属性名为“attribute_1”和“attribute_2”,最终结果应该如下所示:

{
 "id" => 1,
 "attribute_1" => "The attribute value", //The attribute value can be stored in any column suffixed by "_value"
 "attribute_2" => "The attribute value"  //The attribute value can be stored in any column suffixed by "_value"
}

我正在使用 Laravel 和 ATM,这就是我正在做的事情:

//First retrieve all attribute names
$attribute_names = Attribute::select('name')->pluck('name');

//Build the final select statement that will be used
foreach ($attribute_names as $attribute_name) {
     $select_array[] = DB::raw("MAX(CASE WHEN name='" . $attribute_name . "' THEN value ELSE NULL END) as " . $attribute_name);
}

//Sub query to select only the right column (the one that is not NULL)
$sub_query_select_only_not_null_columns = DB::table('attributes')
            ->leftJoin('product_attribute_values', 'product_attribute_values.attribute_id', 'attributes.id')
            ->select(
                DB::raw('CASE 
                        WHEN text_value IS NOT NULL THEN text_value
                        WHEN boolean_value IS NOT NULL THEN boolean_value
                        WHEN integer_value IS NOT NULL THEN integer_value
                        WHEN float_value IS NOT NULL THEN float_value
                        WHEN datetime_value IS NOT NULL THEN datetime_value
                        WHEN date_value IS NOT NULL THEN date_value
                        WHEN json_value IS NOT NULL THEN json_value
                        END AS value
                    '),
                'attributes.name',
                'product_attribute_values.product_id as product_id'
            );

//Final query
Products::leftJoin('products', 'product_flat.product_id', '=', 'products.id') //Ignore this join 
            ->leftJoinSub($sub_query_select_only_not_null_columns, 'mysubquery', function ($join) {
                $join->on('products.id', '=', 'mysubquery.product_id');
            })->select($select_array)->groupBy('products.id')

此查询有效,但存在一些问题:

  • 我必须先查询所有属性名称(对我来说不是真正的问题,但仍然)
  • 如果我没有在主查询中使用某些产品 id 进行过滤 ->whereIn('products.id',[1,2,3,4,5,6]) 那么看起来子查询正在检索所有所有产品的属性值和查询需要一段时间。即使我限制了主查询,子查询也会获取所有产品的所有product_attribute_values。

所以这是我的问题:

  • 有没有办法通过一个查询来实现所有这些?
  • 我应该改用 SQL 视图吗?
  • 如果我想坚持我的查询,我应该怎么做才能只获取与将出现在结果中的产品相关的 product_attribute_values ?
  • 对于那个 SQL 架构,什么是做我想做的最好的方法?

这个问题解释/理解有点复杂。如果您需要,我可以提供更多信息。 PS:由于查询有点长,我没有提供SQL,但我可以分享一下。

提前致谢!

【问题讨论】:

  • 感觉可以使用 group_concat 或 union 找到解决方法,但最重要的是,如果会有这么多的空值,那可能不是一个好的设计。如果可能,应优先考虑标准化。如果可以选择,我会将 attr_values 表更改为具有 type 字段和 value 字段。因此,例如对于浮点值 type 将是 float 并且 value 将具有它的值,依此类推。不再需要担心排除空值。然后这又归结为偏好和可能性。
  • 感谢您的评论!事实上,设计已经在 Bagisto(电子商务 PHP 模块)中完成了,我无法真正修改模型定义,因为我们在我的公司将它用于其他项目......感谢您的建议!
  • 介意分享一个样本数据集吗?

标签: php mysql sql laravel eloquent


【解决方案1】:
  1. 使用 ProductAttributeValue 作为枢轴模型在 ProductAttribute 之间创建 M:N 关系
# Product model

public function attributes()
{
    return $this->belongsToMany(Attribute::class, 'product_attribute_values', 'product_id', 'attribute_id')
                ->withPivot('text_value', 'boolean_value', ..., 'json_value')
                ->using(ProductAttributeValue::class);
}
  1. ProductAttributeValue 模型中的值创建访问器
# ProductAttributeValue model

public function getValueAttribute()
{
    return $this->text_value
        ?? $this->boolean_value
        ?? ...
        ?? $this->json_value;
}
  1. 查询
$product = Product::with('attributes')->find(1);
  1. 格式化结果
$result = json_encode(
    array_merge(
        ['id' => $product->id],
        $product->attributes
                ->mapWithKeys(fn($attribute) => [$attribute->name => $attribute->pivot->value])
                ->toArray()
    )
);

【讨论】:

  • 感谢您的回答!我尝试了您的解决方案,并且我的查询结果中有所有数据。但是我的问题仍未解决,因为您提供的解决方案使用例如集合来映射属性及其名称,而我需要的是纯 SQL 解决方案。因为查询是在DataGrid中使用的,所以我不能玩查询结果。每个属性都必须在 SQL 查询中连接和命名。
  • 没有简单的方法可以做你想做的事。但是,如果您不关心别名部分,您可以使用 group_concat
猜你喜欢
  • 2021-10-03
  • 1970-01-01
  • 2019-03-03
  • 1970-01-01
  • 1970-01-01
  • 2011-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多