getting select value from a sub query - mysql

Below is an sql query that im using to get a list of users who doest have contact details added. The below query seems to work fine. But the only problem is that the image value always returns NULL
I tried to use a sub query to image the image link.Everything works except for the image link.
SELECT a.id,a.name,a.address,a.image_id,(select url
from meta_details b
where b.p_id = 'a.image_id'
and b.meta_val='profile_pic') as image
FROM users
WHERE a.user_id NOT IN (SELECT user_id FROM details where contact_id != '$cid')
Im not sure if this is the correct way, is it possible to make it work to get the image url?

You should remove the quote around a.image_id ( otherwise instead of a join condition on field you have a join on literal value 'a.image_id' that fails because don't match )
SELECT
a.id
,a.name
,a.address
,a.image_id
,(select url
from meta_details b
where b.p_id = a.image_id and b.meta_val='profile_pic' ) as image
FROM users a
WHERE a.user_id NOT IN (SELECT user_id FROM details where contact_id != '$cid')

This type of query can also be expressed with join operations:
select u.*, md.url as image
from users u left join
meta_details md
on md.p_id = u.image_id and
md.meta_val = 'profile_pic' left join
details d
on d.user_id = u.user_id and d.contact_id <> '$cid'
where d.user_id is null;
The advantage to this approach is that the SQL optimizer has a chance of producing a better optimization plan. Also, NOT IN is dangerous because if any rows from the subquery return NULL, then no rows at all will be returned from the query.

Related

Order by inside the LEFT JOIN

I am trying to write a query. I got it work half way, but I am having problems with the LEFT JOIN.
I have three tables:
user
user_preferences
user_subscription_plan
User will always have one user_preference, but it can have many or no entries in the user_subscription_plan
If the user has no entry in the user_subscription_plan, or if he has only one then my sql works. If I have more then one, then I have issue. In the case of two entries, how can I make it to return the last one entered? I tried playing with ORDER statement, but it does not work as expected. Somehow I get empty rows.
Here is my query:
SELECT u.id AS GYM_USER_ID, subscription_plan.id AS subscriptionId, up.onboarding_completed AS CompletedOnboarding,
(CASE
WHEN ((up.onboarding_completed = 1)
AND (ISNULL(subscription_plan.id)))
THEN 'freemiun'
WHEN (ISNULL(up.onboarding_completed)
AND (ISNULL(subscription_plan.id)))
THEN 'not_paying'
END) AS subscription_status
FROM user AS u
INNER JOIN user_preferences up ON up.user_id = u.id
LEFT JOIN (
SELECT * FROM user_subscription_plan AS usp ORDER BY usp.id DESC LIMIT 1
) AS subscription_plan ON subscription_plan.user_id = u.id
GROUP BY u.id;
If I run it as it is, then subscription_plan.id AS subscriptionId is always empty.
If I remove the LIMIT clause, then its not empty, but I am still getting the first entry, which is wrong in my case
I have more CASE's to cover, but I can't process until I solve this problem.
Please try to use "max(usp.id)" that "group by subscription_plan.user_id" instead of limit 1.
If you limit 1 in the subquery, the subquery's result will always return only 1 record (if the table has data).
So the above query can be rewritten like this.
Sorry, I didn't test, because I don't have data, but please try, hope this can help.
SELECT
u.id AS GYM_USER_ID,
subscription_plan.id AS subscriptionId,
up.onboarding_completed AS CompletedOnboarding,
(CASE
WHEN
((up.onboarding_completed = 1)
AND (ISNULL(subscription_plan.id)))
THEN
'freemiun'
WHEN
(ISNULL(up.onboarding_completed)
AND (ISNULL(subscription_plan.id)))
THEN
'not_paying'
END) AS subscription_status
FROM
user AS u
INNER JOIN
user_preferences up ON up.user_id = u.id
LEFT JOIN
(SELECT
usp.user_id, MAX(usp.id)AS id
FROM
user_subscription_plan AS usp
GROUP BY usp.user_id) AS subscription_plan ON subscription_plan.user_id = u.id;

how to change this mysql query to a efficient one

my table user contains these fields
id,company_id,created_by,name,image
table valet contains
id,vid,dept_id
table cart contains
id,dept_id,map_id,purchase,time
to get the details i have written this mysql query
SELECT c.id, a.id, c.purchace, c.time
FROM user a
LEFT JOIN valet b ON a.vid = b.id
AND a.is_deleted = 0
LEFT JOIN cart c ON b.dept_id = c.dept_id
WHERE a.company_id = 18
AND a.created_by = 102
AND a.is_deleted = 0
AND c.time
IN ( SELECT MAX( time ) FROM cart WHERE dept_id = b.dept_id )
from these three table i want to select last updated raw from cart along with id from user table which is mapped in valet table
this query works fine but it takes almost 15 sec to retrieve the details .
is there any way to improve this query or may be i am doing some wrong.
any help would be appreciated
For one thing, I can see that you’re running the subquery for each row. Depending on what the optimiser does, that may have an impact. max is a pretty expensive operation (there’s nothing for it but to read every row).
If you plan to update and use this query repeatedly, perhaps you should at least index the table on cart.time. This will make it much easier to find the maximum value.
MySQL has the concept of user variables, so you can set a variable to the result of the subquery, and that might help:
SELECT c.id, a.id, c.purchace, c.time
FROM
user a
LEFT JOIN valet b ON a.vid = b.id AND a.is_deleted = '0'
LEFT JOIN cart c ON b.dept_id = c.dept_id
LEFT JOIN (SELECT dept_id,max(time) as mx FROM cart GROUP BY dept_id) m on m.dept_id=c.dept_id
WHERE
a.company_id = '18'
AND a.created_by = '102'
AND a.is_deleted = '0'
AND c.time=m.mx;
Note also:
since you’re only testing a single value (max) for c.time, you should be using = not in.
I’m not sure about is why you are using strings instead of integers. I shold have though that leaving off the quotes makes more sense.
Your JOIN includes AND a.is_deleted = '0', though you make no mention of it in your table description. In any case, why is it in the JOIN and not in the WHERE clause?

What Would be the Correct SELECT Statement for This?

SELECT *
FROM notifications
INNER JOIN COMMENT
ON COMMENT.id = notifications.source_id
WHERE idblog IN (SELECT blogs_id
FROM blogs
WHERE STATUS = "active")
INNER JOIN reportmsg
ON reportmsg.msgid = notifications.source_id
WHERE uid =: uid
ORDER BY notificationid DESC
LIMIT 20;
Here I am INNER JOINing notifications with comment and reportmsg; then filtering content with WHERE.
But my problem is that for the first INNER JOIN [i.e, with comment], before joining notifications with comment, I want to match notifications.idblog with blogs.blogs_id and SELECT only those rows where blogs.status = "active".
For better understanding of the code above:
Here, for INNER JOIN, with comment I want to SELECT only those rows in notifications whose idblog matches blogs.blogs_id and has status = "active".
The second INNER JOIN with reportmsg needs not to be altered. I.e, it only filters through uid.
As you can see from the image below, you can just need to merge other tables to notifications table using LEFT JOIN like that:
SELECT n.notificationid, n.uid, n.idblog, n.source_id,
b.blogs_id, b.status,
c.id,
r.msgid
-- ... and the other columns you want
FROM notifications n
LEFT JOIN blogs b ON b.blogs_id = n.idblog AND b.STATUS = "active" AND n.uid =: uid
LEFT JOIN comment c ON c.id = n.source_id
LEFT JOIN reportmsg r ON r.msgid = n.source_id
ORDER BY n.notificationid DESC
LIMIT 20;
There's no need/reason to filter before the second join because you only use inner joins and then the order of joins and WHERE-conditions don't matter:
SELECT n.*, c.*, r.*
FROM notifications AS n
JOIN COMMENT as c
ON n.source_id = c.id
LEFT JOIN blogs as b
ON n.idblogs = b.blogs_id
AND B.STATUS = 'active'
JOIN reportmsg AS R
ON n.source_id = r.msgid
WHERE uid =: uid
ORDER BY notificationid DESC
LIMIT 20
You can switch the order of joins, you can move B.STATUS = 'active' into the join-condition, but all queries will return the same result. (After the edit it's a LEFT JOIN, of course now the result differs)
And of course you shouldn't use *, better list only the columns you actually need.
if query optimizer does its work, it does not matter where you put filtering statement in INNER JOIN case but in the LEFT JOIN it has effects. Putting filtering statement in LEFT JOIN conditions cause table filtered at first and joined after while putting filtering statement in WHERE clause will filter results of join. Hence, if you want to use LEFT JOIN your query must look like:
SELECT nt.*
FROM notifications nt
LEFT JOIN Blogs bg on nt.blogs_id = bg.blogs_id and bg.STATUS = "active"
LEFT JOIN COMMENT cm ON cm.id = nt.source_id
LEFT JOIN reportmsg rm ON rm.msgid = nt.source_id
WHERE uid =: uid
ORDER BY nt.notificationid DESC
LIMIT 20;
It's very unclear what you are after here.. while your table diagram is useful, you should really supply some sample data and an expected result even if it is just a couple of dummy rows for each table.
Queries work row by row, both INNER JOINs are applied to the same notification row and non-matching rows are discarded.
Any filter applies to both JOIN and any returned rows must have a match in BOTH comment and reportmsg.
Perhaps you want two LEFT JOINs that can apply different filters and guessing from the table names perhaps it could look like this:
SELECT *
FROM notifications n
LEFT JOIN blogs b
ON n.blogId = b.blogs_id
LEFT JOIN comment c
ON c.id = n.source_id
AND b.status = "Active"
LEFT JOIN reportmsg rm
ON rm.msgid = n.source_id
WHERE n.uid =: uid
AND (c.id IS NOT NULL OR rm.msgid IS NOT NULL)
ORDER BY n.notificationid DESC
LIMIT 20
You also should work on your naming convention:
notifications, comment -> pick either plural or singular table names
notifications.notificationid, comment.id -> pick adding table name to id
notificationid, source_id -> pick underscore or no separation
idblog, notificationid -> pick prepending or appending id
Currently you pretty much have to look up every id field every time you want to use one.
You should change your query to this:
SELECT *
FROM notifications
INNER JOIN comment ON comment.id = notifications.source_id
INNER JOIN reportmsg ON reportmsg.msgid=notifications.source_id
LEFT JOIN blogs ON notifications.idblog = blogs.blogs_id
WHERE blogs.status = 'active'
ORDER BY notificationid DESC
LIMIT 20;

MYSQL multiple INNER JOIN retrieve every information

i want to run the following query in my script but it won't work correctly.
I'm not getting any errors, it just selects the data from hs_data correct (and everything) but from hs_download_links it only retrieved as much data as there are entries in hs.images.
The goal of this query should be to get any entry from hs_data. If there's information related to these entries in hs_download_links or hs_images it should get them as well.
SELECT
h.hacks_ID, h.hacks_Name, h.hacks_Name_Full,
h.hacks_Version, h.hacks_Description, h.hacks_AddDate,
h.hacks_Type, SUM(dl.link_count) AS link_count, i.image_NameThumb
FROM
hs_data h
LEFT JOIN
(hs_download_links dl CROSS JOIN hs_images i)
ON
((dl.link_hackID = h.hacks_ID AND i.image_HackID = h.hacks_ID)
OR
(dl.link_hackID = h.hacks_ID AND i.image_HackID is NULL)
OR
(dl.link_hackID is NULL AND i.image_HackID = h.hacks_ID)
OR
(dl.link_hackID is NULL AND i.image_HackID is NULL) )
GROUP BY
h.hacks_ID
ORDER BY link_count DESC
Would be great if you guys could help me, much thanks :)
I think you can write this as:
SELECT h.hacks_ID, h.hacks_Name, h.hacks_Name_Full,
h.hacks_Version, h.hacks_Description, h.hacks_AddDate,
h.hacks_Type, SUM(dl.link_count) AS link_count, i.image_NameThumb
FROM hs_data h LEFT JOIN
hs_download_links dl
on dl.link_hackID = h.hacks_ID LEFT JOIN
hs_images i
on i.image_HackID = h.hacks_ID
GROUP BY h.hacks_ID
ORDER BY link_count DESC;
I'm not sure what you are trying to accomplish with the cross join, but it seems unnecessarily confusing. You can chain left outer joins together. They still keep all the rows in the first table.
By the way, your select has a dangling i.image_NameThumb. That means that only one such value will appear. If there are multiple values, you might want to use group_concat(). Or even min() or max() just to clarify the intent of the query.
This will return the number of download links and the first image, if any, for each record in hs_data:
SELECT d.*,
(
SELECT COUNT(*)
FROM hs_download_links dl
WHERE dl.link_hackId = d.hacks_Id
),
(
SELECT i.image_NameThumb
FROM hs_images i
WHERE i.image_hackId = d.hacks_Id
ORDER BY
image_NameThumb
LIMIT 1
)
FROM hs_data d

How can I join conditionally based on a value joined to the join?

I am not sure if this is possible, but I want to get all records back, as well as their attachment, if its type (a definition table) is 'main' (if it has an attachment, but it's type is something else, I want it to be NULL.
SELECT r.*
FROM record r
LEFT JOIN attachment d on d.record_id = r.id
LEFT JOIN attachment_type at on d.type_id = at.id
WHERE at.name = "main"
GROUP BY r.id
I would do some redesign of the data here, but that's not possible. Could I use a subquery to get the id before doing the join?
I don't know what your attachment column table looks like (beyond the fact that it has a type column), but something like this should be close, meaning (a) it'll return all rows, and (b) the returned attachment value will be null if the type is main:
SELECT
r.*,
CASE WHEN at.name = 'main' THEN d.whatever ELSE NULL END AS attach_thingie
FROM record r
LEFT JOIN attachment d on d.record_id = r.id
LEFT JOIN attachment_type at on d.type_id = at.id
And, as #FreshPrinceOfSO mentioned in the comment above, I don't see any need for the GROUP BY.
One more thing: based on what I can infer from the query, I don't see any glaring design problems among the three tables. I read it as record can have any number of attachment and attachment has (maybe) a type. If that's what your requirements call for you should be OK.
Addendum: choose a maximum of one attachment per record based on the attachment.id column:
SELECT r.*, d.whatever
FROM record r
LEFT JOIN
(
SELECT attachment.record_id, MAX(attachment.id) AS Max_ID
FROM attachment
INNER JOIN attachment_type at ON attachment.type_id = at.id
WHERE at.name = 'main'
GROUP BY attachment.record_id
) att ON r.id = att.record_id
LEFT JOIN attachment d ON d.id = att.Max_ID