MySQL Select use IN and GROUP BY - mysql

I have two tables messages and users I want to find out which users received the messages however the query is only returning one message.
My Schemas are as follow
Messages
msg_id | msg_content | recipients |
-----------------------------------
1 | Hello world | 1,2,3,4,5
2 | Test | 1,3,5
3 | Welcome | 1,2,4
Users
uid | fname | lname |
---------------------------
1 | John |Doe |
2 | Jane |Doe |
3 | Mark |Someone |
4 | Mary |lady |
5 | Anthony |Doe |
So I would love to see my results simply as
msg_id | msg_content | recipients |
-----------------------------------
1 | Hello world | John,Jane,Mark,Mary,Anthony
2 | Test | John,Mark,Anthony
3 | Welcome | John,Jane,Mary
So I am doing my query as so
SELECT msg_id,msg_content,fname AS recepients FROM messages a
LEFT JOIN users ON uid IN(a.recipients)
When I run that query I only get one recipient. Please advice. Thanks.

I think you have to use a alternative way for create tables
Messages
msg_id | msg_content |
----------------------
1 | Hello world |
2 | Test |
3 | Welcome |
Users
uid | fname | lname |
---------------------------
1 | John |Doe |
2 | Jane |Doe |
3 | Mark |Someone |
4 | Mary |lady |
5 | Anthony |Doe |
users_has_messages
uhm_id | uid | msg_id |
---------------------------
1 | 1 | 1 |
2 | 2 | 1 |
3 | 3 | 1 |
4 | 2 | 2 |
5 | 1 | 3 |
Then you can use your code

Okay, so this schema isn't the best (using comma separated lists of IDs is not a great idea, and the performance of any joins will get pretty bad pretty quick). Best bet is to have a third table mapping uid's to msg_id's as mentioned by #Thilina.
That said, this query will do probably what you're after:
SELECT msg_id,msg_content,GROUP_CONCAT(fname) AS recepients FROM messages a
LEFT JOIN users ON FIND_IN_SET(uid, a.recipients)
GROUP BY msg_id

I tried this in Oracle 12c and it is working fine.
So basically what I did is
- Separate the userid from recipient field and used this a columns.
- Join with USERS table to get user fnames
- Used LISTAGG function to aggregate it back.
For MySql we need to find the corresponding functions to Separate the IDs between commas, Convert it to rows and Aggregate. But the inherent logic would be same.
with users (user_id,fname) as (
select 1 ,'John' from dual union
select 2 ,'Jane' from dual union
select 3 ,'Mark' from dual union
select 4 ,'Mary' from dual union
select 5 ,'Anthony' from dual
),
messages(msg_id, msg_content,recipients) as(
select 1,'Hello world','1,2,3,4,5' from dual union
select 2 , 'Test' ,'1,3,5' from dual union
select 3,' Welcome','1,2,4' from dual
),
flat as(
select msg_id,msg_content,
REGEXP_SUBSTR (recipients, '[^,]+', 1, COLUMN_VALUE) as user_id
from messages,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT(recipients ,',' ) + 1
) AS SYS.ODCINUMBERLIST
)
)
),
unames as
( select f.msg_id,f.msg_content,u.fname from flat f inner join users u
on f.user_id = u.user_id
order by f.msg_id
)
SELECT msg_id,msg_content,LISTAGG(fname, ',') WITHIN GROUP (ORDER BY fname) as recipients
from unames
group by msg_id,msg_content

Related

Sum of Counted records that calculated using "group by" with condition and "group by"

I'm sorry for fuzzy title of this question.
I have 2 Tables in my database and want to count records of first_table using "group by" on a foreign key id that exists in a column of second_table (which stores ids like array "1,2,3,4,5").
id | name | fk_id
1 | john | 1
2 | mike | 1
3 | jane | 2
4 | tailor | 1
5 | jane | 3
6 | tailor | 5
7 | jane | 4
8 | tailor | 5
9 | jane | 5
10 | tailor | 5
id | name | fk_ids | s_fk_id
1 | xxx | 1,5,6 | 1
2 | yyy | 2,3 | 1
3 | zzz | 9 | 1
4 | www | 7,8 | 1
Now i wrote the following query but it not working properly and displays wrong numbers.
I WANT TO:
1-Count records in first_table group by "fk_id"
2-Sum the counted records which exists in "fk_ids"
3-Display the sum result (sum of related counts) grouped by id.
symbol ' ' means ``.
select sum(if(FIND_IN_SET('fk_id', 'fk_ids')>0,'count',0) 'sum', 'count', 'from'.'fk_id', 'second_table'.* FROM 'second_table'
LEFT JOIN
(
SELECT 'fk_id', count(*) 'count'
FROM 'first_table'
group BY 'fk_id'
) AS 'from'
ON FIND_IN_SET('fk_id', 'fk_ids')>0
WHERE 'second_table'.'s_fk_id'=1
GROUP BY 'id'
ORDER by 'count' DESC
This table has many data and we have no plan to change the structure.
Edit:
Desired output:
id | name | sum
1 | xxx | 7 (3+4+0)
2 | yyy | 2 (1+1)
3 | zzz | 0 (0)
4 | www | 0 (0+0)
After two holidays i came back to work and found out that the "FIND_IN_SET" function is not working properly with space contained string.
And the problem is that i was ignored the spaces too, (same as this question)
Finnaly this query worked:
select sum(`count`) `sum`, `count`, `from`.`fk_id`, `second_table`.* FROM `second_table`
LEFT JOIN
(
SELECT `fk_id`, count(*) `count`
FROM `first_table`
group BY `fk_id`
) AS `from`
ON FIND_IN_SET(`fk_id`, replace(`fk_ids`,' ',''))>0
WHERE `second_table`.`s_fk_id`=1
GROUP BY `id`
ORDER by `count` DESC
And the magic is replace(fk_ids,' ','')

MySQL Order by before group by in view (No Sub-Querys)

I realize this question has been asked quite a few times, however i haven't managed to find a working solution for my case.
Essentially my problem arises because MySQL Doesn't allow sub-querys in views.
I found a few workarounds but they don't seem to work.
In more detail...
My first table (competitions) stores a users competitions:
id_tournament | id_competition | id_user | result
-------------------------------------------------
1 | 1 | 1 | 10
1 | 1 | 2 | 30
1 | 2 | 1 | 20
1 | 2 | 3 | 50
1 | 3 | 2 | 90
1 | 3 | 3 | 100
1 | 3 | 4 | 85
In this example there are three competitions:
(
user1 vs. user2,
user1 vs. user3,
user2 vs. user3 vs. user4
)
My problem is that i need to define a view that gives me the winners in each competition.
Expected Result:
id_tournament | id_competition | id_winner
------------------------------------------
1 | 1 | 2
1 | 2 | 3
1 | 3 | 3
This can be solved with the query:
SELECT
id_tournament,
id_competition,
id_user as id_winner
FROM (
SELECT * FROM competitions ORDER BY result DESC
) x GROUP BY id_tournament, id_competition
This query however uses a subquery (not allowed in views), so my first solution was to define a 'helper view'as :
CREATE VIEW competitions_helper AS (
SELECT * FROM competitions ORDER BY result DESC
);
CREATE VIEW competition_winners AS (
SELECT
id_tournament,
id as id_competition,
id_user as winner
FROM competitions_helper GROUP BY id_tournament, id_competition
);
However this does not seem to give the correct result.
It's result will then be:
id_tournament | id_competition | id_winner
------------------------------------------
1 | 1 | 1
1 | 2 | 1
1 | 3 | 1
What i don't understand is why it works when i use Sub-querys and why it gives a different result with the exact same statement in a view.
Any help is appreciated, thanks alot.
This is due to the GROUP BY behaviour.
In this case, the server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate, which is probably not what you want.
I would solve the problem in this way:
CREATE VIEW competitions_helper AS (
SELECT id_tournament,
id_competition,
MAX(result) as winning_result
FROM competitions
GROUP BY id_tournament,
id_competition
);
CREATE VIEW competition_winners AS (
SELECT c.id_tournament,
c.id_competition,
c.id_user
FROM competitions c
INNER JOIN competitions_helper ch
ON ch.id_tournament = c.id_tournament
AND ch.id_competition = c.id_competition
AND ch.winning_result = c.result
);

Mysql data from table with condition

I have two mysql tables as follows:
contacts
---------------
id | name | email
---------------
1 | Jack | jack#test.com
2 | John | john#test.com
3 | Liz | liz#test.com
5 | Jack | jack#test.com
6 | Liz | liz#test.com
7 | Mike | mike#test.com
8 | Jack | jack#test.com
purchases
-------------------
id | contact_id | paid
-------------------
1 | 3 | true
2 | 5 | true
I need unique contact_ids that made purchase and other unique contact_ids that don't have made purchases.
So the final result will be as:
-------------------
id | name | email
------------------
2 | John | john#test.com
3 | Liz | liz#test.com
5 | Jack | jack#test.com
7 | Mike | mike#test.com
I tried the query as:
SELECT * FROM contacts LEFT JOIN purchases ON contacts.id = purchases.user_id
But this is not giving me unique rows as required. I tried several combination of DISTINCT, but I am not getting the result as required.
did you try this?
SELECT COALESCE(purchases.contact_id, contacts.id) as id, name, email
FROM contacts
LEFT JOIN purchases ON contacts.id = purchases.user_id
GROUP BY name
SQL FIDDLE
Something like that should work, but its performance is "?".
SELECT * FROM contacts WHERE id IN (
SELECT DISTINCT id as i FROM purchases
UNION
SELECT DISTINCT contact_id as i FROM purchases
)
GROUP BY id

Selecting multiple unrelated data from two tables and insert into one table mysql

This is my scenario
I have a permissions table with the following fields.
id | module | permission
1 | client | add
2 | client | edit
3 | client | delete
4 | someth | edit
5 | someth | delete
employee table
id | status | somestatus
1 | act | 1
2 | den | 1
3 | act | 0
4 | den | 1
5 | act | 0
6 | act | 1
Now what i would need to do is select the employee who have status="act" and somestatus=1 and give them all permissions where module="client"
so the table employee_permissions should have these rows
id | empid | permid | permvalue
1 | 1 | 1 | 1
2 | 1 | 2 | 1
3 | 1 | 3 | 1
1 | 6 | 1 | 1
2 | 6 | 2 | 1
3 | 6 | 3 | 1
This is the query I tried and I'm stuck here
INSERT INTO at2_permission_employee (employee_id,permission_id)
SELECT at2_employee.employee_id as employee_id
, (SELECT at2_permission.permission_id as permission_id
FROM at2_permission
where at2_permission.permission_module='client'
)
from at2_employee
where at2_employee.employee_status='Active'
and at2_employee.employees_served_admin = 1;
I get the error sub query returns multiple rows which makes sense to me. But I'm not sure how to modify the query to account for iterating over the rows returned by sub query
If I'm not wrong, like this:
INSERT INTO at2_permission_employee (employee_id, permission_id, permvalue)
SELECT
at2_employee.employee_id,
at2_permission.permission_id,
1
FROM at2_permission cross join at2_employee
WHERE
at2_employee.employee_status='Active'
and at2_employee.employees_served_admin = 1
and at2_permission.permission_module='client';
It's a bit unclear where the value for permvalue should come from so I hard coded it and used the permission.id for both id and permid, but this query should give you an idea on how to accomplish what you want:
insert employee_permissions (id, empid, permid, permvalue)
select p.id, e.id, p.id, 1
from employee e, permissions p
where p.module = 'client' and e.status = 'act' and e.somestatus = 1;

Stuck with mysql join with count query

I'm having a problem with joining 2 queries but in the second query I only want to bring in the count.
This first query works well
SELECT DISTINCT forum_sub.sub_id, forum_id, title, text, url, forum_sub.userid, members.first_name, views
FROM forum_sub, members
WHERE members.userid = forum_sub.userid AND forum_sub.forum_id = 1
ORDER BY forum_sub.timestamp DESC
which displays the following
----------------------------------------------------------------------
sub_id | forum_id | title | text | url | userid | first_name | views |
----------------------------------------------------------------------
20 | 1 | test | test |test | 1001 | JOhn | 123 |
----------------------------------------------------------------------
1 | 1 | test | test |test | 1002 | Pete | 23 |
----------------------------------------------------------------------
10 | 1 | test | test |test | 1003 | Harry | 34 |
----------------------------------------------------------------------
But now I want to join the above sub_id to another table called forum_topics and count how many of the same sub_id's there are and bring in that value
for example I could use
SELECT sub_id, COUNT(sub_id) as topics FROM forum_topics GROUP BY sub_id
-----------------
|sub_id | topics|
---------------
| 1 | 4 |
-----------------
| 10 | 3 |
-----------------
| 20 | 5 |
-----------------
My question is how can I join those 2 queries so I get something like this
----------------------------------------------------------------------------
sub_id | forum_id | title | text | url | userid | first_name | views | count|
-----------------------------------------------------------------------------
20 | 1 | test | test |test | 1001 | JOhn | 123 | 5 |
-----------------------------------------------------------------------------
1 | 1 | test | test |test | 1002 | Pete | 23 | 4 |
-----------------------------------------------------------------------------
10 | 1 | test | test |test | 1003 | Harry | 34 | 3 |
-----------------------------------------------------------------------------
Any help would be great, I know I need to use a subquery but I've been stuck on this nearly all day with no luck
First avoid SELECT DISTINCT when ever possible. It is evil. It will hide cross joins etc that you have in your query. From this query it is hard to tell what exactly your are doing.
However to include the count you have a couple of options:
One would be to do a sub select in the query:
SELECT forum_sub.sub_id, forum_id, title, text, url, forum_sub.userid,
members.first_name, views , (SELECT COUNT(sub_id) as topics FROM forum_topics WHERE sub_id = forum_sub.sub_id) count
FROM forum_sub, members
WHERE members.userid = forum_sub.userid AND forum_sub.forum_id = 1
ORDER BY forum_sub.timestamp DESC
Another would be to actually join to the sub query you created:
SELECT forum_sub.sub_id, forum_id, title, text, url, forum_sub.userid,
members.first_name, views, counts.topics
FROM forum_sub, members
JOIN (SELECT sub_id, COUNT(sub_id) as topics FROM forum_topics GROUP BY sub_id) counts ON (counts.sub_id = forum_sub.sub_id)
WHERE members.userid = forum_sub.userid AND forum_sub.forum_id = 1
ORDER BY forum_sub.timestamp DESC
You should also look at joins instead of selecting multiple tables in your from
SELECT forum_sub.sub_id, forum_id, title, text, url, forum_sub.userid,
members.first_name, views, counts.topics
FROM forum_sub
JOIN members ON (members.userid = forum_sub.userid)
JOIN (SELECT sub_id, COUNT(sub_id) as topics FROM forum_topics GROUP BY sub_id) counts ON (counts.sub_id = forum_sub.sub_id)
WHERE forum_sub.forum_id = 1
ORDER BY forum_sub.timestamp DESC
Try
group by sub_id
at the end of your second query instead of WHERE.
Then join it to the other table.