mySQL Left Join 5 Tables? - mysql

Thanks in advance for any help. I am working with 5 tables in a mySQL database. The system is such that I have a top level table called "owners" (clients) that have local business (shops). These owners go out and create accounts at websites like yelp (citation_sources) and as such have login credential (citation_login). Once they have an account at a citation source, they add shops to the directory.
I am hoping to create one query that would select ALL of the citation sources, regardless of if an owner has an account or not, and loop through the recordset, showing login for each citation source they have an account with, as well as any shop listings.
My question pertains to doing a left join on 5 tables. I left out most fo the fields but have set up primary and foreign keys Is the sequence of the join important, ie. start with one particular table, ending with another?
I tried this command but it only brings back 33 rows when in fact there are 96 citation_sources.
I think I figured it out. I created a new table called "citation_shop" with a composite primary key - citation - shop. I then ran a query and it got me the results I was after. I ended up putting a condition in the first left join.
SELECT citation_sources.name, citation_shop.shop from citation_sources left join citation_shop on citation_sources.id = citation_shop.citation and citation_shop.shop in (6,7) left join shops on citation_shop.shop = shops.id group by citation_sources.name, citation_shop.shop limit 100
CREATE TABLE `citation_shop` (
`shop` smallint(5) UNSIGNED NOT NULL,
`citation` smallint(6) UNSIGNED NOT NULL,
`url` text NOT NULL,
`count` smallint(3) UNSIGNED NOT NULL,
`status` tinyint(1) UNSIGNED NOT NULL,
`sort` tinyint(3) UNSIGNED NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `citation_shop`
--
ALTER TABLE `citation_shop`
ADD PRIMARY KEY (`citation`,`shop`);
select owners.id as owner_id, shops.id as shop_id, citation_sources.name, citation_shop_urls.url, citation_logins.password
from owners
inner join shops on owners.id = shops.owner_id
left join citation_logins on owners.id = citation_logins.owner
left join citation_sources on citation_logins.c_source = citation_sources.id
left join citation_shop_urls on citation_sources.id = citation_shop_urls.citation_id
where owners.id = 3
group by citation_sources.name
Here are my tables in order of what I think is relevlance:
CREATE TABLE `owners` (
`id` smallint(6) UNSIGNED NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
CREATE TABLE `shops` (
`id` smallint(5) UNSIGNED NOT NULL,
`title` varchar(50) DEFAULT '',
`owner_id` smallint(5) UNSIGNED NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `citation_sources` (
`id` smallint(6) UNSIGNED NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `citation_shop_urls` (
`shop` smallint(5) UNSIGNED NOT NULL DEFAULT '0',
`citation_id` tinyint(5) UNSIGNED NOT NULL DEFAULT '0',
`owner` smallint(6) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `citation_logins` (
`c_source` smallint(5) UNSIGNED NOT NULL DEFAULT '0',
`owner` smallint(6) NOT NULL,
`user_name` text NOT NULL,
`password` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

In a LEFT JOIN, the first table is the one where you get all the rows, even if they don't have a match in other tables. So if you want all citation_sources, even those not associated with any owner, then citation_sources should be the table on the left of the LEFT JOIN.
To filter the owner information only to id = 3, put o.id = 3 in the ON clause that joins with owners. Then use a WHERE clause to remove all the other rows.
SELECT o.id as owner_id, s.id as shop_id, cs.name, u.url, cl.password
FROM citation_sources AS cs
LEFT JOIN citation_shop_urls AS u ON u.citation_id = cs.id
LEFT JOIN citation_logins AS cl ON cs.id = cl.c_source
LEFT JOIN owners AS o ON o.id = cl.owner AND o.id = 3
LEFT JOIN shops AS s ON s.owner_id = o.id
WHERE o.id IS NULL OR o.id = 3

Related

how to add one more table to already complicated query

I am trying to build search query for hotel rooms availibility but it seems that his query is way over my head, and i need help to build it.
Note that there will be multiply hotels in the database.
Even that I am looking for available rooms, my idea was not to build availibilty table, but instead to use reserevation table,
and I assume that if the rooom is not in the reservation table, it is available.
I have the following fields in the search form:
area (represented with areaid), checkInDate, checkOutDate, rooms (how many rooms he need), adults and childrens.
Here are the tables that should be involved in this search:
room roomType reservationroom reservation and hotels
(for those confused why I have the resrevation room, reason is simple, one reservation can have more than one room, so it is helper table)
Here are the tables:
CREATE TABLE `room` (
`roomID` int(11) NOT NULL AUTO_INCREMENT,
`hotelID` int(11) NOT NULL,
`roomtypeID` int(11) NOT NULL,
`roomNumber` int(11) NOT NULL,
`roomName` varchar(255) NOT NULL,
`roomDescription` text,
`roomVisible` tinyint(4) NOT NULL,
PRIMARY KEY (`roomID`),
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
CREATE TABLE `roomtype` (
`roomtypeID` int(11) NOT NULL AUTO_INCREMENT,
`hotelID` int(11) NOT NULL,
`roomtypeName` varchar(255) NOT NULL,
`roomtypeAdults` int(11) NOT NULL,
`roomtypeChildrens` int(11) NOT NULL,
`roomtypeDescription` text,
PRIMARY KEY (`roomtypeID`),
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
CREATE TABLE `hotel` (
`hotelID` int(11) NOT NULL AUTO_INCREMENT,
`areaID` int(11) NOT NULL,
`hotelcategoryID` int(11) DEFAULT NULL,
`hotelName` varchar(255) NOT NULL,
`hotelShortDescription` text,
`hotelAddress` varchar(255) DEFAULT NULL
PRIMARY KEY (`hotelID`),
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
CREATE TABLE `reservation` (
`reservationID` int(11) NOT NULL AUTO_INCREMENT,
`customerID` int(11) NOT NULL,
`hotelID` int(11) NOT NULL,
`reservationCreatedOn` datetime NOT NULL,
`reservationCreatedFromIp` varchar(255) CHARACTER SET greek NOT NULL,
`reservationNumberOfAdults` tinyint(4) NOT NULL,
`reservationNumberOfChildrens` tinyint(4) NOT NULL,
`reservationArrivalDate` date NOT NULL,
`reservationDepartureDate` date NOT NULL,
`reservationCustomerComment` text CHARACTER SET greek,
PRIMARY KEY (`reservationID`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
CREATE TABLE `reservationroom` (
`reservationroomID` int(11) NOT NULL AUTO_INCREMENT,
`reservationID` int(11) NOT NULL,
`hotelID` int(11) NOT NULL,
`roomID` int(11) NOT NULL,
PRIMARY KEY (`reservationroomID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Please note that I have removed the unnecessery fields from the tables, to make the code shorter and more easier to read.
At this moment, I have the following query which works but the problem is tha I have to include the reservation table in this query,
having in mind that my search have 2 fields checkInDate, checkOutDate which are main parameters to check which hotels have available rooms.
Here is the current query:
SELECT r.*, h.*,rr.* FROM room r
LEFT JOIN `reservationroom` rr
ON r.`hotelID` = rr.`hotelID` AND r.`roomID` = rr.`roomID`
LEFT JOIN `hotel` h
ON h.`hotelID` = r.`hotelID`
WHERE ( rr.`reservationroomID` = '' OR rr.`reservationroomID` IS NULL );
Anyone can help me to add the reservation table in this query?
Regards, John
Main issue is elminating rooms from the search is they are reserved. To do this you would need to join to your reservation table and check that the date range doesn't overlap with the range being booked.
The basics of this would be:-
SELECT r.*, h.*,rr.*
FROM room r
INNER JOIN `hotel` h
ON h.`hotelID` = r.`hotelID`
LEFT JOIN `reservationroom` rr
ON r.`hotelID` = rr.`hotelID`
AND r.`roomID` = rr.`roomID`
LEFT JOIN reservation res
ON rr.reservationID = res.reservationID
AND res.reservationArrivalDate < $checkOutDate
AND res.reservationDepartureDate > $checkInDate
WHERE ( rr.`reservationroomID` = ''
OR rr.`reservationroomID` IS NULL );
It gets more complicated when you need to check the number of rooms available. To do this you probably need to use something similar to the above as a sub query, getting the hotel id and a count of rooms, then using HAVING to check that count is greater than or equal to the number rooms required, then joining the results of that back against hotels and rooms to get the required details of the available rooms.
EDIT - bit more details (not tested). Sub query gets the hotels with enough free rooms in the time required, then joins back against hotel and rooms to get the details of those.
SELECT r.*, h.*
FROM room r
INNER JOIN hotel h
ON h.hotelID = r.hotelID
INNER JOIN
(
SELECT h.hotelID, COUNT(r.roomID) AS RoomCount
FROM room r
INNER JOIN hotel h
ON h.hotelID = r.hotelID
LEFT JOIN reservationroom rr
ON r.hotelID = rr.hotelID
AND r.roomID = rr.roomID
LEFT JOIN reservation res
ON rr.reservationID = res.reservationID
AND res.reservationArrivalDate < $checkOutDate
AND res.reservationDepartureDate > $checkInDate
WHERE ( res.reservationID IS NULL )
AND h.areaID = $areaID
GROUP BY h.hotelID
HAVING RoomCount >= $rooms
) sub0
ON h.hotelID = sub0.hotelID

Joining different tables based on column value

I have a table called notifications:
CREATE TABLE `notifications` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`type` varchar(20) NOT NULL DEFAULT '',
`parent_id` int(11) DEFAULT NULL,
`parent_type` varchar(15) DEFAULT NULL,
`type_id` int(11) DEFAULT NULL,
`etc` NULL
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8;
Each notification is related to a different table, the value of parent_type field specifies the name of the table that I want to * join the table with. All target tables have several similar columns:
CREATE TABLE `tablename` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`is_visible` tinyint(1) NOT NULL,
`etc` NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
Currently I'm using this query for selecting notifcations that their related row in the target table exists and their is_visible field is 1:
SELECT n.id,
FROM notifications n
LEFT JOIN books b ON n.parent_id = b.id AND n.parent_type = 'book' AND b.is_visible = 1
LEFT JOIN interviews i ON n.parent_id = i.id AND n.parent_type = 'interview' AND i.is_visible = 1
LEFT JOIN other tables...
WHERE n.user_id = 1
GROUP BY n.id
But since it is a LEFT JOIN it returns the notification if it matches any table or not, how can I rewrite it so it doesn't return notifications that don't match with any row in the target table? I have also tried the CASE statement unsuccessfully.
I'm not 100% sure the syntax is right and I have no chance to test it right now, but the idea should be clear.
SELECT DISTINCT n.id
FROM notifications n
JOIN (
(SELECT b.id, 'book' AS type FROM books b WHERE b.is_visible = 1)
UNION
(SELECT i.id, 'interview' AS type FROM interviews i WHERE i.is_visible = 1)
) ids ON n.parent_id = ids.id AND n.parent_type = ids.type
WHERE n.user_id = 1

Joining tables back to themselves in MySql

Let's say I have three tables:
user table:
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`loc` int(11) DEFAULT NULL,
`doc` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
location table:
CREATE TABLE `location` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
and document table:
CREATE TABLE `document` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`maintainer` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
I can successfully pull the user info and it's corresponding location and document info with the following query:
SELECT * from `user` LEFT JOIN `location` on user.loc = location.id LEFT JOIN `document` on user.doc = document.id;
The location info is easily referenced as its information doesn't refer to any other rows in any other tables. The document table, however, contains a maintainer field which directly corresponds back to another user in the user table. This field encapsulates the user information, and does not give me actual user data.
Is there not a way of querying the tables such that the data for the maintainer returns the actual user data as opposed to an id?
select
u.name as user_name,
m.name as maintainer_name,
l.name as location_name
from user as u
left outer join document as d on d.id = u.doc
left outer join user as m on m.id = d.maintainer
left outer join location as l on l.id = u.loc

Join two tables, matching a column with multiple values

I am trying to get a product matching some custom parameters.
So I have to three tables - products, parameters and parametersitems.
Products table:
CREATE TABLE `products` (
`ID` int(10) unsigned NOT NULL AUTO_INCREMENT
`Title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`Content` longtext COLLATE utf8_unicode_ci NOT NULL,
`Price` float(10,2) unsigned NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Parameter table:
CREATE TABLE `parameters` (
`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`Label` varchar(80) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Parameter items table:
CREATE TABLE `parametersitems` (
`ProductID` int(10) unsigned NOT NULL DEFAULT '0',
`ParameterID` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`ProductID`,`ParameterID`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
So my question is how can I get only the products matching all the parameters.
The only way I could think of is joining the parameteritems table couple of times.
For example, here is a query to get the products matching two parameters:
SELECT
products.*
FROM
products
INNER JOIN
parametersitems AS paritems1
ON
paritems1.ItemID = products.ID
AND paritems1.ParameterID = 7
INNER JOIN
parametersitems AS paritems2
ON
paritems2.ItemID = products.ID
AND paritems2.ParameterID = 11
My only concern is that the SELECT query will get slower and slower if there more parameters selected.
So is there a better way to handle this problem?
Thank you
Adjust the value tested in the HAVING clause to match the number of values listed in the IN clause.
SELECT p.*
FROM products p
WHERE p.ID IN (SELECT pi.ItemID
FROM parameteritems pi
WHERE pi.ItemID = p.ID
AND pi.ParameterID IN (7,11)
GROUP BY pi.ItemID
HAVING COUNT(DISTINCT pi.ParameterID) = 2)
select p.*
from products p
inner join (
select ItemID
from parametersitems
where ParameterID in (7, 11)
group by ItemID
having count(distinct ParameterID) = 2
) pm on p.ID = pm.ItemID
SELECT
p.ID, p.Title, p.Content, p.Price
FROM
products AS p
INNER JOIN
parametersitems AS pi ON pi.ProductID = p.ID
GROUP BY
p.ID, p.Title, p.Content, p.Price
HAVING COUNT(DISTINCT pi.ParameterID) = (SELECT COUNT(ID) FROM parameters);
This will always get you products matching every parameter no matter how many parameters you add. (This could become bogus if you delete a parameter without deleting the corresponding rows in paramatersitems. This is what constraints are for.)

trouble in find child field from primary field in mysql

I have two table like below
CREATE TABLE IF NOT EXISTS `countries` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=196 ;
ANd ANother one
CREATE TABLE IF NOT EXISTS `students` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`admission_no` varchar(255) DEFAULT NULL,
`nationality_id` int(11) DEFAULT NULL,
`country_id` int(11) DEFAULT NULL,
`is_active` tinyint(1) DEFAULT '1',
`is_deleted` tinyint(1) DEFAULT '0',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `admission_no` (`admission_no`)
) ENGINE=InnoDB DEFAULT CHARSET=latin
1 AUTO_INCREMENT=2 ;
So the problem is i want fetch both nationality_id,country_id name from countries table for this im have to use LEFT JOIN query so in this case i am facing problem as im getting same name for both if nationality_id,country_id are different as i can only join on one table only so could someone plz help me to solve this.
If I understand you correctly, you can achieve this by LEFT JOINING the same table twice, using aliases.
Something like
SELECT *
FROM students s LEF TJOIN
countries c ON s.country_id = c.id LEFT JOIN
countries n ON s.nationality_id = n.id
#astander there is a little bug in your query (second alias for countries n is not used in on statement). here is a correct statement.
select s.Id, cNationality.Name, cCountry.Name
from Students as s
left outer join Countries as cNationality on cNationality.Id = s.Nationality_id
left outer join Countries as cCountry on cCountry.Id = s.Country_id