mysql Alternatives to INTERSECT - mysql

I have been trying for the best part of the day to remove the intersect since it is not supported by mysql. If anyone can provide some pointers it would be really helpful.
SELECT *, DATE_FORMAT(NOW(), '%Y') - DATE_FORMAT(dob, '%Y') - (DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(dob, '00-%m-%d')) AS age
FROM user U, user_utilisation UU
WHERE U.id_user = UU.id_user AND cp >= 1 AND cp <= 3000 AND sexe = 'M' AND UU.id_mailing = 6
GROUP BY U.id_user
HAVING age >= 1 AND age <= 100
ORDER BY nom, prenom
INTERSECT
SELECT *, DATE_FORMAT(NOW(), '%Y') - DATE_FORMAT(dob, '%Y') - (DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(dob, '00-%m-%d')) AS age
FROM user U, user_utilisation UU
WHERE U.id_user = UU.id_user AND cp >= 1 AND cp <= 3000 AND sexe = 'M' AND UU.id_mailing = 7
GROUP BY U.id_user
HAVING age >= 1 AND age <= 100
ORDER BY nom, prenom
I tried with JOIN(s) but here's what I have right now:
SELECT *, DATE_FORMAT(NOW(), '%Y') - DATE_FORMAT(naissance, '%Y') - (DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(naissance, '00-%m-%d')) AS age
FROM user U, user_utilisation UU
WHERE U.id_user = UU.id_user AND cp >= 1 AND cp <= 3000 AND sexe = 'M'
AND UU.id_user IN (select id_user from user_utilisation where id_mailing = 6 OR id_mailing = 7)
HAVING age >= 1 AND age <= 100
ORDER BY nom, prenom
but by removing the GROUP BY I see that the query selected 2 records where id_mailing = 1, while the GROUP BY is hiding the wrong record. I'm pretty sure that this could cause problems...
user_utilisation only has three fields id_user, id_mailing, and date.

Interpreting your queries, it seems to me:
You want to get all users who participate in mailing_id 6 and 7 (that's why you are doing the INTERSECT, is that right?).
You want to restrict those users by criteria and perform an age calculation.
If my interpretation of your intent is correct (not at all sure that is the case!), you don't need INTERSECT at all, you should be able to simply select the users you want with the criteria you want, and restrict participation in both mailing 6 and 7 via JOIN:
SELECT *, DATE_FORMAT(NOW(), '%Y') - DATE_FORMAT(dob, '%Y') - (DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(dob, '00-%m-%d')) AS age
FROM user U
INNER JOIN user_utilisation UU on U.id_user = UU.id_user and UU.mailing_id = 6
INNER JOIN user_utilisation UU2 on U.id_user = UU2.id_user and UU2.mailing_id = 7
WHERE cp >= 1 AND cp <= 3000 AND sexe = 'M'
HAVING age >= 1 AND age <= 100
ORDER BY nom, prenom

You could try something like the following:
SELECT *
FROM
(SELECT *
, DATE_FORMAT(NOW(), '%Y') - DATE_FORMAT(dob, '%Y') - (DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(dob, '00-%m-%d')) AS age
FROM user U
INNER JOIN user_utilisation UU
ON U.id_user = UU.id_user
WHERE cp >= 1 AND cp <= 3000
AND sexe = 'M'
AND UU.id_mailing = 6
GROUP BY U.id_user
HAVING age >= 1 AND age <= 100) QUERY1
WHERE EXISTS(SELECT *
, DATE_FORMAT(NOW(), '%Y') - DATE_FORMAT(dob, '%Y') - (DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(dob, '00-%m-%d')) AS age
FROM user U
INNER JOIN user_utilisation UU
ON U.id_user = UU.id_user
WHERE cp >= 1 AND cp <= 3000
AND sexe = 'M'
AND UU.id_mailing = 7
AND U.id_user = QUERY1.id_user
GROUP BY U.id_user
)
ORDER BY nom, prenom
The trick in the query above is that the WHERE EXISTS( ... ) clause acts as a filter, similar to the INTERSECT you were using before. The idea is that you will want to only select those records from the query QUERY1 that meet the criteria that there is at least one record in the query in the EXITS clause which is conditioned on the id_user being the same as the id_user from the QUERY1 query.. I hope this makes some sense..
I could probably tune it a little bit for you if you would provide the structure of the User table. And by the way, using those blanket SELECT * statements is not a good idea..

I think you can simplify the query. It is has several curiosities. First, the query doesn't use proper join syntax. Second, the query is using the having clause just to filter on an alias from the select. I recommend a subquery in that case.
It looks like you are trying to get users who are in both "groups" as defined by two very similar queries (only the where clause is different). The following may be what you are looking for:
select t.*
from (SELECT *, DATE_FORMAT(NOW(), '%Y') - DATE_FORMAT(dob, '%Y') - (DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(dob, '00-%m-%d')) AS age,
(case when cp >= 1 AND cp <= 3000 AND sexe = 'M' AND UU.id_mailing = 6 then 'FirstGroup'
when cp >= 1 AND cp <= 3000 AND sexe = 'M' AND UU.id_mailing = 7 then 'SecondGroup'
end) as thegroup
FROM user U join user_utilisation UU
on U.id_user = UU.id_user
) t
where thegroup is not null and age between 1 and 100
GROUP BY U.id_user
having max(thegroup) <> min(thegroup)
ORDER BY nom, prenom
The having clause is a short-hand way of saying that the user is in both groups.

Related

MySQL using IF or CASE statement across joined tables

HI all here is a MySQL problem that uses results from a 2 table join, conditionally assess them and outputs 2 values.
Here is the database structure.
The 1st table gtpro contains
a user ID (column name id)
a samples/year number ie 2, 4 or 12 times/year (column name labSamples__yr)
The 2nd table labresults contains
that same user ID (column name idgtpro)
and a date column for the sample dates (when the samples were provided) column name date
so this query returns an overview of all id's and when were the last samples submitted for that id.
SELECT a.id, a.labSamples__yr, max(b.date) as ndate from gtpro as a
join labresults as b on a.id = b.idgtpro group by a.id
the conditions I want to evaluate looks like this.
a.labSamples__yr = 2 and ndate >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
a.labSamples__yr = 4 and ndate >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH)
a.labSamples__yr = 12 and ndate >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)
So if number of samples /year is 2 and the last samle date was more than 6 months ago I want to know the id and latest date of samples for that id.
I tried using CASE and IF statements but can't quite get it right. This was my latest attempt.
select id, ndate,
case when (labSamples__yr = 2 and ndate <= DATE_SUB(CURDATE(), INTERVAL 6 MONTH))is true
then
(SELECT id from gtpro as a join labresults as b on a.id = b.idgtpro where
labSamples__yr = 2 and max(b.date) <= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)) end as id
from (SELECT a.id, a.labSamples__yr, max(b.date) as ndate from gtpro as a
join labresults as b on a.id = b.idgtpro group by a.id) d
this tells me invalid use of group function.
Desperate for a bit of help
EDIT I messed up some of the names in the code above which i have now fixed.
If I understand your question correctly, you should be able to put the conditions in the where clause:
SELECT a.id, a.labSamples__yr, max(b.date) as ndate
from gtpro a join
labresults b
on a.id = b.idgtpro
where (a.labSamples__yr = 2 and b.date >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)) or
(a.labSamples__yr = 4 and b.date >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH)) or
(a.labSamples__yr = 12 and b.date >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH))
group by a.id;
That fixes your syntax problem. But, if you want the id with the maximum date, try doing this:
select a.labSamples__yr, max(b.date) as ndate,
substring_index(group_concat(a.id order by b.date desc)) as maxid
from gtpro a join
labresults b
on a.id = b.idgtpro
where (a.labSamples__yr = 2 and b.date >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)) or
(a.labSamples__yr = 4 and b.date >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH)) or
(a.labSamples__yr = 12 and b.date >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH))
group by a.labSamples__yr;
Putting a.id in the group by is not going to give you the maximum id of anything.
Is this meant to be valid MySQL? I wasn't aware of "is true" being valid in a CASE statement. In fairness though I'm more familiar with Oracle and SQL Server but nevertheless... does any part of this statement work?
EDIT
Ok, here is what I have edited the code to be:
select id, ndate,
case when (labSamples__yr = 2 and ndate <= DATE_SUB(CURDATE(), INTERVAL 6 MONTH))
then
(SELECT id from bifipro as a join labresults as b on a.id = b.idBifipro where
labSamples__yr = 2 and max(b.date) <= DATE_SUB(CURDATE(), INTERVAL 6 MONTH) where a.id=d.id) end as id
from (SELECT a.id, a.labSamples__yr, max(b.date) as ndate from bifipro as a
join labresults as b on a.id = b.idBifipro group by a.id) d
In your correlated subquery I have added a predicate of "where a.id =
d.id"
I have removed the text "is true" from your case statement (this may
be incorrect but I didnt' think it should be there.
The answer partly inspired by Tomas (sql clarification and syntax clarification) I got rid of the CASE all together. It seems nice and clean to me but I would like to hear any other suggestions
select id, labSamples__yr, ndate from
(SELECT a.id, a.labSamples__yr, max(b.date) as ndate from gtpro as a
join labresults as b on a.id = b.idgtpro group by a.id)d
where (ndate <= DATE_SUB(CURDATE(), INTERVAL 6 MONTH) and labSamples__yr = 2)
or (ndate <= DATE_SUB(CURDATE(), INTERVAL 3 MONTH) and labSamples__yr = 4)
or (ndate <= DATE_SUB(CURDATE(), INTERVAL 1 MONTH) and labSamples__yr = 12)
Thanks for looking but it would still be nice to see a solution using a CASE statement for future reference???

MySQL : Different behaviors depending on WHERE result

I'm modifying an existing project. I want to group 3 mysql request in one.
These 3 request have the same selected data, only the WHERE change.
here's one of the request for exemple :
SELECT COUNT(seg.my_seg1) FROM (
SELECT COUNT(DISTINCT cp.conference_id) as my_seg1 FROM A.Account a
INNER JOIN A.ConferenceParticipant cp ON a.account_id = cp.user_id
INNER JOIN A.Conference cf ON cf.id = cp.conference_id
WHERE cf.`status` = 0
AND DATE_SUB(CURDATE(), INTERVAL 30 DAY) <= cf.creation_timestamp
GROUP BY a.account_id) as seg
WHERE seg.my_seg1 >= 30
The 2 other requests are exactly the same except :
WHERE seg.my_seg1 >= 11 AND seg.my_seg1 <= 30;
and :
WHERE seg.my_seg1 >= 30;
So my question is how can I get 3 different values depending on the WHERE result in the same request ?
Like this you'll have 3 virtual columns:
SELECT
COUNT(IF(seg.my_seg1 >= 30, 1, 0)) AS res1,
COUNT(IF(seg.my_seg1 >= 11 AND seg.my_seg1 < 30, 1, 0)) AS res2
FROM (
SELECT COUNT(DISTINCT cp.conference_id) as my_seg1
FROM A.Account a
JOIN A.ConferenceParticipant cp ON a.account_id = cp.user_id
JOIN A.Conference cf ON cf.id = cp.conference_id
WHERE
cf.`status` = 0
AND DATE_SUB(CURDATE(), INTERVAL 30 DAY) <= cf.creation_timestamp
GROUP BY a.account_id
) AS seg
But you have to revise your filters, you talk about 3 but I only see 2 different ones.

Update age column in MySQL

I have a user table with the user's birthday (YYYY-MM-DD) as well as age. I want to run a script to calculate and update the age column nightly via cron.
This SQL works well for selecting and calculating the age:
SELECT
DATE_FORMAT(NOW(), '%Y') -
DATE_FORMAT(`birthday`, '%Y') -
(DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(`birthday`, '00-%m-%d')) AS age
FROM
`jos_jcourse_students`
But is it possible to update the age column with a single statement? Tried the following, but all I managed to do was populate the age column with all 0s! Do I need to use some sort of MySQL loop?
UPDATE
`jos_jcourse_students`
SET
age = "SELECT DATE_FORMAT(NOW(), '%Y') -
DATE_FORMAT(`birthday`, '%Y') -
(DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(`birthday`, '00-%m-%d')) AS age
FROM
`jos_jcourse_students`"
UPDATE `jos_jcourse_students`
SET age = DATE_FORMAT(FROM_DAYS(TO_DAYS(NOW())-TO_DAYS(birthday)), '%Y')+0
OR according to your logic it will be
UPDATE `jos_jcourse_students`
SET age =((DATE_FORMAT(NOW(), '%Y') - DATE_FORMAT(`birthday`, '%Y')) -
(DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(`birthday`, '00-%m-%d')))
UPDATE
`jos_jcourse_students`
SET
age = (SELECT DATE_FORMAT(NOW(), '%Y') -
DATE_FORMAT(`birthday`, '%Y') -
(DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(`birthday`, '00-%m-%d')) AS age
FROM
`jos_jcourse_students`)
One way you can achieve the above result by using the self join technique. i.e join to the same table using its unique id (primary key example studentId)
UPDATE jos_jcourse_students a,
( SELECT studentId, DATE_FORMAT(NOW(), '%Y') - DATE_FORMAT(`birthday`, '%Y') - (DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(`birthday`, '00-%m-%d')) AS age
FROM `jos_jcourse_students`) b
SET a.age = b.age
WHERE a.studentId = b.studentId
Note: studentId is primary key in jos_jcourse_students

mySQL Populate DB Column using calculated value from anouther table

What I am trying to do is have a field called 'age' autopopulate from a persons date of birth when a row is added - the trick is the persons date of birth resides in anouther field.
My two tables are:
student
student_id (PK), first_name, last_name, date_of_birth
fitness_report
report_id (PK), test_date, test_period, age_tested, student_id (FK)
ideally the age_tested will be caluclated from the test_period however happy to use ()NOW as that'll be within reasonable limits.
Obviously what i need to do here is create a trigger - but not sure on the SELECT statement to get the age to populate. Help is much appreciated.
As rwilliams said, it is not advisable to store the age_tested as it is redundant but if it is what you want to do you can wrap a simple trigger around Mosty's solution -
CREATE TRIGGER fitness_report_insert BEFORE INSERT ON `fitness_report`
FOR EACH ROW
BEGIN
SET NEW.age_tested = (SELECT DATE_FORMAT(NEW.test_period, '%Y') - DATE_FORMAT(student.date_of_birth, '%Y') - (DATE_FORMAT(NEW.test_period, '00-%m-%d') < DATE_FORMAT(student.date_of_birth, '00-%m-%d')) FROM student WHERE student.student_id = NEW.student_id);
END
This assumes that test_period is a date as previously mentioned by Mosty. I have not tried this as I do not have access to a server right now so it may need a little tweaking.
UPDATE: You could try the following as a starting point for your stats -
SELECT
DATE_FORMAT(test_period, '%Y') - DATE_FORMAT(date_of_birth, '%Y') - (DATE_FORMAT(test_period, '00-%m-%d') < DATE_FORMAT(date_of_birth, '00-%m-%d')) AS age,
s.gender,
AVG(r.score) AS score
FROM student s
INNER JOIN fitness_report r
ON s.student_id = r.student_id
GROUP BY age, s.gender
This is how to get current age:
select s.name, s.date_of_birth,
date_format(now(), '%Y') - date_format(date_of_birth, '%Y') -
(date_format(now(), '00-%m-%d') < date_format(date_of_birth, '00-%m-%d'))
as age
from s
Example
This is how to get age up to the test_period (shouldn't it be test_date?):
select s.name, s.date_of_birth, r.test_period,
date_format(test_period, '%Y') - date_format(date_of_birth, '%Y') -
(date_format(test_period, '00-%m-%d') < date_format(date_of_birth, '00-%m-%d'))
as age
from r
join s on s.id_student = r.student_id
Example

mysql join only return one result from the join

I have the following query,
SELECT `candidates`.`candidate_id`,
`candidates`.`first_name`,
`candidates`.`surname`,
`candidates`.`DOB`,
`candidates`.`gender`,
`candidates`.`talent`,
`candidates`.`location`,
`candidates`.`availability`,
DATE_FORMAT(NOW(), '%Y') - DATE_FORMAT(`candidates`.`DOB`, '%Y') - (DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(`candidates`.`DOB`, '00-%m-%d')) as `age`,
`candidate_assets`.`url`,
`candidate_assets`.`asset_size`
FROM `candidates`
LEFT JOIN `candidate_assets` ON `candidate_assets`.`candidates_candidate_id` = `candidates`.`candidate_id`
WHERE `candidates`.`availability` = 'yes'";
The query is currently returning multiple rows from the joined table is possible to return only one result per join?
It is possible. Try to use GROUP_CONCAT function, e.g. -
SELECT
c.`candidate_id`,
c.`first_name`,
c.`surname`,
c.`DOB`,
c.`gender`,
c.`talent`,
c.`location`,
c.`availability`,
DATE_FORMAT(NOW(), '%Y') - DATE_FORMAT(c.`DOB`, '%Y') - (DATE_FORMAT(NOW(), '00-%m-%d') < DATE_FORMAT(c.`DOB`, '00-%m-%d')) as `age`,
GROUP_CONCAT(ca.`url`) `url`,
GROUP_CONCAT(ca.`asset_size`) `asset_size`
FROM `candidates` c
LEFT JOIN `candidate_assets` ca ON ca.`candidates_candidate_id` = c.`candidate_id`
WHERE c.`availability` = 'yes'
GROUP BY c.candidate_id