Say I have the below 2 tables:
text
id
text_lang
id
media_id
lang
text
(these are not the actual table structures - but I just have 2 tables with one containing all the localized strings that "belong" to the first table)
I want to join the tables, with a "preferred" language. So basically:
SELECT text.id, text_lang.text
FROM text
LEFT JOIN text_lang ON text.id = text_lang.text_id
AND (only a single row, and if there's a row with lang="en", pick that,
otherwise just pick any language)
The part I'm having trouble with is the "only a single row, and if there's a row with lang="en" pick that, otherwise just pick any language".
I think this does what I'm looking for:
SELECT text.id, text_lang.text
FROM text
JOIN text_lang ON text_lang.text_id = text.id AND
text_lang.lang = (select lang
FROM text_lang sub_text_lang
WHERE sub_text_lang.text_id = text.id
ORDER BY text_lang.lang = "en" DESC
limit 1)
... but I'd like to avoid a sub-query if possible, as I assume that would be quite a hit to the query performance.
I can think of a few ways but since you're trying to avoid subquery, perhaps you can do something like this:
SELECT t.id,
SUBSTRING_INDEX(
GROUP_CONCAT(text ORDER BY CASE WHEN lang='en' THEN 0 ELSE RAND() END),',',1)
AS extracted_text
FROM
text t
LEFT JOIN text_lang tl ON tl.text_id=t.id
GROUP BY t.id;
Use GROUP_CONCAT() with a customized ORDER BY then use SUBTRING_INDEX() to get the first value using comma (the default GROUP_CONCAT() separator) as the delimiter.
I've made a fiddle with sample data here: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=0d0501f36af1ba4e181856f761dbd5f7 . You can run the fiddle a few times to see that the text value other than 'en' change.
Related
I have a MySQL query and I need to SUM the content of a column if the content contains the number 1,
This is what I have but the TotalFails returns 0
SELECT
d.SeqNo,
d.SeqText,
h.UniqueID,
h.SeqID,
h.Room,
IF(h.Status = '1', SUM(h.Status), 0) AS TotalFails
FROM ".$SequenceNo_default." d
LEFT
JOIN ".$Hist." h ON h.SeqID = d.SeqID
WHERE d.SeqActive = 1 AND DATE(CompStamp) BETWEEN DATE( '".$_SESSION['StartDate']."') AND DATE('".$_SESSION['EndDate']."')
GROUP BY h.Room
ORDER BY h.Status ASC
Can any one see where I am going wrong?
Please note that Status is a reserved keyword so you may want to change it to something else like myStatus in the statement above.
Edit
Based upon your updated question, I would rewrite your query as follows (again, avoiding the keyword STATUS by replacing Status with myStatus):
SELECT
d.SeqNo,
d.SeqText,
h.UniqueID,
h.SeqID,
h.Room,
SUM(h.myStatus) AS TotalFails
FROM ".$SequenceNo_default." d
LEFT
JOIN ".$Hist." h ON h.SeqID = d.SeqID
WHERE d.SeqActive = 1 AND DATE(CompStamp) BETWEEN DATE( '".$_SESSION['StartDate']."') AND DATE('".$_SESSION['EndDate']."')
GROUP BY h.Room, h.myStatus
ORDER BY h.myStatus ASC
As a separate side note, this should be a prepared query, rather than including the $_SESSION variable directly into the string. $_SESSION is better than $_POST or $_GET, but for maintaining the code, this would be a good habit (for example, what if the code later changes to $_POST?).
I've (on reflection ridiculously) stored our 'punters' in 2 tables depending on whether they registered or paid through the express checkout form.
My SQL looks like this:
SELECT
DISTINCT(sale_id), sale_punter_type, sale_comment, sale_refund, sale_timestamp,
punter_surname, punter_firstname,
punter_checkout_surname, punter_checkout_firstname,
punter_compo_surname, punter_compo_firstname,
sale_random, sale_scanned, sale_id
FROM
sale
LEFT JOIN
punter ON punter_id = sale_punter_no
LEFT JOIN
punter_checkout ON punter_checkout_id = sale_punter_no
LEFT JOIN
punter_compo ON punter_compo_id = sale_punter_no
WHERE
sale_event_no = :id
ORDER BY
punter_surname, punter_firstname,
punter_checkout_surname, punter_checkout_firstname
It returns results BUT lists the registered users alphabetically first, then the checkout punters alphabetically.
My question is there a way to get all of the users (registered or checkout) all together sorted alphabetically in 1 sorted list instead of 2 joined sorted lists.
I thought maybe I could use something like punter_checkout_surname AS punter_surname but that didn't work.
Any thoughts? I know now that I shouldn't have used 2 separate tables but but I'm stuck with it now.
Thank you.
I think you just want to use coalesce().
ORDER BY COALESCE(punter_surname, punter_checkout_surname)
COALESCE(punter_firstname, punter_checkout_firstname)
Other comments:
I doubt that DISTINCT is necessary. Why would this generate multiple rows for a single sale_id?
When a query has multiple tables, qualify all the column names (that is, include table aliases so you and others know where the table comes from).
Your data has three sets of names. That seems overkill.
You might want to put the COALESCE() in the SELECT so you don't have quite so many names generated by the query.
Here's my answer:
ORDER BY
COALESCE( UCASE( punter_surname) , UCASE( punter_checkout_surname ), UCASE( punter_compo_surname ) ) ,
COALESCE( UCASE( punter_firstname ) , UCASE( punter_checkout_firstname ), UCASE( punter_compo_firstname) )
I cant seem to find a solution for Searching a group_concatenated value,
I have 3 table that are connected with id's
1st table have the same value with 2nd table, but no same value with 3rd,
2nd table have the same value with 1st and 3rd table,
I want to get the value inside 3rd table,
concat the values in accordance to Distinct ID's of 2nd table, display them, and be able to search
this are my tables look like
how do i search for the concatenated values
please if there's a better way, your help is much appreciated?
the query below is what i have so far
$query = $db->prepare("
SELECT
a.problem_encountered,
GROUP_CONCAT(
DISTINCT
c.full_name)
AS
fnames
FROM
maintenance_sheet_table a
LEFT JOIN
mis_incharge_table b
ON
b.mis_incharge_id = a.mis_incharge_id
INNER JOIN
users_table c
ON
c.mis_id=b.mis_id
WHERE
a.problem_encountered
LIKE
:findMe
HAVING
fnames
LIKE
:findMe
GROUP BY a.id ORDER BY a.id
");
$query->bindValue(':findMe', '%' . $keywordSearch. '%');
A potential answer is to filter the Users_table in a subquery. There are a number of different forms of this option, and hard to tell from your data which is required. The one I have below simply returns the users that match the search criteria.
SELECT a.problem_encountered, GROUP_CONCAT(DISTINCT innerc.full_name) AS fnames
FROM maintenance_sheet_table a
LEFT JOIN mis_incharge_table b ON b.mis_incharge_id = a.mis_incharge_id
LEFT JOIN (SELECT c.mis_id, c.full_name
FROM users_table c
WHERE c.full_name LIKE :findMe) innerc ON innerc.mis_id=b.mis_id
WHERE a.problem_encountered LIKE :findMe
GROUP BY a.id
ORDER BY a.id
However, you could also do the concatenation within the subquery if required.
SELECT a.problem_encountered, innerc.fnames
FROM maintenance_sheet_table a
INNER JOIN (SELECT mit.mis_incharge_id, GROUP_CONCAT(DISTINCT ut.full_name) AS fnames
FROM users_table ut
INNER JOIN mis_incharge_table mit ON ut.user_id = mit.user_id
GROUP BY mit.mis_incharge_id
HAVING fnames LIKE :findMe) innerc ON innerc.mis_incharge_id = a.mis_incharge_id
WHERE a.problem_encountered LIKE :findMe
GROUP BY a.id
ORDER BY a.id
Note: I agree with spencer7593, that you shouldn't use the same :findMe variable against 2 separate fields. Even if it works, to a maintenance programmer or even yourself in a few years time, will probably look at this and think that the wrong fields are being interrogated.
You can "search" the return from the GROUP_CONCAT() expression in the HAVING clause. As a more efficient alternative, I suspect you could use an EXISTS predicate with a subquery.
I suspect part of the problem is that your query is referencing the same bind placeholder more than one time. (In previous releases of PDO, this was a restriction, a named bind placeholder could be referenced only once.)
The workaround to this issue is to use a separate bind placeholder, e.g.
HAVING fnames LIKE :findMeToo
And then bind a value to each placeholder:
$query->bindValue(':findMe', '%' . $keywordSearch. '%');
$query->bindValue(':findMeToo', '%' . $keywordSearch. '%');
(With this issue, I don't think PDO issued a warning or error; the effect was as if no value was supplied for the second reference to the named bind placeholder. Not sure if this issue is fixed, either by a code change or a documentation update. The workaround as above, reference a bind placeholder only once within a query.)
Beyond that, it's not clear what problem you are observing.
Your HAVING clause should come after your GROUP BY clause
change
HAVING
fnames
LIKE
:findMe
GROUP BY a.id ORDER BY a.id
to
GROUP BY a.id
HAVING
fnames
LIKE
:findMe
ORDER BY a.id
I use data from http://geonames.org. The table structure is as follows:
GN_Name 1 - 0:N GN_AlternateName
They are linked on:
(PK)GN_Name.GeoNameId == (FK)GN_AlternateName.GeoNameId
GN_Name is the main table containing all place names.
GN_AlternateName contains names in other languages if any.
EX:
GN_Name.Name - Stockholm
GN_AlternateName.AlternateName - Estocolmo (if IsoLanguage=="es")
Rules:
I want to use GN_AlternateName.AlternateName if it exists for the specified language and if it starts with the search string.
If not, i want to use GN_Name.Name if it starts with the search string.
I want GeoNameId to be unique.
Basically I could outer join in first record only, but that seemed to decrease performance.
I've got the following SQL (basically modified SQL from a LINQ query). The problem is that it only finds 'Estocolmo' if search string starts with "stock". "estoc" yields nothing.
select
distinct(n.GeoNameId) as Id,
an.IsoLanguage,
CASE WHEN (an.AlternateName like N'estoc%')
THEN an.AlternateName
ELSE n.Name
END AS [The name we are going to use]
from GN_Name as n
LEFT OUTER JOIN GN_AlternateName as an
ON n.GeoNameId = an.GeoNameId
AND 'es' = an.IsoLanguage
WHERE n.Name like N'estoc%'
UPDATE
Thanks Rahul and Lee D.
I now have the following:
select
distinct(n.GeoNameId) as Id,
an.IsoLanguage,
CASE WHEN (an.AlternateName like N'estoc%')
THEN an.AlternateName
ELSE n.Name
END AS [The final name]
from GN_Name as n
LEFT OUTER JOIN GN_AlternateName as an
ON n.GeoNameId = an.GeoNameId
AND 'es' = an.IsoLanguage
WHERE (n.Name LIKE N'estoc%' OR an.AlternateName LIKE N'estoc%')
This performs LIKE twice on an.AlternateName. Is there any way i could get rid of on LIKE clause?
UPDATE 2
Andriy M made a nice alternative query using COALESCE. I changed it a little bit and ended up with the following:
SELECT Id, LocalisedName
FROM (
SELECT
n.GeoNameId AS Id,
an.IsoLanguage,
COALESCE(an.AlternateName, n.Name) AS LocalisedName
FROM n
LEFT JOIN GN_AlternateName AS an ON n.GeoNameId = an.GeoNameId
AND IsoLanguage = 'es'
) x
WHERE LocalisedName LIKE 'estoc%'
This query does exactly what i am looking for. Thanks!
Here's a probable solution of the problem, which uses a slightly different apporach:
SELECT Id, LocalisedName
FROM (
SELECT
n.GeoNameId AS Id,
an.IsoLanguage,
COALESCE(an.AlternateName, n.Name) AS LocalisedName
FROM GN_Name AS n
LEFT JOIN GN_AlternateName AS an ON n.GeoNameId = an.GeoNameId
AND IsoLanguage = 'es'
) x
WHERE LocalisedName LIKE 'estoc%'
(Changed it based on your update.)
If I understand correctly, in your example the value 'Estocolmo' is in the GN_AlternateName.AlternateName column, so would be filtered out by the where clause which only looks at GN_Name.Name. What if you change the last line of SQL to:
WHERE n.Name LIKE N'estoc%' OR an.AlternateName LIKE N'estoc%'
I'm assuming 'estoc%' is your search string.
I guess you need to modify the WHERE clause to check in GN_AlternateName table as well
WHERE n.Name like N'estoc%' OR an.AlternateName like 'N'estoc%'
Not quite sure what I'm missing, but my SQL statement is only returning one row.
SELECT
tl.*,
(tl.topic_total_rating/tl.topic_rates) as topic_rating,
COUNT(pl.post_id) - 1 as reply_count,
MIN(pl.post_time) AS topic_time,
MAX(pl.post_time) AS topic_bump
FROM topic_list tl
JOIN post_list pl
ON tl.topic_id=pl.post_parent
WHERE
tl.topic_board_link = %i
AND topic_hidden != 1
ORDER BY %s
I have two tables (post_list and topic_list), and post_list's post_parent links to a topic_list's topic_id.
Instead of returning all the topics (where their board's topic_board_link is n), it only returns one topic.
You would normally need a GROUP BY clause in there. MySQL has different rules from Standard SQL on the subject of when GROUP BY is needed. This is therefore closer to Standard SQL:
SELECT tl.*,
(tl.topic_total_rating/tl.topic_rates) AS topic_rating,
COUNT(pl.post_id) - 1 AS reply_count,
MIN(pl.post_time) AS topic_time,
MAX(pl.post_time) AS topic_bump
FROM topic_list AS tl
JOIN post_list AS pl ON tl.topic_id = pl.post_parent
WHERE tl.topic_board_link = ? -- %i
AND tl.topic_hidden != 1
GROUP BY tl.col1, ..., topic_rating
ORDER BY ? -- %s
In Standard SQL, you would have to list every column in topic_list, plus the non-aggregate value topic_rating (and you might have to list the expression rather than the display label or column alias in the select list).
You also have a restriction condition on 'topic_board_link' which might be limiting your result set to one group. You cannot normally use a placeholder in the ORDER BY clause, either.