MySQL - Join tables, retrieve only Max ID - sql

I've seen solutions for something similar on other posts, but I've been having an issue applying it to my specific problem.
Here is my initial join:
SELECT service_note_task, comment_id, comment FROM service_note_task LEFT JOIN service_note_task_comments ON service_note_task.service_note_task_id = service_note_task_comments.service_note_task_id;
Which results in:
+-----------------------------+------------+--------------+
| service_note_task | comment_id | comment |
+-----------------------------+------------+--------------+
| This is service note task 3 | 25 | Comment |
| This is service note task 3 | 26 | Comment Blah |
| This is service note task 3 | 36 | aaa |
| This is service note task 2 | 13 | Awesome comm |
| This is service note task 1 | 12 | Cool Comm |
+-----------------------------+------------+--------------+
But for each service_note_task, I really only need one row representing the comment with the highest comment_id, like this:
+-----------------------------+------------+--------------+
| service_note_task | comment_id | comment |
+-----------------------------+------------+--------------+
| This is service note task 3 | 36 | aaa |
| This is service note task 2 | 13 | Awesome comm |
| This is service note task 1 | 12 | Cool Comm |
+-----------------------------+------------+--------------+
I figure I could use MAX in a sub-select statement to narrow down the results as I want them. How can I incorporate that into my statement to get these results?

For reference, this is known as "groupwise-maximum"
http://dev.mysql.com/doc/refman/5.0/en/example-maximum-column-group-row.html

since you haven't mention the RDBMS you are using, this query below mostly works on many RDBMS (not all)
SELECT a.*, b.* -- select only the columns you want.
FROM service_note_task a
INNER JOIN service_note_task_comments b
ON a.service_note_task_id = b.service_note_task_id
INNER JOIN
(
SELECT service_note_task_id, MAX(commentID) max_ID
FROM service_note_task_comments
GROUP BY service_note_task_id
) c ON b.service_note_task_id = c.service_note_task_id AND
b.commentID = c.max_ID
if your RDBMS supports Analytical Functions, you can use this below,
SELECT a.service_note_task, b.comment_id, b.comment
FROM service_note_task a
INNER JOIN
(
SELECT service_note_task_id, comment_id, comment,
ROW_NUMBER() OVER (PARTITION BY service_note_task_id
ORDER BY comment_id DESC) rn
FROM service_note_task_comments
GROUP BY
) c ON a.service_note_task_id = b.service_note_task_id AND
b.rn = 1

try:
SELECT service_note_task, comment_id, comment
FROM service_note_task SNT1
LEFT JOIN service_note_task_comments ON service_note_task.service_note_task_id = service_note_task_comments.service_note_task_id
WHERE comment_id = (SELECT MAX(comment_id) FROM service_note_task SNT2 WHERE SNT1.service_note_task = SNT2.service_note_task);

SELECT service_note_task, comment_id, comment
FROM service_note_task s LEFT JOIN service_note_task_comments sc
ON s.service_note_task_id = sc.service_note_task_id;
WHERE EXISTS (
SELECT 1
FROM service_note_task_comments s2
WHERE s.service_note_task_id = s2.service_note_task_id
HAVING MAX(s2.comment_id) = sc.comment_id
)

Related

Multiple select from CTE with different number of rows in a StoredProcedure

How to do two select with joins from the cte's which returns total number of columns in the two selects?
I tried doing union but that appends to the same list and there is no way to differentiate for further use.
WITH campus AS
(SELECT DISTINCT CampusName, DistrictName
FROM dbo.file
),creditAcceptance AS
(SELECT CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal, COUNT(id) AS N
FROM dbo.file
WHERE (EligibilityStatusFinal LIKE 'Eligible%') AND (CollegeCreditEarnedFinal = 'Yes') AND (CollegeCreditAcceptedFinal = 'Yes')
GROUP BY CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal
),eligibility AS
(SELECT CampusName, EligibilityStatusFinal, COUNT(id) AS N, CollegeCreditAcceptedFinal
FROM dbo.file
WHERE (EligibilityStatusFinal LIKE 'Eligible%')
GROUP BY CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal
)
SELECT a.CampusName, c.[EligibilityStatusFinal], SUM(c.N) AS creditacceptCount
FROM campus as a FULL OUTER JOIN creditAcceptance as c ON a.CampusName=c.CampusName
WHERE (a.DistrictName = 'xy')
group by a.CampusName ,c.EligibilityStatusFinal
Union ALL
SELECT a.CampusName , b.[EligibilityStatusFinal], SUM(b.N) AS eligible
From Campus as a FULL OUTER JOIN eligibility as b ON a.CampusName = b.CampusName
WHERE (a.DistrictName = 'xy')
group by a.CampusName,b.EligibilityStatusFinal
Expected output:
+------------+------------------------+--------------------+
| CampusName | EligibilityStatusFinal | creditacceptCount |
+------------+------------------------+--------------------+
| M | G | 1 |
| E | NULL | NULL |
| A | G | 4 |
| B | G | 8 |
+------------+------------------------+--------------------+
+------------+------------------------+----------+
| CampusName | EligibilityStatusFinal | eligible |
+------------+------------------------+----------+
| A | G | 8 |
| C | G | 9 |
| A | T | 9 |
+------------+------------------------+----------+
As you can see here CTEs can be used in a single statement only, so you can't get the expected output with CTEs.
Here is an excerpt from Microsoft docs:
A CTE must be followed by a single SELECT, INSERT, UPDATE, or DELETE
statement that references some or all the CTE columns. A CTE can also
be specified in a CREATE VIEW statement as part of the defining SELECT
statement of the view.
You can use table variables (declare #campus table(...)) or temp tables (create table #campus (...)) instead.

Create a flag in a left join

I am trying to create a new column (a sort of identifier flag) for the "Null" rows resulting of my following left join :
with CTE (...) as (
... unrelated code
) select * from CTE
left join (select columnID from table1) Pu
on CTE.columnID = Pu.columnID
left join (select case when bz.column2 is null then 'null test is working' else columnID2, column2 end FROM table2) Bz
ON CTE.columnID2 = Bz.columnID2
This code is working properly when I don't try to use a 'case when'. Actually, you could very well ignore the first left join.
My purpose would be to be able test the left join result while doing it, and act depending on the result :
If the left join result give a null row : creation of a flag column for the row,
If the left join result give a normal row : the left join is done normally, and the flag column is empty (as I suspect it cant be un existent).
I'd be glad if you could give me a hand!
EDIT : tables example:
CTE
| columnID | columnID2 | InformationsCTE |
| ab | mp | randominfo1 |
| ac | ma | randominfo2 |
| ae | me | randominfo3 |
| ad | mb | randominfo4 |
table2
| columnID2 | InformationsTable2 |
| mp | randominfo5 |
| ma | randominfo6 |
| me | randominfo7 |
Result after the second left join :
new CTE
| columnID | columnID2 | InformationsCTE | InformationsTable2| FLAG |
| ab | mp | randominfo1 | randominfo5 | OK |
| ac | ma | randominfo2 | randominfo6 | OK |
| ae | me | randominfo3 | randominfo7 | OK |
| ad | mb | randominfo4 | NULL | NOK |
Just use
T-SQL:
SELECT ISNULL(Column_to_check,'flag') FROM SomeTable
PL/SQL:
SELECT NVL(Column_to_check,'flag') FROM SomeTable
Also use NVL2 as below if you want to return other value from the Column_to_check:
NVL2(Column_to_check, value_if_NOT_null, value_if_null )
Would it not be more practical to SELECT this column, use the ISNULL operator and just use a straightforward LEFT JOIN? I feel like you're over-complicating it a bit.
Something like:
with CTE (...) as (
... unrelated code
)
SELECT CTE.*, NVL(bz.InformationsTable2, 'TEST OK')
FROM CTE
LEFT JOIN table2 Bz ON CTE.columnID2 = Bz.columnID2
EDIT: Based on your example table, if you join on the ID, then use NVL on the other column, it should work for you.
Here is an example I prepared for a previous question: SQL Fiddle
Example was build in mysql, so beware syntax, but logically it works the same way
Why the joins? It seems you only want to look up data in other table, for which you'd use EXISTS or IN:
with cte (...) as (
... unrelated code
)
select
cte.*,
case when columnid in (select columnid from table1) then 'okay' else 'fail' end as test1,
case when columnid2 in (select columnid2 from table2) then 'okay' else 'fail' end as test2
from cte;

Count how many times a value appears in tables SQL

Here's the situation:
So, in my database, a person is "responsible" for job X and "linked" to job Y. What I want is a query that returns: name of person, his ID and he number of jobs it's linked/responsible. So far I got this:
select id_job, count(id_job) number_jobs
from
(
select responsible.id
from responsible
union all
select linked.id
from linked
GROUP BY id
) id_job
GROUP BY id_job
And it returns a table with id in the first column and number of occurrences in the second. Now, what I can't do is associate the name of person to the table. When i put that in the "select" from beginning it gives me all the possible combinations... How can I solve this? Thanks in advance!
Example data and desirable output:
| Person |
id | name
1 | John
2 | Francis
3 | Chuck
4 | Anthony
| Responsible |
process_no | id
100 | 2
200 | 2
300 | 1
400 | 4
| Linked |
process_no | id
101 | 4
201 | 1
301 | 1
401 | 2
OUTPUT:
| OUTPUT |
id | name | number_jobs
1 | John | 3
2 | Francis | 3
3 | Chuck | 0
4 | Anthony | 2
Try this way
select prs.id, prs.name, count(*) from Person prs
join(select process_no, id
from Responsible res
Union all
select process_no, id
from Linked lin ) a on a.id=prs.id
group by prs.id, prs.name
I would recommend aggregating each of the tables by the person and then joining the results back to the person table:
select p.*, coalesce(r.cnt, 0) + coalesce(l.cnt, 0) as numjobs
from person p left join
(select id, count(*) as cnt
from responsible
group by id
) r
on r.id = p.id left join
(select id, count(*) as cnt
from linked
group by id
) l
on l.id = p.id;
select id, name, count(process_no) FROM (
select pr.id, pr.name, res.process_no from Person pr
LEFT JOIN Responsible res on pr.id = res.id
UNION
select pr.id, pr.name, lin.process_no from Person pr
LEFT JOIN Linked lin on pr.id = lin.id) src
group by id, name
order by id
Query ain't tested, give it a shot, but this is the way you want to go

SQL return max value from child for each parent row

I have 2 tables - 1 with parent records, 1 with child records. For each parent record, I'm trying to return a single child record with the MAX(SalesPriceEach).
Additionally I'd like to only return a value when there is more than 1 child record.
parent - SalesTransactions table:
+-------------------+---------+
|SalesTransaction_ID| text |
+-------------------+---------+
| 1 | Blah |
| 2 | Blah2 |
| 3 | Blah3 |
+-------------------+---------+
child - SalesTransactionLines table
+--+-------------------+---------+--------------+
|id|SalesTransaction_ID|StockCode|SalesPriceEach|
+--+-------------------+---------+--------------+
| 1| 1 | 123 | 99 |
| 2| 1 | 35 | 50 |
| 3| 2 | 15 | 75 |
+--+-------------------+---------+--------------+
desired results
+-------------------+---------+--------------+
|SalesTransaction_ID|StockCode|SalesPriceEach|
+-------------------+---------+--------------+
| 1 | 123 | 99 |
| 2 | 15 | 75 |
+-------------------+---------+--------------+
I found a very similar question here, and based my query on the answer but am not seeing the results I expect.
WITH max_feature AS (
SELECT c.StockCode,
c.SalesTransaction_ID,
MAX(c.SalesPriceEach) as feature
FROM SalesTransactionLines c
GROUP BY c.StockCode, c.SalesTransaction_ID)
SELECT p.SalesTransaction_ID,
mf.StockCode,
mf.feature
FROM SalesTransactions p
LEFT JOIN max_feature mf ON mf.SalesTransaction_ID = p.SalesTransaction_ID
The results from this query are returning multiple rows for each parent, and not even the highest value first!
select stl.SalesTransaction_ID, stl.StockCode, ss.MaxSalesPriceEach
from SalesTransactionLines stl
inner join
(
select stl2.SalesTransaction_ID, max(stl2.SalesPriceEach) MaxSalesPriceEach
from SalesTransactionLines stl2
group by stl2.SalesTransaction_ID
having count(*) > 1
) ss on (ss.SalesTransaction_ID = stl.SalesTransaction_ID and
ss.MaxSalesPriceEach = stl.SalesPriceEach)
OR, alternatively:
SELECT stl1.*
FROM SalesTransactionLines AS stl1
LEFT OUTER JOIN SalesTransactionLines AS stl2
ON (stl1.SalesTransaction_ID = stl2.SalesTransaction_ID
AND stl1.SalesPriceEach < stl2.SalesPriceEach)
WHERE stl2.SalesPriceEach IS NULL;
I know I'm a year late to this party but I always prefer using Row_Number in these situations. It solves the problem when there are two rows that meet your Max criteria and makes sure that only one row is returned:
with z as (
select
st.SalesTransaction_ID
,row=ROW_NUMBER() OVER(PARTITION BY st.SalesTransaction_ID ORDER BY stl.SalesPriceEach DESC)
,stl.StockCode
,stl.SalesPriceEach
from
SalesTransactions st
inner join SalesTransactionLines stl on stl.SalesTransaction_ID = st.SalesTransaction_ID
)
select * from z where row = 1
SELECT SalesTransactions.SalesTransaction_ID,
SalesTransactionLines.StockCode,
MAX(SalesTransactionLines.SalesPriceEach)
FROM SalesTransactions RIGHT JOIN SalesTransactionLines
ON SalesTransactions.SalesTransaction_ID = SalesTransactionLines.SalesTransaction_ID
GROUP BY SalesTransactions.SalesTransaction_ID, alesTransactionLines.StockCode;
select a.SalesTransaction_ID, a.StockCode, a.SalesPriceEach
from SalesTransacions as a
inner join (select SalesTransaction_ID, MAX(SalesPriceEach) as SalesPriceEach
from SalesTransactionLines group by SalesTransaction_ID) as b
on a.SalesTransaction_ID = b.SalesTransaction_ID
and a.SalesPriceEach = b.SalesPriceEach
subquery returns table with trans ids and their maximums so just join it with transactions table itself by those 2 values

MySQL query help (involving joins?)

Although I've figured out several queries that almost do this, I can't quite get it perfectly and I'm getting frustrated. Here is the setup:
Table: Issue
| id | name | value |
+-------------------+
| 1 | a | 10 |
| 2 | b | 3 |
| 3 | c | 4 |
| 4 | d | 9 |
Table: Link
| source | dest |
+---------------+
| 1 | 2 |
| 1 | 3 |
The link table sets up a source/dest relationship between rows in the issue table. Yes, I know this is normalized terribly, but I did not create this schema even though I now have to write queries against it :(.
What I want is results that look like this:
| name | value |
+--------------+
| a | 17 |
| d | 9 |
The values in the results should be the sum of the values in the issue table when you aggregate together a source with all its dests along with the name of the source.
Some notes
(1) A source->dest is a one->many relationship.
(2) The best answer will not have any hardcoded id's or names in the query (meaning, it will be generalized for all setups like this).
(3) This is in MySQL
Thank you and let me know if I should include any more information
Its fairly simple, but the stickler is the fact that A is not a destination of A yet it is included in the table. The robust solution would involve modifying the data to add
Table: Link
| source | dest |
+---------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
Then a simple
SELECT a.name, SUM(d.value) FROM
Issues as a
JOIN Link as b on a.id=b.source
JOIN Issues AS d on b.dest=d.id;
GROUP BY a.name;
If you can't modify the data.
SELECT a.name, SUM(d.value)+a.value FROM
Issues as a
JOIN Link as b on a.id=b.source
JOIN Issues AS d on b.dest=d.id;
GROUP BY a.name,a.value;
MAY work.
SELECT S.name, S.value + SUM(D.value) as value
FROM Link AS L
LEFT JOIN Issue AS S ON L.source = S.id
LEFT JOIN Issue AS D ON L.dest = D.id
GROUP BY S.name
You could use a double join to find all linked rows, and add the sum to the value of the source row itself:
select src.name, src.value + sum(dest.value)
from Issue src
left join Link l
on l.source = src.id
left join Link dest
on dest.id = l.dest
group by src.name, src.value
This one should return the SUM of both source and dests, and only return items which are source.
SELECT s.name, COALESCE( SUM(d.value), 0 ) + s.value value
FROM Issue s
LEFT JOIN Link l ON ( l.source = s.id )
LEFT JOIN Issue d ON ( d.id = l.dest )
WHERE s.id NOT IN ( SELECT dest FROM Link )
GROUP BY s.name, s.value
ORDER BY s.name;