Conditional Select in MySQL - mysql

I am working on a mysql query and its giving me headache!
The Scenario:
I am building a website where people can select industries they are interested in (NOTIFY_INDUSTRY). I join the selected values and store in a database field.
Example: a member selects agriculture (id = 9) and oil and gas (id = 13). I join them as 9-13 and store in the database.
Users can select several industries, not limited to two.
Also, members can select an industry (COMPANY_INDUSTRY) it belongs in assuming Information Technology which is stored in the database too.
Sample table (members):
ID
EMAIL
COMPANY_NAME
COMPANY_INDUSTRY
NOTIFY_INDUSTRY
The problem:
When a new user registers on the website, mail (the mails are sent on daily basis) is sent to existing users who have the new user's industry (COMPANY_INDUSTRY) as one of their interested industries (NOTIFY_INDUSTRY).
What i have done:
$sql="select id, email
from members
where notify_industry in (
select company_industry
from members
where datediff($today, date_activated) <= 1)"
This does not select the right members and i do not know the right way to go about it
EDIT - Exact Problem with current output:
Does not return any row, even when it should.
Assuming the new user's company_industry is 9, and there is an existing user with notify_industry: 10-9-20; it is meant to return the existing members email as the new member is in the existing member's categories of interest; but i get blanks

As #Shiplu pointed out, this is largely a normalization issue. Despite what some people seem to think, multi-value columns are murder to try to get right.
Your basic issue is:
You have members, who are interested in one or more companies/industries, which belong to one or more industries. You table structure should probably start as:
Industry
===============
id -- autoincrement
name -- varchar
Company
==============
id -- autoincrement
name -- varchar
Company_Industry
===============
companyId -- fk reference to Company.id
industryId -- fk reference to Industry.id
Member
===============
id -- autoincrement
name -- varchar
email -- varchar
Member_Interest_Industry
=========================
memberId -- fk reference to Member.id
industryId -- fk reference to Industry.id
Member_Interest_Company
========================
memberId -- fk reference to Member.id
companyId -- fk reference to Company.id
To get all companies a member is interested in (directly, or through an industry), you can then run something like this:
SELECT a.name, a.email, c.name
FROM Member as a
JOIN Member_Interest_Company as b
ON b.memberId = a.id
JOIN Company as c
ON c.id = b.companyId
WHERE a.id = :inputParm
UNION
SELECT a.name, a.email, d.name
FROM Member as a
JOIN Member_Interest_Industry as b
ON b.memberId = a.id
JOIN Company_Industry as c
ON c.industryId = b.industryId
JOIN Company as d
ON d.id = c.companyId
WHERE a.id = :inputParm

You should redesign the tables, as others have suggested.
However, barring that, there is a gross hack you can do:
SET sql_mode = 'ANSI';
SELECT notify_members.id, notify_members.email
FROM members notify_members
INNER JOIN members new_members
WHERE CURRENT_DATE - new_members.date_activated <= 1
AND
new_members.company_industry RLIKE ('[[:<:]](' || REPLACE(notify_members.notify_industry, '-', '|') || ')[[:>:]]');
Yuck. Basically, you turn 9-13 into the MySQL regular expression [[:<:]](9|13)[[:>:]], which matches 9, 13, 13-27-61, etc., but does not match 19-131 and the like. (This supports a compound COMPANY_INDUSTRY field, too.)

Use join SQL syntax rather than a select in style..
You need to join the members table to itself.
Currently:
select id, email
from members where notify_industry in
(select company_industry
from members
where datediff($today, date_activated) <= 1
)
Use this style:
select m1.id, m1.email
from members m1
inner join members m2 on m1.company_industry = m.notify_industry
where datediff($today, m2.date_activated) <= 1
Note the use of aliasing to m1 and m2 to help understand which id and emails are returned.

This may get a little ugly but you could try the following
WARNING This will make a Cartesian Product worthy of any Mad Scientist
SELECT NotifyIndustry.id,NotifyIndustry.email
FROM
(
SELECT CONCAT('-',COMPANY_INDUSTRY,'-') company FROM members
WHERE datediff($today, date_activated) <= 1)"
) CompanyIndustry
INNER JOIN
(
SELECT CONCAT('-', NOTIFY_INDUSTRY,'-') who_to_notify
FROM members
) NotifyIndustry
ON LOCATE(company,who_to_notify)>0;

probably not the fastest query ever but this should do the job:
select m_to_notify.id, m_to_notify.email
from members m_to_notify
join members m_new_member
on '-' || m_to_notify.notify_industry || '-'
like '%-' || m_new_member.company_industry || '-%'
where datediff($today, m_new_memberdate_activated) <= 1)

Related

Optimisation of subqueries

I have a relation between users and groups. Users can be in a group or not.
EDIT : Added some stuff to the model to make it more convenient.
Let's say I have a rule to add users in a group considering it has a specific town, and a custom metadata like age 18).
Curently, I do that to know which users I have to add in the group of the people living in Paris who are 18:
SELECT user.id AS 'id'
FROM user
LEFT JOIN
(
SELECT user_id
FROM user_has_role_group
WHERE role_group_id = 1 -- Group for Paris
)
AS T1
ON user.id = T1.user_id
WHERE
(
user.town = 'Paris' AND JSON_EXTRACT('custom_metadata', '$.age') = 18
)
AND T1.user_id IS NULL
It works & gives me the IDs of the users to insert in group.
But when I have 50 groups to proceed, like for 50 town or various ages, it forces me to do 50 requests, it's very slow and not efficient for my Database.
How could I generate a result for each group ?
Something like :
role_group_id user_to_add
1 1
1 2
2 1
2 3
The only way I know to do that for now is to do an UNION on several sub queries like the one above, but of course it's very slow.
Note that the custom_metadata field is a user defined field. I can't create specific columns or tables.
Thanks a lot for your help.
if I good understood you:
select user.id, grp.id
from user, role_group grp
where (user.id, grp.id) not in (select user_id, role_group_id from user_has_role_group) and user.town in ('Paris', 'Warsav')
that code give list of users and group which they not belong from one of towns..
To add the missing entries to user_has_role_group, you might want to have some mapping between those town names and their group_id's.
The example below is just using a subquery with unions for that.
But you could replace that with a select from a table.
Maybe even from role_group, if those names correlate with the user town names.
insert into user_has_role_group (user_id, group_id)
select u.user_id, g.group_id
from user u
join (
select 'Paris' as name, 1 as group_id union all
select 'Rome', 2
-- add more towns here
) g on (u.town = g.name)
left join user_has_role_group ug
on (ug.user_id = u.user_id and ug.role_group_id = g.group_id)
where u.town in ('Paris','Rome') -- add more towns here
and json_extract(u.custom_metadata, '$.age') = 18
and ug.id is null;

SQL - need help writing/modifying query

I've battling with this for a few days now and as SQL is not something I use too often I can't get my query to work the way I want it to.
I have a simple test database for tagging records using "toxi" solution (three tables, one acts as a link "ProgramCourses")
My tables
Program:
- ProgramID : int
- ProgramName : varchar
Course:
- CourseID : int
- CourseName : varchar
ProgamCourses:
- ProgramID : int
- CourseID : int
What I'm trying to do is to collect all tags as string and then add them as a new column to Program table.
My query
SELECT * , GROUP_CONCAT(c.CourseName) AS tags_list
FROM `Program` p
LEFT JOIN ProgramCourses AS pc ON p.ProgramID = pc.ProgramID
LEFT JOIN Course AS c ON pc.CourseID = c.CourseID
GROUP BY p.ProgramID
ORDER BY p.ProgramID
this kinda does what I need but it duplicates some columns and result I get is:
ProgramID ProgramName ProgramID CourseID CourseID CourseName tags_list
instead of
ProgramID ProgramName CourseName
Any help would be very appreciated.
If someone is kind enough to write a query for me could you please write it with full names of tables and columns as this would make it easier for me to understand and adopt for other purposes.
Thank you.
T.
DISTINCT c.CourseName Returns a count of the number of rows with different CourseName.
SELECT * , GROUP_CONCAT(DISTINCT c.CourseName) AS tags_list
FROM `Program` p
LEFT JOIN ProgramCourses AS pc ON p.ProgramID = pc.ProgramID
LEFT JOIN Course AS c ON pc.CourseID = c.CourseID
GROUP BY p.ProgramID
ORDER BY p.ProgramID
For More Info visit it Official
Try this
SELECT * , GROUP_CONCAT(c.CourseName) AS tags_list
FROM `Program` p
LEFT JOIN ProgramCourses AS pc using(ProgramID)
LEFT JOIN Course AS c using(CourseID)
GROUP BY p.ProgramID
ORDER BY p.ProgramID

SQL Genius need .. Complex MySQL query

I am trying to optimise my php by doing as much work on the MySQL server as possible. I have this sql query which is pulling data out of a leads table, but at the same time joining two tags tables to combine the result. I am looking to add a company which is linked through a relations table.
So the table that holds the relationship between the two is relations_value which simply states (I add example data)
parenttable (companies) | parentrecordid (10) | childtable (leads) | childrecordid (1)
the companies table has quite a few columns but the only two relevant are;
id (10) | companyname (my company name)
So this query currently grabs everything I need but I want to bring the companyname into the query:
SELECT leads.id,
GROUP_CONCAT(c.tag ORDER BY c.tag) AS tags,
leads.status,
leads.probability
FROM `gs_db_1002`.leads
LEFT JOIN ( SELECT *
FROM tags_module
WHERE tagid IN ( SELECT id
FROM tags
WHERE moduleid = 'leads' ) ) as b
ON leads.id = b.recordid
LEFT JOIN `gs_db_1002`.tags as c
ON b.tagid = c.id
GROUP BY leads.id,
leads.status,
leads.probability
I need to be able to go into the relations_values table and pull parenttable and parentrecordid by selecting childtable = leads and childrecordid = 1 and somehow join these so that I am able to get companyname as a column in the above query...
Is this possible?
I have created a sqlfiddle: sqlfiddle.com/#!2/023fa/2 So I am looking to add companies.companyname as column to the query.
I don't know what your primary keys and foreign keys are that link each table together.. if you could give a better understanding of what ID's are linked to eachother it would make this a lot easier... however i did something that does return the correct result... but since all of the ID's are = 1 then it could be incorrect.
SELECT
leads.id, GROUP_CONCAT(c.tag ORDER BY c.tag) AS tags,
leads.status, leads.probability, companyname
FROM leads
LEFT JOIN (
SELECT * FROM tags_module WHERE tagid IN (
SELECT id FROM tags WHERE moduleid = 'leads' )
) as b ON leads.id = b.recordid
LEFT JOIN tags as c ON b.tagid = c.id
LEFT JOIN relations_values rv on rv.id = b.recordid
LEFT JOIN companies c1 on c1.createdby = rv.parentrecordid
GROUP BY leads.id,leads.status, leads.probability

sql - join 2 tables 10 times

I have 2 tables structured like this:
People
----------
>ID, Name, Surname
Projects
----------
>ID, Assistant#1, Assistant#2, Assistant#3, Assistant#4,
> Assistant#5, Assistant#6, Assistant#7, Assistant#8, Assistant#9,
> Assistant#10, lot of other columns..... (every assistant column
> contains an integer, the people ID)
Now, I want to query my DB and get a single row from Project table (let's suppose row with ID=3) containing all columns of project table and in particular have each assistant replaced with it's correct name and surname.
I've managed to do this (with a lot of joins), but with my little sql experience my code looks very bulky and i'm sure that there's a better way to do this.
Thanks in advance.
SELECT p.Name, p.Surname
FROM People p
CROSS JOIN Project j ON (p.PeopleID = j.Assistant1 OR
p.PeopleID = j.Assistant2 OR
p.PeopleID = j.Assistant3 OR
.. etc)
AND j.ProjectID = 3
Your performance will be awful, but that's the price you pay for having a horribly designed database. The better solution is to map a project and people by the means of a decomposition:
CREATE TABLE proj_people
People_ID INT
Project_ID INT
Your structure is not clear to me. I advice you to create a clutch for projects.
Tables:
Peoples
people_id, people_name, people_surname
Projects
project_id, project_name
Clutch
project_id, people_id
Because you set a number of (possible) assistants at 10, that dictates that you are going to have to write code to accomodate those 10 assistants. This can be done a few ways: (have not tested)
select * from projects proj
left join people p1 on proj.assistant1 = p1.peopleid
left join people p2 on proj.assistant2 = p2.peopleid
left join people p3 on proj.assistant3 = p3.peopleid
left join people p4 on proj.assistant4 = p4.peopleid
left join people p5 on proj.assistant5 = p5.peopleid
left join people p6 on proj.assistant6 = p6.peopleid
left join people p7 on proj.assistant7 = p7.peopleid
left join people p8 on proj.assistant8 = p8.peopleid
left join people p9 on proj.assistant9 = p9.peopleid
left join people p10 on proj.assistant10 = p10.peopleid
otherwise you could do some trickery with
select proj.projectID,
(select * from people where peopleID = proj.assistant1),
(select * from people where peopleID = proj.assistant2),
(select * from people where peopleID = proj.assistant3),
(select * from people where peopleID = proj.assistant4),
(select * from people where peopleID = proj.assistant5),
(select * from people where peopleID = proj.assistant6),
(select * from people where peopleID = proj.assistant7),
(select * from people where peopleID = proj.assistant8),
(select * from people where peopleID = proj.assistant9),
(select * from people where peopleID = proj.assistant10)
from projects proj
If possible, it might be best to re-structure your datatables, and have a single assistant mapped to a single projectID:
PeopleID, Name, Surname
ProjectID, PeopleID
Therefore, you could do just one inner join and you will return a row for each assistant:
select * from projects proj
inner join people p
on p.peopleID = proj.peopleid
where proj.projectID = PROJECTID
Your problem is that you are trying to solve a data presentation issue at the database level and this is rarely a good idea. You should have a table holding projectid and assistantid only, having the combination of both columns as the primary key of the table. This will allow you to join this table to the assistant table and the project table only once and get all the results you need. This will also have the benefit of allowing you to support many more assistants on a project in the future without having to alter your queries to add yet more joins.
In conclusion you should have 3 tables:
Project
Assistant
ProjectAssistant (projectid,assistantid)
you want to restructure your tables. I hate seeing tables with multiple columns for essentially the same thing multiple times. If you restructure to have 3 tables:
Person
PeopleID | Name | Surname
Project
ProjectID | ...other columns except Assistant
Project_Person
ProjectID | PersonID
That way you can assign multiple assistants to the same project without the need for multiple Assistant columns. Then you could use something like this to get the required results:
SELECT proj.ProjectID, pers.Name, pers.Surname
FROM Person pers
INNER JOIN Project_Person pp ON pp.PersonID = pers.PersonID
INNER JOIN Project proj ON proj.ProjectID = pp.ProjectID
This will not return a single row, but that is not how SQL is designed to work.
If you don't want to/can't change the table structure then maybe use a user defined function?
CREATE FUNCTION GET_ASS_NAME (n1 ASS_NO)
RETURNS VARCHAR(50)
DETERMINISTIC
BEGIN
DECLARE fullname VARCHAR(50);
SELECT fullname = CONCAT(NAME, ' ', SURNAME) FROM PEOPLE WHERE ID = n1;
RETURN fullname;
END|
then
select GET_ASS_NAME(1), GET_ASS_NAME(2), GET_ASS_NAME(3), ...
from PROJECTS
where ID = 3
Perhaps a cleaner design is:
People table:
People ID, Name, Surname
ProjectAssistants table:
Project ID, Assistant Person ID
Projects table:
Project ID, lots of other columns (but no Assistant columns)
Then, the SQL becomes something like:
SELECT pr.*, p.*
FROM Projects pr
JOIN ProjectAssistants p_a ON pr.ProjectID = p_a ProjectID
JOIN People p ON p_a.AssistantPersonID = p.PeopleID
WHERE ProjectID = X
Not tested :)
Select * from project pr
left join people pe1 on pe1.peopleid = pr.astint1
left join people pe2 on pe2.peopleid = pr.astint2
left join people pe3 on pe3.peopleid = pr.astint3
left join people pe4 on pe4.peopleid = pr.astint4
left join people pe5 on pe5.peopleid = pr.astint5
left join people pe6 on pe6.peopleid = pr.astint6
left join people pe7 on pe7.peopleid = pr.astint7
left join people pe8 on pe8.peopleid = pr.astint8
left join people pe9 on pe9.peopleid = pr.astint9
left join people pe10 on pe10.peopleid = pr.astint10
where pr.projectid = 3

Join query with flattened result

I have the following entities
AddressType is simply an enum field that define if the Email is Personal/Work/Other.
Is it possible to do a query that returns a flattened result like the one in the following sample?
CustomerID Full Name Personal Email Work Email
----------- -------------- ----------------- -----------------------
1 John Doe johndoe#hotmail.com john.doe#company.com
select c.CustomerID,
c.FullName as [Full Name],
epersonal.AddressText as [Personal Email],
ework.AddressText as [Work Email]
from Customer c
left outer join Email epersonal on c.CustomerID = epersonal.CustomerID
and epersonal.AddressType = 'personal'
left outer join Email ework on c.CustomerID = ework.CustomerID
and epersonal.AddressType = 'work'
Two main choices:
1) Select it as typical (with two rows, one for each email), then use the pivot operator to flatten.
Example of pivot (I call it an example as I wrote it in notepad. It may be slightly wrong, but it should point you the right way):
select
CustomerID,
FullName
[1] as WorkEmail,
[2] as HomeEmail
from
(select
c.CustomerID, c.FullName, e.AddressText, e.AddressType
from
Customer c
join emails e on e.CustomerID = c.CustomerID) as Source
pivot (
AddressText
FOR AddressType in ([1], [2])
)
2) Join to the email table twice, once for each type of address. Suggest outer joins so if one is missing you still get the other.