MYSQL query return empty result - mysql

I need to look up email preferences for users.
This table contains the types of email a user can receive, broken down by category.
email_preferences_categories
+----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | text | YES | | NULL | |
| overview | text | YES | | NULL | |
+----------+------------------+------+-----+---------+----------------+
This table contains their preference for receiving various types. If they haven't set their preferences, this table won't have any rows for them.
email_preferences
+------------+---------------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| user_id | int(10) unsigned | NO | | NULL | |
| name | text | YES | | NULL | |
| frequency | enum('Daily','Monthly','None') | YES | | Daily | |
+------------+---------------------------------+------+-----+---------+----------------+
I need to construct a MYSQL query that returns the name and frequency corresponding to the email preferences for a given user.
SELECT name, frequency
FROM email_preferences
LEFT JOIN email_preferences_categories using (name)
WHERE user_id = 42
Where I'm having trouble: If the user hasn't set their preferences, this query doesn't return any rows. I would like it to return the default of 'Daily' for email categories that are missing.

Change LEFT JOIN to RIGHT JOIN.
...
FROM email_preferences
RIGHT JOIN email_preferences_categories
...
Or alternatively you can swap the tables around:
...
FROM email_preferences_categories
LEFT JOIN email_preferences
...
These two options both do the same thing - ensure that you get all rows from email_preferences_categories even if there is no matching row in email_preferences.
You also need to change the join condition as you already noticed.
I would like it to return the default of 'Daily' for email categories that are missing.
You can use IFNULL:
SELECT name, IFNULL(frequency, 'Daily') AS frequency

This query doesn't need a WHERE clause. It needs a more restrictive JOIN. Here is the full query combined with Mark Byers answer above.
SELECT email_preferences_categories.name, IFNULL(frequency, 'Daily') AS frequency
FROM email_preferences_categories
LEFT JOIN email_preferences
ON email_preferences.name = email_preferences_categories.name
AND user_id = 42;

Related

MySQL - UPDATE one column based on results of a SELECT when the SELECT returns multiple columns

I've read MySQL - UPDATE query based on SELECT Query and am trying to do something similar - i.e. run an UPDATE query on a table and populate it with the results from a SELECT.
In my case the table I want to update is called substances and has a column called cas_html which is supposed to store CAS Numbers (chemical codes) as a HTML string.
Due to the structure of the database I am running the following query which will give me a result set of the substance ID and name (substances.id, substances.name) and the CAS as a HTML string (cas_values which comes from cas.value):
SELECT s.`id`, GROUP_CONCAT(c.`value` ORDER BY c.`id` SEPARATOR '<br>') cas_values, GROUP_CONCAT(s.`name` ORDER BY s.`id`) substance_name FROM substances s LEFT JOIN cas_substances cs ON s.id = cs.substance_id LEFT JOIN cas c ON cs.cas_id = c.id GROUP BY s.id;
Sample output:
id | cas_values | substance_name
----------------------------------------
1 | 133-24<br> | Chemical A
455-213<br>
21-234
-----|----------------|-----------------
2 999-23 | Chemical B
-----|----------------|-----------------
3 | | Chemical C
-----|----------------|-----------------
As you can see the cas_values column contains the HTML string (which may also be an empty string as in the case of "Chemical C"). I want to write the data in the cas_values column into substances.cas_html. However I can't piece together how to do this because other posts I'm reading get the data for the UPDATE in one column - I have other columns returned by my SELECT query.
Essentially the problem is that in my "sample output" table above I have 3 columns being returned. Other SO posts seem to have just 1 column being returned which is the actual values that are used in the UPDATE query (in this case on the substances table).
Is this possible?
I am using MySQL 5.5.56-MariaDB
These are the structures of the tables, if this helps:
mysql> DESCRIBE substances;
+-------------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-----------------------+------+-----+---------+----------------+
| id | mediumint(8) unsigned | NO | PRI | NULL | auto_increment |
| app_id | varchar(8) | NO | UNI | NULL | |
| name | varchar(1500) | NO | | NULL | |
| date | date | NO | | NULL | |
| cas_html | text | YES | | NULL | |
+-------------+-----------------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)
mysql> DESCRIBE cas;
+-------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-----------------------+------+-----+---------+----------------+
| id | mediumint(8) unsigned | NO | PRI | NULL | auto_increment |
| value | varchar(13) | NO | UNI | NULL | |
+-------+-----------------------+------+-----+---------+----------------+
2 rows in set (0.01 sec)
mysql> DESCRIBE cas_substances;
+--------------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-----------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| cas_id | mediumint(8) unsigned | NO | MUL | NULL | |
| substance_id | mediumint(8) unsigned | NO | MUL | NULL | |
+--------------+-----------------------+------+-----+---------+----------------+
3 rows in set (0.02 sec)
Try something like this :
UPDATE substances AS s,
(
SELECT s.`id`,
GROUP_CONCAT(c.`value` ORDER BY c.`id` SEPARATOR '<br>') cas_values,
GROUP_CONCAT(s.`name` ORDER BY s.`id`) substance_name
FROM substances s
LEFT JOIN cas_substances cs ON s.id = cs.substance_id
LEFT JOIN cas c ON cs.cas_id = c.id
GROUP BY s.id
) AS t
SET s.cas_html=t.cas_values
WHERE s.id = t.id
If you don't want to modify all the value, the best way to limit the update to test it, is to add a condition in the where, something like that :
...
WHERE s.id = t.id AND s.id = 1

MYSQL - output extra column based on a certain condition

At first, I want to apologize for providing such a weak title; I couldn't describe it in a better way.
Consider the following: We have three tables, one for users, one for records and one for ratings. The tables are quite self-explanatory but the schema for database is as following:
+---------------------+
| Tables_in_relations |
+---------------------+
| records |
| ratings |
| users |
+---------------------+
The schema for records table is as following:
+----------+----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+----------------------+------+-----+---------+----------------+
| id | smallint(5) unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(256) | NO | | NULL | |
| year | int(4) | NO | | NULL | |
+----------+----------------------+------+-----+---------+----------------+
The schema for users table is as following:
+----------+----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+----------------------+------+-----+---------+----------------+
| id | smallint(5) unsigned | NO | PRI | NULL | auto_increment |
| email | varchar(256) | NO | | NULL | |
| name | varchar(256) | NO | | NULL | |
| password | varchar(256) | NO | | NULL | |
+----------+----------------------+------+-----+---------+----------------+
ratings table is, obvoiusly, where the ratings are stored among with the record_id and user_id and works as a relation table.
It's schema is as following:
+----------+----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+----------------------+------+-----+---------+----------------+
| id | smallint(5) unsigned | NO | PRI | NULL | auto_increment |
| record_id| smallint(5) unsigned | NO | MUL | NULL | |
| user_id | smallint(5) unsigned | NO | MUL | NULL | |
| rating | int(1) | NO | | NULL | |
+----------+----------------------+------+-----+---------+----------------+
Now, In my application, I have a search function that fetches records based on a certain keyword. The output should also include the average rating of a certain record and a total amount of ratings per record. This can be accomplished by following query:
SELECT re.id, re.title, re.year, ROUND(avg(ra.rating)) as avg_rate,
COUNT(ra.record_id) as total_times_rated
FROM records re
LEFT JOIN ratings ra ON ra.record_id = re.id
GROUP BY re.id;
which will give me the following output:
+----+------------------------+------+----------+-------------------+
| id | title | year | avg_rate | total_times_rated |
+----+------------------------+------+----------+-------------------+
| 1 | Test Record 1 | 2008 | 3 | 4 |
| 2 | Test Record 2 | 2012 | 2 | 4 |
| 3 | Test Record 3 | 2003 | 3 | 4 |
| 4 | Test Record 4 | 2012 | 3 | 3 |
| 5 | Test Record 5 | 2003 | 2 | 3 |
| 6 | Test Record 6 | 2006 | 2 | 3 |
+----+------------------------+------+----------+-------------------+
Question:
Now, here comes the tricky part, at least for me. Within my app, you can search records whether signed in or not and if signed in, I'd also like to include the user's own rating value in the above query.
I know that I can run a conditional to check whether user is signed in or not by reading the session value and execute a corresponding query based on that. I just don't know how to include that individual rating value of a certain user to the above query.
You can add user's rating in the result by adding a SELECT query in columns:
SELECT re.id, re.title, re.year, ROUND(avg(ra.rating)) as avg_rate,
COUNT(ra.record_id) as total_times_rated,
(SELECT rating FROM ratings WHERE user_id = ? AND record_id = re.id) as user_rating
FROM records re
LEFT JOIN ratings ra ON ra.record_id = re.id
GROUP BY re.id;
We can get the user_id from session and pass it to this query in order to generate user_rating column in the result.
Assuming user can rate a record multiple times, I have used SUM. If not, we can remove it from the query.
Update
If you don't want GROUP BY to consider that value then you can wrap the existing query into another query and add a column to it, e.g.:
SELECT a.id, a.title, a.year, a.avg_rate, a.total_times_rated,
(SELECT rating FROM ratings WHERE user_id = ? AND record_id = a.id) as user_rating
FROM (SELECT re.id as id, re.title as title, re.year as year, ROUND(avg(ra.rating)) as avg_rate,
COUNT(ra.record_id) as total_times_rated
FROM records re
LEFT JOIN ratings ra ON ra.record_id = re.id
GROUP BY re.id) a;

Writing a many-to-many query for common table attributes

It's been a long time since I've worked directly with MySQL, so for fun, I have a MySQL database of train information, based off the NYC Subway system. I have a Station table and Route table, and there is a many-to-many relationship between the two.
What I want to do is find out which stations two or more different routes have in common. I'm trying different join techniques but none of them seem to be working (I probably have the syntax wrong).
So for example, I want to see which stations serve both routes "1" and "2" (i.e., which stations do these routes have in common?).
mysql> describe station;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(45) | YES | UNI | NULL | |
+-------+-------------+------+-----+---------+----------------+
mysql> describe route;
+-------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(4) | YES | | NULL | |
+-------+------------+------+-----+---------+----------------+
The table between them is called RouteStation that has ID's for each of the two tables.
The query I want is to find the names of the stations that serve any group of routes. So in this example, I want to find the names of the stations that serve both routes "1" and "2".
SELECT Station.name FROM Station
JOIN RouteStation ON (Station.id = RouteStation.stationId)
JOIN Route ON (Route.id = RouteStation.routeId)
WHERE Route.name = "1" AND Route.name = "2";
I know there is probably an issue with the last part because it's not possible for the route name to be "1" and "2" at the same time, but I hope that the gist of what I'm looking for is clear enough.
EDIT: RouteStation schema:
mysql> describe routestation;
+-----------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+-------+
| stationId | int(11) | NO | MUL | NULL | |
| routeId | int(11) | NO | MUL | NULL | |
+-----------+---------+------+-----+---------+-------+
Can you try this one:
SELECT name
FROM station
WHERE id IN (
SELECT RS.stationId
FROM RouteStation RS
WHERE RS.routeId IN (1, 2)
GROUP BY RS.stationId
HAVING COUNT(RS.routeId) > 1
)
Instead of using AND, you should use the OR keyword: routeId = 1 OR routeId = 2. It is the same with IN keyword.
After that, we used GROUP BY stationId so we can COUNT the routeIds because we need only the stations which are HAVING more than 1 routes.

MYSQL need AVERAGE from one table based on information from second table

I'm slowly teaching myself MySQL methods, and I'm having a tough time with this. I haven't even been able to figure out HOW to Google the question.
I have the following two tables (I think my data is normalized, but suggestions welcome):
Table 1
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| rate_id | int(11) | NO | PRI | NULL | auto_increment |
| rate | decimal(9,2) | YES | | NULL | |
| guess | decimal(9,2) | YES | | NULL | |
| date | date | YES | MUL | NULL | |
| house_id | int(11) | NO | MUL | NULL | |
| date_mod | date | YES | | NULL | |
+----------+--------------+------+-----+---------+----------------+
Table 2
+-----------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+---------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(64) | YES | | NULL | |
| beds | int(2) | YES | | NULL | |
| baths | int(2) | YES | | NULL | |
| pets | char(4) | YES | | NULL | |
| pool | char(4) | YES | | NULL | |
+-----------------+---------------+------+-----+---------+----------------+
I want to populate guess with the average rate of all properties during the same date period, based on similar properties in table 2. That is, for each id/house_id, I need all the houses that are similar (same beds, baths, view, etc.), and then the average of all the rates on the same dates.
My biggest issue is that I don't understand how to reference a field in a second table based on the id selected. This is what I'm starting with - just to see if I can get averages to return (I know this won't UPDATE the guess field).
SELECT AVG(t1.rate)
INNER JOIN t2 ON (t1.house_id = t2.id)
WHERE t2.beds = t2.beds
AND t2.baths = t2.baths
AND t2.pets = t2.pets
AND t1.date = t1.date
AND t1.house_id = 2;
Aside from the fact that the SQL command doesn't complete - I think it's obvious that my SQL knowledge is woefully inadequate - I think I'm just missing a more complex SQL method to identify the fields I'm looking for. Can anybody help?
#Strawberry - appreciate the comments. As it turns out, I already had AVG() working (I had already Googled it)
My problems were in two sets. First, how does one get column values from one table to use to get keys for us on another table. I didn't realize that you could use a nested SELECT statement to do this - although I still haven't convinced myself this is the most efficient way to do this.
Second, the average function was grouping all rates together into one average. I was able to do a bit of manipulation to produce the output I was looking for, but ultimately, the GROUP BY function was able to provide me most of the functionality I needed. Below is basically what I ended up with.
SELECT date, AVG(t1.rate) FROM rates
JOIN houses ON (t1.house_id = t2.id)
WHERE beds = (SELECT beds FROM t2 WHERE id = 2)
AND baths = (SELECT baths FROM t2 WHERE id = 2)
AND pets = (SELECT pets FROM t2 WHERE id = 2)
GROUP BY date;
Thanks for commenting.

How to join tables so every item in one of them shows up independent of the joined table

I have 2 tables:
mysql> describe solution_sections;
+---------------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+---------------+------+-----+---------+----------------+
| solution_section_id | int(10) | NO | PRI | NULL | auto_increment |
| display_order | int(10) | NO | | NULL | |
| section_name | varchar(1000) | YES | | NULL | |
+---------------------+---------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
mysql> describe suggested_solution_comments;
+-----------------------+----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------+----------------+------+-----+---------+----------------+
| comment_id | int(10) | NO | PRI | NULL | auto_increment |
| problem_id | int(10) | NO | | NULL | |
| suggested_solution_id | int(10) | NO | | NULL | |
| commenter_id | int(10) | NO | | NULL | |
| comment | varchar(10000) | YES | | NULL | |
| solution_part | int(3) | NO | | NULL | |
| date | date | NO | | NULL | |
+-----------------------+----------------+------+-----+---------+----------------+
What I am trying to do is to display the list of section_name from the solution_sections table. It only has about 10 rows in it. And for every section name, to get the list of suggested_solution_comments associated with it.
The tables are linked by suggested_solution_comments.solution_part and solution_sections.solution_section_id
Here is what I am trying so far:
select section_name , comment , solution_part , display_order from solution_sections
left join suggested_solution_comments on
solution_sections.solution_section_id = suggested_solution_comments.solution_part
where suggested_solution_id = 188
group by display_order;
But that returns nothing when there are no comments. But even if there are no comments, I'd like to still display the list of section_names from the solution_sections table.
Thanks!!
The problem is here:
where suggested_solution_id = 188
Your query requires the suggested_solution_id have a value of 188, which will never be true for records that have no comments. Try adding in this:
OR suggested_solution_id IS NULL
By using suggested_solution_id in the where clausule you are eliminating from the result any row that have no content no matching row in suggested_solution_comments table.
If you want to get results even when suggested_solution_comments has no content you can't use this field in the where clausule. or you have to consider the possibility that suggested_solution_id could be NULL.
EDITED to take in consideration the comment by #X-Zero
I think your table structure is not the best to do this. if you have just a primary key in each table, and want to perform on join on those, it would need to refer to the same thing.. Otherwise, introducing a foreign key able to join on a primary key in the other table.