How to skip NULL values from MySQL CONCAT and GROUP_CONCAT - mysql

I have this query that returns the name of the product and the array of images as a JSON string. However, when there is no image attached to a product, I would like this query to return an empty array for the images property.
Currently, it returns this when there is no product image found:
{ "name": "Product Name", "images": [{"id": null, "slug": null}]}
I tried to add an IF condition into the CONCAT method, but it returns the same response.
SELECT p.name,
CONCAT('[',
IF(i.id = NULL,
'',
GROUP_CONCAT(DISTINCT (
JSON_OBJECT(
'id', i.id,
'slug', i.slug
)
))
),
']') AS images
FROM products AS p
LEFT JOIN _product_images AS pi ON pi.pId = p.id
LEFT JOIN images AS i ON i.id = pi.iId
WHERE p.id = 4;
Thank you!

As other have mentioned, i.id = NULL will always evaluate to NULL. But your approach is needlessly complicated and would raise an error on a strictly configured server. On db-fiddle I get the following error:
ER_MIX_OF_GROUP_FUNC_AND_FIELDS: In aggregated query without GROUP BY,
expression #2 of SELECT list contains nonaggregated column
'test.i.id'; this is incompatible with sql_mode=only_full_group_by
demo
So the check i.id IS NULL needs to be done within the GROUP_CONCAT() function:
SELECT p.name,
CONCAT('[',
GROUP_CONCAT(DISTINCT (
IF (i.id IS NULL, '',
JSON_OBJECT(
'id', i.id,
'slug', i.slug
)
)
)),
']') AS images
FROM products AS p
LEFT JOIN _product_images AS pi ON pi.pId = p.id
LEFT JOIN images AS i ON i.id = pi.iId
WHERE p.id = 4
However - You can avoid the check, when you use an INNER JOIN. But the INNER JOIN will also ignore the data from the products table - So I would do that JOIN in a correlated subquery. An finally you can use JSON_ARRAYAGG() to generate a JSON array.
demo
SELECT p.name, COALESCE((
SELECT JSON_ARRAYAGG(JSON_OBJECT(
'id', i.id,
'slug', i.slug
))
FROM _product_images AS pi
JOIN images AS i ON i.id = pi.iId
WHERE pi.pId = p.id
), JSON_ARRAY()) AS images
FROM products AS p
#WHERE p.id = 4;
demo

You should be using IS NULL to check for a NULL value:
SELECT
p.name,
CONCAT('[',
GROUP_CONCAT(
IF(id IS NULL,
'',
DISTINCT JSON_OBJECT('id', i.id, 'slug', i.slug))),
']') AS images
FROM products AS p
LEFT JOIN _product_images AS pi ON pi.pId = p.id
LEFT JOIN images AS i ON i.id = pi.iId
WHERE p.id = 4
GROUP BY p.name;
As a side note, DISTINCT is not a function, and you should not be using it as such, so I removed the function parentheses which you were using.
Edit: Updated SQL to satisfy the group restrictions. The IF-Statement for checking i.id outside of the GROUP_CONCAT would fail, due to multiple image items per row.

IF(i.id = NULL, ...) returns NULL, for whatever value has i.id (even NULL), which is a falsy value.
You want to use instead IF (i.id IS NULL, ...)
From documentation :
You cannot use arithmetic comparison operators such as =, <, or <> to test for NULL. To demonstrate this for yourself, try the following query:
mysql> SELECT 1 = NULL, 1 <> NULL, 1 < NULL, 1 > NULL;
+----------+-----------+----------+----------+
| 1 = NULL | 1 <> NULL | 1 < NULL | 1 > NULL |
+----------+-----------+----------+----------+
| NULL | NULL | NULL | NULL |
+----------+-----------+----------+----------+

Related

duplicates in mysql full join

I have two tables and I want to put them together and dont want to get the same id, instead I want to have the rows with the value and not with the null values - if there is an id with values.
This is my query
(SELECT users_antworten.user,
users_antworten.antwort,
profilfragen.id,
profilfragen.frage,
profilfragen.status,
profilfragen.aktiviert
FROM users_antworten
right JOIN profilfragen ON users_antworten.frage = NULL
WHERE profilfragen.aktiviert = 1
group by profilfragen.id
)
UNION
(SELECT users_antworten.user,
users_antworten.antwort,
profilfragen.id,
profilfragen.frage,
profilfragen.status,
profilfragen.aktiviert
FROM users_antworten
LEFT JOIN profilfragen ON users_antworten.frage = profilfragen.id
WHERE profilfragen.aktiviert = 1 AND users_antworten.user = 6
group by profilfragen.id
)
Thank you!
Finally after hours :-) I've got it:
(select null as user,
null as antwort,
p.id,
p.frage,
p.status,
p.aktiviert
FROM profilfragen p
WHERE not exists
( SELECT null
FROM users_antworten u
WHERE u.frage = p.id AND u.user=6
)
)
UNION
(SELECT u.user,
u.antwort,
p.id,
p.frage,
p.status,
p.aktiviert
FROM users_antworten u left join profilfragen p on p.id = u.frage WHERE u.user= :id
)

MySQL - Way to set a default return value when NULL is found?

Is there a way in SQL to set a default return value when NULL is returned for part of the results?
Here is my SQL:
SELECT p.id, p.title, concat( u1.meta_value, ' ', u2.meta_value ) as fullname, concat( r.name, ', ', c.name ) as location
FROM modules_profiles p
LEFT JOIN moonlight_usermeta u1 ON p.user_id = u1.user_id AND u1.meta_key = 'first_name'
LEFT JOIN moonlight_usermeta u2 ON p.user_id = u2.user_id AND u2.meta_key = 'last_name'
LEFT JOIN modules_regions r ON r.id = p.region_id
LEFT JOIN modules_countries c ON c.id = p.country_id
WHERE p.certification IN ( 'certified' ) AND p.country_id IN ( 2 )
ORDER BY p.user_id ASC
There are times when there is no region_id set for a given profile; therefore, NULL is returned for location for that respective user_id, even though we do have a country's name (c.name).
Is there a way in this case to just return the c.name only?
Use COALESCE() function like below, it will return the first non NULL value provided in list
COALESCE(col_name, 'default_value')
For your case, do
COALESCE(region_id, c.name)
I think, you are specifically talking about the part
concat( r.name, ', ', c.name ) as location
You can modify this using CASE expression as well
case when r.name is not null and c.name is not null
then concat( r.name, ', ', c.name ) else c.name end as location
You want to use MySQL's IFNULL(value, default) function.
Coalesce could help you
COALESCE(region_id, 'default value')

SQL returning NULL instead of empty result

SQL FIDDLE HERE
I am making a simple blog system where posts can have more than one category, so I have this query:
SELECT *, GROUP_CONCAT(item_category) AS item_categories
FROM (`dev_pages`)
LEFT JOIN `dev_items_to_categories` ON `dev_items_to_categories`.`item_id` = `dev_pages`.`page_id`
WHERE deleted_time IS NULL
AND `page_type` = 'blog'
AND `item_category` = '16'
ORDER BY `page_title` ASC
It works fine but if there are no results instead of coming back as nothing it returns NULL or the default value (See SQL Fiddle)
I managed to make a around but I was wondering if anyone had a better solution to this:
SELECT a.* FROM (
SELECT *, GROUP_CONCAT(item_category) AS item_categories
FROM (`dev_pages`)
LEFT JOIN `dev_items_to_categories` ON `dev_items_to_categories`.`item_id` = `dev_pages`.`page_id`
WHERE deleted_time IS NULL
AND `page_type` = 'blog'
AND `item_category` = '16'
ORDER BY `page_title` ASC
) AS a
WHERE page_id > 0
You need a GROUP BY clause:
SELECT *, GROUP_CONCAT(item_category) AS item_categories
FROM (`dev_pages`)
LEFT JOIN `dev_items_to_categories` ON `dev_items_to_categories`.`item_id` = `dev_pages`.`page_id` AND `item_category` = '16'
WHERE deleted_time IS NULL
AND `page_type` = 'blog'
GROUP BY dev_pages.page_id
ORDER BY `page_title` ASC
You also need to put item_category = '16' into the ON clause. Otherwise, you will filter out rows with no matches in dev_items_to_categories; since you're using LEFT JOIN rather than INNER JOIN, I assume you want those rows with null matches.
FIDDLE
you can also try coalesce function.
SELECT *, GROUP_CONCAT(coalece(item_category,'')) AS item_categories
FROM (`dev_pages`)
LEFT JOIN `dev_items_to_categories` ON `dev_items_to_categories`.`item_id` = `dev_pages`.`page_id`
WHERE deleted_time IS NULL
AND `page_type` = 'blog'
AND `item_category` = '16'
ORDER BY `page_title` ASC

Where clause inside joined select

I'm trying to accommodate a similar solution to this one - what I have is a SELECT query inside a JOIN, and the problem is that the query runs at full for all rows (I'm talking 60,000 rows per table - and it runs on 3 tables!).
So what I want to do, is add a WHERE clause to the SELECTs inside the JOIN.
But, I can't access the outer SELECT and get the proper WHERE condition I need.
The query I'm attempting is here:
SELECT c.compete_id AS id,
s.id AS store_id,
c.enabled AS enabled,
s.store_name AS store_name,
s.store_url AS store_url,
c.verified AS verified,
r.rating_total AS rating,
r.positive_percent AS percent,
r.type AS type
FROM compete_settings c
LEFT JOIN stores s
ON c.compete_id = s.id
LEFT JOIN (
(SELECT store_id, rating_total, positive_percent, 'ebay' AS type FROM ebay_sellers WHERE store_id = c.compete_id)
UNION
(SELECT store_id, rating_total, positive_percent, 'amazon' AS type FROM amazon_sellers WHERE store_id = c.compete_id)
UNION
(SELECT store_id, CASE WHEN rank = 0 THEN NULL ELSE (200000 - rank) END AS rating_total, '100' as positive_percent, 'alexa' AS type FROM alexa_ratings WHERE store_id = c.compete_id)
) AS r
ON c.compete_id = r.store_id
WHERE c.store_id = :store_id
Note, :store_id is a variable bound through the framework - let's imagine it's the number 12345.
How can I do this? Any ideas?
We ended up going witha different approach - we just JOINed everything and only selected the right columns with a CASE. Here's the final query:
SELECT c.id AS id,
s.id AS store_id,
c.enabled AS enabled,
s.store_name AS store_name,
s.store_url AS store_url,
c.verified AS verified,
(CASE WHEN eb.rating_total IS NOT NULL THEN eb.rating_total
WHEN am.rating_total IS NOT NULL THEN am.rating_total
WHEN ax.rank IS NOT NULL THEN ax.rank
END) AS rating,
(CASE WHEN eb.positive_percent IS NOT NULL THEN eb.positive_percent
WHEN am.positive_percent IS NOT NULL THEN am.positive_percent
WHEN ax.rank IS NOT NULL THEN '100'
END) AS percent,
(CASE WHEN eb.positive_percent IS NOT NULL THEN 'ebay'
WHEN am.positive_percent IS NOT NULL THEN 'amazon'
WHEN ax.rank IS NOT NULL THEN 'alexa'
END) AS type
FROM compete_settings c
LEFT JOIN stores s
ON c.compete_id = s.id
LEFT JOIN ebay_sellers eb ON c.compete_id = eb.store_id
LEFT JOIN amazon_sellers am ON c.compete_id = am.store_id
LEFT JOIN alexa_ratings ax ON c.compete_id = ax.store_id
WHERE c.store_id = :store_id

Nested GROUP_CONCAT query doesn't return all values

I have a query with a few subqueries, but the strange thing is that the subqueries won't return the same values as if I execute the queries one by one manually.. At first I used 'IN' inside the queries, but no indexes were used, so I converted them to '='. The results are the same with 'IN' or when I use the converted to '=' variation.
SELECT *
FROM partners
WHERE id = (
SELECT GROUP_CONCAT( partner_id
SEPARATOR ' OR id = ' )
FROM product_feeds
WHERE id = (
SELECT GROUP_CONCAT( DISTINCT feed_id
SEPARATOR ' OR id = ' )
FROM product_data
WHERE category_id = (
SELECT GROUP_CONCAT( id
SEPARATOR ' OR category_id = ' )
FROM product_categories
WHERE parent_id = (
SELECT GROUP_CONCAT( id
SEPARATOR ' OR parent_id = ' )
FROM product_categories
WHERE parent_id =1 )
ORDER BY NULL )
ORDER BY NULL )
ORDER BY NULL )
When I, for example, execute the deepest nested 3 subqueries manually, I get 10,11,12,33,34,35 as the final result. When I execute the full 3 subqueries at once, they return 10,11,12.
I am missing results..
Instead of trying to rely on GROUP_CONCAT, this is a job for INNER JOIN to get results from multiple tables where relationships exist.
SELECT
-- Best to specify the precise fields you want here instead of *
*
FROM partners p
INNER JOIN product_feeds pf
ON pf.partner_id = p.id
INNER JOIN product_data pd
ON pd.feed_id = pf.id
INNER JOIN product_categories pc
ON pc.id = pd.category_id
INNER JOIN product_categories pcparent
ON pcparent.id = pc.parent_id
AND pcparent.parent_id = 1