Better MySQL Syntax for my query - mysql

HI I have the following query:
SELECT PAS_User.user_user_id,
PAS_User.user_city,
PAS_User.user_company,
PAS_User.user_country,
PAS_User.user_account_type,
PAS_User.user_account_premium,
PAS_User.user_sign_up_date,
PAS_User.user_first_name,
PAS_User.user_last_name,
PAS_User.user_avatar_url,
PAS_User.user_cover_image_url,
PAS_User.user_bio,
PAS_User.user_sector,
PAS_User.user_job_type,
(SELECT COUNT(*) FROM `PAS_Follow` WHERE `folw_follower_user_id`=:sid) AS user_following_count,
(SELECT COUNT(*) FROM `PAS_Follow` WHERE `folw_followed_user_id`=:sid) AS user_followed_count,
(SELECT COUNT(*) FROM `PAS_Post` WHERE `post_user_id`=:sid) AS user_post_count,
(SELECT COUNT(*) FROM `PAS_Follow` WHERE `folw_follower_user_id`=:sid AND `folw_followed_user_id`=:cid) AS user_this_user_is_following,
(SELECT COUNT(*) FROM `PAS_Follow` WHERE `folw_followed_user_id`=:cid AND `folw_follower_user_id`=:sid) AS user_this_user_is_followed
FROM PAS_User
WHERE `PAS_User`.`user_user_id`=:sid
Which is designed to get counts from other tables for a profile page and the basic user details where :sid = 1 and :cid = 2.
The question is, is there any better way of achieving this in perhaps a smaller query or in a cleaner way?
The tables used are
PAS_User , PAS_Follow & PAS_Post
Thanks
Justin

You should denormalize all those counter fields and only update them when a user posts something, or presses a Follow button. Your current query is going to blow up your database server in the foreseeable future if your website gets actual active users.

I'm going to be honest. I don't really like nested select statements (select within select). However, in your case, the alternative may be uglier and prone to performance errors. The temptation is to do something like:
select . . .
from PAS_User u left outer join
(select folw_foller_user_id, count(*) as user_following_count
from PAS_Follow pf
group by folw_foller_user_id
) pf
on pf.folw_foller_user_id = u.user_user_id left outer join
. . .
Fine. But this will perform much worse than your original query, because of the outside filter on user_user_id. To fix this, you would repeat the condition in the subquery:
select . . .
from PAS_User u left outer join
(select folw_foller_user_id, count(*) as user_following_count
from PAS_Follow pf
where folw_foller_user_id = :sid
group by folw_foller_user_id
) pf
on pf.folw_foller_user_id = u.user_user_id left outer join
. . .
Or even:
select . . .
from PAS_User u cross join
(select count(*) as user_following_count
from PAS_Follow pf
where folw_foller_user_id = :sid
) pf
on pf.folw_foller_user_id = u.user_user_id left outer join
. . .
And I might even argue that repeating the condition in one subquery would be good. I cannot make that argument for five subqueries.
To make this clear, the best syntax is to user window functions:
select . . .
count(*) over (partition by folw_fllower_user_id) as user_following_count,
. . .
Alas, MySQL does not support window functions. A very reasonable replacement, in my opinion, are subqueries (which would normally be correlated). This supports your initial syntax.

Related

MySQL Workbench, how to fix "SELECT is not valid at this position for this server version" error

As the title says, the MySQL Workbench is reporting an error. The error is from the software, not from MySQL, as far as I can tell. However, it's preventing me from bug fixing the code, as it will not work on my website.
Originally I used this query, which works perfectly fine, but is very slow, as it has a select inside the inner join.
SELECT *
FROM modeller AS m
INNER JOIN
bilder AS b ON b.ID = (
SELECT ID FROM bilder AS b2
WHERE b2.modellID = m.id
ORDER BY filnavn
DESC LIMIT 1
)
order by m.fornavn
So I've been looking around for a replacement and ended up with this:
SELECT *
FROM (
SELECT p.*,
m.*,
row_number() over (partition by m.id order by filnavn desc) rono
FROM modeller AS m
INNER JOIN bilder AS B
on B.modellID= m.ID
) x
WHERE x.rono = 1
What this query is supposed to do it to look through a database of 3D models ('modeller') and get the latest picture added to that model in the table 'bilder'. This is a one-to-many connection, bilder has modellID as a column, which corresponds to the ID in modeller.
Instead, I get the error
SELECT is not valid at this position for this server version, expecting : '(', WIDTH
Any tips hints or suggestions are greatly appreciated, especially something that not only circumvents the Workbench-error, but also improves on my initial query.
An alternative you could try is:
SELECT *
FROM modeller AS m
INNER JOIN (
SELECT model1ID, MAX(filnavn) AS filnavnMax
FROM bilder
GROUP BY model1ID
) AS maxVals
ON m.id = maxVals.model1ID
INNER JOIN bilder AS b
ON maxVals.model1ID = b.model1ID AND maxVals.filnavnMax = b.filnavn
ORDER BY m.fornavn
;
However, if filnavn is not unique for a model1ID, you could end up with more results than your original query returned. But if that is the case, your original query could provide inconsistent results (since there is no guarantee the ID returned from the original subquery would be consistent when there is more than one row with the highest filnavn)

MySQL Query limiting results by sub table

I'm really struggling with this query and I hope somebody can help.
I am querying across multiple tables to get the dataset that I require. The following query is an anonymised version:
SELECT main_table.id,
sub_table_1.field_1,
main_table.field_1,
main_table.field_2,
main_table.field_3,
main_table.field_4,
main_table.field_5,
main_table.field_6,
main_table.field_7,
sub_table_2.field_1,
sub_table_2.field_2,
sub_table_2.field_3,
sub_table_3.field_1,
sub_table_4.field_1,
sub_table_4.field_2
FROM main_table
INNER JOIN sub_table_4 ON sub_table_4.id = main_table.id
INNER JOIN sub_table_2 ON sub_table_2.id = main_table.id
INNER JOIN sub_table_3 ON sub_table_3.id = main_table.id
INNER JOIN sub_table_1 ON sub_table_1.id = main_table.id
WHERE sub_table_4.field_1 = '' AND sub_table_4.field_2 = '0' AND sub_table_2.field_1 != ''
The query works, the problem I have is sub_table_1 has a revision number (int 11). Currently I get duplicate records with different revision numbers and different versions of sub_table_1.field_1 which is to be expected, but I want to limit the result set to only include results limited by the latest revision number, giving me only the latest sub_table_1_field_1 and I really can not figure it out!
Can anybody lend me a hand?
Many Thanks.
It's always important to remember that a JOIN can be on a subquery as well as a table. You could build a subquery that returns the results you want to see then, once you've got the data you want, join it in the parent query.
It's hard to 'tailor' an answer that's specific to you problem, as it's too obfuscated (as you admit) to know what the data and tables really look like, but as an example:
Say table1 has four fields: id, revision_no, name and stuff. You want to return a distinct list of name values, with their latest version of stuff (which, we'll pretend varies by revision). You could do this in isolation as:
select t.* from table1 t
inner join
(SELECT name, max(revision_no) maxr
FROM table1
GROUP BY name) mx
on mx.name = t.name
and mx.maxr = t.revision_no;
(Note: see fiddle at the end)
That would return each individual name with the latest revision of stuff.
Once you've got that nailed down, you could then swap out
INNER JOIN sub_table_1 ON sub_table_1.id = main_table.id
....with....
INNER JOIN (select t.* from table1 t
inner join
(SELECT name, max(revision_no) maxr
FROM table1
GROUP BY name) mx
on mx.name = t.name
and mx.maxr = t.revision_no) sub_table_1
ON sub_table_1.id = main_table.id
...which would allow a join with a recordset that is more tailored to that which you want to join (again, don't get hung up on the actual query I've used, it's just there to demonstrate the method).
There may well be more elegant ways to achieve this, but it's sometimes good to start with a simple solution that's easier to replicate, then simplify it once you've got the general understanding of the what and why nailed down.
Hope that helps - as I say, it's as specific as I could offer without having an idea of the real data you're using.
(for the sake of reference, here is a fiddle with a working version of the above example query)
In your case where you only need one column from the table, make this a subquery in your select clause instead of than a join. You get the latest revision by ordering by revision number descending and limiting the result to one row.
SELECT
main_table.id,
(
select sub_table_1.field_1
from sub_table_1
where sub_table_1.id = main_table.id
order by revision_number desc
limit 1
) as sub_table_1_field_1,
main_table.field_1,
...
FROM main_table
INNER JOIN sub_table_4 ON sub_table_4.id = main_table.id
INNER JOIN sub_table_2 ON sub_table_2.id = main_table.id
INNER JOIN sub_table_3 ON sub_table_3.id = main_table.id
WHERE sub_table_4.field_1 = ''
AND sub_table_4.field_2 = '0'
AND sub_table_2.field_1 != '';

Replacing Subqueries with Joins in MySQL

I have the following query:
SELECT PKID, QuestionText, Type
FROM Questions
WHERE PKID IN (
SELECT FirstQuestion
FROM Batch
WHERE BatchNumber IN (
SELECT BatchNumber
FROM User
WHERE RandomString = '$key'
)
)
I've heard that sub-queries are inefficient and that joins are preferred. I can't find anything explaining how to convert a 3+ tier sub-query to join notation, however, and can't get my head around it.
Can anyone explain how to do it?
SELECT DISTINCT a.*
FROM Questions a
INNER JOIN Batch b
ON a.PKID = b.FirstQuestion
INNER JOIN User c
ON b.BatchNumber = c.BatchNumber
WHERE c.RandomString = '$key'
The reason why DISTINCT was specified is because there might be rows that matches to multiple rows on the other tables causing duplicate record on the result. But since you are only interested on records on table Questions, a DISTINCT keyword will suffice.
To further gain more knowledge about joins, kindly visit the link below:
Visual Representation of SQL Joins
Try :
SELECT q.PKID, q.QuestionText, q.Type
FROM Questions q
INNER JOIN Batch b ON q.PKID = b.FirstQuestion
INNER JOIN User u ON u.BatchNumber = q.BatchNumber
WHERE u.RandomString = '$key'
select
q.pkid,
q.questiontext,
q.type
from user u
join batch b
on u.batchnumber = b.batchnumber
join questions q
on b.firstquestion = q.pkid
where u.randomstring = '$key'
Since your WHERE clause filters on the USER table, start with that in the FROM clause. Next, apply your joins backwards.
In order to do this correctly, you need distinct in the subquery. Otherwise, you might multiply rows in the join version:
SELECT q.PKID, q.QuestionText, q.Type
FROM Questions q join
(select distinct FirstQuestion
from Batch b join user u
on b.batchnumber = u.batchnumber and
u.RandomString = '$key'
) fq
on q.pkid = fq.FirstQuestion
As to whether the in or join version is better . . . that depends. In some cases, particularly if the fields are indexed, the in version might be fine.

How to use mySQL count using joins?

I am having following database schema, I want to fetch name of all categories with no of quotes related to that category . The query that i wrote giving me one row only can u please tell me the resource efficient query.
SELECT SC.Name, Count(*) AS Quotes
FROM status_categories AS SC
INNER JOIN status_quotes AS SQ ON SC._id = SQ._category_id
GROUP BY SC.Name
SELECT status_categories.NAME, COUNT(status_quotes.category_id)
FROM status_categories JOIN status_quotes ON status_categories._id = status_quotes.category_id
GROUP BY status_categories._id;
Try the following:
SELECT `c`.`name`, COUNT(*) AS `Number of quotes`
FROM `status_categories` AS `c`
INNER JOIN `status_quotes` AS `q`
ON `q`.`category_id` = `c`.`_id`
GROUP BY `c`.`_id`;
EDIT
Feel free to leave out the ` character. But that is the safe way of doing it, even though it looks a bit nasty.

MySQL join query using like?

I have been trying to get this working for quite a while now but it just doesn't seem to work, maybe it is is not even possible, what i am wanting to do is to perform a MySQL join query using like, such as this example i found...
SELECT *
FROM Table1
INNER JOIN Table2 ON Table1.col LIKE '%' + Table2.col + '%'
but it just doesn't seem to work at all, any help that can be given would be brilliant, thanks !
Try
SELECT *
FROM Table1
INNER JOIN Table2 ON Table1.col LIKE CONCAT('%', Table2.col, '%')
MySQL does string concatenation differently from other databases, so in case you want to port your app, you need to have an alternate version where || is used as concatenation operator, as mentioned by Michael in another answer. This operator doesn't work in MySQL though, since it means or.
How about this instead:
SELECT * FROM Table1, Table2 WHERE Table1.col LIKE '%'+Table2.col+'%';
Since you're selecting everything from both tables anyway, this would probably get you there.
SELECT p.products_id, pd.products_name, p.products_model
FROM products_description pd
JOIN products p ON p.products_id = pd.products_id
WHERE pd.products_name LIKE CONCAT( '%', p.products_model, '%' )
LIMIT 0 , 100
First off you have to restrict your request by (p.products_id = pd.products_id) and LIMIT. And look what time it took. After that you can go and make comparison with (WHERE). If you gonna compare directly within JOIN you will put down your db if there are at list 20 thousands items. Beware.)