Mysql group_concat with distinct and where gives strange results - php

I have the following query which works fine (see below).
But when I add a condition, for example AND (specialtyName = '...') the main results are fine, but the GROUP_CONCAT only shows the results that match the condition.
Can anyone please help me with this?
Thanks in advance.
Fred.
SELECT
tblJobs.jobID,
tblJobs.jobName,
DATE_FORMAT(tblJobs.jobDate,'%d-%m-%Y'),
tblCompanies.companyID,
tblCompanies.companyName,
tblCompanies.companyNameConvert,
GROUP_CONCAT(DISTINCT tblSpecialties.specialtyName
ORDER BY FIELD (
specialtyName,
'specialtyName1',
'specialtyName2',
'specialtyName3'),
specialtyName ASC)
AS specialtyNames,
GROUP_CONCAT(DISTINCT tblSpecialties.specialtyNameConvert
ORDER BY FIELD (
specialtyName,
'specialtyName1',
'specialtyName2',
'specialtyName3'),
specialtyName ASC)
AS specialtyNamesConvert,
GROUP_CONCAT(DISTINCT tblRegions.regionName),
GROUP_CONCAT(DISTINCT tblRegions.regionNameConvert)
FROM tblJobs
LEFT JOIN tblCompanies ON
(tblJobs.jobCompany = tblCompanies.companyID)
LEFT JOIN tblSpecialties ON
FIND_IN_SET(tblSpecialties.specialtyID, REPLACE(tblJobs.jobSpecialty,' ',','))
LEFT JOIN tblRegions ON
FIND_IN_SET(tblRegions.regionID, REPLACE(tblJobs.jobRegion,' ',','))
WHERE
AND jobActive = '1'
AND jobDate >= '2013-01-01'
AND companyActive = '1'
GROUP BY jobID
ORDER BY jobDate DESC, jobID DESC, jobCompany DESC

If you say:
WHERE jobActive = '1' AND jobDate >= '2013-01-01' AND companyActive = '1' AND
specialties = XXX
Then you are only going to get exactly those specialties. The filtering is done before the aggregation. As a note: including such conditions in the where clause also turns the outer joins to inner joins. Your joins are probably on properly aligned foreign key relationships, so inner joins may be appropriate.
I'm guessing what you really want is to filter jobs by those having that specialty, but to keep all other information. You want to do the filtering after the aggregation. Do this with a having clause instead of a where clause:
having sum(specialties = XXX) > 0;
This will keep only the rows that have the particular specialty, and keep all the other information.

I suppose that using aliases for your tables and subqueries could resolve your problem.
You can try something like this:
SELECT
tblJobs.jobID,
tblJobs.jobName,
DATE_FORMAT(tblJobs.jobDate,'%d-%m-%Y'),
tblCompanies.companyID,
tblCompanies.companyName,
tblCompanies.companyNameConvert,
(SELECT GROUP_CONCAT(DISTINCT ts.specialtyName
ORDER BY FIELD (
specialtyName,
'specialtyName1',
'specialtyName2',
'specialtyName3'),
specialtyName ASC)
FROM tblSpecialties ts) AS specialtyNames ,
, ... ,
FROM tblJobs
LEFT JOIN tblCompanies ON
(tblJobs.jobCompany = tblCompanies.companyID)
LEFT JOIN tblSpecialties ON
FIND_IN_SET(tblSpecialties.specialtyID, REPLACE(tblJobs.jobSpecialty,' ',','))
LEFT JOIN tblRegions ON
FIND_IN_SET(tblRegions.regionID, REPLACE(tblJobs.jobRegion,' ',','))
WHERE
AND jobActive = '1'
AND jobDate >= '2013-01-01'
AND companyActive = '1'
GROUP BY jobID
ORDER BY jobDate DESC, jobID DESC, jobCompany DESC
I didn't tested this code, but It could help.

Related

EXISTS query optimization on mysql query

I have a big data problem with MySQL.
I have:
a users table with 59033 rows, and
a user_notes table with 8753 rows.
But when I search which users have user note in some dates.
My query like this :
SELECT u.*, rep.name as rep_name FROM users as u
LEFT JOIN users as rep on rep.id = u.add_user
LEFT JOIN authorization on authorization.id = u.authorization
LEFT JOIN user_situation_list on user_situation_list.user_situation_id = u.user_situation
WHERE
EXISTS(
select * from user_notes
where user_notes.note_user_id = u.id AND user_notes.create_date
BETWEEN "2017-10-20" AND "2017-10-22"
)
ORDER BY u.lp_modify_date DESC, u.id DESC
Turn it around -- find the ids first; deal with the joins later.
SELECT u.*,
( SELECT rep.name
FROM users AS rep
WHERE rep.id = u.add_user ) AS rep_name
FROM (
SELECT DISTINCT note_user_id
FROM user_notes
WHERE create_date >= "2017-10-20"
AND create_date < "2017-10-20" + INTERVAL 3 DAY
) AS un
JOIN users AS u ON u.id = un.note_user_id
ORDER BY lp_modify_date DESC, id DESC
Notes
No GROUP BY needed;
2 tables seem to be unused; I removed them;
I changed the date range;
User notes needs INDEX(create_date, note_user_id);
Notice how I turned a LEFT JOIN into a subquery in the SELECT list.
If there can be multiple rep_names, then the original query is "wrong" in that the GROUP BY will pick a random name. My Answer can be 'fixed' by changing rep.name to one of these:
MAX(rep.name) -- deliver only one; arbitrarily the max
GROUP_CONCAT(rep.name) -- deliver a commalist of names
Rewriting your query to use a JOIN rather than an EXISTS check in the where should speed it up. If you then group the results by the user.id it should give you the same result:
SELECT u.*, rep.name as rep_name FROM users as u
LEFT JOIN users as rep on rep.id = u.add_user
LEFT JOIN authorization on authorization.id = u.authorization
LEFT JOIN user_situation_list on user_situation_list.user_situation_id = u.user_situation
JOIN user_notes AS un
ON un.note_user_id
AND un.create_date BETWEEN "2017-10-20" AND "2017-10-22"
GROUP BY u.id
ORDER BY u.lp_modify_date DESC, u.id DESC

Doctrine Query Language get Max/Latest Row Per Group

I am trying and failing to translate my relatively simple SQL statement into one that will work within Doctrine.
This is the SQL statement, which works as required when run against my database:
SELECT a.*
FROM score a
INNER JOIN (
SELECT name, MAX(score) AS highest
FROM score
GROUP BY name
) b
ON a.score = b.highest AND a.name = b.name
GROUP BY name
ORDER BY b.highest DESC, a.dateCreated DESC
Here's the DQL attempt thus far:
$kb = $em->createQuery(
"SELECT a
FROM ShmupBundle:Score a
INNER JOIN a.name ShmupBundle:Score b WITH a.score = b.score AND a.name = b.name GROUP BY b.name
WHERE a.platform='keyboard'
GROUP BY a.name
ORDER BY b.score DESC, a.dateCreated DESC"
);
Which is currently giving this error:
[Semantical Error] line 0, col 73 near 'ShmupBundle:Score': Error: Class ShmupBundle\Entity\Score has no association named name
The table itself is pretty simple:
id, name, score, platform, dateCreated
There are multiple entries with the same name, but different scores. I want to show only the "high score" per name. I've been trying on and off for a day or two now, with no luck. Can anyone point me in the right direction?
The query you are trying to do with doctrine is related to greatest-n-per-group. To use a sub query and then join with main query get things complicated to handle with doctrine. So below is the rewritten SQL version to get the same results without use of any aggregate functions:
SELECT
a.*
FROM
score a
LEFT JOIN score b
ON a.name = b.name
AND a.score < b.score
WHERE b.score IS NULL
ORDER BY a.score DESC
DEMO
To convert above query equivalent to doctrine or DQL is easy, below is the DQL version of above SQL:
SELECT a
FROM AppBundle\Entity\Score a
LEFT JOIN AppBundle\Entity\Score b
WITH a.name = b.name
AND a.score < b.score
WHERE b.score IS NULL
ORDER BY a.score DESC
Or with query builder you can write something like i have tested below with symfony 2.8 using the DEMO Schema
$DM = $this->get( 'Doctrine' )->getManager();
$repo = $DM->getRepository( 'AppBundle\Entity\Score' );
$results = $repo->createQueryBuilder( 'a' )
->select( 'a' )
->leftJoin(
'AppBundle\Entity\Score',
'b',
'WITH',
'a.name = b.name AND a.score < b.score'
)
->where( 'b.score IS NULL' )
->orderBy( 'a.score','DESC' )
->getQuery()
->getResult();
Another idea would be create a view using your query in database and in symfony create an entity put the view name in table annotation and just start calling your entity it will give the results returned by your query but this approach is not recommended just a temporary fix.
Inner Join Statement needs first argument as a table, that is a semantic error in your query.
$kb = $em->createQuery(
"SELECT a
FROM ShmupBundle:Score a
INNER JOIN ShmupBundle:Score b ON a.score = b.score AND a.name = b.name GROUP BY b.name
WHERE a.platform='keyboard'
GROUP BY a.name
ORDER BY b.score DESC, a.dateCreated DESC");
MySQL does not understand the : syntax. If ShmupBundle:Score is supposed to be a database and table, then use .. If Doctrine is supposed to replace it with something, then what does it do with it?
There can be only one GROUP BY clause, and it must be after the WHERE clause. Try removing the GROUP BY b.name.
There is no need to GROUP BY both b.name and a.name since they are equal.
use this
in class
$name = $em->getRepository('AppBundle:BlogPost')->getMaxId();
in repository you can use something like
public function getMaxId()
{
$qb = $this->createQueryBuilder('u');
$qb->select('u, MAX(id) as idMax');
return $qb->getQuery()->getSingleResult();
}
each entity come with some default repository functions either we defined or not
if we wish to add some extra functionality we do write down custom functions in repository class. like i want to add one more function getMaxId i will write down in repository to access this function.
for getting max or min from each group we can do with given query
select * from (select * from mytable order by `Group`, age desc, Person) x group by `Group
this is not good way to fetch max from each group as we need to write down sub query for that.
other than that we have Row_number() function
SELECT sd.* FROM ( SELECT sale_person_id,sale_person_name,no_products_sold,commission_percentage,sales_department,ROW_NUMBER() OVER(PARTITION BY sale_person_id ORDER BY no_products_sold DESC) rowNumber FROM sales_department_details )sd WHERE sd.rowNumber =1;
here you find out all work arounds

Conditionally order by another column

I query a database and order by a date column in the table and also by another column in another table. This is working perfectly.
SELECT * FROM myTable1 LEFT JOIN myTable2 b ON myTable1.id = b.id WHERE myTable1.id !='foo' ORDER BY myTable1.dateColumn DESC, b.column2 ASC
What I would like is to do is ORDER by myTable1.dateColumn ONLY if there is no value in b.column2 for that particular record. As you can see from my above current implementation, it will always ORDER BY myTable1.dateColumn regardless.
Stackoverflow Suggested tag for this question is mysql but in fact I am using mysqli. It makes no difference for the purpose of this question.
You should better provide some data sample and expected result, but so far you can try:
SELECT * FROM myTable1
LEFT JOIN myTable2 b
ON myTable1.id = b.id
WHERE myTable1.id !='foo'
ORDER BY CASE WHEN b.column2 IS NULL myTable1.dateColumn ELSE NULL END DESC, b.column2 ASC
You can put CASE Statements inside of an ORDER BY
SELECT *
FROM myTable1
LEFT JOIN myTable2 b
ON myTable1.id = b.id
WHERE myTable1.id != 'foo'
ORDER BY CASE WHEN b.column2 IS NULL THEN 1 ELSE 0 END ASC, myTable1.dateColumn DESC
You can use a function in the order part; something like the following:
SELECT * FROM myTable1 LEFT JOIN myTable2 b ON myTable1.id = b.id WHERE myTable1.id !='foo'
ORDER BY IF(b.column2 IS NULL,1,0),IF(b.column2 IS NULL,myTable1.dateColumn,NULL) DESC, b.column2 ASC

Limiting a left join to returning one result?

I currently have this left join as part of a query:
LEFT JOIN movies t3 ON t1.movie_id = t3.movie_id AND t3.popularity = 0
The trouble is that if there are several movies with the same name and same popularity (don't ask, it just is that way :-) ) then duplicate results are returned.
All that to say, I would like to limit the result of the left join to one.
I tried this:
LEFT JOIN
(SELECT t3.movie_name FROM movies t3 WHERE t3.popularity = 0 LIMIT 1)
ON t1.movie_id = t3.movie_id AND t3.popularity = 0
The second query dies with the error:
Every derived table must have its own alias
I know what I'm asking is slightly vague since I'm not providing the full query, but is what I'm asking generally possible?
The error is clear -- you just need to create an alias for the subquery following its closing ) and use it in your ON clause since every table, derived or real, must have its own identifier. Then, you'll need to include movie_id in the subquery's select list to be able to join on it. Since the subquery already includes WHERE popularity = 0, you don't need to include it in the join's ON clause.
LEFT JOIN (
SELECT
movie_id,
movie_name
FROM movies
WHERE popularity = 0
ORDER BY movie_name
LIMIT 1
) the_alias ON t1.movie_id = the_alias.movie_id
If you are using one of these columns in the outer SELECT, reference it via the_alias.movie_name for example.
Update after understanding the requirement better:
To get one per group to join against, you can use an aggregate MAX() or MIN() on the movie_id and group it in the subquery. No subquery LIMIT is then necessary -- you'll receive the first movie_id per name withMIN() or the last with MAX().
LEFT JOIN (
SELECT
movie_name,
MIN(movie_id) AS movie_id
FROM movies
WHERE popularity = 0
GROUP BY movie_name
) the_alias ON t1.movie_id = the_alias.movie_id
LEFT JOIN movies as m ON m.id = (
SELECT id FROM movies mm WHERE mm.movie_id = t1.movie_id
ORDER BY mm.id DESC
LIMIT 1
)
you could try to add GROUP BY t3.movie_id to the first query
Try this:
LEFT JOIN
(
SELECT t3.movie_name, t3.popularity
FROM movies t3 WHERE t3.popularity = 0 LIMIT 1
) XX
ON t1.movie_id = XX.movie_id AND XX.popularity = 0
On MySQL 5.7+ use ANY_VALUE & GROUP_BY:
SELECT t1.id,t1.movie_name, ANY_VALUE(t3.popularity) popularity
FROM t1
LEFT JOIN t3 ON (t3.movie_id=t1.movie_id AND t3.popularity=0)
GROUP BY t1.id
more info
LEFT JOIN only first row
https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
Easy solution to left join the 1 most/least recent row is using select over ON phrase
SELECT A.ID, A.Name, B.Content
FROM A
LEFT JOIN B
ON A.id = (SELECT MAX(id) FROM B WHERE id = A.id)
Where A.id is the auto-incremental primary key.
LEFT JOIN (
SELECT id,movie_name FROM movies GROUP BY id
) as m ON (
m.id = x.id
)
// Mysql
SELECT SUM(db.item_sales_nsv) as total FROM app_product_hqsales_otc as db
LEFT JOIN app_item_target_otc as it ON
db.id = (SELECT MAX(id) FROM app_item_target_otc as ot WHERE id = db.id)
and db.head_quarter = it.hqcode
AND db.aaina_item_code = it.aaina_item_code AND db.month = it.month
AND db.year = it.year
WHERE db.head_quarter = 'WIN001' AND db.month = '5' AND db.year = '2022' AND db.status = '1'

MySQL Where to put in this statement?

This is a bit beyond my skills and I had a lot of help from the good people at SO to get this far. What I need now is to put in a MATCH() ... AGAINST() but I don't know where to insert it?
The query I have is (and this is the short version):
SELECT
SQL_CALC_FOUND_ROWS i.idItems RowCount,
i.* Items,
# Create a JSON formatted field
CONCAT('{',GROUP_CONCAT('"',Attributes.key, '":"', CONVERT(Attributes.value,CHAR),'"'),'}') as Attributes,
IF (te.Key IS NULL,tp.Key,te.Key) as Type,
tc.Value Color,
l.* Location,
c.Name,
c.Mobile,
c.Mail
FROM
(SELECT ItemID, ats.Key, ats.Value FROM attributeStrings as ats
UNION ALL
SELECT ItemID, ati.Key, ati.Value FROM attributeIntegers as ati
) Attributes
JOIN Items i ON
i.idItems = Attributes.ItemID
AND CheckIn >= DATE_SUB('2011-02-16 00:00:00',INTERVAL 90 DAY)
AND CheckIn <= DATE_ADD('2011-02-16 23:59:59',INTERVAL 90 DAY)
AND Checkout IS NULL
LEFT JOIN Customers c ON c.idCustomers = i.CustomerID
LEFT JOIN attributeintegers atli ON atli.itemid = i.idItems AND atli.key = 'Location'
LEFT JOIN locations l ON l.id = atli.value
LEFT JOIN attributestrings atts ON atts.itemid = i.idItems AND atts.key = 'Type' LEFT
JOIN Lists tp ON tp.value = atts.value
LEFT JOIN attributestrings attes ON attes.itemid = i.idItems AND attes.key = 'Tech' LEFT
JOIN Lists te ON te.value = attes.value
LEFT JOIN attributeintegers atci ON atci.itemid = i.idItems AND atci.key = 'Color' LEFT
JOIN Strings tc ON tc.StringID = atci.value
GROUP BY Attributes.ItemID
ORDER BY CheckIn DESC
Now I need to get this statement in here somewhere
MATCH(attributestrings.Value) AGAINST("Nokia" IN BOOLEAN MODE)
As you can see there is a table called attributestrings and it has 3 columns: ItemID,*Key* and Value. I need to search the column Value for the words in the AGAINST() and only show results matching this and the other criterias such as the Date and Checkout above.
I tried to add the statement after the AND Checkout IS NULL like this:
AND Checkout IS NULL
AND MATCH(Attributes.Value) AGAINST("Nokia" IN BOOLEAN MODE)
I had to use the Attributes.Value instead of attributestrings because it didn't found the table. This only resulted in the CONCATENATED column Attributes only contained the value "Nokia", even if there where more to CONCATENATE.
I hope someone are willing to take on this challenge...
// Tank you.
[EDIT]
I tried to put in the WHERE before the GROUP as Tim Fultz sugested, but I get the error
Unknown column 'attributestrings.Value' in 'Where clause'
LEFT JOIN attributeintegers atci ON atci.itemid = i.idItems AND atci.key = 'Color' LEFT JOIN Strings tc ON tc.StringID = atci.value
WHERE MATCH(attributestrings.Value) AGAINST("Nokia Sony" IN BOOLEAN MODE)
GROUP BY Attributes.ItemID
Typically this is put in the Where clause:
WHERE MATCH(attributestrings.Value) AGAINST("Nokia" IN BOOLEAN MODE)
I think I came up with a solution...
I added another:
LEFT JOIN attributestrings fts ON fts.itemid = i.idItems AND MATCH(fts.Value) AGAINST("Nokia Sony" IN BOOLEAN MODE)
Just before the GROUP BY... and then included fts.Value ftsv in the main select statement. Now I could insert a HAVING ftsv IS NOT NULL between GROUP BY... and ORDER BY...
This gave me the result I wanted but the query starts to get a bit slow now...
There is a problem in your query when you are assigning table names with as use every where in you query the name you assigned. Like you gave attributestrings as ats now use ats everywhere and this will work.

Categories