MySQL column flattened to string - mysql

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

Related

SQL Distinct based on different colum

I have problem to distinct values on column based on other column. The case study is:
Table: List
well | wbore | op|
------------------
wella|wbore_a|op_a|
wella|wbore_a|op_b|
wella|wbore_a|op_b|
wella|wbore_b|op_c|
wella|wbore_b|op_c|
wellb|wbore_g|op_t|
wellb|wbore_g|op_t|
wellb|wbore_h|op_k|
So, I want the output to be appear in different field/column like:
well | total_wbore | total_op
----------------------------
wella | 2 | 3
---------------------------
wellb | 2 | 2
the real study case come from different table but to simplify it I just assume this case happened in 1 table.
The sql query that I tried:
SELECT well.well_name, wellbore.wellbore_name, operation.operation_name, COUNT(*)
FROM well
INNER JOIN wellbore ON wellbore.well_uid = well.well_uid
INNER JOIN operation ON wellbore.well_uid = operation.well_uid
GROUP BY well.well_name,wellbore.wellbore_name
HAVING COUNT(*) > 1
But this query is to calculate the duplicate row which not meet the requirement. Anyone can help?
you need to use count distinct
SELECT
count(distinct wellbore.wellbore_name) as total_wbore
count(distinct operation.operation_name) as total_op
FROM well
INNER JOIN wellbore ON wellbore.well_uid = well.well_uid
INNER JOIN operation ON wellbore.well_uid = operation.well_uid
Final query:
SELECT
well.well_name,
COUNT(DISTINCT wellbore.wellbore_name) AS total_wbore,
COUNT(DISTINCT operation.operation_name) AS total_op
FROM well
INNER JOIN wellbore ON wellbore.well_uid = well.well_uid
INNER JOIN operation ON wellbore.well_uid = operation.well_uid
GROUP BY well.well_name

Static SQL query replace to dynamic column

I have following query:
http://www.sqlfiddle.com/#!9/752e34/3
This query use SELECT in SELECT queries.
"SELECT a.*
,(SELECT s.value FROM tbl_scd AS s WHERE s.tag_id = 1 AND s.main_id = a.id ORDER BY s.date_time DESC LIMIT 1) AS title
,(SELECT s.value FROM tbl_scd AS s WHERE s.tag_id = 2 AND s.main_id = a.id ORDER BY s.date_time DESC LIMIT 1) AS alt
FROM tbl_main AS a
WHERE 1;"
Now I'm looking for a solution to add a new row into tbl_tag without change the above query (that the SELECT in SELECT part will be dynamic) to get a reference to tbl_tag
To get this:
+----+---------------+-----------+-----------+--------------+
| id | date | title | alt | new_column |
+----+---------------+-----------+-----------+--------------+
| 1 | 2018-10-10 | test1-1 | test1-3 | NULL |
| 2 | 2018-10-11 | test2-1 | test2-1 | NULL |
+----+---------------+-----------+-----------+--------------+
It would be great to get an idea or help.
Thanks
Your last comment on your question about using JOIN makes it clearer to me (I think) what you are after. JOINs will definitely help you a lot here, in place of the rather cumbersome query you are currently using.
Try this:
SELECT
tbl_main.date,
tblA.value AS title,
tblB.value AS alt
FROM
tbl_main
INNER JOIN (SELECT main_id, tag_id, value
FROM tbl_scd
INNER JOIN tbl_tag ON (tbl_scd.tag_id = tbl_tag.id)
WHERE tbl_tag.name = 'title') tblA
ON (tbl_main.id = tblA.main_id)
INNER JOIN (SELECT main_id, tag_id, value
FROM tbl_scd
INNER JOIN tbl_tag ON (tbl_scd.tag_id = tbl_tag.id)
WHERE tbl_tag.name = 'alt') tblB
ON (tbl_main.id = tblB.main_id);
I think this will get you much closer to a general solution to what it looks like you are trying to achieve, or at least point you in a good direction with using JOINs.
I also think you might benefit from re-thinking your database design, because this kind of pivoting rows from one table into columns in a query output can be an indicator that the data might be better off structured differently.
In any case, I hope this helps.

SQL GROUP CONCAT and LEFT JOIN return only one result from database

Am trying to run sql query that can return all user post and also join post image if the user attached image while submiting a post. But using the below code it only return data from post table when the post id exsit on post image id table if not it will ignore all post without images. How can i write a query that return both post with image and without?
Below are my table structure
Table vendor_account
eu_vendor_id | uname | mypagename
-------------|-------|--------------
v801 | peter | pageA
v900 | john |
Table social_posts
social_page_id | post_id | vendor_owner_id | post_body_message
----------------|----------|-----------------|------------------------------
pageA | p100 | v801 | This is post without image
pageA | p101 | v801 | This is post with 2 images
pageA | p102 | v801 | This is post with 1 image
Table social_post_photos
img | post_image_id | photo_url
----|---------------|---------------
1 | p101 | image1.png
2 | p101 | image2.png
3 | p102 | image01.png
My SQL Query
SELECT
im.post_image_id, p.post_body_message,
group_concat(im.photo_url ORDER BY im.photo_url) imgs
FROM social_post_photos im
LEFT JOIN social_posts p
ON p.post_id = im.post_image_id
INNER JOIN vendor_account v
ON p.vendor_owner_id = v.eu_vendor_id
WHERE p.social_page_id = 'pageA'
AND p.vendor_owner_id = 'v801'
GROUP BY im.post_image_id
Current result
When i run the above sql query it will return it will result like below and ignore other post that doen't have image attached to it
post_image_id | post_body_message | photo_url
---------------|----------------------------|-------------------------
p101 | This is post with 2 images | [IMAGE1.PNG],[IMAGE2.PNG]
p102 | This is post with 1 image | [IMAGE01.PNG]
My expected result
I want have a query that can return all data even when it has no image since i used LEFT JOIN because is not mandatly that the post will have a photo attached to it
post_image_id | post_body_message | photo_url
---------------|----------------------------|-------------------------
p101 | This is post with 2 images | [IMAGE1.PNG],[IMAGE2.PNG]
p102 | This is post with 1 image | [IMAGE01.PNG]
p100 | This is post without image | null
Since you are selecting from the social_post_photos you would need to change your join to social_posts to a RIGHT JOIN in order to get all records from social_posts. Since I abhor RIGHT JOINs, and have yet to find a good reason to ever use one, I would instead suggest you change the order of the tables to select from social_posts AND LEFT JOIN to social_post_photos:
SELECT
p.post_id, p.post_body_message,
group_concat(im.photo_url ORDER BY im.photo_url) imgs
FROM social_posts p
LEFT JOIN social_post_photos im
ON im.post_image_id = p.post_id
INNER JOIN vendor_account v
ON p.vendor_owner_id = v.eu_vendor_id
WHERE p.social_page_id = 'pageA'
AND p.vendor_owner_id = 'v801'
GROUP BY p.post_id, p.post_body_message
N.B. In doing this you also need to select (and group by) p.post_id instead of im.post_image_id.
When LEFT JOIN, move the right side table conditions from the WHERE clauseto the ON clause (otherwise you'll get regular INNER JOIN result):
SELECT
im.post_image_id, p.post_body_message,
group_concat(im.photo_url ORDER BY im.photo_url) imgs
FROM social_post_photos im
LEFT JOIN social_posts p
ON p.post_id = im.post_image_id
AND p.social_page_id = 'pageA'
AND p.vendor_owner_id = 'v801'
INNER JOIN vendor_account v
ON p.vendor_owner_id = v.eu_vendor_id
GROUP BY im.post_image_id
Nothe that you have an invalid group by. It won't execute on newer MySQL versions (unless in compatibility mode), may return unpredictable results with older MySQL versions. The general GROUP BY rule says: If a GROUP BY clause is specified, each column reference in the SELECT list must either identify a grouping column or be the argument of a set function!
U can try below code.
SELECT
im.post_image_id, p.post_body_message,
group_concat(im.photo_url ORDER BY im.photo_url) imgs
FROM social_posts p
LEFT JOIN social_post_photos im
ON p.post_id = im.post_image_id
INNER JOIN vendor_account v
ON p.vendor_owner_id = v.eu_vendor_id
WHERE p.social_page_id = 'pageA'
AND p.vendor_owner_id = 'v801'
GROUP BY im.post_image_id
From your post:
INNER JOIN vendor_account v
ON p.vendor_owner_id = v.eu_vendor_id
This part will cut your results to 2 max, since you only have 1 vendor ID (peter) that's your only result (I assume that's the single line you're getting).
In order to get both lines you'll need to LEFT JOIN instead of INNER JOIN the last table. Also the Where condition will only select one post and one vendor, I'm not sure that's what you want.
SELECT
im.post_image_id, p.post_body_message,
group_concat(im.photo_url ORDER BY im.photo_url) imgs
FROM social_post_photos im
LEFT JOIN social_posts p
ON p.post_id = im.post_image_id
LEFT JOIN vendor_account v
ON p.vendor_owner_id = v.eu_vendor_id
-- -- I DON'T KNOW IF THIS IS RIGHT?!
-- WHERE p.social_page_id = 'pageA'
-- AND p.vendor_owner_id = 'v801'
GROUP BY im.post_image_id
Here's a fiddle with everything you need: http://sqlfiddle.com/#!9/e7ce30/17

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

MySQL: Creating Multiple Variables with COALESCE

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.