Join on piped column data - mysql

I have to work with a MySQL database where some columns have different IDs saved in one column, separated by a piped character. For example:
Users table:
id | username | groups
1 | user1 | 1|2|3|5
The group IDs correspond to groups. It shows in what groups a user is placed in. So there is also a Groups table:
Groups table:
id | groupname
1 | group1
2 | group2
3 | group3
4 | group4
5 | group5
What i'd like to do is select a user and then select all the groupnames that this user is placed in, something like a JOIN. For user1 this is all groups except group4.
This would obviously be easier if the groups were saved in another table called user_groups, which could look like this:
user_groups table:
fk_user_id | fk_group_id
1 | 1
1 | 2
1 | 3
1 | 5
But, unfortunately i cannot change the database schema since an entire system is already based in this.
I'd like to know if there is a smart way of getting all the groupnames when selecting a user in a single query. For example a result could look something like this:
id | username | groups | groupnames
1 | user1 | 1|2|3|5 | group1|group2|group3|group5
Is it possible somehow to do something like this in one query with MySQL?

In MySQL, you can use find_in_set() for this type of join:
select *
from user u join
groups g
on find_in_set(g.groupname, u.groups) > 0;
In MySQL, or other databases, you can use like as well:
select *
from user u join
groups g
on concat('|', g.groupname, '|') like concat('%|', u.groups, '|%');

Related

Select count with value from different tables

I want to count all entries in one table grouped by the user id.
This is the query I used which works fine.
select uuid_mapping_id, count(*) from t_message group by uuid_mapping_id;
and these are the results:
+-----------------+----------+
| uuid_mapping_id | count(*) |
+-----------------+----------+
| 1 | 65 |
| 4 | 277 |
Now I would like to display the actual user name, instead of the ID.
To achieve this I would need the help of two different tables.
The table t_uuid_mapping which has two columns:
uid_mapping_id, which equals uuid_mapping_id in the other table.
And f_uuid which is also unique but completely different.
f_uuid can also be found in another table t_abook which also contains the names in the column f_name.
The result I am looking for should be:
+-----------------+----------+
| f_name | count(*) |
+-----------------+----------+
| admin | 65 |
| user1 | 277 |
I am new to the database topic and understand that this could be achieved by using JOIN in the query, but to be honest I did not completely understand this yet.
if I understand you correctly:
SELECT tm.f_name, COUNT(*) as count
FROM t_message tm
LEFT JOIN t_abook ta ON (tm.uuid_mapping_id = ta.uid_mapping_id)
GROUP BY tm.f_name

SQL Tables of the Same Column to Join

I currently have a problem as how to fetch data separately, in the same table, but of different conditions.
To better illustrate, take the example below as my tables.
disputes table
id | user_to | bidder_id
1 | 1 | 2
users table
user_id | user_name
1 | userone
2 | usertwo
I'd like to have an output that combines both like this:
final output table
id | user_to | bidder_id | user_to_name | bidder_id_name
1 | 1 | 2 | userone | usertwo
I do not know how to really put it into words but I hope the illustration helps :
It seeks for the "user_to" and "bidder_id" rows, associates them to the "user_id" in the users table, where it creates two new columns that associates the "user_id" and "bidder_id" to the respective ids in the users table and fetches the user_name in the id given in the field.
LEFT JOIN is your friend. see the exsample:
sample
SELECT d.*,
utn.user_name AS user_to_name ,
bin.user_name AS bidder_id_name
FROM disputes d
LEFT JOIN users utn on utn.user_id = d.user_to
LEFT JOIN users bin on bin.user_id = d.bidder_id;

MySQL JOIN to show required output

Is there a way I can select all columns from two tables in MySQL, but only show the data in the end column if a match is found in both tables?
For example…
Main table (contacts)
id| token | Name
------------------
1 | ABC | Test person 1
2 | DEF | Test person 2
3 | GHI | Test person 3
Subscriptions table (subscribed)
id| contact_token
-------------------
1 | ABC
Desired output
id| token | Name | contact_token
------------------------------------------
1 | ABC | Test person 1 | ABC
2 | DEF | Test person 2 |
3 | GHI | Test person 3 |
The contents of contact_token is show (ABC) because it appears in both tables.
Thanks
You should consider storing the subscription data in the main contacts table, unless you can have multiple subscriptions per user. If the latter is the case, then you have to consider if you want to see the subscription tokens within one line in the output or in multiple lines.
The basic query is simple, you need to left join the subscribed table to the contacts:
select contacts.id, token, name, contact_token
from contacts left join subscribed
on contacts.id=subscribed.id
Yes. This is what a LEFT JOIN is for:
You select all records from table contacts and you do a left join on the subscribed one.
SELECT subscribed.id,token,name,contact_token
FROM contacts LEFT JOIN subscribed
ON contacts.id = subscribed.id;
For the records that are only in the first table you will get NULL in the last column.

Counting votes in a MySQL table only once or twice

I've got the following table:
+-----------------+
| id| user | vote |
+-----------------+
| 1 | 1 | text |
| 2 | 1 | text2|
| 3 | 2 | text |
| 4 | 3 | text3|
| 5 | 2 | text |
+-----------------+
What I want to do is to count the "votes"
SELECT COUNT(vote), vote FROM table GROUP BY vote
That works fine. Output:
+-------------------+
| count(vote)| vote |
+-------------------+
| 3 | text |
| 1 | text2|
| 1 | text3|
+-------------------+
But now I only want to count the first or the first and the second vote from a user.
So result what I want is (if I count only the first vote):
+-------------------+
| count(vote)| vote |
+-------------------+
| 2 | text |
| 1 | text3|
+-------------------+
I tried to work with count(distinct...) but can get it work.
Any hint in the right direction?
You can do this in a single SQL statement with something like this:
SELECT vote, COUNT(vote)
FROM
(
SELECT MAX(user), vote
FROM table1
GROUP BY user
) d
GROUP BY vote
Note that this only gives you 1 vote not 1 or 2.
The easiest way would be to use one of the "row numbering" solutions listed in this SO question. Then your original query's almost there:
SELECT
COUNT(vote),
vote
FROM tableWithRowNumberAdded
WHERE MadeUpRowNumber IN (1,2)
GROUP BY vote
My alternative is much longer winded and calls for working tables. These can be "real" tables in your schema, or whatever flavour of intermediate resultsets you are comfortable with.
Start by getting the first vote for each user:
SELECT user, min(id) FROM table GROUP BY user
Put this in a working table; let's call it FirstVote. Next we can get each user's second vote, if any:
SELECT user, min(id) FROM table WHERE id not in (select id from FirstVote) GROUP BY user
Let's call the result of this SecondVote. UNION FirstVote to SecondVote, join this to the original table and group by vote. There's your answer!
SELECT
vote,
COUNT(*)
FROM table
INNER JOIN
(
SELECT id FROM FirstVote
UNION ALL
SELECT id FROM SecondVote
) as BothVotes
ON BothVotes.id = table.id
GROUP BY vote
Of course it could be structured as a single statement with multiple sub-queries but that would be horrendous to maintain, or read in this forum.
This is a very triky question for MySQL. On other systems there windowed functions: it performs a calculation across a set of table rows that are somehow related to the current row.
MySQL lacks this functionality. So one should look for a workaround. Here is the problem description and couple solutions suggested: MySQL and window functions.
I also assume that first 2 votes by the User can be determined by Id: earlier vote has smaller Id.
Based on this I would suggest this solution to your problem:
SELECT
Vote,
Count (*)
FROM
Table,
(
SELECT
user_id, SUBSTRING_INDEX(GROUP_CONCAT(Id ORDER BY user_id ASC), ',', 2) AS top_IDs_per_user
FROM
Table
GROUP BY
user_id
) s_top_IDs_per_User
WHERE
Table.user_id = s_top_IDs_per_User.User_id and
FIND_IN_SET(Id, s_top_IDs_per_User.top_IDs_per_user)
GROUP BY Vote
;

Chaining results from multiple tables using SQL

I have a set of tables with following structures
**EntityFields**
fid | pid
1 | 1
2 | 1
3 | 2
4 | 2
5 | 1
**Language**
id | type | value
1 | Entity | FirstEntity
2 | Entity | SecondEntity
1 | Field | Name
2 | Field | Age
3 | Field | Name
4 | Field | Age
5 | Field | Location
Now as you may have understood, the first table gives the EntityField assignment to each Entity. The second table gives out the names for those IDs. What I want to output is something like the following
1 | FirstEntity / Name (i.e. a concat of the Entity and the EntityField name)
2 | FirstEntity / Age
3 | FirstEntity / Location
4 | SecondEntity / Name
5 | SecondEntity / Age
Is this possible?
Thank you for the answers, unfortunately the table structure is something that I cannot change. The table structure it self belongs to another data directory system which is quite flexible and which I am using to pull out data. I know that without providing the necessary background, this table structure looks quite weird, but it is something that works quite well (except in this scenario).
I will try out the examples here and will let you know.
For your current table structure, I think the following will work
SELECT EntityFields.fid, CONCAT(L1.value, ' / ' L2.value)
FROM EntityFields INNER JOIN Language as L1 ON EntityFields.pid=L1.id and L1.type='Entity'
INNER JOIN Language as L2 ON EntityFields.fid=L2.id and L2.type='Field'
ORDER BY EntityFields.fid
However, this query could be made much easier by having a better table structure. For example, with the following structure:
**EntityFields**
fid | pid | uid
1 | 1 | 1
2 | 1 | 2
1 | 2 | 3
2 | 2 | 4
3 | 1 | 5
**Entities**
id | value
1 | FirstEntity
2 | SecondEntity
**Fields**
id | value
1 | Name
2 | Age
3 | Location
you can use the somewhat simpler query:
SELECT uid, CONCAT(Entities.value, Fields.value)
FROM EntityFields INNER JOIN Entities ON EntityFields.pid=Entities.id
INNER JOIN Fields ON EntityFields.fid=Fields.id
ORDER BY uid
Well, I have no idea what you're trying to accomplish here. The fact that you label some records "Entity" and others "Field" and then try to connect them to each other makes it look to me like you are mixing two totally different things in the same table. Why not have an Entity table and a Field table?
You could get the results you seem to want by writing
select fid, le.value, lf.value
from entittyfields e
join language le on e.pid=le.id and type='Entity'
join language lf on e.fid=lf.id and type='Field'
order by fid
But I think you'd be wise to rethink your table design. Perhaps you could explain what you're trying to accomplish.
SELECT ef.fid AS id
, COALESCE(e.value, '-', ef.pid, ' / ', f.value)
AS entity_field
FROM EntityFields ef
JOIN Language AS e
ON e.id = ef.id
AND e.type = 'Entity'
JOIN Language AS f
ON f.id = ef.id
AND f.type = 'Field'
ORDER BY ef.pid
, ef.fid
If I understand your question, which I don't think I do, this is simple. It appears to be a set of very poorly designed tables (Language doing more than one thing, for example). And it appears that the Language table has two types of records: a) The Entity records, which have type='Entity' and b) Field records, which have type='Field'.
At any rate, the way I would approach it is to treat the Language table as if it were two tables:
select ef.fid, Entities.value, Fields.value
from entityfields ef
inner join language Entities
on Entities.id = ef.id
and Entities.type = 'Entity'
inner join language Fields
on Fields.id = ef.id
and Fields.Type = 'Field'
order by 2, 3
First stab, anyway. That should help you get the answer.