MySQL Selecting a complex sub query as a field - php

I currently have:
SELECT tbl_review.*, users.first_name, users.last_name, (
SELECT order_ns.tran_date
FROM order_ns
LEFT JOIN product_2_order_ns.external_order_id = order_ns.order_id
WHERE product_2_order_ns.bkfno IN :id
ORDER BY order_ns.trandate ASC
LIMIT 1
) as purchase_date
FROM tbl_review
LEFT JOIN users ON users.sequal_user_id = tbl_review.user_id
WHERE tbl_review.product_id IN :id AND tbl_review.approved = 1
Which, in its sub query, selects an order the user has which has a product in question (defined in :id) get the the oldest transaction date on file for one of the found orders.
I would really like to keep this to one call of the database (don't really want to call again for each returned user for just one field, or even do a range query of all users) but obviously this particular query isn't working.
What can I do, if anything, to get this working?
I cannot make the sub query into a join since they are two distinct pieces of data, the sub query needs to return detail for each row in the main query.

I think you just want a correlated subquery. It is unclear exactly what the relationship is between the inner query and the outer one. My guess is that it is on users and orders:
SELECT tbl_review.*, users.first_name, users.last_name,
(SELECT order_ns.tran_date
FROM order_ns LEFT JOIN
product_2_order_ns
on product_2_order_ns.external_order_id = order_ns.order_id and
product_2_order_ns.bkfno = tbl_review.product_id and
WHERE order_ns.user_id = tbl_review.user_id
ORDER BY order_ns.trandate ASC
LIMIT 1
) as purchase_date
FROM tbl_review LEFT JOIN
users
ON users.sequal_user_id = tbl_review.user_id
WHERE tbl_review.product_id IN :id AND tbl_review.approved = 1;
EDIT:
Oh, the inner query has no relationship to the outer query. Then it is easier. Move it to the from clause using cross join:
SELECT tbl_review.*, users.first_name, users.last_name,
innerquery.tran_date as purchase_date
FROM tbl_review LEFT JOIN
users
ON users.sequal_user_id = tbl_review.user_id cross join
(SELECT order_ns.tran_date
FROM order_ns LEFT JOIN
product_2_order_ns
on product_2_order_ns.external_order_id = order_ns.order_id
WHERE product_2_order_ns.bkfno IN :id
ORDER BY order_ns.trandate ASC
LIMIT 1
) innerquery
WHERE tbl_review.product_id IN :id AND tbl_review.approved = 1;

#Gordons answer is really close but I wanted it to return even if no data was found for tran_date so I changed my query to:
SELECT tbl_review.*, users.first_name, users.last_name, order_ns.tran_date
FROM tbl_review
LEFT JOIN users ON users.sequal_user_id = tbl_review.user_id
LEFT JOIN order_ns ON order_ns.order_id = (
SELECT order_ns.order_id
FROM order_ns
LEFT JOIN product_2_order_ns on product_2_order_ns.external_order_id = order_ns.order_id
WHERE product_2_order_ns.bkfno IN :id
ORDER BY order_ns.tran_date ASC
LIMIT 1
)
WHERE tbl_review.product_id IN :id AND tbl_review.approved = 1;
This will return the distinct data of tran_date irrespective of whether it is found or not.

Related

Get the last records from 3 tables

My tables
$sql="SELECT *
FROM addresses
LEFT JOIN users ON address_id = user_id
LEFT JOIN notes ON note_id = user_id
ORDER BY id DESC
LIMIT 1";
This is my SQL query, my task is to show the last records from 3 tables, but the table is blank, I don't know why,thanks in advance people :)
I guess the problem is coming from the ORDER BY id DESC .
Indeed, you have no column so called id.
You should probably remove this clause, in order to make your code work.
If you want to take the last records anyway, you can put an ORDER BY address_id DESC which will do the job !
The code directly edited :
$sql="SELECT *
FROM addresses
LEFT JOIN users ON address_id = user_id
LEFT JOIN notes ON note_id = user_id
ORDER BY adress_id DESC
LIMIT 1";
This may work:
SELECT a.address_id, u.user_id, n.note_id
FROM addresses a
LEFT JOIN users_addresses ua ON ua.ua_address_id = a.address_id
LEFT JOIN users u ON u.user_id = ua.ua_user_id
LEFT JOIN notes n ON n.note_user_id = u.user_id
ORDER BY a.address_id DESC
LIMIT 1
Here is the query to get all data from all the tables, not sure what do you mean last records from 3 tables, I can see four tables there:
SELECT *
FROM `addresses`
LEFT JOIN `users_addresses` ON `users_addresses`.`ua_address_id` = `addresses`.`address_id`
LEFT JOIN `users` ON `users`.`user_id` = `users_addresses`.`ua_user_id`
LEFT JOIN `notes` ON `notes`.`note_user_id` = `users`.`user_id`;

EXISTS query optimization on mysql query

I have a big data problem with MySQL.
I have:
a users table with 59033 rows, and
a user_notes table with 8753 rows.
But when I search which users have user note in some dates.
My query like this :
SELECT u.*, rep.name as rep_name FROM users as u
LEFT JOIN users as rep on rep.id = u.add_user
LEFT JOIN authorization on authorization.id = u.authorization
LEFT JOIN user_situation_list on user_situation_list.user_situation_id = u.user_situation
WHERE
EXISTS(
select * from user_notes
where user_notes.note_user_id = u.id AND user_notes.create_date
BETWEEN "2017-10-20" AND "2017-10-22"
)
ORDER BY u.lp_modify_date DESC, u.id DESC
Turn it around -- find the ids first; deal with the joins later.
SELECT u.*,
( SELECT rep.name
FROM users AS rep
WHERE rep.id = u.add_user ) AS rep_name
FROM (
SELECT DISTINCT note_user_id
FROM user_notes
WHERE create_date >= "2017-10-20"
AND create_date < "2017-10-20" + INTERVAL 3 DAY
) AS un
JOIN users AS u ON u.id = un.note_user_id
ORDER BY lp_modify_date DESC, id DESC
Notes
No GROUP BY needed;
2 tables seem to be unused; I removed them;
I changed the date range;
User notes needs INDEX(create_date, note_user_id);
Notice how I turned a LEFT JOIN into a subquery in the SELECT list.
If there can be multiple rep_names, then the original query is "wrong" in that the GROUP BY will pick a random name. My Answer can be 'fixed' by changing rep.name to one of these:
MAX(rep.name) -- deliver only one; arbitrarily the max
GROUP_CONCAT(rep.name) -- deliver a commalist of names
Rewriting your query to use a JOIN rather than an EXISTS check in the where should speed it up. If you then group the results by the user.id it should give you the same result:
SELECT u.*, rep.name as rep_name FROM users as u
LEFT JOIN users as rep on rep.id = u.add_user
LEFT JOIN authorization on authorization.id = u.authorization
LEFT JOIN user_situation_list on user_situation_list.user_situation_id = u.user_situation
JOIN user_notes AS un
ON un.note_user_id
AND un.create_date BETWEEN "2017-10-20" AND "2017-10-22"
GROUP BY u.id
ORDER BY u.lp_modify_date DESC, u.id DESC

MySQL Using SUM with multiple joins

I have a projects table and a tasks table I want to do a query that gets all projects and the sum of the time_spent columns grouped by project id. So essentially list all projects and get the total of all the time_spent columns in the tasks table belonging to that project.
With the query posted below I get the latest added time_spent column and not the sum of all the columns.. :S
Below is the query I have at the moment:
SELECT `projects`.`id`, `projects`.`description`, `projects`.`created`,
`users`.`title`, `users`.`firstname`, `users`.`lastname`, `users2`.`title`
as assignee_title, `users2`.`firstname` as assignee_firstname,
`users2`.`lastname` as assignee_lastname,
(select sum(tasks2.time_spent)
from tasks tasks2
where tasks2.id = tasks.id)
as project_duration
FROM (`projects`)
LEFT JOIN `users`
ON `users`.`id` = `projects`.`user_id`
LEFT JOIN `users` as users2
ON `users2`.`id` = `projects`.`assignee_id`
LEFT JOIN `tasks` ON `tasks`.`project_id` = `projects`.`id`
GROUP BY `projects`.`id`
ORDER BY `projects`.`created` DESC
Below is my projects table:
Below is my tasks table:
Thanks in advance!
Usually this query will help you.
SELECT p.*, (SELECT SUM(t.time_spent) FROM tasks as t WHERE t.project_id = p.id) as project_fulltime FROM projects as p
In your question, you don't say about users. Do you need users?
You are on right way, maybe your JOINs can't fetch all data.
This query should do it for you.
Note, whenever you do a group by you must include every column that you select from or order by. Some MySql installations don't prevent you from doing this, but in the end it results in an incorrect result set.
As well you should never do a query as part of your SELECT statement, known as a sub-query, as it will result in an equal amount of additional queries in relation to the number of rows returned. So if you got 1,000 rows back, it would result in 1,001 queries instead of 1 query.
SELECT
p.id,
p.description,
p.created,
u.title,
u.firstname,
u.lastname,
a.title assignee_title,
a.firstname assignee_firstname,
a.lastname assignee_lastname,
SUM(t.time_spent) project_duration
FROM
projects p
LEFT JOIN
users u ON
u.id = p.user_id
LEFT JOIN
users a ON
a.id = u.assignee_id
LEFT JOIN
tasks t ON
t.project_id = p.id
GROUP BY
p.id,
p.description,
p.created,
u.title,
u.firstname,
u.lastname,
a.title,
a.firstname,
a.lastname
ORDER BY
p.created DESC

SQL query problem

I've got reporting of a user's score everytime it happens. Now I want to show the best score a user has had. The table set up is like this:
Player(id, name)
PlayerHasAchievement(id, playerId,
achievementId)
Achievement(id, type, amount, time)
This is what I have right now:
$query = "SELECT MAX(ach.amount) as amount, p.username, ach.time
FROM achievement as ach
INNER JOIN playerHasAchievement as playAch ON ach.id = playAch.id
INNER JOIN player as p ON p.userId = playAch.userid
WHERE ach.type = 2
GROUP BY amount
ORDER by `amount` DESC
LIMIT $amount";
I tried to select it distinctly but it didn't work. I'm stumped, it's supposed to be so easy! Thanks for reading, I'll be grateful for any help!
The problem is the the ach.time you are getting is not the same row as the MAX(amount). Join another subquery to get the MAX(amount) first.
Note: In the table definitions you posted, playerHasAchievement has a field playerId not userId
SELECT MAX(ach.amount) as amount, p.username, MAX(ach.time) MaxTime
FROM achievement as ach
INNER JOIN playerHasAchievement as playAch ON ach.id = playAch.id
INNER JOIN player as p ON p.userId = playAch.playerId
INNER JOIN (
SELECT playAch.playerId, MAX(ach.amount) as MaxAmount
FROM achievement as ach
INNER JOIN playerHasAchievement as playAch ON ach.id = playAch.id
WHERE ach.type = 2
GROUP BY playAch.playerId
) g ON p.playerId = g.playerId AND ach.amount = g.MaxAmount
WHERE ach.type = 2
GROUP BY p.playerId
ORDER by `amount` DESC
LIMIT $amount";
The reason why we group the outer query, is to avoid ties - say a player had the same score twice.
In your join on line 3 don't you really want
INNER JOIN playerHasAchievement as playAch ON ach.id = playAch.achievementId
and others are correct, you need to group by your non aggregate columns, not the aggregate one.
Assuming your db layout is as specified in the question here is the query I would use.
SELECT ach.amount, p.Name, ach.time
FROM achievement as ach
JOIN playerHasAchievement as playAch ON ach.id=playAch.achievementId
JOIN player AS p ON p.id = playAch.playerId
WHERE ach.type = 2
AND ach.amount = (SELECT MAX(ach.amount)
FROM achievement as ach
JOIN playerHasAchievement as playAch ON ach.id=playAch.achievementId
JOIN player AS p ON p.id = playAch.playerId
WHERE ach.type = 2)
GROUP BY ach.amount
ORDER by ach.time
taking the first result (in case there are multiples of the same score) will give you the high score and the lowest time.
Hope that helps!
You are not using group by appropriately, as you are only grouping by amount.
What about the user name and the time?

mysql - subqueries and joins

I'm not quite sure if this is the right approach, this is my situation:
I'm currently trying to select 15 galleries and then left join it with the user table through the id but I also want to select one random picture from each gallery however from what I know you can't limit the left join (picture) to only pick up one random picture without doing a subquery.
Here is what I got so far but its not working as it should:
SELECT galleries.id, galleries.name, users.username, pictures.url
FROM galleries
LEFT JOIN users ON users.id = galleries.user_id
LEFT JOIN pictures ON (
SELECT pictures.url
FROM pictures
WHERE pictures.gallery_id = galleries.id
ORDER BY RAND()
LIMIT 1)
WHERE active = 1
ORDER BY RAND()
LIMIT 15
I also tried to do this with Active Record but I got stuck after doing two left joins, is it possible to do get a subquery in here:
$this->db->select('galleries.id, galleries.name, users.id as user_id, users.username');
$this->db->from('galleries');
$this->db->join('users', 'users.id = galleries.user_id','left');
$this->db->join('pictures','pictures.gallery_id = galleries.id AND','left');
$this->db->where('active',1);
I hope its not to messy but I'm really starting to get confusing by all the sql queries..
Edit:
Active Record with CodeIgniter
You could fetch a random picture in a subquery:
select
g.name, u.username,
(select url from pictures p where p.gallery_id = g.gallery_id
order by rand() limit 1) as url
from galleries g
left join users u on g.user_id = u.id
where g.active = 1
Based on your comment, you could select a picture for each gallery in a subquery. This is assuming the picture table has an ID column.
select
g.name, u.username, p.url, p.name
from (
select id, user_id, name,
(select id from pictures p
where p.gallery_id = g.gallery_id
order by rand() limit 1) as samplepictureid
from galleries
where g.active = 1
) g
left join users u on g.user_id = u.id
left join pictures p on p.id = g.samplepictureid
SELECT
g.id,
g.name,
u.username,
p.url
FROM
galleries g
INNER JOIN (SELECT DISTINCT
gallery_id,
(SELECT url FROM pictures ss WHERE ss.gallery_id = s.gallery_id
ORDER BY RAND() LIMIT 1) AS url
FROM
pictures s) p ON
g.id = p.gallery_id
LEFT OUTER JOIN users u ON
g.user_id = u.id
WHERE
g.active = 1
This query will go out and select a gallery, then it will find any gallery with a picture (if you want to return galleries without a picture, change INNER JOIN to LEFT OUTER JOIN, and you'll be fine). After that, it joins it up with users. Now, of course, this puppy is going to return every frigging gallery for however many users you have (hoorah!). You may want to limit the user in the WHERE clause (e.g.-WHERE u.id = 123). Otherwise, you're going to get more results than you'd expect. That, or do an INNER JOIN on it.

Categories