Join three MySQL tables so that I don't get repeating data - mysql

I have three mysql table tblOne, tblTwo and tblThree
SELECT tblOne.bookID AS bookID,
tblOne.title AS title,
tblOne.author AS author,
tblOne.blurb AS blurb,
tblOne.isbn AS isbn,
tblOne.coverImage As coverImage,
CONCAT_WS(" ", tblThree.firstName, tblThree.lastName) AS fullName,
tblTwo.rating,
tblTwo.reviewText AS review,
CONCAT_WS(" ", tblTwo.reviewDate, tblTwo.reviewTime) AS reviewDate
FROM tblTwo
INNER JOIN tblOne
ON tblTwo.bookID = tblOne.bookID
INNER JOIN tblThree
ON tblTwo.userID = tblThree.userID
WHERE tblTwo.bookID = 1000102;
The output populates an XML file, for this book, there are two reviews and as such the data for title, author etc is pulled twice. How can I overcome this as when I try to access the tag from within a JavaScript query, it fails as it says it is undefined. I think that this is due to having more than one result.
The above SQL actually does work, my PHP was erroneous, however...
How can I return the book details even when there is no review? Currently, only books that have been reviewed get their details returned.

You are using INNER JOIN, which requires rows to exist in joined table. Use LEFT JOIN instead, which returns rows from the first-listed table even if rows from the left-joined table don't exist:
SELECT tblBooks.bookID AS bookID,
tblBooks.title AS title,
tblBooks.author AS author,
tblBooks.blurb AS blurb,
tblBooks.isbn AS isbn,
tblBooks.coverImage As coverImage,
CONCAT_WS(' ', tblMembers.firstName, tblMembers.lastName) AS fullName,
IFNULL(tblReviews.rating, '0'),
tblReviews.reviewText AS review,
CONCAT_WS(' ', tblReviews.reviewDate, tblReviews.reviewTime) AS reviewDate
FROM tblBooks
LEFT JOIN tblReviews
ON tblReviews.bookID = tblBooks.bookID
LEFT JOIN tblMembers
ON tblReviews.userID = tblMembers.userID
GROUP BY 1, 2, 3, 4, 5, 7, 8;
Notice that I've listed tblOne first in the FROM clause, which is necessary to get all books even if some have no reviews.
You'll have to deal with null values when there are no reviews. Consider using IFNULL() to generate blanks or a default value, eg
IFNULL(tblTwo.rating, '') -- instead of just tblTwo.rating
Or
IFNULL(tblTwo.reviewText, 'None') -- instead of just tblTwo.reviewText
etc

SQL will always form the cartesian product between the tables meaning that if you have one book with 2 reviews and each review has 3 tags, you will have 6 rows returned to cover all of the data combinations.
You cannot have one book per row if you are joining on a table where the number of expected elements per join is greater than 1. You will need to restructure the XML logic (not sure how you go from SQL -> XML) to cluster taking the redundancies into account.

use GROUP_CONCAT and join the table/table_three results. Try something like:
SELECT tblOne.bookID AS bookID,
tblOne.title AS title,
tblOne.author AS author,
tblOne.blurb AS blurb,
tblOne.isbn AS isbn,
tblOne.coverImage As coverImage,
GROUP_CONCAT(
(SELECT CONCAT_WS(" ", tblThree.firstName, tblThree.lastName)
AS fullName,
tblTwo.rating,
tblTwo.reviewText AS review,
CONCAT_WS(" ", tblTwo.reviewDate, tblTwo.reviewTime)
AS reviewDate
FROM tblTwo INNER JOIN
tblOne ON tblTwo.bookID = tblOne.bookID
INNER JOIN
tblThree ON tblTwo.userID = tblThree.userID
WHERE tblTwo.bookID = 1000102)
SEPARATOR ' '
)
FROM tblTwo INNER JOIN
tblOne ON tblTwo.bookID = tblOne.bookID
INNER JOIN
tblThree ON tblTwo.userID = tblThree.userID
WHERE tblTwo.bookID = 1000102;

Related

Limit multiple joined results to 1, by order

I have persons, monsters and vampires, all of which can have multiple files referenced from a files table.
Simplified tables:
files: id, name;
character: id, type;
person: id, characterId FK character.id;
monster: id, characterId FK character.id;
vampire: id, characterId FK character.id;
person_files: personId FK person.id, fileId FK file.id, order;
monster_files: monsterId FK monster.id, fileId FK file.id, order;
vampire_files: vampireId FK vampire.id, fileId FK file.id, order;
As input I have a character.id. Am trying to get only one file.id while looking up the lowest ordered files in person_files, vampire_files and monster_files.
If person_files returns a result, we're done. Otherwise look up vampire_files and / or monster_files. Should return null if no files are found.
I could do something like
SELECT *
FROM character
LEFT JOIN person ON person.characterId = character.id
LEFT JOIN monster ON monster.characterId = monster.id
LEFT JOIN vampire ON vampire.characterId = monster.id
LEFT JOIN files as f1 ON person.fileID = f1.fileId
LEFT JOIN files as f2 ON monster.fileID = f2.fileId
LEFT JOIN files as f3 ON monster.fileID = f3.fileId
and filter out the data with where/min/max etc.
However it seems unnecessary to triple join on the files table when I am just looking up one file.id. Is there a subquery possible to prevent joining so many times and return just one myFileId? Would like to do this as efficiently as possible.
ie
LEFT JOIN (SELECT *one file.id*, MIN(order) as myFileId FROM person/monster/vampire...)
One optimization I could think of would be to select one fileId value based on which of person, monster or vampire table has a non-null field. The query will look like this:
SELECT * FROM (
SELECT COALESCE(person.file_id, monster.file_id, vampire.file_id) AS realFileId
FROM character
LEFT JOIN person ON person.characterId = character.id
LEFT JOIN monster ON monster.characterId = monster.id
LEFT JOIN vampire ON vampire.characterId = monster.id
) x
LEFT JOIN files f
ON x.realFileId=f.fileId
If you want to make life simpler for yourself, you may also consider adding a characterType field to character table and use it together with CASE operator to determine which table you need to join:
SELECT * FROM (
SELECT COALESCE(person.file_id, monster.file_id, vampire.file_id) AS realFileId
FROM character
LEFT JOIN person ON person.characterId = character.id
AND character.characterType="person"
LEFT JOIN monster ON monster.characterId = monster.id
AND character.characterType="monster"
LEFT JOIN vampire ON vampire.characterId = monster.id
AND character.characterType="vampire"
) x
LEFT JOIN files f
ON x.realFileId=f.fileId
While this looks more complex than the previous version, the optimizer in this case should optimize away the joins you won't be using (since you'll be literally joining on FALSE) and thus speed up the query quite a bit.

LEFT JOIN functionality in a (maybe) double joined query with WHERE

I have the following 3 tables:
Clients AS c
--------
ClientID,
ClientGUID,
InitializationDate,
LastCheckin
ClientServices AS cs
---------------
ClientID,
ServiceID,
Status,
Version
Services AS s
---------
ServiceID,
Name,
Description
I need to build a query that will give me the following results:
s.Name, cs.Version
I need it to act as a left join, in the sense that I'd like to have all Services display, regardless of whether that service is in ClientServices. The selected version will simply display as NULL in this case.
I tried doing some simple joins, but every combination of LEFT JOIN and JOIN I used resulted in ONLY the ClientServices that belonged to that client showing up. An example:
SELECT s.`Name`,
cs.`Version`
FROM `Services` s LEFT JOIN ClientServices cs ON
s.`ServiceID` = cs.`ServiceID`
JOIN Clients c ON
cs.`ClientID` = c.`ClientID`
WHERE `ClientGUID`='thisisanewguid'
I was able to finally get the desired result with this query:
SELECT s.`Name`,
cs.`Version`
FROM `Services` s LEFT JOIN ClientServices cs
ON s.`ServiceID` = cs.`ServiceID`
WHERE cs.ClientID = (
SELECT ClientID
FROM Clients
WHERE ClientGUID='thisisanewguid'
)
OR cs.ClientID IS NULL
but I feel as though its a little "hacky". Is there a better way to get the same result set, but without doing multiple selects in one query? (preferably with only joins, but I'm not sure if thats possible anymore)
Example Data Set:
Clients:
ClientID, ClientGUID, InitializationDate, LastCheckin
1, 'xxxxxxxxxxxxxxxx', 10/10/2017, 10/12/2017
2, 'thisisanewguid', 05/23/2017, 10/12/2017
ClientServices:
ClientID, ServiceID, Status, Version
1, 1, 1, '0.1'
2, 1, 1, '0.1'
2, 2, 1, '0.2'
Services:
ServiceID, Name, Description
1, InITManager, 'Manages and updates all InIT services.'
2, InITIAM, 'InIT's Inventory/Asset Management.'
3, InITTesting, 'testing'
Desired Result Set WHERE ClientGUID='thisisanewguid':
Name, Version
InITManager, '0.1'
InITIAM, '0.2'
InITTesting, NULL
for obatin the result you showed in question you could use left table with left join without where clause for column involved id left join
SELECT s.`Name`,
cs.`Version`
FROM `Services` s
LEFT JOIN ClientServices cs ON s.`ServiceID` = cs.`ServiceID`
LEFT JOIN Clients c ON c.ClientID = cs.ClientID AND c.`ClientGUID`='thisisanewguid'
Hi can you please try the following query
SELECT cs.Version, s.Name
FROM ClientServices cs
RIGHT JOIN Clients c ON c.ClientID = cs.ClientID
RIGHT JOIN Services s ON s.ServiceID = cs.ServiceID

LEFT JOIN - fetching all data from left table with no matches in the right one

In my project, I have two tables like this:
parameters (
id PRIMARY KEY,
name
)
and
parameters_offeritems (
id_offeritem,
id_parameter,
value,
PRIMARY KEY (id_offeritem, id_parameter)
)
I'm not showing structure of offeritems table, because it's not necessary.
Some sample data:
INSERT INTO parameters (id, name) VALUES
(1, 'first parameter'), (2, 'second parameter'), (3, 'third parameter')
INSERT INTO parameters_offeritems (id_offeritem, id_parameter, value) VALUES
(123, 1, 'something'), (123, 2, 'something else'), (321, 2, 'anything')
Now my question is - how to fetch (for given offer ID) list of all existing parameters, and moreover, if for the given offer ID there are some parameters set, I want to fetch their value in one query.
So far, I made query like this:
SELECT p.*, p_o.value FROM parameters p LEFT JOIN parameters_offeritems p_o
ON p.id = p_o.id_parameter WHERE id_offeritem = OFFER_ID OR id_offeritem IS NULL
But it fetches only those parameters, for which there are no existing records in parameters_offeritems table, or parameters, for which value are set only for the current offer.
To get all parameters, plus the value of any parameters set for a specific Offer Item, you need to move the Offer ID logic into the join like this (see below).
SELECT p.*, p_o.value
FROM parameters p
LEFT JOIN parameters_offeritems p_o
ON p.id = p_o.id_parameter
AND id_offeritem = OFFER_ID;
If you have logic in your WHERE clause referring to fields in a table you are doing a LEFT JOIN on, you effectively change your JOIN to an INNER JOIN (unless you are checking for a NULL).
The magic word you're looking for is OUTER JOIN. Jeff Atwood did a nice Venn-diagram explanation here.
Your query was almost perfect, just WHERE in wrong pace:
SELECT p.*, p_o.value FROM parameters p
LEFT JOIN (
SELECT * FROM parameters_offeritems
WHERE id_offeritem = OFFER_ID) as p_o
ON p.id = p_o.id_parameter

How to add check against data within a MySQL query?

I have three tables:
$sTable = a table of songs (songid, mp3link, artwork, useruploadid etc.)
$sTable2 = a table of projects with songs linked to them (projectid, songid, project name etc.)
$sTable3 = a table of song ratings (songid, userid, rating)
All this data is output to a JSON array and displayed in a table in my application to provide a list of songs, combined with the projects and ratings data.
What I want to be able to do is check to see if the "logged in user" has voted on a particular song so that I can add a class of 'voted' to the parent element of the returned data.
I would lke to do this in the most permance optimal way which I would guess would be to return a boolean value (1 or 0) with 1 being voted and 0 being returned otherwise.
I can then use javascript to apply the class client side.
The 'logged in user id' is stored in my PHP script as $loggedin_ID so I need to simply check this variable against the column 'userid' of $sTable3 for a given songid and return a new column (I would imagine using AS) with a result of 1 if there is a matching entry for that songid and 0 if there is not.
How would I go about modifying my query to add this?
The above are just my thoughts and there may indeed be a far better/more effecient method. If so please do not hesitate to provide what you think might be a better solution to achieving this functionality.
Below is my current query. Thanks.
$sQuery = "SELECT SQL_CALC_FOUND_ROWS ".str_replace(" , ", " ", implode(", ", $aColumns))."
FROM $sTable b
LEFT JOIN (
SELECT COUNT(*) AS projects_count, a.songs_id
FROM $sTable2 a
GROUP BY a.songs_id
) bb ON bb.songs_id = b.songsID
LEFT JOIN (
SELECT AVG(rating) AS rating, COUNT(rating) AS ratings_count,c.songid
FROM $sTable3 c
GROUP BY c.songid
) bbb ON bbb.songid = b.songsID
May be you are looking for something like this:
select s.songid, s.userid,
case when exists
(
select songid
from sTable3
where songid = $songid
) Then 'Voted'
else
(
'Not Voted'
)
end
as 'Voted or Not'
from sTable3 s
where s.userid = $userid

How do i deal with this situation for searching records in mysql?

i am developing a PHP/MYSQL search module, where i have to search tables based on many different criteria, i have some 11 tables, and i have used multiple joins to create one single MySQL query and based on WHERE clause i intend to search for specific records, here is the MYSQL Query that i am using.
SELECT
prop.id,
prop.serial,
prop.title,
prop.rating,
prop.addDate,
prop.approve,
prop.status,
prop.user_id as userId,
user_det.email as email,
user_det.name as name,
prop.area_id as areaId,
area.name as areaName,
area.zipCode as zipCode,
area.city_id as cityId,
city.name as cityName,
city.state_id as stateId,
state.name as stateName,
state.country_id as countryId,
country.name as countryName,
prop.subCategory_id as subCategoryId,
subCat.name as subCategoryName,
subCat.category_id as categoryId,
cat.name as categoryName,
prop.transaction_id as transactionId,
trans.name as transactionName,
price.area as landArea,
price.price as priceSqFt,
price.total_price as totalPrice,
features.bedroom,
features.bathroom,
features.balcony,
features.furnished,
features.floorNum,
features.totalFloor
FROM properties prop
LEFT JOIN user_details user_det ON (prop.user_id = user_det.user_id)
LEFT JOIN areas area ON (prop.area_id = area.id)
LEFT JOIN cities city ON (area.city_id = city.id)
LEFT JOIN states state ON (city.state_id = state.id)
LEFT JOIN countries country ON (state.country_id = country.id)
LEFT JOIN subCategories subCat ON (prop.subCategory_id = subCat.id)
LEFT JOIN categories cat ON (subCat.category_id = cat.id)
LEFT JOIN transactions trans ON (prop.transaction_id = trans.id)
LEFT JOIN prop_prices price ON (price.property_id = prop.id)
LEFT JOIN prop_features features ON (features.property_id = prop.id)
although all works well here, i have a situation where i have a table called prop_amenities below are the content of this table.
as the table above have multiple property_id if i query it using JOINS then mostly it will return duplicate records or single record omitting others depending on the type of JOIN i use. so instead i would like to deal it this way.
use the table prop_amenities to only deal with conditions not to return the result.
for example i am searching for a property with amenity id 1,5,9,17 and 24, then it should check if all the records exist in the prop_amenities table, i.e 1,5,9,17 and 24 in this case. and return the appropriate records with all above selected columns.
i am clueless on dealing this situation using MySQL. how do i go on this?
thank you..
You said "check if all the records exist in the prop_amenities table" and that's the key word here.
SELECT ...
FROM properties AS prop
LEFT JOIN ...
WHERE EXISTS (SELECT 1 FROM prop_amenities AS pa WHERE pa.property_id = prop.property_id AND pa.amenity_id = 7);