Join 2 tables, display one column and group by same column - mysql

I have 2 tables:
[CompanyGroups] (id,id_company,id_group)
[CompanyRegions] (id,id_company,id_region)
I want to select only id_company where (id_group in (...) or id_region in (..))
Select g.id_company, r.id_company
from CompanyGroups g, CompanyRegions r
where (g.id_group in (...) or r.id_region in (...)) group by id_company
The results are in 2 columns: g.id_company has some ids and r.id_company has others ids.
How to grup them in only 1 column?

I learnt Database concept quite long ago. So I try to recall those concept and answer what I remember to your question.
You are joining two tables without specifying any joining relationships between them. Then you select fields from each table, so the MySQL will join the two table by mapping each record from CompanyGroups to each record from CompanyRegions.
To solve your problem, you should use UNION instead.
SELECT id_company
FROM CompanyGroups
WHERE id_group in (...)
UNION
SELECT id_company
FROM CompanyRegions
WHERE id_region in (...)

The answer by sagi might result in duplicates so I would extend the answer:
SELECT DISTINCT a.id_company
FROM (
SELECT g.id_company FROM CompanyGroup g
WHERE g.id_group in(...)
UNION
SELECT r.id_company FROM CompanyRegions r
WHERE r.id_region in(...)
) a

Related

Querying a Many-to-Many Linking Table

I have a linking table for a many-to-many relationship, with the fields
-
idNote
idTag
I would like to filter for all the tags that are associated with the notes that contain a specified number of tags.
For example, if I select the tags 'Running', 'Form', and 'Times', I would then like to see all the tags that are associated with the notes that have these 3 tags.
This process will be used by the user on the front end to refine the results they are looking for, so I need to be able to generate this SQL with code (node.js), with the filtering by tags potentially occurring many times over.
I have the below SQL code, which can query for two tags, but there are some problems with it:
It does not seem efficient
It can not be easily generated through code if another layer of filers needs to be added
SELECT DISTINCT idtag FROM table WHERE idnote IN (SELECT idnote FROM
(SELECT * FROM table WHERE idnote IN (SELECT idnote FROM table WHERE idtag
= 'Example')) as t1 where t1.idtag = 'SecondExample');
I am hoping for some suggestions on how to improve the efficiency of this code, as well as turning the sql statement into something that is easily code generateable.
Sounds like a data trap, the Cartesian product
https://en.wikipedia.org/wiki/Cartesian_product
Is there anything to bridge the two tables?
Like a common table between the two that we can join to?
Instead of N:N
Table A would be something in common with the notes table (B) and tags table (C)
we could have Table A join to Table B as 1:N
and Table A also join to C as 1:N
Then you could stitch the two separate facts together with a common table
I used your example of 'Running','Form','Times' as the specified set of tags.
select distinct idTag from table
where idNote in (select idNote from table where idTag in ('Running'))
and idNote in (select idNote from table where idTag in ('Form'))
and idNote in (select idNote from table where idTag in ('Times'))
Try something like this:
; with cteTagList as
(select 'Example' idtag
union select 'SecondExample'
--...
union select 'LastExample'
)
select t.idnote
from table t inner join cteTabList l on l.idtag = t.idtag
group by t.idnote
having count(*) = [NUMBER_OF_SEARCH_TAGS]
Where you generate a CTE (Common Table Expression) which contains all the search tags. Join it with the many-to-many relation table, and only select those notes that hat count equal to the number of search tags inputed by the user, noted [NUMBER_OF_SEARCH_TAGS] in the query

SQL Comment Grouping

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

query to join a table with two fields into one list containing items in both fields

Looking at similar questions, I actually want the exact opposite of this:
SQL query for getting data in two fields from one column
I have a table meetings with paired users:
A_user_id | B_user_id
1 2
3 4
There is a user table as well.
Is there a simple mysql query that lists all the user_ids into one long list?
query result
1
2
3
4
I was thinking something like this but it doesn't work:
select *
from user
where user.id in (
(select A_user_id from meeting)
or
(select B_user_id from meeting)
)
Thanks!
UPDATE (UNION solved this, but let's make this a bit more challenging):
I want to get a list of usernames and location names (both are reference tables) so I need to join this union query to them. Here's what I tried:
select u1.fname, l1.name
from meeting m1
join user u1 on m1.A_user_id=u1.id
join locations l1 on m1.location_id=l1.id
union
select u2.fname, l2.name
from meeting m2
join user u2 on m2.A_user_id=u2.id
join locations l2 on m2.location_id=l2.id
order by location_id asc
I'm getting two errors:
1- Not sure what kind of joins I need on these. (without the last 'order by' line) I'm getting a list of only 2 (there should be 4, as there are 2 pairs of people meeting). It seems to be pulling only the first item from each part of the union. I believe this relates to the type of join I'm doing for each, but not sure. So, users are distinct (there is only 1 user in the meeting table and it matches only 1 user in the user table), but locations are not (2 users are meeting at 1 location, and I think when I join on locations it is messing things up).
2- How do I use the "order by" at the end to order by the resulting list of "location_id"s, since now I have two named tables to deal with.
Thanks!
UPDATE 2:
Ok I put the two selects into parenthesis and UNIONed them and now I can order by the location_id... but I still have no idea how to join on the location table. Mysql doesn't like what I tried
(select u1.fname, m1.location_id
from meeting m1
join user u1 on m1.A_user_id=u1.id)
union
(select u2.fname, m2.location_id
from meeting m2
join user u2 on m2.B_user_id=u2.id)
#join locations l on l.id = location_id // this line messes things up *
order by location_id asc
Doesn't there need to be an all encompassing select around this whole thing?
How do I join the locations.id field on the "location_id" field that gets kicked off of the union query? Since the "location_id" field is technically in two different tables?
THe join above throws an error.
UPDATE 3: SOLVED
Here's my final query:
select tb1.fname, l.name
from (
(select u1.fname, m1.location_id
from meeting m1
join user u1 on m1.A_user_id=u1.id)
union
(select u2.fname, m2.location_id
from meeting m2
join user u2 on m2.B_user_id=u2.id)
) tb1
join locations l on l.id = tb1.location_id
order by location_id asc
select A_user_id as id from meetings
union
select B_user_id as id from meetings
in your example code, you could use an 'or', but the 'or' has to join two 'in' statements, if you get what I mean.
select *
from user
where
(
(user.id in (select A_user_id from meeting))
or
(user.id in ((select B_user_id from meeting))
)
And to answer you second update, you want something like
select locations.* from
(
(select A_user_id as id from meeting)
union
(select B_user_id as id from meeting)
) as UIDS
join
locations on locations.id = UIDS.id
select A_user_id as user_id from meetings
union all
select B_user_id as user_idfrom meetings
order by user_id
Notes:
UNION ALL keeps duplicates, UNION doesn't
Any ORDER BY goes at the end of the UNION

mysql: merge two columns into two rows

I have the following statement
SELECT disease.id, disease.name, disease_synonym.name FROM disease JOIN disease_synonym where diseaseId=code
the result is a table with an id and two columns with the names. how can i transform this into 2 columns with only an id and the name? (of course, the id will now occur several times)
Bye,
Nico
Two ways come to mind...
Run the query twice (once for name, and once for synonym), then union the results together...
SELECT disease.id, disease.name FROM disease
UNION ALL
SELECT disease.id, disease_synonym.name FROM disease JOIN disease_synonym where diseaseId=code
Or join on a two row table, and use a CASE statement to do a pivot...
SELECT
disease.id,
CASE WHEN pivot.field = 'name' THEN disease.name
WHEN pivot.field = 'syno' THEN disease_synonym.name
END
FROM
disease
INNER JOIN
disease_synonym
ON diseaseId=code
CROSS JOIN
(SELECT 'name' AS field UNION ALL SELECT 'syno' AS field) AS Pivot
I think you want SELECT disease.id, disease_synonym.name FROM ... don't you? Just displaying the synonym names?

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