MySQL: CASE outside JOIN - mysql

I have a table where I want to do different joins based on the value of a field in the source table. This is my current attempt:
SELECT i.name, c.name, loc.name FROM `item` i
CASE WHEN (i.loc_exception IS NOT NULL) THEN (
JOIN `location_exceptions` le ON i.loc_exception = le.id
JOIN `location` loc ON le.id_location = loc.id
JOIN `city` c ON loc.`id_city` = c.id
) ELSE (
JOIN `location` loc ON i.`id_location` = loc.id
JOIN `city` c ON i.`id_city` = c.id
)
WHERE i.id = 5
Is it even possible to use CASE as I've attempted here? If not, is there any other way I can achieve what I want?

Related

error 1175 when updating table by another table - WHERE clause exists

I'm trying to update a table using another table, like so, but I get error 1175 saying I don't have a WHERE clause when I do.
update t1 c, t2 dk
set c.date = dk.defaultdate, c.filter = 'word'
where c.id = dk.id and c.name = dk.name and c.color = dk.color and c.country = dk.country
and c.id is not null and c.date is null and c.name is not null and c.color is not null and c.country is not null;
how can I make the needed change anyway?
(im on MariaDB)
The first thing I would suggest here is to rewrite your update query using a proper explicit join:
UPDATE t1 c
INNER JOIN t2 dk
ON c.id = dk.id AND c.name = dk.name AND c.color = dk.color AND
c.country = dk.country
SET c.date = dk.defaultdate,
c.filter = 'word'
WHERE c.id IS NOT NULL AND c.date IS NULL AND c.name IS NOT NULL AND
c.color IS NOT NULL AND c.country IS NOT NULL;

WHERE clause, on joined table, with multiple rows

I have an incidents table which has a 1 to many relationship with a few tables - mainly, for the context of this question, people.
Basically, one incident may have many people (involved).
At the moment, I'm retrieving the incident details - plus a concatenated comma-delimited string of people's IDs using this query:
SELECT
i.`ID` AS `id`,
i.`Author_ID` AS `author_id`,
i.`Description` AS `description`,
i.`Date` AS `date`,
i.`Datetime_Created` AS `created`,
p.`Title` AS `period`,
GROUP_CONCAT(DISTINCT ip.`Person_ID` ORDER BY FIELD(ip.`Involvement`, 'V', 'P', 'W') ASC SEPARATOR ',') AS `people_ids`,
( SELECT COUNT(`ID`) FROM `reports` r WHERE r.Incident_ID = i.ID ) AS `reports`,
i.`Status` AS `status`
FROM `incidents` i
LEFT JOIN `reports` ir ON ir.Incident_ID = i.ID
LEFT JOIN `people` ip ON ip.Incident_ID = i.ID
LEFT JOIN `periods` p ON i.Period_ID = p.ID
WHERE 1 NOT IN ( SELECT Category_ID FROM `categories_link` WHERE `Incident_ID` = i.ID )
GROUP BY i.ID
ORDER BY i.`Date` DESC, p.`ID` DESC
This works fine, and produces data like:
What I'm trying to do now is filter these reports so that only incidents where one of the people involved is a student from a certain year group.
This information can be found by joining their IDs to the students table. The students table contains their ID and a Year_Group field.
One of the complexities is that some of the IDs from the people_involved table may not relate just to students - they could be staff, parents or other members of our community.
I don't want to exclude reports which have other people involved, as long as there is a student from a specific year group involved too.
I've written a query which seems to partially work:
SELECT
i.`ID` AS `id`,
i.`Author_ID` AS `author_id`,
i.`Description` AS `description`,
i.`Date` AS `date`,
i.`Datetime_Created` AS `created`,
p.`Title` AS `period`,
GROUP_CONCAT(DISTINCT ip.`Person_ID` ORDER BY FIELD(ip.`Involvement`, 'V', 'P', 'W') ASC SEPARATOR ',') AS `people_ids`,
( SELECT COUNT(`ID`) FROM `reports` r WHERE r.Incident_ID = i.ID ) AS `reports`,
i.`Status` AS `status`
FROM `incidents` i
LEFT JOIN `reports` ir ON ir.Incident_ID = i.ID
LEFT JOIN `people` ip ON ip.Incident_ID = i.ID
<< LEFT JOIN `student` stu ON ip.Person_ID = stu.db_id >>
LEFT JOIN `periods` p ON i.Period_ID = p.ID
WHERE 1 NOT IN ( SELECT Category_ID FROM `categories_link` WHERE `Incident_ID` = i.ID )
<< AND `stu`.`Year_Group` = 11 >>
GROUP BY i.ID
ORDER BY i.`Date` DESC, p.`ID` DESC
But I just can't imagine that a single simple JOIN would be sufficient for the task I'm trying to achieve.
I think a subquery might do it, but I don't know where to begin with that.
The code I would use to access this information (for year 7 students) without all of the necessary incidents data would be (I think):
SELECT DISTINCT( p.`Incident_ID` )
FROM `people` p
LEFT JOIN `student` stu ON p.Person_ID = stu.db_id
WHERE stu.Year_Group = 7
How do I bundle that into this code?
To get incidents where students of only specific age group is included,use the following query.
SELECT p.Incident_ID
FROM people p
JOIN student stu ON p.Person_ID = stu.db_id
WHERE stu.Year_Group = 11
group by p.Incident_ID
Your original query returns the incidents and the group of people involved ,So in your original query filter incidents by comparing with the above query written by me.This way you will get all incidents where students from a specific year group involved plus other people also involved(if any).I think this will solve your problem.
SELECT
i.`ID` AS `id`,
i.`Author_ID` AS `author_id`,
i.`Description` AS `description`,
i.`Date` AS `date`,
i.`Datetime_Created` AS `created`,
p.`Title` AS `period`,
GROUP_CONCAT(DISTINCT ip.`Person_ID` ORDER BY FIELD(ip.`Involvement`, 'V', 'P', 'W') ASC SEPARATOR ',') AS `people_ids`,
( SELECT COUNT(`ID`) FROM `reports` r WHERE r.Incident_ID = i.ID ) AS `reports`,
i.`Status` AS `status`
FROM `incidents` i
LEFT JOIN `reports` ir ON ir.Incident_ID = i.ID
LEFT JOIN `people` ip ON ip.Incident_ID = i.ID
LEFT JOIN `periods` p ON i.Period_ID = p.ID
WHERE 1 NOT IN ( SELECT Category_ID FROM `categories_link` WHERE `Incident_ID` = i.ID )
and i.ID in //Here you will put the above query
(
SELECT p.Incident_ID
FROM people p
JOIN student stu ON p.Person_ID = stu.db_id
WHERE stu.Year_Group = 11
group by p.Incident_ID
)
GROUP BY i.ID
ORDER BY i.`Date` DESC, p.`ID` DESC
It looks to me like you want an OUTER JOIN on students.
LEFT OUTER JOIN 'student' stu on ip.Person_ID = stu.db_id
That will include all the incidents. Then, in the WHERE clause, add the filter
WHERE 1 NOT IN ( SELECT Category_ID FROM `categories_link` WHERE `Incident_ID` = i.ID ) AND `stu`.`Year_Group` = 7

LEFT JOIN of only the latest row from a many-to-one

Been banging my head against the wall and cannot solve this :\
SELECT
`people`.*,
`students`.*,
`student_class_relationships`.*,
`geo_checkin_on_campus`.`datetime_created` as checkin_time
FROM `student_class_relationships`
LEFT OUTER JOIN `students`
ON `student_class_relationships`.`student` = `students`.`id`
LEFT OUTER JOIN `people`
ON `students`.`student` = `people`.`id`
LEFT OUTER JOIN `geo_checkin_on_campus`
ON `students`.`id` = (
SELECT MIN(`geo_checkin_on_campus`.`student`)
FROM `geo_checkin_on_campus`
WHERE `geo_checkin_on_campus`.`student` = `students`.`id`
)
WHERE `class` = 56
The expected result is many rows that have only one entry per students.id.
Here is my schema
It is not the best query from performance perspective,
but just to fix your query here is my attempt:
SELECT
`people`.*,
`students`.*,
`student_class_relationships`.*,
geoCheckinOnCampus.datetimeCreated as checkin_time
FROM `student_class_relationships`
LEFT JOIN `students`
ON `student_class_relationships`.`student` = `students`.`id`
LEFT JOIN `people`
ON `students`.`student` = `people`.`id`
LEFT JOIN
(
SELECT
student,
MAX(datetime_created) datetimeCreated
FROM `geo_checkin_on_campus`
GROUP BY `student`
) geoCheckinOnCampus
ON `students`.`id` = geoCheckinOnCampus.`student`
WHERE `class` = 56
Note According to #xQbert answer I would really change MIN to MAX function if you are looking for the latest datetime.
If i assume you want the most recent checkin (and not the earliest created date) for each student in go_checkin_on_Campus then...
SELECT
`people`.*,
`students`.*,
`student_class_relationships`.*,
B.`datetime_Updated` as checkin_time
FROM `student_class_relationships`
LEFT OUTER JOIN `students`
ON `student_class_relationships`.`student` = `students`.`id`
LEFT OUTER JOIN `people`
ON `students`.`student` = `people`.`id`
LEFT OUTER JOIN (
SELECT max(datetime_updated), student
FROM `geo_checkin_on_campus`
group by student
) B
ON `students`.`id` = B.Student
WHERE `class` = 56
NOTE: This is a probable answer. I will edit / modify this according to comments from OP.
This basically does nothing. This is as good as just joining on the student id
LEFT OUTER JOIN `geo_checkin_on_campus`
ON `students`.`id` = (
SELECT MIN(`geo_checkin_on_campus`.`student`)
FROM `geo_checkin_on_campus`
WHERE `geo_checkin_on_campus`.`student` = `students`.`id`
)
If you want the min (or earliest) datetime_created use something like
LEFT OUTER JOIN (
SELECT `geo_checkin_on_campus`.`student` student,
MIN(`geo_checkin_on_campus`.`datetime_created`) dt
FROM `geo_checkin_on_campus`
WHERE `geo_checkin_on_campus`.`student` = `students`.`id`
GROUP BY `geo_checkin_on_campus`.`student`
) t
ON `students`.`id` = t.student

MySQL - How to separate out multiple table rows into one like query result

We have several tables that we need to pull data from based on the user_id. Everything works great, except that now we've separated out the addresses and there can be more than one physical address location per user (up to 3 max). We need to identify each of these addresses as a1, a2, a3, knowing that a2 and a3 are optional and may be null.
Here is the addresses table schema:
$sql[] = "CREATE TABLE {$modules->tables->members_addresses} (
id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id bigint(20) NOT NULL,
mailing tinyint(1) DEFAULT 0 NOT NULL,
public tinyint(1) DEFAULT 0 NOT NULL,
address varchar(255) NOT NULL,
city varchar(255) NOT NULL,
region_id bigint(20) NOT NULL,
country_id bigint(20) NOT NULL,
postalcode varchar(200) NOT NULL,
date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
date_updated datetime DEFAULT '0000-00-00 00:00:00' NOT NULL
) {$modules->tables->charset_collate};";
Here is the query SQL that I have so far. The problem with it is I get a separate result for each location (i.e. instead of wrapping a1-a3 into one query result, it is giving me separate query results):
SELECT p.*, concat( u1.meta_value, ' ', u2.meta_value ) as fullname, u1.meta_value as first_name, u2.meta_value as last_name, a.address, a.city, a.postalcode, case when p.certifications = '' then roles.name else concat( roles.name, ',', p.certifications ) end as certification, case when r.name is null then c.name else concat( r.name, ', ', c.name ) end as location, case when r.name is null then '' else r.name end as region, case when c.name is null then '' else c.name end as country
FROM modules_profiles p
LEFT JOIN moonlight_usermeta u1 ON p.user_id = u1.user_id AND u1.meta_key = 'first_name'
LEFT JOIN moonlight_usermeta u2 ON p.user_id = u2.user_id AND u2.meta_key = 'last_name'
LEFT JOIN modules_members_addresses a ON p.user_id = a.user_id LEFT JOIN modules_roles roles ON roles.slug = p.certification
LEFT JOIN modules_regions r ON r.id = a.region_id LEFT JOIN modules_countries c ON c.id = a.country_id
WHERE p.user_id IN ( 1 )
ORDER BY p.user_id
What is the SQL to separate out a1, a2, and a3? BTW I need a1.address, a1.city, a1.region, a1.country, a1.postalcode (and repeat for a2 and a3).
You can join the same table many times in a query
SELECT u.id, u.name, a1.*, a2.*, a3.*
FROM users AS u
LEFT OUTER JOIN addresses AS a1 ON a1.userId = u.id
LEFT OUTER JOIN addresses AS a2 ON a2.userId = u.id AND a2.id < a1.id
LEFT OUTER JOIN addresses AS a3 ON a3.userId = u.id AND a3.id < a2.id
WHERE u.id = 1 AND a1.id = (SELECT MIN(id) FROM addresses WHERE userId = 1)
The a1.id = (SELECT MIN(id) FROM addresses WHERE userId = 1) is to get you the 'first' address first. It's often a bad idea to use ids for sequencing so you might want to implement a 'sequence' or 'preference' field in addresses.

Joining over 4 tables

Here's a fiddle with the schema and a sample query: http://sqlfiddle.com/#!2/573ec4/9
I'm looking to return C.price using I.section and L.level, which are obtained via other joins. When using an inner join with Cost I am left with no result: http://sqlfiddle.com/#!2/573ec4/2
Since cost is a table that maps a section and level to a price, I would like to be able to calculate the price of all the showings in my query
When you are joining to the Cost table you do not include the proper join conditions:
INNER JOIN `cost` `C`
ON `I`.`Section` = c.section -- = c.section is missing
AND `L`.`level` = c.level; -- = c.level is missing
So your full query will be:
SELECT `F`.`date`,
`F`.`time`,
`F`.`tname`,
`I`.`section`,
`L`.`level`,
c.price
FROM `booking_for_schedule` `F`
INNER JOIN `booking_in_seats` `I`
on `F`.`tname`=`I`.`tname`
AND `F`.`booking_num` = `I`.`booking_num`
INNER JOIN `level` `L`
on `F`.`date`=`L`.`date`
AND `F`.`time`=`L`.`time`
AND `F`.`tname`=`L`.`tname`
INNER JOIN `cost` `C`
ON `I`.`Section` = c.section
AND `L`.`level` = c.level;
See SQL Fiddle with Demo
Looks like you're missing some clause on the INNER JOIN cost C (when you are joining on the Cost table).
It should look like this:
INNER JOIN `cost` `C` ON `I`.`Section`=`C`.`Section` AND `L`.`level`=`C`.`level`
instead of:
INNER JOIN `cost` `C` ON `I`.`Section` AND `L`.`level`
You have missed to join the columns. the reason why you have no result is because I.Section AND L.level will always return false.
SELECT F.date,
F.time,
F.tname,
I.section,
L.level,
c.*
FROM booking_for_schedule F
INNER JOIN booking_in_seats I
ON F.tname = I.tname AND
F.booking_num = I.booking_num
INNER JOIN level L
ON F.date = L.date AND
F.time = L.time AND
F.tname = L.tname
INNER JOIN cost C
ON I.Section = C.Section AND -- <<== HERE
L.level = C.level -- <<== HERE
SQLFiddle Demo
Your last line is the problem:
INNER JOIN `cost` `C` ON `I`.`Section` AND `L`.`level`
It should be this:
INNER JOIN `cost` `C` ON `I`.`Section` = `C`.`Section` AND `L`.`level` = `C`.`Level`