How to use GROUP_CONCAT functionality without GROUP BY in MySQL? - mysql

This is the original SQL statement to retrieve course data:
SELECT
`courses`.`id` AS `id`,
`courses`.`title` AS `title`,
`courses`.`description` AS `description`,
MATCH (coursedata.title) AGAINST ('Salsa') * 5 + MATCH (coursedata.description) AGAINST ('Salsa') * 2 AS `relevance`,
GROUP_CONCAT(trainers.name SEPARATOR '|||') AS `trainers`
FROM `courses`
INNER JOIN `coursedata` ON `courses`.`id` = `coursedata`.`id`
INNER JOIN `courses_trainers` ON `courses`.`id` = `courses_trainers`.`course_id`
INNER JOIN `trainers` ON `trainer_id` = `trainers`.`id`
GROUP BY `courses`.`id`
HAVING `relevance` >= '3'
Since it uses the MySQL Extensions to GROUP BY and cannot be executed, if the sql_mode is set to ONLY_FULL_GROUP_BY, I had to make it standard SQL conform:
SELECT * FROM (
SELECT
`courses`.`id` AS `id`,
`courses`.`title` AS `title`,
`courses`.`description` AS `description`,
MATCH (coursedata.title) AGAINST ('Salsa') * 5 + MATCH (coursedata.description) AGAINST ('Salsa') * 2 AS `relevance`
-- , GROUP_CONCAT(trainers.name SEPARATOR '|||') AS `trainers`
FROM `courses`
INNER JOIN `coursedata` ON `courses`.`id` = `coursedata`.`id`
LEFT JOIN `courses_trainers` ON `courses`.`id` = `courses_trainers`.`course_id`
LEFT JOIN `trainers` ON `trainer_id` = `trainers`.`id`
) AS subselect
WHERE `relevance` >= '3'
This one wors fine, but I cannot use the GROUP_CONCAT function (in order to concatenate multiple trainer fields to one field/string) anymore.
Error Code: 1064
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'GROUP_CONCAT(trainers.name SEPARATOR '|||') AS `trainers`
FROM `courses`
INNER' at line 7
Is there another way to use the GROUP_CONCAT functionality?

Related

use CTE instead of subquery

I tried to re-write a SQL query using subquery to one using common table expression (CTE). The former is as below
select accounting_id, object_code, 'active', name
from master_data md
where md.id in (
select MIN(md1.id)
from master_data md1
where md1.original_type = 'tpl'
group by md1.object_code
);
The one below failed with
Error Code: 1064. You have an error in your SQL syntax; check the manual that corresponds
to your MySQL server version for the right syntax to use near 'distinct_object_code_id' at line 9
with distinct_object_code_id(id) as (
select MIN(md1.id)
from master_data md1
where md1.original_type = 'tpl'
group by md1.object_code
)
select md.accounting_id, md.object_code, 'active', md.name
from master_data md
where md.id in distinct_object_code_id;
This one also failed
with distinct_object_code_id as (
select MIN(md1.id)
from master_data md1
where md1.original_type = 'tpl'
group by md1.object_code
)
select md.accounting_id, md.object_code, 'active', md.name
from master_data md
join distinct_object_code_id using(id)
;
whereas this one below works (by explicitly specify the column_list)
with distinct_object_code_id(id) as (
select MIN(md1.id)
from master_data md1
where md1.original_type = 'tpl'
group by md1.object_code
)
select md.accounting_id, md.object_code, 'active', md.name
from master_data md
join distinct_object_code_id using(id)
;
I tried another one but it also failed (using subquery inside CTE)
with distinct_object_code_id as (
select accounting_id, object_code, 'active', name
from master_data md
where md.id in (
select MIN(md1.id)
from master_data md1
where md1.original_type = 'tpl'
group by md1.object_code
)
select * from distinct_object_code_id;
The MYSQL database version is 8.0.29-0ubuntu0.20.04.3.
what did I do wrong when using CTE?
EDIT:
I add a third part, in which I inserted the data from those queries above into another table tpl_transferred_debtor like below
with distinct_object_code_id as (
select MIN(id) as id
from master_data
where original_type = 'tpl'
group by object_code
),
tmp_tpl_table as (
select md.accounting_id, md.object_code, 'active', md.name
from master_data md
join distinct_object_code_id using(id)
)
insert into tpl_transferred_debtor(debtor_id, tpl_code, status, description)
select * from tmp_tpl_table;
This one failed with
Error Code: 1064. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'insert into tpl_transferred_debtor(debtor_id, tpl_code, status, description) sel' at line 12
I tried to write a simple one to check if the second CTE tmp_tpl_table and it is ok
with distinct_object_code_id as (
select MIN(id) as id
from master_data
where original_type = 'tpl'
group by object_code
),
tmp_tpl_table as (
select md.accounting_id, md.object_code, 'active', md.name
from master_data md
join distinct_object_code_id using(id)
)
select * from tmp_tpl_table;
Your first query fails because you cannot use IN to reference cte you have to join or the correct syntax for IN ie in (select..from cte)
Your second query fails because you didn't alias MIN(md1.id)
Your third query works because you referenced MIN(md1.id) by providing (ID)
Personally I prefer join..on over join..using

Calcite Optimize cause duplicated column name error

We use calcite to execute a mysql query, the SQL is:
select a.title, c.score from phx_ep.phx_gql_product a left join phx_ep.phx_gql_product_tag c on a.product_id = c.product_id limit 3
It's simple and clear, but calcite optimized it to:
SELECT `t2`.`title`, `t2`.`score`
FROM (SELECT *
FROM (SELECT `product_id`, `title`
FROM (SELECT *
FROM `phx_ep`.`phx_gql_product`
LIMIT 3) AS `t`) AS `t0`
LEFT JOIN (SELECT `product_id`, `score`
FROM `phx_ep`.`phx_gql_product_tag`) AS `t1` ON `t0`.`product_id` = `t1`.`product_id`
LIMIT 3) AS `t2`
Which caused a Duplicate column name error
If remove 'limit 3', it works well.
Is it a bug?

Error when trying to use aliased column name in where clause

I have a problem with my SQL query. I want to select records by debt amount. Amount is counted by relation with two tables.
This works fine:
SELECT `i`.*,
(i.amount_netto + (i.amount_netto * i.vat / 100)) - (SUM(p.amount_netto)) AS `debt`,
`e`.`name` AS `user_name`,
`e`.`surname` AS `user_surname`,
`c`.`name` AS `contractor_name`
FROM `invoices` AS `i`
INNER JOIN `payments` AS `p` ON i.id = p.invoice_id
INNER JOIN `employees` AS `e` ON i.employee_id = e.id
INNER JOIN `contractors` AS `c` ON i.contractor_id = c.id
GROUP BY `i`.`id`
ORDER BY `debt` ASC
But when I add a WHERE clause by debt I get an error:
Unknown column 'debt' in 'where clause'
Query looks like this:
SELECT `i`.*,
(i.amount_netto + (i.amount_netto * i.vat / 100)) - (SUM(p.amount_netto)) AS `debt`,
`e`.`name` AS `user_name`,
`e`.`surname` AS `user_surname`,
`c`.`name` AS `contractor_name`
FROM `invoices` AS `i`
INNER JOIN `payments` AS `p` ON i.id = p.invoice_id
INNER JOIN `employees` AS `e` ON i.employee_id = e.id
INNER JOIN `contractors` AS `c` ON i.contractor_id = c.id
WHERE `debt` > 1
GROUP BY `i`.`id`
ORDER BY `debt` ASC
Why I can't access debt in the WHERE clause, but I can in the ORDER clause?
debt is not a column, but an alias. Column (expression) aliases are not resolved until after the query is executed, therefore they cannot be used in WHERE clauses.
For example this query is not legal:
select foo + 3 as bar
from baz
where bar = 39
And instead you have to rewrite the whole expression in the WHERE clause:
select foo + 3 as bar
from baz
where foo + 3 = 39
Furthermore, since debt is actually based on an aggregate, you cannot filter this in a WHERE clause. You must instead use HAVING to evaluate the predicate after the aggregation. Therefore your query should be:
SELECT ...
FROM ...
GROUP BY ...
HAVING (i.amount_netto + (i.amount_netto * i.vat / 100)) - (SUM(p.amount_netto)) > 1
ORDER BY ...
Note that MySql offers an SQL extension which enables the use of aliases in the HAVING clause, so you can also do:
SELECT ...
FROM ...
GROUP BY ...
HAVING debt > 1
ORDER BY ...
SQL doesn't typically allow you to reference column aliases in WHERE, GROUP BY or HAVING clauses. MySQL does support referencing column aliases in the GROUP BY and HAVING
try change your where query to
WHERE (i.amount_netto + (i.amount_netto * i.vat / 100)) - (SUM(p.amount_netto)) > 1 GROUP BY i.id ORDER BY debt ASC
another option, you could use a subquery
SELECT * from (`i`.*, (i.amount_netto + (i.amount_netto * i.vat / 100)) - (SUM(p.amount_netto)) AS `debt`, `e`.`name` AS `user_name`, `e`.`surname` AS `user_surname`, `c`.`name` AS `contractor_name` FROM `invoices` AS `i` INNER JOIN `payments` AS `p` ON i.id = p.invoice_id INNER JOIN `employees` AS `e` ON i.employee_id = e.id INNER JOIN `contractors` AS `c` ON i.contractor_id = c.id GROUP BY `i`.`id`) as newTable
WHERE `debt` > 1 ORDER BY `debt` ASC

MySQL Joined SELECT COUNT()

I wish to select results across several tables, but I only want to return rows based on the COUNT() result of joined SELECT query.
Here's how the query looks at the moment:
SELECT `s`.`venue_id` AS `id`,
CONCAT(`u`.`First_name`, ' ', `u`.`Surname`) AS `user_name`,
`u`.`avatar` AS `avatar`,
`u`.`facebookId` AS `fid`,
`x`.`imgs` AS `num_imgs`
FROM `new_shortlists_venues` `s`
INNER JOIN `new_shortlists` ON `new_shortlists`.`id` = `s`.`list_id`
INNER JOIN `users` `u` ON `u`.`id` = `new_shortlists`.`bride_id`
LEFT JOIN (SELECT `listing_id`, COUNT(*) `imgs` FROM `listingsImages`) `x` ON `s`.`venue_id` = `x`.`listing_id`
WHERE `new_shortlists`.`venues` > 4
AND `new_shortlists`.`bride_id` != 0
GROUP BY `s`.`list_id`
ORDER BY `s`.`date_added` DESC
LIMIT 6
For some reason, the query returns NULL for num_imgs. Essentially, I'd like to select only records which have at least 4 records in the listingsImages table.
Please note that this is for a legacy system, and I didn't design the DB! As a result, I have now option to change the schema.
You left off the GROUP BY of your subquery. Your current query is returning COUNT(*) associated with a random listing_id. Add GROUP BY listing_id and you should return the correct counts.
SELECT `s`.`venue_id` AS `id`,
CONCAT(`u`.`First_name`, ' ', `u`.`Surname`) AS `user_name`,
`u`.`avatar` AS `avatar`,
`u`.`facebookId` AS `fid`,
`x`.`imgs` AS `num_imgs`
FROM `new_shortlists_venues` `s`
INNER JOIN `new_shortlists` ON `new_shortlists`.`id` = `s`.`list_id`
INNER JOIN `users` `u` ON `u`.`id` = `new_shortlists`.`bride_id`
LEFT JOIN (SELECT `listing_id`, COUNT(*) `imgs`
FROM `listingsImages`
GROUP BY `listing_id`) `x` ON `s`.`venue_id` = `x`.`listing_id`
WHERE `new_shortlists`.`venues` > 4
AND `new_shortlists`.`bride_id` != 0
GROUP BY `s`.`list_id`
ORDER BY `s`.`date_added` DESC
LIMIT 6
And to return those with at least 4 records, just add that constraint to your WHERE criteria:
AND `x`.`imgs` >= 4
This might be the culprit:
ON `s`.`venue_id` = `x`.`listing_id`

Store the results of a sub-query for use in multiple joins

I have the following MySQL query, which produces the result I want:
SELECT
`l`.`status`,
`l`.`acquired_by`, `a`.`name` AS 'acquired_by_name',
`l`.`researcher`, `r`.`name` AS 'researcher_name',
`l`.`surveyor`, `s`.`name` AS 'surveyor_name'
FROM `leads` `l`
LEFT JOIN (
SELECT '0' AS 'id', 'Unassigned' AS 'name'
UNION ALL
SELECT `id`, `name`
FROM `web_users`
) `r` ON `r`.`id` = `l`.`researcher`
LEFT JOIN (
SELECT '0' AS 'id', 'Unassigned' AS 'name'
UNION ALL
SELECT `id`, `name`
FROM `web_users`
) `s` ON `s`.`id` = `l`.`surveyor`
LEFT JOIN (
SELECT '0' AS 'id', 'Unassigned' AS 'name'
UNION ALL
SELECT `id`, `name`
FROM `web_users`
) `a` ON `a`.`id` = `l`.`acquired_by`
WHERE `l`.`id` = 566
But as you can see, it has the same sub-query in it three times. Is there any way to execute this query once and store the result, so I can LEFT JOIN with the cached results instead of executing the same query three times?
I have tried storing it in a variable:
SET #usercache = (
SELECT '0' AS 'id', 'Unassigned' AS 'name'
UNION ALL
SELECT `id`, `name`
FROM `web_users`
)
...but this gives me an error:
1241 - Operand should contain 1 column(s)
...and some Googling on this error has left me none the wiser.
Does anyone know how I can make this query more efficient? Or am I just worrying about something that doesn't matter anyway?
I am using PHP/MySQLi if it makes any difference.
Do you really need the subqueries? How about this:
SELECT
`l`.`status`,
`l`.`acquired_by`, COALESCE(`a`.`name`, 'Unassigned') AS 'acquired_by_name',
`l`.`researcher`, COALESCE(`r`.`name`, 'Unassigned') AS 'researcher_name',
`l`.`surveyor`, COALESCE(`s`.`name`, 'Unassigned') AS 'surveyor_name'
FROM `leads` `l`
LEFT JOIN `web_users` `r` ON `r`.`id` = `l`.`researcher`
LEFT JOIN `web_users` `s` ON `s`.`id` = `l`.`surveyor`
LEFT JOIN `web_users` `a` ON `a`.`id` = `l`.`acquired_by`
WHERE `l`.`id` = 566
you cannot run it once - you are actually using it three times to get three different results...