SQL - Using Subquery with Aggregate Function - sql

I'm trying to return the last time entry posted for a particular client, and the case (matter) number associated to that entry. The relationship is one client has many matters, and one matter has many time entries.
I have the code below, but it obviously returns all the matters and not just the one associated to the time entry. I understand why, but tie myself in knots when trying to correct it. Any help much appreciated.
select c.CLIENT_CODE,
c.CLIENT_NAME,
c.OPEN_DATE,
mp.EMPLOYEE_NAME,
MAX(tt.TRAN_DATE)[Last Time],
m.MATTER_NUMBER
from HBM_CLIENT c
join HBM_MATTER m
on m.CLIENT_UNO=c.CLIENT_UNO
left join TAT_TIME tt
on tt.MATTER_UNO=m.MATTER_UNO
left join HBM_PERSNL mp
on mp.EMPL_UNO=c.RESP_EMPL_UNO
where c.STATUS_CODE = 'Targ'
group by c.CLIENT_CODE,
c.CLIENT_NAME,
c.OPEN_DATE,
mp.EMPLOYEE_NAME,
m.MATTER_NUMBER
order by OPEN_DATE

Completely untested but in the right direction
select
<whatever>
from
HBM_CLIENT c
join HBM_MATTER m on
m.CLIENT_UNO = c.CLIENT_UNO
join TAT_TIME tt on
tt.MATTER_UNO = m.MATTER_UNO AND
tt.tran_date = (
select max(tran_date)
from TAT_TIME
where matter_uno = m.matter_uno)
where
m.CLIENT_UNO = ? and
c.STATUS_CODE = 'Targ'

One way to do this is using row_number(). I think the following will do what you want:
select c.CLIENT_CODE, c.CLIENT_NAME, c.OPEN_DATE, mp.EMPLOYEE_NAME,
tt.TRAN_DATE as [Last Time], m.MATTER_NUMBER
from HBM_CLIENT c join
(select m.*, tt.TRAN_DATE,
row_number() over (partition by m.CLIENT_UNO
order by tt.TRAN_DATE desc
) as seqnum
from HBM_MATTER m LEFT JOIN
TAT_TIME tt
ON tt.MATTER_UNO = m.MATTER_UNO
) m
ON m.CLIENT_UNO = c.CLIENT_UNO and seqnum = 1 left join
HBM_PERSNL mp
on mp.EMPL_UNO=c.RESP_EMPL_UNO
where c.STATUS_CODE = 'Targ';
I don't think you need the group by, unless the other joins create duplicates.

Related

Why am I getting ORA-00979: not a GROUP BY expression error?

Just a disclaimer, I'm very new to SQL... so please go easy on me. This is the script I have so far, and for the most part it's working, I'm just having a problem trying to only get the most recent DTTM record. What's wrong with my group by expression?
select
i.item_name,
i.description,
MAX(w.LAST_UPDATED_DTTM)as MOST_RECENT_DTTM,
w.last_updated_source,
w.tc_lpn_id,
lh1.dsp_locn as from_locn,
l.dsp_locn,
lh2.dsp_locn as dest_locn,
lp.inventory_lock_code
from wm_inventory W
left join Locn_hdr l
on w.location_id = L.locn_id
left join item_cbo i
on w.item_id = i.item_id
left join lpn_lock lp
on lp.tc_lpn_id = w.tc_lpn_id
left join task_dtl td
on td.cntr_nbr = w.tc_lpn_id
left join locn_hdr lh2
on td.dest_locn_id = lh2.locn_id
left join locn_hdr lh1
on td.PULL_LOCN_ID = lh1.locn_id
where i.color_desc = '00607'
and l.DSP_LOCN like '%SRT-0%'
and w.tc_lpn_id not like '%J%'
group by i.item_name, i.description, w.last_updated_source, w.tc_lpn_id, l.dsp_locn, lp.inventory_lock_code, lh1.dsp_locn, lh2.dsp_locn
order by w.last_updated_dttm asc;
To order by a column that's aggregated, you have to specify the aggregation:
order by MAX(w.last_updated_dttm) asc
Some implementations (including Oracle, you gave an Oracle error code) allow you to order by the aggregated column's alias:
order by MOST_RECENT_DTTM asc

How to create distinct count from queries with several tables

I am trying to create one single query that will give me a distinct count for both the ActivityID and the CommentID. My query in MS Access looks like this:
SELECT
tbl_Category.Category, Count(tbl_Activity.ActivityID) AS CountOfActivityID,
Count(tbl_Comments.CommentID) AS CountOfCommentID
FROM tbl_Category LEFT JOIN
(tbl_Activity LEFT JOIN tbl_Comments ON
tbl_Activity.ActivityID = tbl_Comments.ActivityID) ON
tbl_Category.CategoryID = tbl_Activity.CategoryID
WHERE
(((tbl_Activity.UnitID)=5) AND ((tbl_Comments.PeriodID)=1))
GROUP BY
tbl_Category.Category;
I know the answer must somehow include SELECT DISTINCT but am not able to get it to work. Do I need to create multiple subqueries?
This is really painful in MS Access. I think the following does what you want to do:
SELECT ac.Category, ac.num_activities, aco.num_comments
FROM (SELECT ca.category, COUNT(*) as num_activities
FROM (SELECT DISTINCT c.Category, a.ActivityID
FROM (tbl_Category as c INNER JOIN
tbl_Activity as a
ON c.CategoryID = a.CategoryID
) INNER JOIN
tbl_Comments as co
ON a.ActivityID = co.ActivityID
WHERE a.UnitID = 5 AND co.PeriodID = 1
) as caa
GROUP BY ca.category
) as ca LEFT JOIN
(SELECT c.Category, COUNT(*) as num_comments
FROM (SELECT DISTINCT c.Category, co.CommentId
FROM (tbl_Category as c INNER JOIN
tbl_Activity as a
ON c.CategoryID = a.CategoryID
) INNER JOIN
tbl_Comments as co
ON a.ActivityID = co.ActivityID
WHERE a.UnitID = 5 AND co.PeriodID = 1
) as aco
GROUP BY c.Category
) as aco
ON aco.CommentId = ac.CommentId
Note that your LEFT JOINs are superfluous because the WHERE clause turns them into INNER JOINs. This adjusts the logic for that purpose. The filtering is also very tricky, because it uses both tables, requiring that both subqueries have both JOINs.
You can use DISTINCT:
SELECT
tbl_Category.Category, Count(DISTINCT tbl_Activity.ActivityID) AS CountOfActivityID,
Count(DISTINCT tbl_Comments.CommentID) AS CountOfCommentID
FROM tbl_Category LEFT JOIN
(tbl_Activity LEFT JOIN tbl_Comments ON
tbl_Activity.ActivityID = tbl_Comments.ActivityID) ON
tbl_Category.CategoryID = tbl_Activity.CategoryID
WHERE
(((tbl_Activity.UnitID)=5) AND ((tbl_Comments.PeriodID)=1))
GROUP BY
tbl_Category.Category;

Nesting queries on JOINS and top1 with ties

I am trying to use the result of the below SQL query-1 such that I can make another JOIN on this with my second query result to retrieve Fundsrc on the common ID - Project.
QUERY 1-
SELECT top 1 with ties
t.project, r.rel_value AS "FundSrc" ,r.date_to
from atsproject t
LEFT OUTER JOIN aglrelvalue r ON(t.client=r.client AND r.rel_attr_id='ZB18' AND r.attribute_id='B0' AND t.project=r.att_value)
WHERE r.date_To > '04/30/2020' and status='n'
ORDER BY row_number() over (partition by t.project order by t.project, r.rel_value)
I cannot put the JOIN inside the above query as it will mess with the result. Instead, if I can do a nesting on this then I think that should solve the issue.
My second query is -
SELECT
t.project,t.work_order as activity, r1.labor_funding_source2_fx AS "Designated Labour Funding"
FROM atsworkorder t
LEFT OUTER JOIN afxactlaborfund r1 ON( t.work_order = r1.dim_value AND t.client = r1.client AND r1.attribute_id = 'BF')
WHERE t.client='PC' and t.status = 'N'
The Output should be -
t.project,t.work_order from query 2 + Fundsrc from Query 1, with the common id on Project ID.
Any suggestions on this is highly appreciated.
You can wrap 'subqueries' in parenthesis and then join them.
Can you try this?:
SELECT *
FROM (
SELECT top 1 with ties t.project,
r.rel_value AS "FundSrc",
r.date_to
FROM atsproject t
LEFT OUTER JOIN aglrelvalue r
ON t.client=r.client
AND r.rel_attr_id='ZB18'
AND r.attribute_id='B0'
AND t.project=r.att_value
WHERE r.date_To > '04/30/2020' and status='n'
ORDER BY row_number() over (partition by t.project order by t.project, r.rel_value)
) AS TABLE_1
LEFT JOIN
(
SELECT t.project,
t.work_order as activity,
r1.labor_funding_source2_fx AS "Designated Labour Funding"
FROM atsworkorder t
LEFT OUTER JOIN afxactlaborfund r1
ON t.work_order = r1.dim_value
AND t.client = r1.client
AND r1.attribute_id = 'BF'
WHERE t.client='PC' and t.status = 'N'
) AS TABLE_2
ON TABLE_1.PROJECT = TABLE2.PROJECT
I am pretty sure an ORDER BY clause will not work within a subquery. Thus, this should probably work:
SELECT *
FROM (
SELECT t.project,
r.rel_value AS "FundSrc",
r.date_to,
row_number() over (partition by t.project order by t.project, r.rel_value) AS MY_RANKING
FROM atsproject t
LEFT OUTER JOIN aglrelvalue r
ON t.client=r.client
AND r.rel_attr_id='ZB18'
AND r.attribute_id='B0'
AND t.project=r.att_value
WHERE r.date_To > '04/30/2020' and status='n'
) AS TABLE_1
LEFT JOIN
(
SELECT t.project,
t.work_order as activity,
r1.labor_funding_source2_fx AS "Designated Labour Funding"
FROM atsworkorder t
LEFT OUTER JOIN afxactlaborfund r1
ON t.work_order = r1.dim_value
AND t.client = r1.client
AND r1.attribute_id = 'BF'
WHERE t.client='PC' and t.status = 'N'
) AS TABLE_2
ON TABLE_1.PROJECT = TABLE2.PROJECT
WHERE TABLE_1.MY_RANKING = 1
Note: On your formatting, wrap words within ` when they refer to code. They will look like this.
Wrap blocks of code within three of those (three at the beginning and at the end). It will look like the blocks of code above.

Limitting results in association

I want to limit the results in a lateral join, so that it only returns the N most recent matches.
This is my query, but the limit inside the join does not seem to work, as it returns all visitors
select am.id, am.title, ame.event, array_agg(row_to_json(visitors))
from auto_messages am
left join apps a on am.app_id = a.id
left join app_users au on a.id = au.app_id
left join auto_message_events ame on ame.auto_message_id = am.id
left join lateral (
select
id,
name,
avatar,
ame.inserted_at
from visitors v
where v.id = ame.visitor_id
order by ame.inserted_at desc
limit 1
) as visitors on visitors.id = ame.visitor_id
where am.id = '100'
group by am.id, ame.event
I am pretty sure the problem is with ame. That is where the rows are generated. The join to visitors is only picking up additional information.
So, this might solve your problem:
select am.id, am.title, visitors.event, array_agg(row_to_json(visitors))
from auto_messages am left join
apps a
on am.app_id = a.id left join
app_users au
on a.id = au.app_id left join lateral
(select v.id, v.name, v.avatar,
ame.event, ame.inserted_at, ame.auto_message_id
from auto_message_events ame join
visitors v
on v.id = ame.visitor_id
order by ame.inserted_at desc
limit 1
) visitors
on visitors.auto_message_id = am.id
where am.id = '100'
group by am.id, visitors.event;
You also might want to change your select clause, if you only want a subset of columns.

Limiting result sets by future date - SQL

The Query below produces a record for each Entry in the SP_ScheduleEvent Table.
SELECT m.MaterialId, m.MaterialTitle, se.EventDateTime, c.ChannelName
FROM GB_Material m
LEFT OUTER JOIN SP_ScheduleEvent se on se.MaterialName = m.MaterialName
INNER JOIN SP_Schedule s on s.ScheduleID = se.ScheduleID
INNER JOIN GB_Channel c on c.ChannelID = s.ChannelID
WHERE LOWER(m.MaterialName) like '%foo%' OR LOWER(m.MaterialTitle) like '%foo%'
I want to limit the result set by the nearest future EventDateTime.
So per material name i would like to see one EventDateTime, which should be the nearest future date to the current time.
And lastly, a record may not exist in the SP_ScheduleEvent table for a particular materialname, in which case there should be null returned for the EventDateTime column
SQLFiddle
How would i go about doing this?
First, your LEFT JOIN is immaterial, because the subsequent joins make it an INNER JOIN. Either use LEFT JOIN throughout the FROM statement or switch to INNER JOIN.
I think you can use ROW_NUMBER():
SELECT t.*
FROM (SELECT m.MaterialId, m.MaterialName, m.MaterialTitle, se.EventDateTime,
ROW_NUMBER() over (PARTITION BY m.MaterialId OVER se.EventDateTime DESC) as seqnum
FROM GB_Material m INNER JOIN
SP_ScheduleEvent se
on se.MaterialName = m.MaterialName INNER JOIN
SP_Schedule s
on s.ScheduleID = se.ScheduleID INNER JOIN
GB_Channel c
on c.ChannelID = s.ChannelID
WHERE se.EventDateTime > getdate() AND
(LOWER(m.MaterialName) like '%foo%' OR LOWER(m.MaterialTitle) like '%foo%')
) t
WHERE seqnum = 1
ORDER BY se.EventDateTime;
Use the ROW_NUMBER() function:
WITH cte AS (
SELECT m.MaterialId, m.MaterialTitle, se.EventDateTime, c.ChannelName,
ROW_NUMBER() OVER (PARTITION BY m.MaterialId ORDER BY EventDateTime ASC) AS rn
FROM GB_Material m
LEFT OUTER JOIN SP_ScheduleEvent se on se.MaterialName = m.MaterialName
LEFT OUTER JOIN SP_Schedule s on s.ScheduleID = se.ScheduleID
LEFT OUTER JOIN GB_Channel c on c.ChannelID = s.ChannelID
WHERE LOWER(m.MaterialName) like '%foo%' OR LOWER(m.MaterialTitle) like '%foo%'
AND se.EventDateTime > GETDATE()
)
SELECT * FROM cte
WHERE rn=1