SQL Comment Grouping - mysql

I have two table in MySQL
Table 1: List of ID's
--Just a single column list of ID's
Table 2: Groups
--Group Titles
--Members **
Now the member field is basically a comments field where all the ID's that are part of that group are listed. So for instance one whole field of members looks like this:
"ID003|ID004|ID005|ID006|ID007|ID008|... Etc."
There they can be up to 500+ listed in the field.
What I would like to do is to run a query and find out which ID's appear in only three or less groups.
I've been taking cracks at it, but honestly I'm totally lost. Any ideas?

Edit; I misunderstood the question the first time, so I'm changing my answer.
SELECT l.id
FROM List_of_ids AS l
JOIN Groups AS g ON CONCAT('|', g.members, '|') LIKE CONCAT('%|', l.id, '|%')
GROUP BY l.id
HAVING COUNT(*) <= 3
This is bound to perform very poorly, because it forces a table-scan of both tables. If you have 500 id's and 500 groups, it must run 250000 comparisons.
You should really consider if storing a symbol-separated list is the right way to do this. See my answer to Is storing a delimited list in a database column really that bad?
The proper way to design such a relationship is to create a third table that maps id's to groups:
CREATE TABLE GroupsIds (
memberid INT,
groupid INT,
PRIMARY KEY (memberid, groupid)
);
With this table, it would be much more efficient by using an index for the join:
SELECT l.id
FROM List_of_ids AS l
JOIN GroupsIds AS gi ON gi.memberid = l.id
GROUP BY l.id
HAVING COUNT(*) <= 3

select * from
(
select ID,
(
select count(*)
From Groups
where LOCATE(concat('ID', a.id, '|'), concat(Members, '|'))>0
) as groupcount
from ListIDTable as a
) as q
where groupcount <= 3

Related

MySQL ORDER BY only returns one row

This is my code :
SELECT *
FROM Event_list
WHERE interest in
(
SELECT Interest_name
from Interest
where Interest_id in
(
SELECT Interest_id
FROM `User's Interests`
where P_id=Pid and is_canceled=0
)
)
order by count(Eid) desc
I don't use any GROUP BY clause but still only get one row. when removing the ORDER BY clause I get all the correct rows (but not in the right order).
I'm trying to return a view (named Event_list) sorted by most common Eid (Event id), but I want to see every row without any grouping.
COUNT() is a group function, so using it will automatically result in grouping of rows. This is why you get only one row in your result when you use it in your ORDER BY clause.
Unfortunately, it's not clear what you're trying to do, so I can't tell you how to rewrite your query to get your desired results.
I suspect the query you want is more like this:
SELECT el.*,
(select count(*)
from interest i join
UserInterests ui
on ui.is_canceled = 0 and ui.p_id = i.id
where el.interest = i.interest_name
) as cnt
FROM Event_list el
ORDER BY cnt desc;
It is a bit hard to tell without sample data and a better formed query. Some notes:
Don't use special characters in table and column names. Having to escape the names merely leads to queries that are harder to read, write, and understand.
Qualify column names, so you know what tables columns come from.
Use table aliases -- so queries are easier to write and to read.
The WHERE clause only does filtering. Your description of the problem doesn't seem to involve filtering, only ordering.
Any time you use an aggregation function, the query automatically becomes an aggregation query. Without a group by, exactly one row is returned.
Give foreign keys the same names as primary keys, where possible.
You may try:
SELECT L.* , C.Cnt
FROM Event_list L
LEFT JOIN (
SELECT E.EID, COUNT(*) AS Cnt
FROM Event_List E
JOIN Interest I
ON E.Interest = I.Interest_name
JOIN `User's Interests` U
ON U.Interest_id = I.Insert_Id
Where U.P_id=Pid and U.is_canceled=0
GROUP BY E.EID
) C
ON E.Eid = C.Eid
Order By Cnt DESC
I don't have the tables to test so you may want to correct column names and other conditions. Just provide you the idea.

Creating an SQL View With Calculations From Multiple Tables

So I have two tables that I need information from. I have a
ballot_table(vote CHAR(30), username CHAR(30)) that has the name of the candidate each username voted for. I also have another table with a list of the candidates. I need someway to return the list of candidates with the corresponding amount of times there name appears in ballot_table in the same query. Thanks!
This is a horrible design but here is how you do it:
select count(*) as votes, vote as [candidate]
from ballot_table
where ucase(vote) in (select ucase(item) from table_with_list_of_candidates)
group by ucase(vote)
a better design would have the list of candidates table include a key and then just have the key in the ballot_table with a varchar for a write in (if needed).
You can do this with an Outer Join and a Group By. I'm assuming the field name in the candidate table is Name.
Select c.Name, Count(Distinct b.UserName) Votes
From ballot_table b
Right Join candidate c On c.Name = b.Vote
Group By c.Name
This would only return the total votes for the candidates you have in your Candidate table. Any other "write-in" vote wouldn't be included.

MySQL - 3 tables, is this complex join even possible?

I have three tables: users, groups and relation.
Table users with fields: usrID, usrName, usrPass, usrPts
Table groups with fields: grpID, grpName, grpMinPts
Table relation with fields: uID, gID
User can be placed in group in two ways:
if collect group minimal number of points (users.usrPts > group.grpMinPts ORDER BY group.grpMinPts DSC LIMIT 1)
if his relation to the group is manually added in relation tables (user ID provided as uID, as well as group ID provided as gID in table named relation)
Can I create one single query, to determine for every user (or one specific), which group he belongs, but, manual relation (using relation table) should have higher priority than usrPts compared to grpMinPts? Also, I do not want to have one user shown twice (to show his real group by points, but related group also)...
Thanks in advance! :) I tried:
SELECT * FROM users LEFT JOIN (relation LEFT JOIN groups ON (relation.gID = groups.grpID) ON users.usrID = relation.uID
Using this I managed to extract specified relations (from relation table), but, I have no idea how to include user points, respecting above mentioned priority (specified first). I know how to do this in a few separated queries in php, that is simple, but I am curious, can it be done using one single query?
EDIT TO ADD:
Thanks to really educational technique using coalesce #GordonLinoff provided, I managed to make this query to work as I expected. So, here it goes:
SELECT o.usrID, o.usrName, o.usrPass, o.usrPts, t.grpID, t.grpName
FROM (
SELECT u.*, COALESCE(relationgroupid,groupid) AS thegroupid
FROM (
SELECT u.*, (
SELECT grpID
FROM groups g
WHERE u.usrPts > g.grpMinPts
ORDER BY g.grpMinPts DESC
LIMIT 1
) AS groupid, (
SELECT grpUID
FROM relation r
WHERE r.userUID = u.usrID
) AS relationgroupid
FROM users u
)u
)o
JOIN groups t ON t.grpID = o.thegroupid
Also, if you are wondering, like I did, is this approach faster or slower than doing three queries and processing in php, the answer is that this is slightly faster way. Average time of this query execution and showing results on a webpage is 14 ms. Three simple queries, processing in php and showing results on a webpage took 21 ms. Average is based on 10 cases, average execution time was, really, a constant time.
Here is an approach that uses correlated subqueries to get each of the values. It then chooses the appropriate one using the precedence rule that if the relations exist use that one, otherwise use the one from the groups table:
select u.*,
coalesce(relationgroupid, groupid) as thegroupid
from (select u.*,
(select grpid from groups g where u.usrPts > g.grpMinPts order by g.grpMinPts desc limit 1
) as groupid,
(select gid from relations r where r.userId = u.userId
) as relationgroupid
from users u
) u
Try something like this
select user.name, group.name
from group
join relation on relation.gid = group.gid
join user on user.uid = relation.uid
union
select user.name, g1.name
from group g1
join group g2 on g2.minpts > g1.minpts
join user on user.pts between g1.minpts and g2.minpts

SQL query: What groups is a given member NOT a member?

I have three tables in MySQL,
groups (key: group_id)
members (key: member_id)
group_member_relations key: group_id, member_id
The last table has combinations of members and groups (members that have joined that group).
I've been struggling with a way to perform a single query that gives me a list of members and groups that are NOT IN the group_member_relations table. (Basically I want to eventually ask the question "What groups is a given member not a member"). I can do this the hard way in code but was wondering if a single query was possible.
Not a SQL wiz at all, but have used it a lot over the last 20 years, mostly basic stuff. This is obviously over my head. Made many attempts over the last few days but, embarrassingly don't seem to get close.
Any pointers from the sql wizards out there..
Groups that a member is not in:
select *
from group
where id not in (
select group_id
from group_member_relations
where member_id = ?)
The following query will list all groups available and the members that are not present on each group. The query will also give all the columns for each table.
SELECT a.*, b.*
FROM members a
CROSS JOIN groups b
LEFT JOIN group_member_table c
ON a.memberID = c.memberID AND
b.groupID = c.groupID
WHERE c.memberID IS NULL OR -- actually this condition is already enough
c.groupID IS NULL
SQLFiddle Demo

MySQL: how to determine which rows in tables A and B are referenced by rows in table C in linear time?

I am working with a poorly designed database that I am not at liberty to restructure. In this database, there are three tables (let's call them 'companiesA', 'companiesB', and 'items') that are involved in a query that I need to optimize. 'companiesA' and 'companiesB' describe companies in the same way in that the column values are the same, but they represent two different groups of companies and have different column names. Essentially, the ID and company name columns are 'aID' and 'aName' in 'companiesA', and 'idB' and 'nameB' in 'companiesB'. 'items' contains a column, 'companyID', that contains a foreign key value from one of the two company tables.
The query I need to optimize gets a page's worth of company IDs and names from the union of the two tables, sorted by the names column, with an added column that states whether the row's company has any items associated with it. This query can also filter by the company names if the user requests it in the front-end. In its current state, I think it runs in THETA(companies * items) time, which is prohibitively slow:
select
a.aID as companyID,
a.aName as companyName,
(select
count(companyID)
from
items
where
companyID = a.aID
) as items
from
companiesA as a
where
a.aName like '%<string>%'
union
select
b.idB as companyID,
b.nameB as companyName,
(select
count(companyID)
from
items
where
companyID = b.idB
) as items
from
companiesB as b
where
b.nameB like '%<string>%'
order by
companyName ASC
limit
[optional_starting_index, ] 50;
It is not important that the items column contain the actual counts as this query returns (it was the only way I could figure out to cleanly return a value regarding the entire 'items' table). I suppose that I can count myself fortunate that with 1500 companies and 9000 items, this algorithm only takes seven seconds.
If I were writing this in another language in which I had access to the tables myself, I could easily write this in O(companies + items) time, but I am finding it difficult to figure out how to do so in MySQL. Is it possible to do this, preferably without stored functions or procedures? I CAN add them if necessary, but I have had a hard time adding them through phpMyAdmin now that the server's host only allows that interface to access the database by GUI.
In this solution, I took the daring assumption that the company names in each of the tables are unique by using Union All. If they are not, then you can switch back to Union but you'll get the performance hit of making the list unique. Basically, I'm eliminating your need for correlated subqueries to return the counts by using derived tables.
Select Companies.CompanyID, Companies.CompanyName
, Coalesce(ItemTotals.ItemCount,0) As ItemCount
From (
Select a.aID As CompanyID, a.aName As CompanyName
From companiesA As a
Where a.aName Like '%<string>%'
Union All
Select b.IDB, b.nameB
From companiesB As b
Where b.bName Like '%<string>%'
) As Companies
Left Join (
Select companyID, Count(*) As ItemCount
From items
Group By companyID
) As ItemTotals
On ItemTotals.companyID = Companies.CompanyID
Order By Company.CompanyName
Here is another variant. This one is similar to your original except that I replaced the correlated subqueries with two Group By queries. As before, if the names and IDs between the two tables are mutually exclusive, you can use Union All otherwise you will need to use Union.
Select Z.CompanyId, Z.CompanyName, Z.ItemCount
From (
Select A.companyID, A.aName As CompanyName
, Count(I.CompanyID) As ItemCount
From companiesA As A
Left Join items As I
On I.CompanyId = A.CompanyId
Where A.aName Like '%<string>%'
Group By A.companyID, A.aName
Union All
Select B.companyID, B.bName, Count(I.CompanyID)
From companiesB As B
Left Join items As I
On I.CompanyId = B.CompanyId
Where B.bName Like '%<string>%'
Group By B.companyID, B.bName
) As Z
Order By Z.CompanyName