MySQL Group By Concatenate - php

I have two SQL tables. customer and tag joined on the customer.id=tag.attach_id
customer
+------+-------------+--------------+
| id | name | email |
| 9 | Alan | alan#me.com |
+------+-------------+--------------+
tag
+------+-------------+--------------+
| id | attach_id | content |
| 1 | 9 | alan-tag |
| 2 | 9 | second-tag |
+------+-------------+--------------+
I want to output this:
+-------+-----------------+-----------------------+
| name | email | content |
+-------+-----------------+-----------------------+
| alan | alan#me.com | alan-tag, second-tag |
+-------+-----------------+-----------------------+
Here's my best attempt at SQL for this:
SELECT customer.name, customer.email, tag.content
FROM customer
INNER JOIN tag
ON customer.id=tag.attach_id
GROUP BY customer.id,tag.content;
Is this even possible without first processing the data in another language like PHP?

Yes you need to use GROUP_CONCAT as suggested by others on comment area, more specifically (exactly) your query is
SELECT `name`, email, GROUP_CONCAT(tag.content SEPARATOR ', ') as content
FROM
customer
INNER JOIN tag ON customer.id = tag.attach_id
GROUP BY customer.id
This Query will give you the exact result that you have osted on your post

You can use listagg if you are using sql
select c.name,email,listagg(content,',') within group (order by c.name) "content"
from customer c, tag t
where c.id = t.attach_id
group by c.name,email

Use this:
SELECT customer.name, customer.email, GROUP_CONCAT(tag.content SEPARATOR ', ') as content
FROM customer
INNER JOIN tag
ON customer.id=tag.attach_id
GROUP BY customer.id;

Related

MySQL - inner join - add column with value based on other value

I'm struggling with mysql joins :/
I've multiple tables inside database fe. tasks, users etc.
Table tasks containing tasks with various variables, but the most important - id's of users signed to task (as different roles inside the task - author, graphic, corrector):
+---------+-------------+--------------+
| task_id | task_author | task_graphic |
+---------+-------------+--------------+
| 444 | 1 | 2 |
+---------+-------------+--------------+
Table users
+---------+----------------+------------+-----------+
| user_id | user_nice_name | user_login | user_role |
+---------+----------------+------------+-----------+
| 1 | Nice Name #1 | login1 | 0 |
+---------+----------------+------------+-----------+
| 2 | Bad Name #2 | login2 | 1 |
+---------+----------------+------------+-----------+
Using PDO I'm getting the whole data I want while using INNER JOIN with data from different tables (and $_GET variable)
SELECT tasks.*, types.types_name, warehouse.warehouse_id, warehouse.warehouse_code, warehouse.warehouse_description
FROM tasks
INNER JOIN types ON types.types_id = tasks.task_id
INNER JOIN warehouse ON warehouse.warehouse_id = tasks.task_id
WHERE tasks.task_id = '".$get_id."'
ORDER BY tasks.task_id
Above query returns:
+---------+--------------+--------------+----------------+------------+-----------+------------------+------------------------+------------+-------------+-----------------+-----------+----------------+--------------------+---------------------+-----------+---------------------+------------------+---------------------+
| task_id | task_creator | task_graphic | task_purchaser | task_title | task_lang | task_description | task_description_files | task_files | task_status | task_prod_index | task_type | task_print_run | task_print_company | task_warehouse_code | task_cost | task_time_added | task_deadline | task_date_warehouse |
+---------+--------------+--------------+----------------+------------+-----------+------------------+------------------------+------------+-------------+-----------------+-----------+----------------+--------------------+---------------------+-----------+---------------------+------------------+---------------------+
| 2 | 1 | 2 | 1 | Test | PL | Lorem ipsum (?) | | | w | 2222 | 3 | 456546 | Firma XYZ | 2 | 124 | 29.09.2016 15:48:20 | 01.10.2016 12:00 | 07.10.2016 14:00 |
+---------+--------------+--------------+----------------+------------+-----------+------------------+------------------------+------------+-------------+-----------------+-----------+----------------+--------------------+---------------------+-----------+---------------------+------------------+---------------------+
And I'd like to get query with added user_nice_name after task_creator, task_author and task_graphic - obviously nice names selected from table users based on ID's provide in 3 above fields fe.
+---------+--------------+------------------------------------+--------------+--------------------------------------+
| task_id | task_creator | task_creator_nn | task_graphic | task_graphic |
+---------+--------------+------------------------------------+--------------+--------------------------------------+
| 2 | 1 | Nice Name (from task_creator ID=1) | 2 | Nice Name (from task_graphic ID = 2) |
+---------+--------------+------------------------------------+--------------+--------------------------------------+
How can I achieve that?
You need three joins:
SELECT t.*,
uc.user_nice_name as creator_name,
ug.user_nice_name as graphic_name,
up.user_nice_name as purchaser_name,
ty.types_name, w.warehouse_id, w.warehouse_code, w.warehouse_description
FROM tasks t INNER JOIN
types ty
ON ty.types_id = t.task_id INNER JOIN
warehouse w
ON w.warehouse_id = t.task_id LEFT JOIN
users uc
ON uc.user_id = t.task_creator LEFT JOIN
users ug
ON ug.user_id = t.task_graphic LEFT JOIN
users up
ON up.user_id = t.task_purchaser
WHERE t.task_id = '".$get_id."'
ORDER BY t.task_id;
Notes:
Table aliases make the query easier to write and to read. They are also required because you have three references to users in the FROM clause.
This uses LEFT JOIN for the users in case some of the reference values are missing.
You need to work on your naming. It doesn't make sense that a "warehouse" id matches a "task" id. Or that a "task" id matches a "types" id. But that is how you phrased the query in your question.
The ORDER BY effectively does nothing, because all rows have the same task_id.
Assuming that the task_graphic_name is inside a table name task_graphic_table and the relation field are task_graphic_id
SELECT tasks.*
, types.types_name
, warehouse.warehouse_id
, warehouse.warehouse_code
, warehouse.warehouse_description
, users.user_nice_name
FROM tasks
INNER JOIN types ON types.types_id = tasks.task_id
INNER JOIN warehouse ON warehouse.warehouse_id = tasks.task_id
INNER JOIN users ON users.user_nice_name = tasks.task_graphic
WHERE tasks.task_id = '".$get_id."'
ORDER BY tasks.task_id
And if you need the column appear in a specific order you should explicitally call the column name in sequence eg:
SELECT tasks.col1
, task.col2
, types.types_name
, warehouse.warehouse_id
, warehouse.warehouse_code
, task.col2
, warehouse.warehouse_description
, task_graphic_table.task_graphic_name
Add two sub query in with your query. like
SELECT tasks.*,
....
....,
(select user_nice_name from users where id = tasks.task_author) AS task_creator_name,
(select user_nice_name from users where id = tasks.task_graphic) AS task_graphic_name
FROM tasks
INNER JOIN types ON types.types_id = tasks.task_id
....
....

SQL request files with multiples tags

I'm currently designing a website for file hosting. I'm sorting files by tags and I have an issue for building a search request in SQL. If I search for multiple tags at once, i want to be able to get the files that have all these tags.
Below is an example of what i want to do. If my search is "#code #python", my request must return me the files that have both tags (file with id 2 in this example). If the search is just "#code", the request must return file 2 and 3.
Here is how i store all the data, i have a table for files, one for tags and an other one with the links between files and tags (files_tags).
Table Tags
------------------------
| id | name |
------------------------
| 1 | Code |
------------------------
| 2 | Python |
------------------------
| 3 | PHP |
------------------------
Table Files
------------------------
| id | name |
------------------------
| 1 | python.pdf | ( #python )
------------------------
| 2 | main.py | ( #code #python )
------------------------
| 3 | class.php | ( #code #php )
------------------------
Table files_tags
------------------------
| id | tag | file |
------------------------
| 1 | 1 | 2 |
------------------------
| 2 | 1 | 3 |
------------------------
| 3 | 2 | 1 |
------------------------
| 4 | 2 | 2 |
------------------------
| 5 | 3 | 3 |
------------------------
My current request is :
SELECT DISTINCT `files`.* FROM `files`, `files_tags`, `tags` WHERE `files`.`id` = `files_tags`.`file` AND `files_tags`.`tags` = `tags`.`id` AND `tags`.`name` LIKE %"#code #python"%;
But it gets me all the 3 files, not just file 2.
What am i doing wrong ?
Thanks for help
Your syntax looks odd. Try the following one instead:
SELECT `files`.`id`, `files`.`name`
FROM `files`
JOIN `files_tags` ON `files`.`id`=`files_tags`.`file`
JOIN `tags` ON `files_tags`.`tag` = `tags`.`id`
WHERE `tags`.`name` IN ('code','python');
GROUP BY `files`.`id`, `files`.`name`
I would recommend something like this:
SELECT f.*
FROM `files` f JOIN
`files_tags` ft
ON f.id = ft.file JOIN
`tags` t
ON ft.tags = t.id
WHERE FIND_IN_STR(t.name, #SearchStr)
GROUP BY f.id
HAVING COUNT(*) = LENGTH(#SearchStr) - LENGTH(REPLACE(#SearchStr, ',', '')) + 1;
This takes the liberty of changing the format of your search string. Instead of "#", just use commas (with no spaces). Of course, the above can be adapted to support "#", but the code requires more string manipulation.
first of all, you sould take your search text, and use it as an array, that is , not a string. Split values by space " " so you have an array where each element represents a tag.
select * from files where
length_of_tags_array =
(select count(1) from tags inner join files_tags on files_tags.tag = tags.id where files_tags.file = files.id and tags.name in ("+putyourtagshere+")
)
since your tags are strings, add each element enbetween quitations
dont use like in this case for it would retrieve tags that are substring pof other tags such as "C" is a substring of "C#"
remove the hashtag as well since it is not in your stored tag's name
Thank you, it works know.
select * from files where 2 = (select count(1) from tags inner join files_tags on files_tags.tag = tags.id where files_tags.file = files.id and tags.name in ("code","python"))

SQL query from two tables ordering results by both tables columns

I have two tables for registering out-going phone calls.
First table is people. The scheme is:
-----------------------------------
id | fname | lname | phone_number |
-----------------------------------
1 | John | Black | 132333312 |
2 | Marry | White | 172777441 |
-----------------------------------
Second tables called calls. The scheme is:
----------------------------------------------------------------------
id | scr_phone | dst_phone | dst_public_id | date | notes |
----------------------------------------------------------------------
1 | 555 | 132333312 | 1 | 1.12.2013 | chit-chat |
2 | 555 | 172777441 | 2 | 1.12.2013 | serios talk |
3 | 555 | 172777441 | 2 | 2.12.2013 | conversation|
----------------------------------------------------------------------
I'm displaying list of phones for users not in an alphabetical order but by frequency of calls. So whoever calls from local phone 555 sees at the top the people who he/she calls more often. Here is the MySQL query I use for it:
SELECT p.fname, p.lname, c.notes, COUNT(c.dst_public_id) AS amount
FROM people p
JOIN calls c ON c.dst_public_id = p.id
WHERE phones != ''
GROUP BY p.id
ORDER BY amount DESC, p.lname ASC
As a result I get person number 2 at the top of the phones list and person number 1 is on second place (I count how many calls are for each individual public and order it by that amount). Here is the query result:
--------------------------------
fname | lname | notes | amount |
--------------------------------
Marry | White | | 2 |
John | Black | | 1 |
--------------------------------
This is all good. But I want always to display last note made on last conersation with that person. I can probably make a totally separated MySQL query for each person requesting last record of each and getting the data from that, something like:
SELECT notes FROM calls WHERE dst_public_id = 2 ORDER BY date DESC LIMIT 1
... and then add the result of the first query inside my PHP script, but is there any way to do it with one single query?
You can get the last note with the following MySQL trick, using group_concat() and substring_index():
SELECT p.fname, p.lname, c.notes, COUNT(c.dst_public_id) AS amount,
substring_index(group_concat(notes order by id desc separator '^'), '^', 1) as last_notes
FROM people p JOIN
calls c
ON c.dst_public_id = p.id
WHERE phones != ''
GROUP BY p.id
ORDER BY amount DESC, p.lname ASC;
For this to work, you need a separator character that will never appear in the notes. I have chosen '^' for this purpose.
Can you try this? I haven't tested it as I don't have
SQL in front of me but you should get the idea.
SELECT tbl.notes, tbl.amount, p.fname, p.lname
FROM people p
join
(
select t.dst_public_id, c1.notes, t.cnt as amount
from calls c1 join
(
SELECT c2.dst_public_id, count(c2.id) as cnt, max(c2.id) as id
FROM
calls c2
group by c2.dst_public_id
) t on c1.id = t.id
) tbl on p.id = tbl.dst_public_id
WHERE p.phones != ''
GROUP BY p.id
ORDER BY tbl.amount DESC, p.lname ASC

DISTINCT with an inner join

I'm currently working on a website for a photographer, every photo is tagged with multiple keywords which are extracted when uploading.
My database looks like this (simplified)
TAGS
-------------------
| | |
| | |
| ID | TAG |
| | |
| | |
-------------------
IMAGES
-------------------
| | |
| | |
| ID | URL |
| | |
| | |
-------------------
TAGS_IMAGES
-------------------
| | |
| TAG | IMAGE |
| ID | ID |
| | |
| | |
-------------------
So all tags are stored in a seperated table to prevent duplicates, the same goes for the images and then the tags and images are linked together in another table.
When searching for a tag, I execute this SQL to find all images based on the given tag
SELECT DISTINCT SQL_CALC_FOUND_ROWS tags.tag, tags_image.imageID, images.src FROM tags INNER JOIN tags_image ON tags.tagID = tags_image.tagID INNER JOIN images ON tags_image.imageID = images.id WHERE tag LIKE ? ORDER BY id DESC LIMIT ?, ?
But the problem is that I'm still getting multiple duplicates because the DISTINCT only seems to work on the tag.id.
As you can see here: http://pastebin.com/MWt5B0Aq, based on the tag "water", some images have keywords like "water", "watervogel", "waterloop".
Is there a way to get the DISTINCT to work on the images.id?
I tried this, but that didn't help
SELECT DISTINCT images.id SQL_CALC_FOUND_ROWS tags.tag, tags_image.imageID, images.src FROM tags INNER JOIN tags_image ON tags.tagID = tags_image.tagID INNER JOIN images ON tags_image.imageID = images.id WHERE tag LIKE ? ORDER BY id DESC LIMIT ?, ?
Would doing a group by help?
SELECT DISTINCT SQL_CALC_FOUND_ROWS tags.tag, tags_image.imageID, images.src FROM tags INNER JOIN tags_image ON tags.tagID = tags_image.tagID INNER JOIN images ON tags_image.imageID = images.id WHERE tag LIKE ? GROUP BY images.id ORDER BY id DESC LIMIT ?, ?

GROUP_CONCAT With Nested Set Model

I have an application that uses a nested set model class to organise my data, however I'm trying to write a query that will group_concat my results. I know I need to put some sub select statements somewhere but I can't figure it out!
Here's my structure at the moment:
table: person
-----------+------------+-----------
|Person_ID | Name | Age |
-----------+------------+-----------
| 1 | Mark Vance | 19 |
| 2 | Michael Tsu| 22 |
| 3 | Mark Jones | 29 |
| 4 | Sara Young | 25 |
-----------+------------+-----------
table: person_to_group
----+------------+-----------
|ID | Person_ID | Group_ID |
----+------------+-----------
| 1 | 3 | 1 |
| 2 | 3 | 2 |
| 3 | 1 | 2 |
| 4 | 4 | 3 |
----+------------+-----------
table: groups
----------+--------------+--------------+-------------
|Group_ID | Group_Name | Group_Left | Group_Right |
----------+--------------+--------------+-------------
| 1 | Root | 1 | 6 |
| 2 | Node | 2 | 5 |
| 3 | Sub Node | 3 | 4 |
----------+--------------+--------------+-------------
I need to render something like this with my results:
//Grab the group_IDs for this person and put them in the class tag...
<li class="2 3">Sara Young is in the Sub Node Group</li>
Notice that although Sara is in the Sub Node group, she is still being given the id for Node aswell because she is a child of Node.
The following is the query that I am working with as a starting point.
SELECT *, GROUP_CONCAT( CAST( gg.Group_ID AS CHAR ) SEPARATOR ' ' ) Group_IDs
FROM groups gg
LEFT JOIN person_to_group AS t1 ON gg.Group_ID = t1.Group_ID
LEFT JOIN person AS t2 ON t2.Person_ID = t1.Person_ID
GROUP BY t2.per_ID
ORDER BY t2.Name ASC
Any help would be much appreciated!
Here's how I'd write the query:
SELECT p.Name,
GROUP_CONCAT( g.Group_Name ) AS Group_List,
GROUP_CONCAT( CAST( gg.Group_ID AS CHAR ) SEPARATOR ' ' ) AS Group_ID_List
FROM person AS p
INNER JOIN person_to_group AS pg ON p.Person_ID = pg.Person_ID
INNER JOIN groups AS g ON pg.Group_ID = g.Group_ID
INNER JOIN groups AS gg ON g.Group_Left BETWEEN gg.Group_Left AND gg.Group_Right
GROUP BY p.Name
ORDER BY p.Name ASC
Note that if you group by person name, you also need to GROUP_CONCAT the list of group names. According to your schema, a person could belong to multiple groups, because of the many-to-many relationship.
I also recommend against using SELECT * in general. Just specify the columns you need.
This was little bit interesting as I do programming in both MsSQL and MySql. In SQL I have used function called STUFF. In MySQL you can use a function called INSERT. I tried out the below query in MsSQL. Don't have a MySQL handy to try out my query. If I have time I will post the MySQL version of the query.
DECLARE #person TABLE (Person_ID INT, Name VARCHAR(50), Age INT)
INSERT INTO #person VALUES
(1,'Mark Vance',19),
(2,'Michael Tsu',22),
(3,'Mark Jones',29),
(4,'Sara Young',25)
DECLARE #groups TABLE (Group_ID INT, Group_Name VARCHAR(50), Group_Left INT, Group_Right INT)
INSERT INTO #groups VALUES
(1,'Root',1,6),
(2,'Node',2,5),
(3,'Sub Node',3,4)
DECLARE #person_to_group TABLE (ID INT, Person_ID INT, Group_ID INT)
INSERT INTO #person_to_group VALUES
(1,3,1),
(2,3,2),
(3,1,1),
(4,4,1),
(4,1,1)
SELECT *,STUFF((SELECT ',' + CAST(g.Group_ID AS VARCHAR) FROM #groups g
JOIN #person_to_group pg ON g.Group_ID = pg.Group_ID AND pg.Person_ID = a.Person_ID FOR XML PATH('')) , 1, 1, '' ) FROM #person a
Function: INSERT(str,pos,len,newstr)
Documentation: http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_insert

Categories