cannot bind LIMIT value within table result set of a statement - mysql

I have the following which returns all users...from all computers... of a specific user account. Works great, BUT I want to have a ORDER BY computers.computer_id ASC LIMIT 0, ? on the computers table.
I'm fairly new to joins, but have picked it up pretty quickly in the last day or so, but not sure about specific conditions on a table when joins are involved.
The idea is say there are 20 computers in the account... I limit that to only the first 10, for example, in asc order by its computer_id... which in turn would only left join the users for those 10 computers and then left join the account on that.
$stmt = $db->prepare("
SELECT users.*
FROM computers
LEFT JOIN users
on users.computer_id = computers.computer_id
LEFT JOIN accounts
on accounts.account_id = computers.account_id
WHERE accounts.account_id = ?
");
$stmt->execute(array($_SESSION['user']['account_id'], **limit value goes here**));
// return array
$array = $stmt->fetchAll(PDO::FETCH_ASSOC);
EDIT:
In response to Ollie's answer below... I guess I am doing something wrong here because while it does not produce any errors it also produces no result.
$stmt = $db->prepare("
SELECT users.*
FROM (
SELECT * FROM computers ORDER BY computer_id ASC LIMIT 0, ?
) as c
LEFT JOIN users
on users.computer_id = c.computer_id
LEFT JOIN accounts
on accounts.account_id = c.account_id
WHERE accounts.account_id = ?
");
$stmt->execute(array($_SESSION['user']['account_id'], $_SESSION['user']['licenses']));
// return array
$array = $stmt->fetchAll(PDO::FETCH_ASSOC);
Bind values are fine and there is data to be returned as confirmed by my original statement ... so it is something with the statement itself.
The problem :
I've narrowed this down to the LIMIT... if I manually enter LIMIT 0,
whatever in the statement it works. If I enter LIMIT 0, ? where I bind the value it returns null. Any ideas?

The knack you need is this:
Anywhere you can say table in SELECT ... FROM table AS t you can also say
SELECT ...
FROM (
/* some valid query */
) AS t
So you need
SELECT ...
FROM (
SELECT * FROM computers ORDER BY computer_id ASC LIMIT 0,?
) AS t
LEFT JOIN (whatever)
That is, you can either name a physical table, or a virtual table (the result set of a query) anywhere you name a table.
That's why Structured Query Language is called Structured.

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;

MySQL PDO - latest row for table grouped with joins

I apologize for the title description as I wasn't sure how to best describe this.
There are multiple computers, multiple users per computer, and multiple data logs per user. I want to get the latest data log per computer.
I realize now that the query below does not provide the correct data because the ORDER BY happens for the results and the GROUP BY does so in no particular order.
I did read up on this before posting this and it appears a sub query is needed and then the result of that joined again on the same table to get the rest of that rows column values (the one found to be the latest). The examples I have found though are pretty basic and do not involve other tables being joined. It also seems I have to do this twice since I want it grouped by computer... (once for data_logs grouped by user_id and get the max then another grouped by computer_id to get the max of the earlier result) or maybe I am wrong there.
Need some help grasping how to tackle this and the approach to do so.
$stmt = $db->prepare("
SELECT
computers.computer_name,
users.username,
data_logs.window_title,
data_logs.filename,
data_logs.capture_timestamp
FROM computers
INNER JOIN users
ON users.computer_id = computers.computer_id
INNER JOIN data_logs
ON data_logs.user_id = users.user_id AND data_logs.marked != 1
WHERE computers.account_id = :cw_account_id AND computers.status = 1
GROUP BY computers.computer_id
ORDER BY data_logs.capture_timestamp desc
");
$binding = array('cw_account_id' => 1721);
$stmt->execute($binding);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo "<pre>";
print_r($results);
echo "</pre>";
One solution is to use a correlated subquery. The principle is that the outer query is not aggregated, but it has a condition in the WHERE clause that selects the latest log entry for the current computer id, using an aggregated subquery.
I guess that you would need this query (without seeing sample data and expected output though, this might not be 100% accurate) :
SELECT
computers.computer_name,
users.username,
data_logs.window_title,
data_logs.filename,
data_logs.capture_timestamp
FROM computers
INNER JOIN users
ON users.computer_id = computers.computer_id
INNER JOIN data_logs
ON data_logs.user_id = users.user_id AND data_logs.marked != 1
WHERE
computers.account_id = :cw_account_id
AND computers.status = 1
AND data_logs.capture_timestamp = (
SELECT MAX(d.capture_timestamp)
FROM computers c
INNER JOIN users u ON u.computer_id = c.computer_id
INNER JOIN data_logs d ON d.user_id = u.user_id AND d.marked != 1
WHERE c.computer_id = computers.computer_id AND c.account_id = computers.account_id
)
ORDER BY data_logs.capture_timestamp desc

How to use data from 1st query to check 2nd query?

Below is my 1st query where I use to get post data and creator data who creates it in post.date_created order from post and account_data_base tables.
SELECT post.*, account_data_base.* FROM post
JOIN account_data_base ON post.creator_id =
account_data_base.user_uid ORDER BY post.date_created DESC
and this is my 2nd query in php after running 1st query which I use to check if the post creator likes his/her post or not.
$stmt = $conn->prepare("SELECT EXISTS(SELECT * FROM like
WHERE post_id = ? and userLike_Id = ?)");
$stmt->bind_param("ss", $postId, $account_data_base_id);
as you can see $postId and $account_data_base_id are values that I get from my 1st query. I wonder is there a way that I can combine 2 queries at once where I can get post data, creator data as well as my 2nd query to check like or not?
Update: this is what I tried but the like check doesn't show accurate info:
SELECT post.*, account_data_base.*,
EXISTS(SELECt * FROM like WHERE like.post_id = post.id and like.userLike_Id = account_data_base.user_uid) FROM post
JOIN account_data_base ON post.creator_id =
account_data_base.user_uid ORDER BY post.date_created DESC
You can use your EXISTS query as subquery in the SELECT clause:
SELECT post.*, a.*, EXISTS (
SELECT * FROM `like` l
WHERE l.post_id = p.id
and l.userLike_Id = a.id
) as likeExists
FROM post p
JOIN account_data_base a ON p.creator_id = a.user_uid
ORDER BY p.date_created DESC
This will add the field likeExists which is either 1 or 0. And you don't need to run the query once per post.

mysql Multiple left joins using count

I have been researching this for hours and the best code that I have come up with is this from an example i found on overstack. I have been through several derivations but the following is the only query that returns the correct data, the problem is it takes over 139s (more than 2 minutes) to return only 30 rows of data. Im stuck. (life_p is a 'likes'
SELECT
logos.id,
logos.in_gallery,
logos.active,
logos.pubpriv,
logos.logo_name,
logos.logo_image,
coalesce(cc.Count, 0) as CommentCount,
coalesce(lc.Count, 0) as LikeCount
FROM logos
left outer join(
select comments.logo_id, count( * ) as Count from comments group by comments.logo_id
) cc on cc.logo_id = logos.id
left outer join(
select life_p.logo_id, count( * ) as Count from life_p group by life_p.logo_id
) lc on lc.logo_id = logos.id
WHERE logos.active = '1'
AND logos.pubpriv = '0'
GROUP BY logos.id
ORDER BY logos.in_gallery desc
LIMIT 0, 30
I'm not sure whats wrong. If i do them singularly meaningremove the coalece and one of the joins:
SELECT
logos.id,
logos.in_gallery,
logos.active,
logos.pubpriv,
logos.logo_name,
logos.logo_image,
count( * ) as lc
FROM logos
left join life_p on life_p.logo_id = logos.id
WHERE logos.active = '1'
AND logos.pubpriv = '0'
GROUP BY logos.id
ORDER BY logos.in_gallery desc
LIMIT 0, 30
that runs in less than half a sec ( 2-300 ms )....
Here is a link to the explain: https://logopond.com/img/explain.png
MySQL has a peculiar quirk that allows a group by clause that does not list all non-aggregating columns. This is NOT a good thing and you should always specify ALL non-aggregating columns in the group by clause.
Note, when counting over joined tables it is useful to know that the COUNT() function ignores NULLs, so for a LEFT JOIN where NULLs can occur don't use COUNT(*), instead use a column from within the joined table and only rows from that table will be counted. From these points I would suggest the following query structure.
SELECT
logos.id
, logos.in_gallery
, logos.active
, logos.pubpriv
, logos.logo_name
, logos.logo_image
, COALESCE(COUNT(cc.logo_id), 0) AS CommentCount
, COALESCE(COUNT(lc.logo_id), 0) AS LikeCount
FROM logos
LEFT OUTER JOIN comments cc ON cc.logo_id = logos.id
LEFT OUTER JOIN life_p lc ON lc.logo_id = logos.id
WHERE logos.active = '1'
AND logos.pubpriv = '0'
GROUP BY
logos.id
, logos.in_gallery
, logos.active
, logos.pubpriv
, logos.logo_name
, logos.logo_image
ORDER BY logos.in_gallery DESC
LIMIT 0, 30
If you continue to have performance issues then use a execution plan and consider adding indexes to suit.
You can create some indexes on the joining fields:
ALTER TABLE table ADD INDEX idx__tableName__fieldName (field)
In your case will be something like:
ALTER TABLE cc ADD INDEX idx__cc__logo_id (logo_id);
I dont really like it because ive always read that sub queries are bad and that joins perform better under stress, but in this particular case subquery seems to be the only way to pull the correct data in under half a sec consistently. Thanks for the suggestions everyone.
SELECT
logos.id,
logos.in_gallery,
logos.active,
logos.pubpriv,
logos.logo_name,
logos.logo_image,
(Select COUNT(comments.logo_id) FROM comments
WHERE comments.logo_id = logos.id) AS coms,
(Select COUNT(life_p.logo_id) FROM life_p
WHERE life_p.logo_id = logos.id) AS floats
FROM logos
WHERE logos.active = '1' AND logos.pubpriv = '0'
ORDER BY logos.in_gallery desc
LIMIT ". $start .",". $pageSize ."
Also you can create a mapping tables to speed up your query try:
CREATE TABLE mapping_comments AS
SELECT
comments.logo_id,
count(*) AS Count
FROM
comments
GROUP BY
comments.logo_id
) cc ON cc.logo_id = logos.id
Then change your code
left outer join(
should become
inner join mapping_comments as mp on mp.logo_id =cc.id
Then each time a new comment are added to the cc table you need to update your mapping table OR you can create a stored procedure to do it automatically when your cc table changes

Single SQL to retrieve different information from different tables

I have this query which retrives 10 ( $limited ) queries from MySQL ,
"SELECT content.loc,content.id,content.title,
voting_count.up,voting_count.down
FROM
content,voting_count
WHERE names.id = voting_count.unique_content_id
ORDER BY content.id DESC $limit"
This query did great for posts that were allready in database and had votes , however new posts won't show.
Vote row is "inserted" first time someone votes on post. I guess that the reason why they won't be listed as there is no unique_content_id to connect to.
If i change query into this :
"SELECT content.loc,content.id,content.title
FROM
content
ORDER BY content.id DESC $limit"
it works , but i can't access voting_count.up & voting_count.down rows.
How could i access both information in single query ? Is it doable ?
If some data might not exist in one of the tables, instead of using INNER JOIN you should use LEFT JOIN:
SELECT content.loc,content.id,content.title,
-- USE function COALSESCE will show 0 if there are no
-- related records in table voting_count
COALESCE(voting_count.up, 0) as votes_up,
COALSESCE(voting_count.down, 0) as voted_down
FROM content LEFT JOIN voting_count
ON content.id = voting_count.unique_content_id
ORDER BY content.id DESC
As someone else above mentioned, what is names.id? However, perhaps the following might be of use assuming the join should have been from content.id to voting_count.unique_content_id:
$sql="select
c.`loc`,c.`id`, c.`title`,
case
when v.`up` is null then
0
else
v.`up`
end as 'up',
case
when v.`down` is null then
0
else
v.`down`
end as 'down'
from `content` c
left outer join `voting_count` v on v.`unique_content_id`=c.`id`
order by c.`id` desc {$limit}";