MySQL: Creating Multiple Variables with COALESCE - mysql

By using COALESCE, I can create a temporary variable called comment_votes like so:
SELECT comments.*, COALESCE(rs_reputations.value, 0) AS comment_votes FROM `comments`
LEFT JOIN rs_reputations ON comments.id = rs_reputations.target_id AND
rs_reputations.target_type = 'Comment' AND rs_reputations.reputation_name =
'comment_votes' AND rs_reputations.active = 1 WHERE (impression_id = 1)
I want to create a second variable called impression_votes in the came query. I attempted to do this with:
SELECT comments.*, COALESCE(rs_reputations.value, 0) AS comment_votes
FROM 'comments'
LEFT JOIN rs_reputations ON
comments.id = rs_reputations.target_id AND
rs_reputations.target_type = 'Comment' AND
rs_reputations.reputation_name = 'comment_votes' AND
rs_reputations.active = 1
SELECT comments.*, COALESCE(rs_reputations.value, 0) AS impression_votes
FROM 'comments'
LEFT JOIN rs_reputations ON
comments.id = rs_reputations.target_id AND
rs_reputations.target_type = 'Comment' AND
rs_reputations.reputation_name = 'impression_votes' AND
rs_reputations.active = 1
WHERE
This leads to the error:
You have an error in your SQL syntax
Is what I'm attempting even possible? If so, I seem to be bridging the two SELECT/COALESCE statements improperly. How should I write this?

The MySQL COALESCE function is actually an inbuilt function that returns the first non-null value - it's not a variable, it's a function that is actually supported across a wide variety of database systems.
For example, with the following table:
| Id | Name | Counter |
| 1 | lolcat | NULL |
| 2 | codez | 1 |
The sql statement:
SELECT Id, Name, COALESCE(counter, 0) AS NonNullCounter FROM table
will return the results:
| Id | Name | NonNullCounter |
| 1 | lolcat | 0 |
| 2 | codez | 1 |
In this instance, the NULL value has been replaced by 0.
This is useful for you as, if you don't yet have any matching rows in rs_reputations for the row in comments, the LEFT JOIN will return NULL for the column rs_repuations.value, which is then replaced by 0 by COALESCE.
If you are new to JOINs then there is a great visual guide by Jeff Atwood.
Your first query can is actually:
SELECT comments.*,
COALESCE(rs_reputations.value, 0) AS comment_votes
FROM comments
LEFT JOIN rs_reputations ON comments.id = rs_reputations.target_id
AND rs_reputations.reputation_name = 'comment_votes'
WHERE impression_id = 1;
CHOICE 1 - UNION
You have a couple of choices - you can either UNION your results together like this:
SELECT comments.*,
COALESCE(rs_reputations.value, 0) AS votes,
'comment_votes' AS vote_type
FROM comments
LEFT JOIN rs_reputations ON comments.id = rs_reputations.target_id
AND rs_reputations.reputation_name = 'comment_votes'
WHERE impression_id = 1
UNION
SELECT comments.*,
COALESCE(rs_reputations.value, 0) AS votes,
'impression_votes' as vote_type
FROM comments
LEFT JOIN rs_reputations ON comments.id = rs_reputations.target_id
AND rs_reputations.reputation_name = 'impression_votes'
WHERE impression_id = 1;
In this instance your results will look like this:
|comments_columns|votes|vote_type |
| * |12 |comment_vote |
| * |2 |impression_vote |
CHOICE 2 - JOIN ON TO THE SAME TABLE TWICE
Or you can self join onto the same table twice by using the same table name but a different alias:
SELECT comments.*,
COALESCE(CommentRep.value, 0) AS comment_votes,
COALESCE(ImpressionRep.value, 0) AS impression_votes,
FROM comments
LEFT JOIN rs_reputations AS CommentRep ON comments.id = CommentRep.target_id
AND CommentRep.reputation_name = 'comment_votes'
LEFT JOIN rs_reputations AS ImpressionRep ON comments.id = ImpressionRep.target_id
AND ImpressionRep.reputation_name = 'impression_votes'
WHERE CommentRep.impression_id = 1
AND ImpressionRep.impression_id = 1
In this instance your results will look like this:
|comments_columns|comment_votes|impression_votes|
| * |12 |0 |
| * |2 |6 |
Finally (phew) the reason you have an error in your original SQL is that you are chaining two SELECT statements together without actually relating them - the SQL doesn't really make sense in this instance as you need to logically relate them (either via a UNION or a repeated join as per above.

Related

MYSQL Join Left Condition does not work

I have this example of my problem:
http://sqlfiddle.com/#!9/0cc41/1/0
Description:
There are three tables (events, persons and user). The table persons connects events and user. persons.type_id is the events.id and persons.user_id is user.id. I created two events (id 1 and 2). There are one entry in person for each event.
My Sql:
SELECT events.*,
coalesce(part_person.Accept_Participants_LJ, 0) AS Accept_Participants
FROM events
LEFT JOIN (
SELECT GROUP_CONCAT(CONCAT(part_user.forname, ' ', part_user.surname) SEPARATOR ', ') AS Accept_Participants_LJ,
part_person.type_id
FROM persons AS part_person
LEFT JOIN user AS part_user ON part_user.id = part_person.user_id
WHERE part_person.type = 'event_participant'
) part_person ON events.id = part_person.type_id
GROUP BY events.id
My expectation was:
------------------------
|id|Accept_Participants|
------------------------
|1 | Carl Habicht |
------------------------
|2 | Peter Zwegert |
------------------------
As you can see, the result is:
----------------------------------
|id| Accept_Participants |
----------------------------------
|1 | Carl Habicht, Peter Zwegert|
----------------------------------
|2 | 0 |
----------------------------------
It seems, that he ignores the ON-Condition of the Left Join.
But, where is my mistake?
Group_concat should (almost) always have a group by and group by without any aggregation is not useful
Maybe this is what you need
SELECT events.*,
coalesce(part_person.Accept_Participants_LJ, 0) AS Accept_Participants
FROM events
LEFT JOIN (
SELECT GROUP_CONCAT(CONCAT(part_user.forname, ' ', part_user.surname) SEPARATOR ', ') AS Accept_Participants_LJ,
part_person.type_id
FROM persons AS part_person
LEFT JOIN user AS part_user ON part_user.id = part_person.user_id
WHERE part_person.type = 'event_participant'
group by part_person.type_id
) part_person ON events.id = part_person.type_id
order BY events.id

MySQL column flattened to string

I am trying to avoid passing two separate MySQL (version 5.6.37) queries, and using transactions. I think this can be done in a single query, but I need to know where I'm going wrong.
If I use this query:
SELECT titles.t_id,title,cover,pageData.pageNum
FROM titles
JOIN biblio ON titles.t_id = biblio
JOIN pageData ON biblio.t_id = pageData.t_id
WHERE titles.t_id = '1';
It successfully returns a result with three columns of redundant data, and only one column of new data (p_id):
t_id | title | cover | pageNum
1 | The Art of the Deal | 32.jpg | 1
1 | The Art of the Deal | 32.jpg | 2
1 | The Art of the Deal | 32.jpg | 3
1 | The Art of the Deal | 32.jpg | 4
1 | The Art of the Deal | 32.jpg | 5
I think there is a way to modify the query so that the new data in the pageNum column is flattened into a single result (i.e. converted from integer values to a delimited string), like this:
t_id | title | cover | p_id
1 | The Art of the Deal | 32.jpg | 1,2,3,4,5
I have been experimenting with a sub-SELECT within the SELECT, but I have consistent syntax errors. Is there a way to combine these two queries below to get the above result?
SELECT titles.t_id,title,cover
FROM titles
JOIN biblio ON titles.t_id = biblio
WHERE titles.t_id = '1';
and
SELECT pageData.pageNum FROM pageData WHERE pageData.t_id = '1'
You can use GROUP_CONCAT in combination with GROUP BY for that.
SET SESSION group_concat_max_len = ##max_allowed_packet
SELECT
titles.t_id
, title,cover
, GROUP_CONCAT(pageData.pageNum) AS p_id
FROM titles
JOIN biblio ON titles.t_id = biblio
JOIN pageData ON biblio.t_id = pageData.t_id
WHERE titles.t_id = '1'
GROUP BY
t_id
, title
, cover
Use the GROUP_CONCAT function. Also assuming you meant JOIN biblio ON titles.t_id = biblio.t_id
SELECT t.t_id, title, cover, GROUP_CONCAT(pageData.pageNum) AS pageNum
FROM titles t
JOIN biblio b ON t.t_id = b.t_id
JOIN pageData p ON b.t_id = p.t_id
WHERE t.t_id = '1'
GROUP BY t.t_id, title, cover
The result you need can be easily accomplished using the MySQL function GROUP_CONCAT().
In order to produce a valid SQL query and get the results you expect, you also need to add a GROUP BY clause to the query and put in it all the other columns that appear in the SELECT clause:
SELECT titles.t_id, title, cover, GROUP_CONCAT(pageData.pageNum) AS p_id
FROM titles
JOIN biblio ON titles.t_id = biblio
JOIN pageData ON biblio.t_id = pageData.t_id
WHERE titles.t_id = '1'
GROUP BY titles.t_id, title, cover

MySQL left join with default values

I have a couple of tables, one with source data which I'll call SourceData and another which defines overridden values for a given user if they exist called OverriddenSourceData.
The basic table format looks something this like:
SourceData
| source_id | payload |
--------------------------------
| 1 | 'some json' |
| 2 | 'some more json' |
--------------------------------
OverriddenSourceData
| id | source_id | user_id | overrides
| 1 | 2 | 4 | 'a change' |
------------------------------------------
For a given user, I'd like to return all the Source data rows with the overrides column included. If the user has overridden the source then the column is populated, else it is null.
I started by executing a left join and then including a condition for checking the user like so:
SELECT A.source_id, A.payload, B.overrides from SourceData A
LEFT JOIN OverriddenSourceData B
ON A.source_id = B.source_id
WHERE user_id = 4
but then source rows that weren't overridden wouldn't be included ( it was acting like an inner join) (e.g source id 1)
I then relaxed the query and used a strict left join on source_id.
SELECT A.source_id, A.payload, B.overrides from SourceData A
LEFT JOIN OverriddenSourceData B
ON A.source_id = B.source_id
# WHERE user_id = 4
This can return more data than I need though (e.g other users who have overridden the same source data) and then I have to filter programatically.
It seems like I should be able to craft a query that does this all the DB level and gives me what I need. Any help?
You should add your condition on LEFT JOIN clause, if you use WHERE, mysql will do it with INNER JOIN, so try this;)
SELECT A.source_id, A.payload, B.overrides from SourceData A
LEFT JOIN OverriddenSourceData B
ON A.source_id = B.source_id
AND B.user_id = 4

SQL Query giving an error of Unknown Column

I am creating an application in PHP of Power Meter Analysis. I have following table structure:
table: 'feeds'
| feed_id | device_no | current1 | voltage1 | power_factor_1 | vc1 | ic1 | date_added
-------------------------------------------------------------------------------------
| 36752 | 2 | 36.048 | 196.01 | 0.9 | 1 | 1 | 2014-06-23 14:14:44
| 36753 | 2 | 35.963 | 195.59 | 0.9 | 1 | 1 | 2014-06-23 14:15:34
and so on.
table: 'machine'
| machine_id | machine_phone | machine_name | company_id |
----------------------------------------------------------
| 1 | 2 | ABC Machine | 1 |
| 2 | 093 | DEF Machine | 1 |
I need records on hourly basis and I have written the following query for this purpose:
$sql = "
SELECT
SUM(t.power1) AS 'power1'
, HOUR(t.date) AS 'pulse_hour'
FROM (
SELECT
IF(#diff = 0, 0, (((f.voltage1*f.vc1)*(f.current1*f.ic1)*(f.power_factor_1))/1000) * (#diff/3600)) AS 'power1'
, IF(#diff = 0,0, TIME_TO_SEC(f.date_added) - #diff) AS 'deltaT'
, #diff := TIME_TO_SEC(f.date_added)
, f.date_added AS 'date'
FROM
feeds f,
(SELECT #diff := 0) AS X
left join
machine m
on
f.device_no = m.machine_phone
left join
company c
on
c.company_id = m.company_id
";
$sql .= $params['machine_id'] ? " where f.device_no = '".$params['machine_id']."'" : " where f.device_no > 0";
$sql .= $params['machine_pulse_datetime_from'] ? " and f.date_added >= '".$params['machine_pulse_datetime_from']."'" : "";
$sql .= $params['machine_pulse_datetime_to'] ? " and f.date_added <= '".$params['machine_pulse_datetime_to']."'" : "";
$sql .= $params['company_id'] ? " and c.company_id = '".$params['company_id']."'" : "";
$sql .= "
ORDER BY
f.date_added ASC
) t
GROUP BY HOUR(t.date)
ORDER BY HOUR(t.date) ASC
";
The query is running OK if I remove the following part from the query:
left join
machine m
on
f.device_no = m.machine_phone
left join
company c
on
c.company_id = m.company_id
But with this part It is giving me following error:
Error Code : 1054
Unknown column 'f.device_no' in 'on clause'
can you please help me to sort this out... I have spent an hour with this query :(
This is your join:
FROM feeds f,
(SELECT #diff := 0) AS X left join
machine m
on f.device_no = m.machine_phone left join
company c
on c.company_id = m.company_id
The problem is that you are mixing explicit and implicit joins. You can fix this by replacing the comma with cross join:
FROM feeds f cross join
(SELECT #diff := 0) AS X left join
machine m
on f.device_no = m.machine_phone left join
company c
on c.company_id = m.company_id
The issue, which is buried deep in the documentation for select, is that , is a lot like a cross join with the exception of scoping rules -- that is, when the table aliases are recognized. With a comma, the table aliases are not recognized as you expect. They are with a cross join.
Here is the reference:
INNER [CROSS] JOIN and , (comma) are semantically equivalent in the
absence of a join condition: both produce a Cartesian product between
the specified tables (that is, each and every row in the first table
is joined to each and every row in the second table).
However, the precedence of the comma operator is less than of INNER
JOIN, CROSS JOIN, LEFT JOIN, and so on. If you mix comma joins
with the other join types when there is a join condition, an error of
the form Unknown column 'col_name' in 'on clause' may occur.
Information about dealing with this problem is given later in this
section.

Complex Query with related tables - Optimal Solution

The Schema:
I have 3 Tables:
User
Feature
User_has_Feature:
initially all users has no features
Example data:
User:
| id | name |
| 1 | Rex |
| 2 | Job |
Feature:
| id | name |
| 1 | Eat |
| 2 | Walk |
User_has_Feature:
| id | user_id | feature_id | have_feature |
| 1 | 1 | 1 | true |
| 2 | 1 | 1 | true |
| 3 | 2 | 2 | true |
| 4 | 2 | 2 | false |
The questions are:
¿How to get only the records that have all features? (explicitly)
Example:
| user_name | feature_name | feature_status |
| Rex | Eat | true |
| Rex | Walk | true |
How to get records that do not have all the features? (again explicitly)
Example:
| user_name | feature_name | feature_status |
| Job | Eat | true |
| Job | Walk | false |
Some conditions have to be attended
I need the Users list with all features (true or false) in both queries like examples
User have 650k records (for now)
Feature have 45 records (for now)
Is one time query.
The idea is to export the result to a CSV file
Early Solution
thanks to the answers of (#RolandoMySQLDBA, #Tom Ingram, #DRapp) I found a solution:
SELECT u.name, f.name, IF(uhf.status=1,'YES','NO') as status
FROM user u
JOIN user_has_feature uhf ON u.id = uhf.user_id
JOIN feature f ON f.id = uhf.feature_id
JOIN
(
SELECT u.id as id
FROM user u
JOIN user_has_feature uhf ON uhf.user_id = u.id
WHERE uhf.status = 1
GROUP BY u.id
HAVING count(u.id) <= (SELECT COUNT(1) FROM feature)
) as `condition` ON `condition`.id = u.id
ORDER BY u.name, f.id, uhf.status
For get records that do not have all the features and for get all record that have all features change:
WHERE uhf.status = 1 by WHERE uhf.status = 2
HAVING count(u.id) <= (SELECT COUNT(1) FROM feature) by HAVING count(u.id) = (SELECT COUNT(1) FROM feature)
but I want to know if this is an optimal solution?
SELECT
UNF.*,
IF(
(LENGTH(UNF.FeatureList) - LENGTH(REPLACE(UNF.FeatureList,',','')))
= (FC.FeatureCount - 1),'Has All Features','Does Not Have All Features'
) HasAllFeatures
FROM
(SELECT
U.name user_name
GROUP_CONCAT(F.name) Features
FROM
(SELECT user_id,feature_id FROM User_has_Feature
WHERE feature_status = true) UHF
INNER JOIN User U ON UHF.user_id = U.id
INNER JOIN Feature F ON UHF.feature_id = F.id
GROUP BY
U.name
) UNF,
(SELECT COUNT(1) FeatureCount FROM Feature) FC
;
The UNF subquery returns with all users listed in User_has_Feature and a comma-separated list of the features. The column HasAllFeatures is determined by the number of columns in UNF.FeatureList. In your case, there are two features. If the number of commas in UNF.FeatureList is FeatureCount - 1, then the user has all features. Otherwise, user does not have all features.
Here is a better version that shows all users and whether or not they have all, some or no features
SELECT
U.name user_name,
IFNULL(UsersAndFeatures.HasAllFeatures,
'Does Not Have Any Features')
WhatFeaturesDoesThisUserHave
FROM
User U LEFT JOIN
(
SELECT
UHF.user_id id,
IF(
(LENGTH(UHF.FeatureList) - LENGTH(REPLACE(UHF.FeatureList,',','')))
= (FC.FeatureCount - 1),
'Has All Features',
'Does Not Have All Features'
) HasAllFeatures
FROM
(
SELECT user_id,GROUP_CONCAT(Feature.name) FeatureList
FROM User_has_Feature INNER JOIN Feature
ON User_has_Feature.feature_id = Feature.id
GROUP BY user_id
) UHF,
(SELECT COUNT(1) FeatureCount FROM Feature) FC
) UsersAndFeatures
USING (id);
select
u.id,
u.name as User_Name,
f.name as Feature_Name,
uhf.feature_Status
from
( select uhf.user_id,
sum( if( uhf.feature_status, 1, 0 ) ) as UserFeatureCount
from user_has_feature uhf
group by uhf.user_id ) AllUsersWithCounts
join
( select count(*) as AllFeaturesCount
from Feature ) AllFeatures
on AllUsersWithCounts.UserFeatureCount = AllFeatures.AllFeaturesCount
join user u
on AllUsersWithCounts.user_id = u.ID
join user_has_feature uhf
on AllUsersWithCounts.User_id = uhf.user_id
join feature f
on uhf.feature_id = f.id
The above query should get all people that explicitly have ALL features. In order to get those that do NOT have all features, just change the one join from = to <
on AllUsersWithCounts.UserFeatureCount < AllFeatures.AllFeaturesCount
Here's my bash at it
create a view of the general information
CREATE VIEW v_users_have_features AS
SELECT usr.id, usr.name, feature.name, has_feature.status
FROM usr
JOIN has_feature ON usr.id = has_feature.user_id
JOIN feature ON has_feature.feature_id = feature.id;
use the view for other queries
SELECT v_users_have_features.id, v_users_have_features.u_name, v_users_have_features.f_name
FROM v_users_have_features
GROUP BY v_users_have_features.id
HAVING COUNT( v_users_have_features.id ) = (SELECT COUNT( feature.id )
FROM feature
WHERE feature.name = v_users_have_features.f_name )
p.s. you may need to adapt (particularly the latter) to your exact requirements you could also omit creating the view and nest it in the FROM clause like in another answer it just seemed handier to create the view
Count the number of features. Write a query over users that uses a correlated subquery to find all the features a user has and count them. Make the restriction criterion in the top query the equality of that count and the global number of features.
Can MySQL do correlated subqueries? If not, you might need to use a better database.