SQL result addition - mysql

I have database with table :
Loan
Document
DocumentTemplate
Document is having loanId and documentTeplateId as foreign key.
Every loan has multiple documents.
I want to select every loan (or loanId), that don't have document with teplateId 100.
Currently I'm stuck with this following sql:
SELECT l.id as loanId, d.id as documentId, d.document_templateid as documentTeplateId
FROM loan as l
LEFT JOIN document as d ON (d.loanid = l.id)
WHERE d.document_templateid != 100
ORDER BY loanId DESC
It returns me something like this, obviously..
But that's not what I want.
Any suggestions please?

You need only the distinct loan ids:
SELECT distinct l.id as loanid
FROM loan as l
LEFT JOIN document as d
ON (d.loanid = l.id)
WHERE d.document_templateid != 100
ORDER BY loanId desc

SELECT l.id as loanId
FROM loan as l
LEFT JOIN document as d
ON (d.loanid = l.id)
WHERE d.document_templateid != 100
ORDER BY loanId DESC
GROUP BY loanId
"GROUP BY loanId" will group rows that have the same loanId into one row, removing the duplicates. You can only select loan.id for this to work, which fits well for your scenario since you seem to indicate this is the only value you need.

I think you want aggregation and a having` clause:
SELECT l.id as loanid
FROM loan l LEFT JOIN
document d
ON d.loanid = l.id
GROPU BY l.id
HAVING SUM( d.document_templateid = 100 ) = 0;
If you only want to consider loans that have documents, then the JOIN is not needed:
SELECT d.loanid
FROM document d
GROPU BY d.loanid
HAVING SUM( d.document_templateid = 100 ) = 0;

Related

Select highest value per group and associated row from another table, ordered by highest value

I'm trying to get these values from two tables, joining on member id. In the table with the points values, there are running total rows for each sale by each member. I need the member's point total associated with their row in the members table, sorted by value descending. This is my current query, it returns unique values unless a member has two identical entries.
SELECT m.id
, m.fname
, m.lname
, p.points_total
FROM
( SELECT s.member_id
, MAX(s.points_total) points_total
FROM sale_sale s
GROUP
BY s.member_id
) p
JOIN sale_sale x
ON x.member_id = p.member_id
AND x.points_total = p.points_total
JOIN member_member m
ON m.id = p.member_id
WHERE x.site_id = 1
AND m.fname != "Sales"
ORDER
BY p.points_total DESC;
A simple JOIN and GROUP BY would likely do what you're asking for:
SELECT
m.id,
m.fname,
m.lname,
COALESCE(MAX(s.points_total), 0) AS points_total
FROM member_member AS m
LEFT JOIN sale_sale AS s
ON m.id = s.member_id
AND s.site_id = 1
WHERE m.fname != "Sales"
GROUP BY m.id
ORDER BY points_total DESC;
EDIT: Made it a LEFT JOIN with COALESCE(points_total, 0) to allow for members who have no sales totals to show in the results. If you don't want this, you could change LEFT JOIN to INNER JOIN and eliminate the COALESCE function.

How to limit record before group by for pagination?

I have this query that will LEFT JOIN and GROUP BY to get SUM of column.
SELECT
c.id,
SUM(
r.score
) AS score_sum,
SUM(
CASE WHEN r.is_active = '0' THEN r.negative ELSE 0 END
) AS negative_sum
FROM comments AS c
LEFT JOIN rates AS r ON (r.comment_id = c.id)
WHERE r.comment_id = c.id
GROUP BY c.id
DB Fiddle link:
https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=fadba795d8426f91471fa4db83845b6f
The query works, but if the comments records is large (10K for example), I need to implement pagination, how do I modify this query to limit the comments records first before GROUP BY?
In short:
Get the first 5 comments by limit to 5
Left join the table rates
Get the SUM by group by
Example, show the first 4 comments SUM
Thanks
You can use subquery to "select c.id from comments limit N" in the FROM clause.
select c.id,
sum(r.score) as score_sum,
SUM(
CASE WHEN r.is_active = '0' THEN r.negative ELSE 0 END
) AS negative_sum
from ( select c.id from comments c limit 2) c
LEFT JOIN rates AS r ON (r.comment_id = c.id)
GROUP BY c.id;
You may apply order by in the subquery to determine order in which you want to select the comments (Top N).
DB Fiddle link
Try the following:
SELECT
c.id,
SUM(
r.score
) AS score_sum,
SUM(
CASE WHEN r.is_active = '0' THEN r.negative ELSE 0 END
) AS negative_sum
FROM comments AS c
LEFT JOIN rates AS r ON (r.comment_id = c.id)
WHERE r.comment_id = c.id
GROUP BY c.id
ORDER BY c.id ASC
LIMIT 5
The rationale behind the above query is that id is the Primary key (hence indexed) in your comments table. Also, your GROUP BY and ORDER BY is on the same column, that is, id; so MySQL will first utilize the index on id and get first 5 rows (due to LIMIT), and then proceed forward to JOIN with other tables and do aggregation etc.
Give it a Try!! More details here: https://dev.mysql.com/doc/refman/5.7/en/order-by-optimization.html
We can confirm the same using EXPLAIN .. on this query.

MySQL LEFT JOIN only one row, ordered by column without subquery

Is there a possibility to do LEFT JOIN with only one row from other table ordered by column (date) without using sub query. My query is below. It works but it's super slow.
SELECT * FROM clients c
LEFT JOIN loan l ON c.id = l.id_client AND l.id = (
SELECT id FROM loan ll
WHERE ll.id_client = c.id
ORDER BY `create_date` DESC
LIMIT 1)
GROUP BY k.id DESC
ORDER BY c.register_date DESC
LIMIT n , m; (n,m is from pagination)
Is there a way to speed it up?
Im interpreting your question as "Get me all loan details for the most recent loan for each client"
This should work... note the assumption though.
SELECT *
FROM
clients c
LEFT JOIN (select id_client, Max(id) id -- this assumes that a loan with a later create date will also have a higher id.
from loan
group by id_client) il
on il.id_client = c.id
inner join loan l
on l.id = il.id
GROUP BY k.id DESC -- Dont know what "k" is
ORDER BY c.register_date DESC
LIMIT n , m; (n,m is from pagination)

mySQL Sub Select needed

I have three tables, libraryitems, copies and loans.
A libraryitem hasMany copies, and a copy hasMany loans.
I'm trying to get the latest loan entry for a copy only; The query below returns all loans for a given copy.
SELECT
libraryitems.title,
copies.id,
copies.qruuid,
loans.id AS loanid,
loans.status,
loans.byname,
loans.byemail,
loans.createdAt
FROM copies
INNER JOIN libraryitems ON copies.libraryitemid = libraryitems.id AND libraryitems.deletedAt IS NULL
LEFT OUTER JOIN loans ON copies.id = loans.copyid
WHERE copies.libraryitemid = 1
ORDER BY copies.id ASC, loans.createdAt DESC
I know there needs to be a sub select of some description in here, but struggling to get the correct syntax. How do I only return the latest, i.e MAX(loans.createdAt) row for each distinct copy? Just using group by copies.id returns the earliest, rather than latest entry.
Image example below:
in the subquery , getting maximum created time for a loan i.e. latest entry and joining back with loans to get other details.
SELECT
T.title,
T.id,
T.qruuid,
loans.id AS loanid,
loans.status,
loans.byname,
loans.byemail,
loans.createdAt
FROM
(
SELECT C.id, C.qruuid, L.title, MAX(LN.createdAt) as maxCreatedTime
FROM Copies C
INNER JOIN libraryitems L ON C.libraryitemid = L.id
AND L.deletedAt IS NULL
LEFT OUTER JOIN loans LN ON C.id = LN.copyid
GROUP BY C.id, C.qruuid, L.title) T
JOIN loans ON T.id = loans.copyid
AND T.maxCreatedTime = loans.createdAt
A self left join on loans table will give you latest loan of a copy, you may join the query to the other tables to fetch the desired output.
select * from loans A
left outer join loans B
on A.copyid = B.copyid and A.createdAt < B.createdAt
where B.createdAt is null;
This is your query with one simple modification -- table aliases to make it clearer.
SELECT li.title, c.id, c.qruuid,
l.id AS loanid, l.status, l.byname, l.byemail, l.createdAt
FROM copies c INNER JOIN
libraryitems li
ON c.libraryitemid = li.id AND
li.deletedAt IS NULL LEFT JOIN
loans l
ON c.id = l.copyid
WHERE c.libraryitemid = 1
ORDER BY c.id ASC, l.createdAt DESC ;
With this as a beginning let's think about what you need. You want the load with the latest createdAt date for each c.id. You can get this information with a subquery:
select l.copyid, max(createdAt)
from loans
group by l.copyId
Now, you just need to join this information back in:
SELECT li.title, c.id, c.qruuid,
l.id AS loanid, l.status, l.byname, l.byemail, l.createdAt
FROM copies c INNER JOIN
libraryitems li
ON c.libraryitemid = li.id AND
li.deletedAt IS NULL LEFT JOIN
loans l
ON c.id = l.copyid LEFT JOIN
(SELECT l.copyid, max(l.createdAt) as maxca
FROM loans
GROUP BY l.copyid
) lmax
ON l.copyId = lmax.copyId and l.createdAt = lmax.maxca
WHERE c.libraryitemid = 1
ORDER BY c.id ASC, l.createdAt DESC ;
This should give you the most recent record. And, the use of left join should keep all copies, even those that have never been leant.

How can I get the sum of a column ?

I have 3 tables: activites, taks and requirements. I want to return all of the duration of all the tasks for a specific requirement. This is my query:
SELECT r.id as req_id,
r.project_id,
r.name as req_name,
r.cost,r.estimated,
p.name as project_name,
v.name AS `status` ,
t.taskid,
(SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(duration)))
FROM activities a
WHERE a.taskid = t.taskid) AS worked
FROM requirements r
INNER JOIN projects p
ON p.projectid = r.project_id
INNER JOIN `values` v
ON v.id = r.r_status_id
LEFT JOIN tasks t
on t.id_requirement = r.id
WHERE 1 = 1
ORDER BY req_id desc
And this is the result :
As you can see there are 2 same req_id (48) . I want to appear one time and get the sum of the last two rows in worked. How can I manage that ?
this is the activities structure :
this is tasks structure :
and this is the requirement structure :
Include your activities table in the JOIN, GROUP by all requirement columns you need and add a sum. Since you are aggregating tasks, you cannot have taskid in the SELECT clause.
SELECT r.id as req_id,
r.project_id,
r.name as req_name,
r.cost,r.estimated,
p.name as project_name,
v.name AS `status` ,
SEC_TO_TIME(SUM(TIME_TO_SEC(a.duration)))
FROM requirements r
INNER JOIN projects p ON p.projectid = r.project_id
INNER JOIN `values` v ON v.id = r.r_status_id
LEFT JOIN tasks t ON t.id_requirement = r.id
LEFT JOIN activities a ON a.taskid=t.taskid
WHERE 1 = 1
GROUP BY r.id, r.project_id, r.name,r.cost,r.estimated,p.name, v.name
ORDER BY req_id desc
The joins in your query appear to be creating extra rows. I'm sure there is a way to fix the logic directly, possibly by pre-aggregating some results in the from clause.
Your duplicates appear to be complete duplicates (every column is exactly the same). The easy way to fix the problem is to use select distinct. So, just start your query with:
SELECT DISTINCT r.id as req_id, r.project_id, r.name as req_name,
. . .
I suspect that one of your underlying tables has duplicated rows that you are not expecting, but that is another issue.