Rewrite search query - mysql

I have a barebones search system that just got more complicated and there are many weird issues going on with it. How can I rewrite this search query to be more accurate and efficient
The tbl_notes table only has the id of the course but to add it to the search I used a inner query to check it too. There are alot of issues with this and I'm not too strong in mysql any help would be appreciated.
TBL_NOTES:
[id][user_id][note_title][note_title][topic][course][note]
TBL_COURSES:
[id][user_id][course_name]
SELECT * FROM `tbl_notes`
WHERE `active` = '0'
AND `valid_note` = '0'
AND `user_id` = '33'
AND `note` LIKE '%biology%'
OR `topic` LIKE '%biology%'
OR `note_title` LIKE '%biology%'
OR `course` IN (SELECT `id` FROM tbl_courses WHERE `course_name` LIKE '%biology%' AND user_id = '33')
ORDER BY id DESC LIMIT 0, 6
Thanks

I would write it like this:
SELECT
DISTINCT n.*
FROM
`tbl_notes` n left join `tbl_courses` c
on n.course = c.id
WHERE
n.`user_id` = '33'
AND n.`active` = '0'
AND n.`valid_note` = '0'
AND (n.`note` LIKE '%biology%'
OR n.`topic` LIKE '%biology%'
OR n.`note_title` LIKE '%biology%'
OR c.`course_name` LIKE '%biology%')
ORDER BY n.id DESC LIMIT 0, 6

Maybe ?:
SELECT * FROM `tbl_notes`
WHERE `active` = '0'
AND `valid_note` = '0'
AND `user_id` = '33'
AND
(`note` LIKE '%biology%'
OR `topic` LIKE '%biology%'
OR `note_title` LIKE '%biology%'
OR `course` IN (SELECT `id` FROM tbl_courses WHERE `course_name` LIKE '%biology%' AND user_id = '33')
)
ORDER BY id DESC LIMIT 0, 6

Related

Where exists SQL with internal where condition?

I have the following request, to say more precise the SQL parameterized query:
select * from `products`
where (exists (select * from `productslocation` where `products`.`Id` = `productslocation`.`Product_Id` and `Country_Id` = ?) and
exists (select * from `productprices` where `products`.`Id` = `productprices`.`Products_Id` and `Price` >= ?)
and `Organization_Id` = ? and `name` like ? or `Description` like ?) and `Status` = ?
This query retrieves only products where exist country, price using operator WHERE EXISTS.
If it was found it returns TRUE for first sub query, then works remaining two queries WHERE. First sub query returns TRUE, second gives TRUE, cause sub query is right also. result = TRUE * TRUE = TRUE. But it is wrong result.
Problem is, that need to use two intern queries WHERE for result from WHERE EXISTS.
Does it mean I need to replace WHERE EXISTS ON JOIN'S? Or is it possible to modify query above?
Your parentheses and ORs and ANDs are making the statement very complicated.
Try this:
select * from `products`
where
(
exists (select * from `productslocation` where `products`.`Id` = `productslocation`.`Product_Id` and `Country_Id` = ?) and
exists (select * from `productprices` where `products`.`Id` = `productprices`.`Products_Id` and `Price` >= ?) and
`Organization_Id` = ? and (`name` like ? or `Description` like ?) and `Status` = ?
)
It's not better that way? Try this:
select * from `products` inner join productprices on `products`.`Id` = `productprices`.`Products_Id`
INNER JOIN `productslocation` ON `products`.`Id` = `productslocation`.`Product_Id`
where (`Organization_Id` = ? and `name` like ? or `Description` like ?) and `Status` = ?
AND `productprices`.`Price` >= ?
and `productslocation`.Country_Id` = ?
You missed brackets around your or statement:
select * from `products`
where (exists (select * from `productslocation` where `products`.`Id` = `productslocation`.`Product_Id` and `Country_Id` = ?) and
exists (select * from `productprices` where `products`.`Id` = `productprices`.`Products_Id` and `Price` >= ?)
and `Organization_Id` = ? and (`name` like ? or `Description` like ?)) and `Status` = ?
You are looking for a query that filters products on various criteria, some involving referential relationships.
Assuming that you have 1-1 relationships with the referential tables, you should be able to use JOINs :
select *
from
`products`
inner join `productslocation`
on `products`.`Id` = `productslocation`.`Product_Id` and `productslocation`.`Country_Id` = ?
inner join `productprices`
on `products`.`Id` = `productprices`.`Products_Id` and `productprices`.`Price` >= ?
where
`products`.`Organization_Id` = ?
and `products`.`name` like ?
and `products`.`Description` like ?
and `products`.`Status` = ?

Unknown column due to second-level subquery nesting

I want to retrieve a user's rank based on how many points the given user has compared to other users (simply counting users with more points than the given user).
However, with all the queries I have tried, I always end up with Column not found: users.id. From what I can read there is a limit from referencing correlated parent columns more than one level up.
Can I refactor my query, or do I really need to use SET #rownum := 0 style of queries?
SELECT
`users`.*,
(
SELECT COUNT(*) + 1
FROM (
SELECT SUM(`amount`) AS `all_points`
FROM `points`
WHERE `type` = ?
GROUP BY `user_id`
HAVING `all_points` > (
SELECT SUM(`amount`)
FROM `points`
WHERE `type` = ? and `user_id` = `users`.`id`
)
) `points_sub`
) as `rank`
FROM `users`
WHERE `users`.`id` = ?
LIMIT 1
You can move your sub clause one level up, Remove having filter and use where filter
SELECT
`users`.*,
(
SELECT COUNT(*) + 1
FROM (
SELECT user_id,SUM(`amount`) AS `all_points`
FROM `points`
WHERE `type` = ?
GROUP BY `user_id`
) `points_sub`
WHERE `all_points` >
SELECT SUM(`amount`)
FROM `points`
WHERE `type` = ? and `user_id` = `users`.`id`
) as `rank`
FROM `users`
WHERE `users`.`id` = ?
LIMIT 1
I think the below query should work for you. You can pass the user_id of user whose rank you want to compute in both the arguments.
SELECT
`users`.*,
(
SELECT COUNT(*) + 1
FROM (
SELECT SUM(`amount`) AS `all_points`
FROM `points`
WHERE `type` = ?
GROUP BY `user_id`
HAVING `all_points` > (
SELECT COALESCE(SUM(`amount`),0)
FROM `points`
WHERE `type` = ? and `user_id` = ?
)
) `points_sub`
) as `rank`
FROM `users`
WHERE `users`.`id` = ?
LIMIT 1

MySQL query on two tables with conditions

I have two tables:
users and users_img.
I need to create one query to select all users from users table that have zip column not empty AND have img column empty in users_images (both tables have user ids: users.id and users_img.user_id so those could be joined).
users
id
name
zip
last_time
users_ids
id
user_id (same as id in users)
img
I tried this:
SELECT * FROM `users` JOIN `users_ids` on users.id = users_ids.user_id
WHERE `zip` != '' AND `img` = '' ORDER BY `last_time` DESC
with no luck. I know is supposed to be quite simple.
it will also work for you
SELECT * FROM `users` as `u`
JOIN `users_ids` as `uid` on `u`.`id` = `uid`.`user_id`
WHERE `u`.`zip` IS NOT NULL
AND `uid`.`img` IS NULL
ORDER BY `u`.`last_time` DESC
I have modified your query to check for Null values also in OR
Try this:
SELECT * FROM `users`
LEFT JOIN `users_ids` on users.id = users_ids.user_id
WHERE (TRIM(`zip`) != '' OR `zip` is not null) AND
(TRIM(`img`) = '' OR `img` is null) ORDER BY `last_time` DESC
This worked:
SELECT * FROM `users` as `u`
LEFT JOIN `users_ids` as `uid` on `u`.`id` = `uid`.`user_id`
WHERE `u`.`zip` != ''
AND `uid`.`img` IS NOT NULL
ORDER BY `u`.`last_time` DESC

Optimising MySQL Query, Select within Select, Multiple of same

I need help optimising this MySQL statement that I whipped up. It does exactly what I want, however I have a great feeling that it'll be quite slow, since I do multiple selects within the statement, and I also query achievements_new multiple times. This is the first time I do some major statement like this, I'm used to the simple SELECT FROM WHERE style crap.
I might do some explaining, this is for a leaderboard style thing for my website.
--First variable output is a rank that is calculated according to the formula shown, (Log + Log + # of achievements).
--Wepvalue is the sum of the values of the weapons which that id has. playerweapons contains all the weapons, and weaponprices convert the type to the price, and then the SUM calculates the value.
--Achcount is simply the amount of achievements that's unlocked. Maybe this can be optimised somehow with the rank output?
--id in achievements_new and playerweapons are Foreign Keys to the id in playerdata
SELECT
(
IFNULL(LOG(1.5, cashearned),0) +
IFNULL(LOG(1.3, roundswon), 0) +
(
SELECT COUNT(*)
FROM achievements_new
WHERE `value` = -1 AND achievements_new.id = playerdata.id
)
) as rank,
nationality,
nick,
steamid64,
cash,
playtime,
damage,
destroyed,
(
SELECT SUM(price)
FROM weaponprices
WHERE weapon IN
(
SELECT class
FROM playerweapons
WHERE playerweapons.id = playerdata.id
)
) as wepvalue,
(
SELECT COUNT(*)
FROM achievements_new
WHERE `value` = -1 AND achievements_new.id = playerdata.id
) as achcount,
lastplayed
FROM playerdata
ORDER BY rank DESC
Table structures:
playerdata:
CREATE TABLE IF NOT EXISTS `playerdata` (
`id` int(11) unsigned NOT NULL,
`steamid64` char(17) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
`nick` varchar(32) NOT NULL DEFAULT '',
`cash` int(32) unsigned NOT NULL DEFAULT '0',
`playtime` int(32) unsigned NOT NULL DEFAULT '0',
`nationality` char(2) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
`damage` int(32) unsigned NOT NULL DEFAULT '0',
`destroyed` int(32) unsigned NOT NULL DEFAULT '0',
`cashearned` int(10) unsigned NOT NULL,
`roundswon` smallint(5) unsigned NOT NULL,
`lastplayed` datetime NOT NULL,
) ENGINE=InnoDB
achievements_new:
CREATE TABLE IF NOT EXISTS `achievements_new` (
`id` int(10) unsigned NOT NULL,
`achkey` enum(<snip - lots of values here>) NOT NULL,
`value` mediumint(8) NOT NULL DEFAULT '0'
) ENGINE=InnoDB
playerweapons:
CREATE TABLE IF NOT EXISTS `playerweapons` (
`id` int(10) unsigned NOT NULL,
`class` varchar(30) CHARACTER SET ascii NOT NULL
) ENGINE=InnoDB
weaponprices:
CREATE TABLE IF NOT EXISTS `weaponprices` (
`weapon` varchar(30) NOT NULL,
`price` int(10) unsigned NOT NULL
) ENGINE=InnoDB
Thanks in advance!
Try something like the query below.
I used LEFT JOIN instead of joins because there may be players without achievements or weapons. If you do not need these players you can use JOIN
SELECT
IFNULL(LOG(1.5, p.cashearned),0) +
IFNULL(LOG(1.3, p.roundswon), 0) +
SUM(CASE WHEN ac.id IS NOT NULL THEN 1 ELSE 0 END)/COUNT(pw.id) as rank
p.nationality,
p.nick,
p.steamid64,
p.cash,
p.playtime,
p.damage,
p.destroyed,
--SUM(CASE WHEN pw.id IS NOT NULL THEN pw.price ELSE 0 END) as wepvalue,
--wpn.price as wepvalue,
SUM(CASE WHEN pw.id IS NOT NULL THEN wp.price ELSE 0 END)/COUNT(ac.id) as wepvalue,
SUM(CASE WHEN ac.id IS NOT NULL THEN 1 ELSE 0 END)/COUNT(pw.id) as achcount,
lastplayed
FROM playerdata as p
JOIN playerweapons as pw ON pw.id = p.id
JOIN weaponprices as wp ON pw.class = wp.weapon
LEFT JOIN achievements_new as ac ON ac.id = p.id AND ac.value = -1
--LEFT JOIN playerweapons as pw ON pw.id = p.id
--LEFT JOIN weaponprices as wp ON pw.class = wp.weapon
--LEFT JOIN ( SELECT
--pw.id as player,
--SUM(wp.price) as price
--FROM weaponprices as wp
--JOIN playerweapons as pw ON pw.class = wp.weapon
--GROUP BY pw.id
--) as wpn ON wpn.player = p.id
GROUP BY
p.nationality,
p.nick,
p.steamid64,
p.cash,
p.playtime,
p.damage,
p.destroyed,
p.lastplayed
Your query is fairly reasonable, although I would rewrite the subqueries to use explicit joins rather than in and factor out the achievements subquery:
SELECT (IFNULL(LOG(1.5, cashearned),0) + IFNULL(LOG(1.3, roundswon), 0) +
coalesce(an.cnt, 0)
) as rank,
nationality, nick, steamid64, cash, playtime, damage, destroyed,
(SELECT SUM(wp.price)
FROM weaponprices wp JOIN
playerweapons pw
on pw.class = wp.weapons
WHERE pw.id = pd.id
) as wepvalue,
coalesce(an.cnt, 0) as achcount,
lastplayed
FROM playerdata pd left outer join
(SELECT id, count(*) as cnt
FROM achievements_new an
WHERE an.`value` = -1
GROUP BY an.id
) an
on an.id = pd.id
ORDER BY rank DESC;
For this query, create the following indexes:
playerweapons(id, weapon);
weaponprices(class, price);
achievements_new(value, id);
This does the following things:
It eliminates two redundant subqueries on achievements_new.
It should optimize the prices subquery to only use indexes.
It replaces the in with an explicit join, which is sometimes optimized better.
It does not require an outer group by.
I would try to remove all correlated subqueries
SELECT
( COALESCE(LOG(1.5, pd.cashearned), 0)
+ COALESCE(LOG(1.3, pd.roundswon), 0)
+ COALESCE(an.cnt, 0)) AS rank
, pd.nationality
, pd.nick
, pd.steamid64
, pd.cash
, pd.playtime
, pd.damage
, pd.destroyed
, COALESCE(pw.wepvalue, 0) AS wepvalue
, COALESCE(an.cnt, 0) AS achcount
, pd.lastplayed
FROM playerdata pd
LEFT JOIN (
SELECT
id
, COUNT(*) AS cnt
FROM achievements_new
WHERE value = -1
GROUP BY
id
) an
ON pd.id = an.id
LEFT JOIN (
SELECT
playerweapons.id
, SUM(price) AS wepvalue
FROM weaponprices
INNER JOIN playerweapons
ON weaponprices.weapon = playerweapons.class
GROUP BY
playerweapons.id
) pw
ON pd.id = pw.id
ORDER BY
rank DESC;

How to simplify embedded mysql query into a JOIN?

I recently read something about performance problems with embedded mysql queries, so I wanted to know how I could change the following to a "JOIN" (supposedly with better performance?).
I have two tables:
CREATE TABLE IF NOT EXISTS `blog_categories` (
`category_id` int(11) NOT NULL AUTO_INCREMENT,
`category_name` varchar(300) COLLATE utf8_unicode_ci NOT NULL,
`category_name_url` varchar(300) COLLATE utf8_unicode_ci NOT NULL,
`category_status` enum('online','offline') COLLATE utf8_unicode_ci NOT NULL DEFAULT 'offline'
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=8 ;
CREATE TABLE IF NOT EXISTS `blog_articles` (
`article_id` int(11) NOT NULL AUTO_INCREMENT,
`article_title` tinytext COLLATE utf8_unicode_ci NOT NULL,
`category_name` varchar(100) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=26 ;
The logic is basically to select all the categories that have articles associated with them. Each row in the blog_articles table includes a category_name
And here is the query I'm using (which checks out and works fine):
$sql = "SELECT category_name
, category_name_url
FROM blog_categories
WHERE (
(SELECT COUNT(*)
FROM blog_articles, blog_categories
WHERE blog_articles.category_name = blog_categories.category_name
) > 0
AND blog_categories.category_status = 'online')";
I'm still new to "JOIN", and am not sure how to change it when using "COUNT(*)" in the mix too.
Rather than COUNT(*) > 0, use EXISTS
"SELECT category_name, category_name_url FROM blog_categories WHERE EXISTS (" .
"(SELECT 1 FROM blog_articles INNER JOIN blog_categories
ON blog_articles.category_name = blog_categories.category_name) AND " .
"blog_categories.category_status = 'online'" .
")";
This is the old style join syntax:
SELECT 1 FROM blog_articles, blog_categories
WHERE blog_articles.category_name = blog_categories.category_name
This is the ANSI form of the same:
SELECT 1
FROM blog_articles
INNER JOIN blog_categories
ON blog_articles.category_name = blog_categories.category_name
Update (in response to posters comments): This query does what you require:
"SELECT category_name, category_name_url
FROM blog_categories
WHERE category_name IN (SELECT DISTINCT category_name FROM blog_articles)
AND blog_categories.category_status = 'online'"
I'd use EXISTS:
$sql = "SELECT category_name
, category_name_url
FROM blog_categories
WHERE EXISTS
( SELECT *
FROM blog_articles
WHERE blog_articles.category_name = blog_categories.category_name
)
AND blog_categories.category_status = 'online'
";
or JOIN:
$sql = "SELECT c.category_name
, c.category_name_url
FROM blog_categories AS c
JOIN blog_articles AS a
ON a.category_name = c.category_name
WHERE c.category_status = 'online'
GROUP BY c.category_name
";
#Jay: I doubt your query as it is, shows only categories that have articles posted. It counts all articles from all categories and then either shows all categories (if the count is > 0) or no categories at all (if the count = 0).
The reason is that the blog_categories in your subquery is not related to the blog_categories in the main query by any condition.
You can try this also:
SELECT category_name, category_name_url
FROM blog_categories
WHERE category_id IN (
SELECT blog_categories.category_id
FROM blog_articles
INNER JOIN blog_categories
ON blog_categories.category_name = blog_articles.category_name
AND blog_categories.category_status = 'online'
)