mysql join only if a field has a particular value - mysql

I am trying to fetch a table on certain conditions with join. My table is:
tab_registrations
--------------------------------------------
reg_id |familyid| familyname | parent_id |
| | | |
-------|--------|-------------|-----------|
1 | 2 | null | null |
-------|--------|-------------|-----------|
2 | others | abc | 3 |
-------|--------|-------------|-----------|
3 | 3 | null | null |
-------|--------|-------------|-----------|
4 | others | def | 2 |
-------|--------|-------------|-----------|
tab_family
-------------------------------------
family_id | family_name | parent_id |
| | |
-------------------------------------
1 | tyu | 0 |
-------------------------------------
2 | xyz | 1 |
-------------------------------------
3 | mno | 2 |
-------------------------------------
I want to join these tables on:
if tab_registrations.family not equal to null, then select corresponding parent_id from tab_family
SELECT tab_registration.*,tab_family.family_id,tab_family.parent_id
FROM `tab_registration`
join tab_family on tab_registration.family_id = tab_family.family_id
WHERE reg_id = 1
if tab_registrations.family is equal to 'others', then select tab_registrations.familyname and tab_registrations.parent_id
When I try the above query if tab_registrations.family = 'others', no rows fetched
How can I achieve this? Can anyone help me?

Change to LEFT JOIN with the condition that tab_registration.familyid is not equal to others. Also, you can use conditional CASE..WHEN statements to get the familyname and parent_id values.
SELECT tr.*,
CASE WHEN tr.familyid = 'others' THEN tr.familyname
ELSE tf.family_name
END AS familyname,
CASE WHEN tr.familyid = 'others' THEN tr.parent_id
ELSE tf.parent_id
END AS parent_id
FROM tab_registration tr
LEFT JOIN tab_family tf
ON tr.family_id = tf.family_id AND
tr.familyid <> 'others'
WHERE tr.reg_id = 1
For multi-table queries, it if preferable to use Aliasing for code clarity and readability.

may be useful this query
SELECT tr.*,tf.family_id,tf.parent_id,
IF(tr.familyid='others',tr.familyname,tf.family_name) as fname
IF(tr.familyid='others',tr.parent_id,tf.parent_id) as parentId
FROM `tab_registration` tr
left join tab_family tf on tr.family_id = tf.family_id

Related

Get all person rows when left joined values missing [duplicate]

I have two tables and need to get all rows from the first one and then check which values from the second table match the first table.
My goal is to get all so called 'achievements' and then check which one the user has reached.
achievements
+---------------+-------------+
| achievementID | description |
+---------------+-------------+
| 1 | goal1 |
| 2 | goal2 |
| 3 | goal3 |
+---------------+-------------+
achievement_user
+---------------------+---------------+--------+
| achievementRecordID | achievementID | userID |
+---------------------+---------------+--------+
| 1 | 1 | 1 |
| 2 | 1 | 3 |
| 3 | 4 | 2 |
| 4 | 3 | 1 |
+---------------------+---------------+--------+
My desired result for a query where I check the results for userID = 1 would be something like
+---------------+---------------+--------------+
| achievementID | description | solvedByUser |
+---------------+---------------+--------------+
| 1 | goal1 | true |
| 2 | goal2 | false |
| 3 | goal3 | true |
+---------------+---------------+--------------+
The new column solvedByUser could be basically any datatype (boolean, int, ...).
I just need a list of all available achievements and then see which one the user has reached.
You can left join the achievments table with achievement_user:
select a.*, (au.userID is not null) solvedByUser
from achievements a
left join achievement_user au
on au.achievementID = a.achievementID
and au.userID = 1
solvedByUser is a 0/1 flag that indicates whether the given achievement was reached by the given user.
I think you need a left join -
SELECT A.achievementID, A.description, CASE WHEN AU.userID IS NOT NULL THEN 'true' ELSE 'false' solvedByUser
FROM achievements A
LEFT JOIN achievement_user AU ON A.achievementID = AU.achievementID
AND userID = 1; -- YOUR INPUT ID
You need a left join:
select a.*,
case when u.achievementID is null then 'false' else 'true' end solvedByUser
from achievements a left join achievement_user u
on u.achievementID = a.achievementID and u.userid = 1

Performant way to self-join and filter by revised rows

I'm trying to select all rows in this table, with the constraint that revised id's are selected instead of the original ones. So, if a row has a revision, that revision is selected instead of that row, if there are multiple revision numbers the highest revision number is preferred.
I think an example table, output, and query will explain this better:
Table:
+----+-------+-------------+-----------------+-------------+
| id | value | original_id | revision_number | is_revision |
+----+-------+-------------+-----------------+-------------+
| 1 | abcd | null | null | 0 |
| 2 | zxcv | null | null | 0 |
| 3 | qwert | null | null | 0 |
| 4 | abd | 1 | 1 | 1 |
| 5 | abcde | 1 | 2 | 1 |
| 6 | zxcvb | 2 | 1 | 1 |
| 7 | poiu | null | null | 0 |
+----+-------+-------------+-----------------+-------------+
Desired Output:
+----+-------+-------------+-----------------+
| id | value | original_id | revision_number |
+----+-------+-------------+-----------------+
| 3 | qwert | null | null |
| 5 | abcde | 1 | 2 |
| 6 | zxcvb | 2 | 1 |
| 7 | poiu | null | null |
+----+-------+-------------+-----------------+
View Called revisions_max:
SELECT
responses.original_id AS original_id,
MAX(responses.revision_number) AS revision
FROM
responses
WHERE
original_id IS NOT NULL
GROUP BY responses.original_id
My Current Query:
SELECT
responses.*
FROM
responses
WHERE
id NOT IN (
SELECT
original_id
FROM
revisions_max
)
AND
is_revision = 0
UNION
SELECT
responses.*
FROM
responses
INNER JOIN revisions_max ON revisions_max.original_id = responses.original_id
AND revisions_max.revision_number = responses.revision_number
This query works, but takes 0.06 seconds to run. With a table of only 2000 rows. This table will quickly start expanding to tens or hundreds of thousands of rows. The query under the union is what takes most of the time.
What can I do to improve this queries performance?
How about using coalesce()?
SELECT COALESCE(y.id, x.id) AS id,
COALESCE(y.value, x.value) AS value,
COALESCE(y.original_id, x.original_id) AS original_id,
COALESCE(y.revision_number, x.revision_number) AS revision_number
FROM responses x
LEFT JOIN (SELECT r1.*
FROM responses r1
INNER JOIN (SELECT responses.original_id AS
original_id,
Max(responses.revision_number) AS
revision
FROM responses
WHERE original_id IS NOT NULL
GROUP BY responses.original_id) rev
ON r1.original_id = rev.original_id
AND r1.revision_number = rev.revision) y
ON x.id = y.original_id
WHERE y.id IS NOT NULL
OR x.original_id IS NULL;
The approach I would take with any other DBMS is to use NOT EXISTS:
SELECT r1.*
FROM Responses AS r1
WHERE NOT EXISTS
( SELECT 1
FROM Responses AS r2
WHERE r2.original_id = COALESCE(r1.original_id, r1.id)
AND r2.revision_number > COALESCE(r1.revision_number, 0)
);
To remove any rows where a higher revision number exists for the same id (or original_id if it is populated). However, in MySQL, LEFT JOIN/IS NULL will perform better than NOT EXISTS1. As such I would rewrite the above as:
SELECT r1.*
FROM Responses AS r1
LEFT JOIN Responses AS r2
ON r2.original_id = COALESCE(r1.original_id, r1.id)
AND r2.revision_number > COALESCE(r1.revision_number, 0)
WHERE r2.id IS NULL;
Example on DBFiddle
I realise that you have said that you don't want to use LEFT JOIN and check for nulls, but I don't see that there is a better solution.
1. At least this was the case historically, I don't actively use MySQL so don't keep up to date with developments in the optimiser

conditional mysql query multiple tables

I have 2 tables in mysql database as shown below. I am looking for a query that will select * from books but if preview_image = 'none' then preview_image = the hash_id of the row with the largest size where books.id = images.parentid. Hope this makes sense.
table books
+----------------+---------------+
| id | title | preview_image |
+----------------+---------------+
| 1 | book1 | 55859076d906 |
| 2 | book2 | 20a14f9fd7cf |
| 3 | book3 | none |
| 4 | book4 | ce805ecff5c9 |
| 5 | book5 | e60a7217b3e2 |
+----------------+---------------+
table images
+-------------+------+---------------+
| parentid | size | hash_id |
+--------------------+---------------+
| 2 | 100 | 55859076d906 |
| 1 | 200 | 20a14f9fd7cf |
| 3 | 300 | 34805fr5c9e5 |
| 3 | 400 | ce805ecff5c9 |
| 3 | 500 | e60a7217b3e2 |
+--------------------+---------------+
Thanks
You can use SUBSTRING_INDEX() to obtain the first record from a sorted GROUP_CONCAT(), and switch using a CASE expression:
SELECT books.id, books.title, CASE books.preview_image
WHEN 'none' THEN SUBSTRING_INDEX(
GROUP_CONCAT(images.hash_id ORDER BY images.size DESC SEPARATOR ',')
, ',', 1)
ELSE books.preview_image
END AS preview_image
FROM books LEFT JOIN images ON images.parentid = books.id
GROUP BY books.id
Write a subquery that finds the desired hash ID for each parent ID, using one of the techniques in SQL Select only rows with Max Value on a Column. Then join this with the books table.
SELECT b.id, b.title, IF(b.preview_image = 'none', i.hash_id, b.preview_image) AS image
FROM books AS b
LEFT JOIN (SELECT i1.parentid, i1.hash_id
FROM images AS i1
JOIN (SELECT parentid, MAX(size) AS maxsize
FROM images
GROUP BY parentid) AS i2
ON i1.parentid = i2.parentid AND i1.size = i2.size) AS i
ON b.id = i.parentid

Joining and nesting queries in mysql

Currently, I'm using this nice query:
select
users.name,
sum(race_results.winnings) as total_winnings,
count(CASE WHEN race_results.place=1 THEN 1 ELSE 0 END) AS times_won_first_place
from users
inner join race_results
where race_results.userid = users.id and race_results.place = 1
group by users.id
order by total_winnings desc
to get this
************************************************
| name | total_winnings | times_won_first_place |
| Bob | 4000 | 4 |
| John | 1000 | 1 |
************************************************
the race_results table looks like this
*******************************************
| id | raceid | userid | place | winnings |
| 1 | 1 | 1 | 1 | 1000 |
| 2 | 1 | 2 | 5 | 50 |
| 3 | 1 | 3 | 6 | 50 |
| 4 | 2 | 1 | 1 | 1000 |
| 5 | 2 | 2 | 3 | 250 |
*******************************************
I would like to include four three more columns for something like this
***************************************************************************
| name | total_winnings | total_races | 1st_place | 2nd_place | 3rd_place |
| Bob | 4000 | 5 | 4 | 0 | 0 |
| John | 1000 | 5 | 1 | 1 | 1 |
***************************************************************************
If I were to do separate queries for the new columns, I'd use
select count(raceid) from race_results where userid = 1
select count(raceid) from race_results where userid = 1 and place = 1
select count(raceid) from race_results where userid = 1 and place = 2
select count(raceid) from race_results where userid = 1 and place = 3
to do separate queries would be easy but with the existing query I had to use CASE just to get the count of times a user won 1st place. (using
count(CASE WHEN race_results.place=2 THEN 1 ELSE 0 END)
returns the same results).
How would I nest these or join them into my existing query to get what I want?
You can do it this way:
select
users.name,
sum(race_results.winnings) as total_winnings,
count(*) AS total_races,
sum(race_results.place = 1) AS times_won_first_place ,
sum(race_results.place = 2) AS times_won_second_place,
sum(race_results.place = 3) AS times_won_third_place
from users
inner join race_results
where race_results.userid = users.id
group by users.id
order by total_winnings desc;
With ANSI standard SQL you could use case expressions inside the sum function but since MySQL (and some other databases) evaluate boolean expressions to 1 for true you can replace the case expression with the just the condition to evaluate and then just sum them.
So instead of CASE WHEN race_results.place=1 THEN 1 ELSE 0 END you can do sum(race_results.place=1) and save some space and typing :)
See this SQL Fiddle for an example.

MySQL: joining tables with multi results rows to one row

I have 4 tables:
secu_content
| id | created | modified |
| 910 | 26/12/1982 | 28/12/1984 |
| 911 | 24/12/1982 | 25/12/1984 |
secu_data
| element_id | field_id | data |
| 1 | 1 | 25/12/1984 |
| 2 | 1 | 26/12/1984 |
| 3 | 1 | 27/12/1984 |
| 4 | 1 | 25/12/1984 |
| 4 | 2 | google.com |
secu_elements
| id | item_id |
| 1 | 891 |
| 2 | 711 |
| 3 | 204 |
| 4 | 911 |
secu_fields
| id | type |
| 1 | date |
| 2 | input |
Table secu_content, contains many articles, where the id is the article id.
The other 3 tables gives additional information and I want to join them.
I want to get results that includes all secu_content rows and all the columns + calc_date + calc_link
calc_date <- the data column from secu_data where field_id=1 (see secu_fields)
calc_link <- the data column from secu_data where field_id=2 (see secu_fields)
The problem is that I get 2 rows where secu_content id=911 (one row with the correct calc_date and second row with the correct calc_link), and I need one row with both.
This is my SQL:
SELECT a.id
, a.created
, a.modified
, fe.item_id AS calc_date_item_id
, fd.data AS calc_date
, CASE WHEN fd.data IS NOT NULL AND ff.type = "date" THEN fd.data
WHEN a.modified = '0000-00-00 00:00:00' THEN a.created ELSE a.modified
END as calc_date
, CASE WHEN fd.data IS NOT NULL AND ff.type = "input" THEN fd.data
END as calc_link
FROM secu_content AS a
LEFT
JOIN secu_fieldsandfilters_elements AS fe
ON fe.item_id = a.id
AND fe.content_type_id=1
LEFT
JOIN secu_fieldsandfilters_data AS fd
ON fd.element_id = fe.id
LEFT
JOIN secu_fieldsandfilters_fields as ff
ON ff.id = fd.field_id
ORDER BY a.id DESC;
Thanks in advance
Israel
Fast and dirty solution is to use second join to secu_data like that (simplified, add logic you need)
SELECT id, d1.data as `calc_date`, d2.data as `calc_link`
FROM secu_content
LEFT JOIN secu_data d1 ON secu_content.id = d1.element_id AND field_id = 1
LEFT JOIN secu_data d2 ON secu_content.id = d2.element_id AND field_id = 2