How to add check against data within a MySQL query? - mysql

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

Related

Complex SQL query Issues

I have a problem selecting the data from a table. I'm sure the answer is staring me in the face, but my brain is mush. We're trying to find out which customers have bought based on only email blasts and nothing else. I can easily select any row where salesPrompt = 'email' but that might still include folks that also bought due to salesPrompt = 'postcard' or some other method. How do I select only customers that bought due the email blast? I've tried:
SELECT * FROM `customer_sales` WHERE `salesPrompt` = 'email' GROUP BY `accountID`
But that still brought up customers that had salesPrompt equaling other values.
you should check for count distint salePrompt using having and join the result
select s.*
FROM `customer_sales`
inner join (
SELECT accountID
FROM `customer_sales`
GROUP BY `accountID`
having count(distinct salesPrompt) = 1
) t on t.accountID = s.accountID
where `salesPrompt` = 'email'
Another possibility is to select all entries having the desired salesPrompt and then exclude (using EXISTS) all entries that also have further salesPrompts and the same accountid. Assuming, you want to name the main selection "target", this looks like this:
SELECT salesprompt, accountid
FROM customer_sales AS target WHERE salesprompt = 'email'
AND NOT EXISTS (SELECT 1 FROM customer_sales WHERE salesprompt != 'email'
AND accountid = target.accountid);

MySQL query using DISTINCT return one from many possible options

It was hard to come up with a good title for this one. I have a query selecting products from my table. Each product may belong to multiple categories. So when I add in the 'category' to my DISTINCT query, it sometimes returns multiple of the same product because that product belongs to more than one category. I want my query to just pick one of the categories and return that, doesn't matter which one.
Here is my query:
SELECT DISTINCT ci.NAME,
ci.pid,
ci.description,
ci.price,
ci.saleprice,
ci.ingredients,
ci.allergens AS allergens,
ci.isfood,
ci.quantity,
ci.ismostpopular,
ci.activedate,
ci.isfrozen,
cc.NAME AS category
FROM cart_category cc
JOIN cart_item_category cic
ON cc.id = cic.catid
JOIN cart_item ci
ON cic.itemref = ci.itemref
WHERE cc.active = 1
AND ci.active = 1
AND ci.isdeleted = 0
AND ci.isfrozen = 0
AND ( ci.NAME LIKE '%dark%'
OR ci.pid = '%dark%' )
AND ci.quantity > 5
AND cc.NAME <> "nutritionals";
When I add in cc.name as category is when it returns multiple of the same product.
try just to limit it:
... LIMIT 1

MySQL: how can I count number of articles by a join table

I have a table with news items, I have another table with media_types, I want to make one simple query that reads the media_types table and count for each record how many news_items exist.
The result will be turned into a json response that I will use for a chart, this is my SQLstatement
SELECT
gc.country AS "country"
, COUNT(*) AS "online"
FROM default_news_items AS ni
JOIN default_news_item_country AS nic ON (nic.id = ni.country)
JOIN default_country AS c ON (nic.country = c.id)
JOIN default_geo_country AS gc ON (gc.id = c.geo_country)
LEFT JOIN default_medias ON (m.id = ni.media)
WHERE TRUE
AND ni.deleted = 0
AND ni.date_item > '2013-10-23'
AND ni.date_item < '2013-10-29'
AND gc.country <> 'unknown'
AND m.media_type = '14'
GROUP BY gc.country
ORDER BY `online` desc LIMIT 10
This is the json respond I create from the mysql respond
[
{"country":"New Zealand","online":"7"},
{"country":"Switzerland","online":"1"}
]
How do I add print and social data to my output like this
I would like the json respond look like this
[
{"country":"New Zealand","online":"7", "social":"17", "print":"2"},
{"country":"Switzerland","online":"1", "social":"7", "print":"1"}
]
Can I use the count (*) in the select statement to do something like this
COUNT( * ) as online, COUNT( * ) as social, COUNT( * ) as print
Is it possible or do I have to do several SQL statement to get the data I'm looking for?
This is the general structure:
SELECT default_geo_country.country as country,
SUM(default_medias.media_type = 14) as online,
SUM(default_medias.media_type = XX) as social,
SUM(default_medias.media_type = YY) as print
FROM ...
JOIN ...
WHERE ...
GROUP BY country
I think you want conditional aggregation. Your question, however, only shows the online media type.
Your query would be more readable by using table aliases and removing the back quotes. Also, if media_type is an integer, then you should not enclose the constant for comparison in single quotes -- I, for one, find it misleading to compare a string constant to an integer column.
I suspect this is the way you want to go. Where the . . . is, you want to fill in with the counts for the other media types.
SELECT default_geo_country.country as country,
sum(media_type = '14') as online,
sum(default_medias.media_type = XX) as social,
sum(default_medias.media_type = YY) as print
. . .
FROM default_news_items ni JOIN
default_news_item_country nic
ON nic.id = ni.country JOIN
default_country dc
ON nic.country = dc.id JOIN
default_geo_country gc
ON gc.id = dc.geo_country LEFT JOIN
default_medias dm
ON dm.id = dni.media
WHERE ni.deleted = '0'
AND ni.date_item > '2013-10-23'
AND ni.date_item < '2013-10-29'
AND gc.country <> 'unknown'
GROUP BY gc.country
ORDER BY online desc
LIMIT 10

MySQL Database design advice - using joins

I am building an AJAX like search page which allows a customer to select a number filters that will narrow down the search. For instance, a user has selected an 'iPhone 5' and has additional filters for capacity (32GB, 64GB) & colour (black, white..).
The user can only select a single radio box per category (so they could select 32GB & Black).. but they could not select (32GB & 64GB & black as two of these belong to the 'capacity' category).
I have added the schema here on sqlfiddle (please ignore the fact i've removed the primary keys they exist in the proper app they have just been removed along with some other fields/data to minimise the sqlfiddle)
http://sqlfiddle.com/#!2/964425
Can anyone suggest the best way to create the query to do the following:
Get all the prices for device_id '2939' (iPhone 5) which has the 'attributes' of '32GB' AND 'Black'
I currently have this - but this only works when selecting for a single attribute:
// search for device with '64GB' & 'Black' attributes (this currently doesn't return any rows)
SELECT `prices`.*
FROM (`prices`)
LEFT JOIN `prices_attributes` ON `prices_attributes`.`price_id` = `prices`.`id`
WHERE `prices`.`device_id` = '2939'
AND `attribute_option_id` = '19'
AND `attribute_option_id` = '47';
// search for device with '64GB' attribute only (this currently DOES return a row)
SELECT `prices`.*
FROM (`prices`)
LEFT JOIN `prices_attributes` ON `prices_attributes`.`price_id` = `prices`.`id`
WHERE `prices`.`device_id` = '2939'
AND `attribute_option_id` = '19';
Any advice on the database design would be appreciated too
Note: I was thinking to have a new column within the 'prices' table that has the matching attribute_ids serialised - would this be not good for optimisation however (e.g would it be slower than the current method)
Since attribute_option_id is an atomic value, it cannot have two different values for the same row. So your WHERE clause cannot match any record:
SELECT `prices`.*
FROM (`prices`)
LEFT JOIN `prices_attributes` ON `prices_attributes`.`price_id` = `prices`.`id`
WHERE `prices`.`device_id` = '2939'
AND `attribute_option_id` = '19' # Here for one row, attribute_option_id is either 19
AND `attribute_option_id` = '47'; # of '47'. Cannot be the both
Instead of JOIN, you could try a subquery if you feel that is more readable. I think MySQL allow that syntax:
SELECT `prices`.*
FROM `prices`
WHERE `prices`.`device_id` = '2939'
AND EXISTS (SELECT *
FROM prices_attributes
WHERE price_id = `prices`.`id`
AND attribute_option_id IN ('19', '47') )
I don't know how MySQL will optimize the above solution. An alternative would be:
SELECT `prices`.*
FROM `prices`
WHERE `prices`.`id` IN (
SELECT DISTINCT `price_id`
FROM prices_attributes
WHERE attribute_option_id IN ('19', '47')
)
I think you should use the IN operator for the attribute_option_id and you set the values dynamically to the query; Also, using group_by you have only one row per price so in effect you get all the prices. Apart from this, the design is ok.
Here, I have made an example:
SELECT `prices`.*
FROM (`prices`)
LEFT JOIN `prices_attributes` ON `prices_attributes`.`price_id` = `prices`.`id`
WHERE `prices`.`device_id` = '2939'
and `attribute_option_id` in ('19','47')
group by `prices`.`device_id`, `prices`.`price`;
Here, you can also add an order clause to order by price:
order by `prices`.`price` desc;
Another way to solve this would be to use a distinct on price, like this:
select distinct(prices.price)
from prices
where prices.device_id = 2939
and id in (select price_id from prices_attributes where attribute_option_id in (19,47));
Join against the devices_attributes_options table several times, once for each attribute the item must have
Something like this:-
SELECT *
FROM devices a
INNER JOIN prices b ON a.id = b.device_id
INNER JOIN prices_attributes c ON b.id = c.price_id
INNER JOIN devices_attributes_options d ON c.attribute_option_id = d.id AND d.attribute_value = '32GB'
INNER JOIN devices_attributes_options e ON c.attribute_option_id = e.id AND e.attribute_value = 'Black'
WHERE a.id = 2939
As to putting serialised details into a field, this is a really bad idea and would come back to bite you in the future!
SELECT * FROM prices WHERE device_id=2939 AND id IN (SELECT price_id FROM prices_attributes WHERE attribute_option_id IN (19,47));
Is it what you're looking for?
EDIT: sorry, didn't notice you're asking for query using joins

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

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;