简体   繁体   中英

How to join multiple rows from same table and select only the column with not NULL values + add an alias to the column

I'm working on a SQL Query to join multiple rows of the same table and select the relevant columns (only NOT NULL columns). Here's what you have to know about my tables:

在此处输入图像描述

What I am trying to achieve is retrieve all products with all their attributes (named). To illustrate, if I have 2 attributes named "attribute_1" and "attribute_2", the final result shoud look like this:

{
 "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"
}

I'm working with Laravel and ATM, here's what I'm doing:

//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($testsub, 'mysubquery', function ($join) {
                $join->on('products.id', '=', 'mysubquery.product_id');
            })->select($select_array)->groupBy('products.id')

This query is working but there are some problems:

  • I have to query all attribute names first (not really a problem for me but still)
  • If I don't filter in the main query with some products ids ->whereIn('products.id',[1,2,3,4,5,6]) then it looks like the subquery is retrieving all the attribute values for all products and the query take a while. Even if I limit the main query, the subquery will fetch all product_attribute_values of all products.

So here are my questions:

  • Is there a way to achieve all this with a single query?
  • Should I use an SQL View instead?
  • If I want to stick with my query, what should I do to only fetch the product_attribute_values that related to the products that will be present in the result?
  • For that SQL Schema, what is the best way to do what I want?

The problem is a bit complex to explain/understand. I can provide some more informations if you need. PS: I didn't provide the SQL because the query is a bit long, but I can share it.

Thanks in advance !

  1. Create a M:N relationship between Product and Attribute using ProductAttributeValue as a pivot model
# 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. Make an accessorfor the value in ProductAttributeValue model
# ProductAttributeValue model

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM