Select 0 sum for empty years - sql

this seems to be a very common question and yet I can't seem to find an answer that easily transitions into my issue. Typically people are looking for a 0 for missing months, mine is with years. I'm just looking for each product that has been sold in the last 10 years to show for those 10 years the amount or a 0 if none.
My code that shows years where there have been sales:
select upper(_producttype) as _product, YEAR(_date) as _year,
coalesce(SUM(s._quantity * pu._usdperunit),0) as _profit
from QualityControl.dbo._Shipment s
join QualityControl.dbo._ProductUnits pu
on pu._prodid = s._productid
where year(GETDATE())-YEAR(_date) < 11
and upper(_producttype) != 'EQUIPMENT'
group by YEAR(_date), _producttype
order by _producttype, YEAR(_date)
My query that gives me a list of the last 10 years (+ current):
select distinct YEAR(_date) as _year
from QualityControl.dbo._Shipment s
where year(GETDATE())-YEAR(_date) < 11
and _producttype != 'Equipment'
order by YEAR(_date)
I have tried a union with _profit being 0. I have tried a cross join, but seemed to be getting too many columns at that point. Any help at this point would be much appreciated.
Thank you.

Without changing your queries you can do following with them.
Just put your grouped data in one CTE
years in another
create third subset that's combination of distinct products and years
LEFT JOIN between combinations and your data
.
WITH CTE_Data AS
(
select upper(_producttype) as _product, YEAR(_date) as _year,
coalesce(SUM(s._quantity * pu._usdperunit),0) as _profit
from QualityControl.dbo._Shipment s
join QualityControl.dbo._ProductUnits pu
on pu._prodid = s._productid
where year(GETDATE())-YEAR(_date) < 11
and upper(_producttype) != 'EQUIPMENT'
group by YEAR(_date), _producttype
)
, CTE_Years AS
(
select distinct YEAR(_date) as _year
from QualityControl.dbo._Shipment s
where year(GETDATE())-YEAR(_date) < 11
and _producttype != 'Equipment'
)
, CTE_AllCombo AS
(
SELECT DISTINCT y._year, d._Product
FROM CTE_Years y
CROSS JOIN CTE_Data d
)
SELECT c._year, c._product, COALESCE(d._profit, 0) AS _profit
FROM CTE_AllCombo c
LEFT JOIN CTE_Data d ON d._year = c._year and d._product = c._product

Related

SQL Query to retrieve members who didn't make any payment for the past six months

I have tried to create a query to retrieve members who didn't complete a payment in the past six months. I have two tables, one for the members' details and the other for payment management. I have tried the below code but it doesn't work perfectly.
SELECT *
FROM tbl_members e
LEFT OUTER JOIN tbl_paymgt s on e.MemberID=s.memberID
WHERE (((DATEDIFF(NOW(), s.paidDate)<365)
AND (DATEDIFF(NOW(), s.paidDate)>180)))
AND (e.MembershipStatus=1);
This code only retrieves payments completed within the past six months.
Paraphrased; select all customers that are currently members And have no payments in the last 6 months...
Fixing your query directly, that gives...
SELECT
*
FROM
tbl_members e
LEFT OUTER JOIN
tbl_paymgt s
ON e.MemberID=s.memberID
AND s.PaidDate > NOW() - INTERVAL 6 MONTH
WHERE
e.membershipStatus = 1
AND s.MemberID IS NULL
But, using NOT EXISTS() is more readable and often less costly to run.
SELECT
*
FROM
tbl_members m
WHERE
MembershipStatus = 1
AND
NOT EXISTS (
SELECT *
FROM tbl_paymgt p
WHERE p.PaidDate > NOW() - INTERVAL 6 MONTH
AND p.MemberID = m.MemberID
);
I'd recommend using a CTE to capture the members that have made a payment in the past 6 months and then select members who are not in that CTE result set.
something like:
with payers as (
select MemberID
from tbl_paymgt
where (DATEDIFF(NOW(), paidDate) < 180)
)
Select *
from tbl_members
where memberID not in (select memberID from payers)
and (MembershipStatus=1)
Here is an alternative way to use keyword not exists to exclude the members who completed a payment in the past six months.
SELECT *
FROM tbl_members m
WHERE MembershipStatus = 1
AND NOT EXISTS (
SELECT 1
FROM tbl_paymgt p
WHERE DATEDIFF(NOW(), p.paidDate) < 180
AND p.MemberID = m.MemberID);

querying over multiple data from function in PostgreSQL

I have a function which gives stock levels:
select * from stocklevel(ID)
This function gives stock leverl per ID.
For example:
select * from stocklevel(23)
Result is:
ID datelevel stocklevel
23 01.01.17 15
23 02.01.17 18
23 05.01.17 20
This is the stock level for ID=23.
Not all dates appear, it depends on many things.
I want to use this function to get the stock levels for all parts from my system
basically something like this:
select *,(select stocklevel from stocklevel(parts.partid) where datelevel = ??? )
from parts
OR:
select *
from parts
left join ( select stocklevel from stocklevel(parts.partid) where datelevel = ??? ) using (ID)
Now, the issue is with the WHERE condition I want to specific a specific date like '04.01.17' but if the date does not exist i want it to take the date before that so:
for the ID=23 and date '04.01.17' it should show 18,'02.01.17' .
for the ID=23 and date '15.01.13' it should show nothing.
for the ID=23 and date '05.01.17' it should show 20,'05.01.17' .
How can I do that?
First, I would use a lateral join:
select *
from parts p left join lateral
stocklevel(p.partid);
Then the issue is that you want the most recent level before or on a given date. You can do this using distinct on:
select distinct on (p.id) . . .
from parts p left join lateral
stocklevel(p.partid)
where datelevel <= ?
order by p.id, datelevel desc;
Note: This will not return parts that have no dates before the given date. You might want:
where datelevel is null or datelevel <= ?
select *
from parts
left join ( select stocklevel
from stocklevel(parts.partid)
where datelevel = (select max(datelevel)
from stocklevel(parts.partid) sl2
where sl2.datelevel <= '04.01.17') using(ID) ) using (ID)
untested...

Why am I getting an invalid column reference 's' error with this query?

Something is likely wrong with the inner join here, since the two queries I'm joining are fine if run separately, but I can't figure out what... :( I'm sorry for what's probably an easy question for most of you here!
I tried not referencing the s as users, but I still get the invalid column reference error...
SELECT time_spent_bucket, totalrev
FROM
(
SELECT session_aggregate.app_timespent AS time_spent_bucket, COUNT(*) AS users
FROM
(
SELECT session_info.s,
case when SUM(session_info.session_length)/60 > 200 then "200+"
when SUM(session_info.session_length)/60 >= 100 then "100 <-> 200"
when SUM(session_info.session_length)/60 >= 50 then "50 <-> 99"
when SUM(session_info.session_length)/60 >= 20 then "20 <-> 49"
when SUM(session_info.session_length)/60 >= 10 then "10 <-> 19"
when SUM(session_info.session_length)/60 >= 5 then "5 <-> 9"
else "<5" end AS app_timespent
FROM
(
SELECT kt_session(calc_session.s, calc_session.evt_lst, 5) AS (s, session_number, session_length)
FROM
(
SELECT session_set.s, collect_set(session_set.timestamps) evt_lst
FROM
(
SELECT total_list.s, total_list.timestamps
FROM
(
SELECT s, utc_timestamp AS timestamps
FROM appl9_evt
WHERE month = 201512
and s is not null
UNION ALL
SELECT s, utc_timestamp AS timestamps
FROM appl9_evt
WHERE month = 201512
and s is not null
) total_list
)session_set
GROUP BY session_set.s
) calc_session
ORDER BY s,session_number DESC
)session_info
GROUP BY session_info.s
)session_aggregate
GROUP BY session_aggregate.app_timespent
ORDER BY time_spent_bucket) ts
INNER JOIN
(
SELECT s, v
FROM appl9_mtu
WHERE month = "201507"
GROUP BY s, v
) totalrev
ON totalrev.s = ts.s
Your join references totalrev.s, but you aliased that column to 'users' in the totalrev subquery. Just change your join to reference the users column like:
ON totalrev.users = ts.s
You could also not alias the s column in the totalrev subquery.
In addition to the above, your ts subquery also does not have an 's' column, so there is no ts.s to join on. You need to include this in the ts subquery selection (and also the group by), something like:
SELECT session_aggregate.s, session_aggregate.app_timespent AS time_spent_bucket, COUNT(*) AS users
...
GROUP BY session_aggregate.s, session_aggregate.app_timespent

ORA-00904 "invalid identifier" but identifier exists in query

I'm working in a fault-reporting Oracle database, trying to get fault information out of it.
The main table I'm querying is Incident, which includes incident information. Each record in Incident may have any number of records in the WorkOrder table (or none) and each record in WorkOrder may have any number of records in the WorkLog table (or none).
What I am trying to do at this point is, for each record in Incident, find the WorkLog with the minimum value in the field MXRONSITE, and, for that worklog, return the MXRONSITE time and the REPORTDATE from the work order. I accomplished this using a MIN subquery, but it turned out that several worklogs could have the same MXRONSITE time, so I was pulling back more records than I wanted. I tried to create a subsubquery for it, but it now says I have an invalid identifier (ORA-00904) for WOL1.WONUM in the WHERE line, even though that identifier is in use elsewhere.
Any help is appreciated. Note that there is other stuff in the query, but the rest of the query works in isolation, and this but doesn't work in the full query or on its own.
SELECT
WL1.MXRONSITE as "Date_First_Onsite",
WOL1.REPORTDATE as "Date_First_Onsite_Notified"
FROM Maximo.Incident
LEFT JOIN (Maximo.WorkOrder WOL1
LEFT JOIN Maximo.Worklog WL1
ON WL1.RECORDKEY = WOL1.WONUM)
ON WOL1.ORIGRECORDID = Incident.TICKETID
AND WOL1.ORIGRECORDCLASS = 'INCIDENT'
WHERE (WL1.WORKLOGID IN
(SELECT MIN(WL3.WORKLOGID)
FROM (SELECT MIN(WL3.MXRONSITE), WL3.WORKLOGID
FROM Maximo.Worklog WL3 WHERE WOL1.WONUM = WL3.RECORDKEY))
or WL1.WORKLOGID is null)
To clarify, what I want is:
For each fault in Incident,
the earliest MXRONSITE from the Worklog table (if such a value exists),
For that worklog, information from the associated record from the WorkOrder table.
This is complicated by Incident records having multiple work orders, and work orders having multiple work logs, which may have the same MXRONSITE time.
After some trials, I have found an (almost) working solution:
WITH WLONSITE as (
SELECT
MIN(WLW.MXRONSITE) as "ONSITE",
WLWOW.ORIGRECORDID as "TICKETID",
WLWOW.WONUM as "WONUM"
FROM
MAXIMO.WORKLOG WLW
INNER JOIN
MAXIMO.WORKORDER WLWOW
ON
WLW.RECORDKEY = WLWOW.WONUM
WHERE
WLWOW.ORIGRECORDCLASS = 'INCIDENT'
GROUP BY
WLWOW.ORIGRECORDID, WLWOW.WONUM
)
select
incident.ticketid,
wlonsite.onsite,
wlonsite.wonum
from
maximo.incident
LEFT JOIN WLONSITE
ON WLONSITE.TICKETID = Incident.TICKETID
WHERE
(WLONSITE.ONSITE is null or WLONSITE.ONSITE = (SELECT MIN(WLONSITE.ONSITE) FROM WLONSITE WHERE WLONSITE.TICKETID = Incident.TICKETID AND ROWNUM=1))
AND Incident.AFFECTEDDATE >= TO_DATE ('01/12/2015', 'DD/MM/YYYY')
This however is significantly slower, and also still not quite right, as it turns out a single Incident can have multiple Work Orders with the same ONSITE time (aaargh!).
As requested, here is a sample input, and what I want to get from it (apologies for the formatting). Note that while TICKETID and WONUM are primary keys, they are strings rather than integers. WORKLOGID is an integer.
Incident table:
TICKETID / Description / FieldX
1 / WORD1 / S
2 / WORD2 / P
3 / WORDX /
4 / / Q
Work order table:
WONUM / ORIGRECORDID / REPORTDATE
11 / 1 / 2015-01-01
12 / 2 / 2015-01-01
13 / 2 / 2015-02-04
14 / 3 / 2015-04-05
Worklog table:
WORKLOGID / RECORDKEY / MXRONSITE
101 / 11 / 2015-01-05
102 / 12 / 2015-01-04
103 / 12 /
104 / 12 / 2015-02-05
105 / 13 /
Output:
TICKETID / WONUM / WORKLOGID
1 / 11 / 101
2 / 12 / 102
3 / /
4 / /
(Worklog 101 linked to TICKETID 1, has non-null MXRONSITE, and is from work order 11)
(Worklogs 102-105 linked to TICKETID 2, of which 102 has lowest MXRONSITE, and is work order 12)
(No work logs associated with faults 103 or 104, so work order and worklog fields are null)
Post Christmas attack!
I have found a solution which works:
The method I found was to use multiple WITH queries, as follows:
WLMINL AS (
SELECT
RECORDKEY, MXRONSITE, MIN(WORKLOGID) AS "WORKLOG"
FROM MAXIMO.WORKLOG
WHERE WORKLOG.CLASS = 'WORKORDER'
GROUP BY RECORDKEY, MXRONSITE
),
WLMIND AS (
SELECT
RECORDKEY, MIN(MXRONSITE) AS "MXRONSITE"
FROM MAXIMO.WORKLOG
WHERE WORKLOG.CLASS = 'WORKORDER'
GROUP BY RECORDKEY
),
WLMIN AS (
SELECT
WLMIND.RECORDKEY AS "WONUM", WLMIND.MXRONSITE AS "ONSITE", WLMINL.WORKLOG AS "WORKLOGID"
FROM
WLMIND
INNER JOIN
WLMINL
ON
WLMIND.RECORDKEY = WLMINL.RECORDKEY AND WLMIND.MXRONSITE = WLMINL.MXRONSITE
)
Thus for each work order finding the first date, then for each work order and date finding the lowest worklogid, then joining the two tables. This is then repeated at a higher level to find the data by incident.
However this method does not work in a reasonable time, so while it may be suitable for smaller databases it's no good for the behemoths I'm working with.
I would do this with row_number function:
SQLFiddle
select ticketid, case when worklogid is not null then reportdate end d1, mxronsite d2
from (
select i.ticketid, wo.reportdate, wl.mxronsite, wo.wonum, wl.worklogid,
row_number() over (partition by i.ticketid
order by wl.mxronsite, wo.reportdate) rn
from incident i
left join workorder wo on wo.origrecordid = i.ticketid
and wo.origrecordclass = 'INCIDENT'
left join worklog wl on wl.recordkey = wo.wonum )
where rn = 1 order by ticketid
When you nest subqueries, you cannot access columns that belong two or more levels higher; in your statement, WL1 is not accessible in the innermost subquery. (There is also a group-by clause missing, btw)
This might work (not exactly sure what output you expect, but try it):
SELECT
WL1.MXRONSITE as "Date_First_Onsite",
WOL1.REPORTDATE as "Date_First_Onsite_Notified"
FROM Maximo.Incident
LEFT JOIN (
Maximo.WorkOrder WOL1
LEFT JOIN Maximo.Worklog WL1
ON WL1.RECORDKEY = WOL1.WONUM
) ON WOL1.ORIGRECORDID = Incident.TICKETID
AND WOL1.ORIGRECORDCLASS = 'INCIDENT'
WHERE WL1.WORKLOGID =
( SELECT MIN(WL3.WORKLOGID)
FROM Maximo.WorkOrder WOL3
LEFT JOIN Maximo.Worklog WL3
ON WL3.RECORDKEY = WOL3.WONUM
WHERE WOL3.ORIGRECORDID = WOL1.ORIGRECORDID
AND WL3.MXRONSITE IS NOT NULL
)
OR WL1.WORKLOGID IS NULL AND NOT EXISTS
( SELECT MIN(WL4.WORKLOGID)
FROM Maximo.WorkOrder WOL4
LEFT JOIN Maximo.Worklog WL4
ON WL4.RECORDKEY = WOL4.WONUM
WHERE WOL4.ORIGRECORDID = WOL1.ORIGRECORDID
AND WL4.MXRONSITE IS NOT NULL )
I may not have the details right on what you're trying to do... if you have some sample input and desired output, that would be a big help.
That said, I think an analytic function would help a lot, not only in getting the output but in organizing the code. Here is an example of how the max analytic function in a subquery could be used.
Again, the details on the join may be off -- if you can furnish some sample input and output, I'll bet someone can get to where you're trying to go:
with wo as (
select
wonum, origrecordclass, origrecordid, reportdate,
max (reportdate) over (partition by origrecordid) as max_date
from Maximo.workorder
where origrecordclass = 'INCIDENT'
),
logs as (
select
worklogid, mxronsite, recordkey,
max (mxronsite) over (partition by recordkey) as max_mx
from Maximo.worklog
)
select
i.ticketid,
l.mxronsite as "Date_First_Onsite",
wo.reportdate as "Date_First_Onsite_Notified"
from
Maximo.incident i
left join wo on
wo.origrecordid = i.ticketid and
wo.reportdate = wo.max_date
left join logs l on
wo.wonum = l.recordkey and
l.mxronsite = l.max_mx
-- edit --
Based on your sample input and desired output, this appears to give the desired result. It does do somewhat of an explosion in the subquery, but hopefully the efficiency of the analytic functions will dampen that. They are typically much faster, compared to using group by:
with wo_logs as (
select
wo.wonum, wo.origrecordclass, wo.origrecordid, wo.reportdate,
l.worklogid, l.mxronsite, l.recordkey,
max (reportdate) over (partition by origrecordid) as max_date,
min (mxronsite) over (partition by recordkey) as min_mx
from
Maximo.workorder wo
left join Maximo.worklog l on wo.wonum = l.recordkey
where wo.origrecordclass = 'INCIDENT'
)
select
i.ticketid, wl.wonum, wl.worklogid,
wl.mxronsite as "Date_First_Onsite",
wl.reportdate as "Date_First_Onsite_Notified"
from
Maximo.incident i
left join wo_logs wl on
i.ticketid = wl.origrecordid and
wl.mxronsite = wl.min_mx
order by 1

Oracle Left Join not returning all rows

I am using the following CTE. The first part collects all unique people and the second left joins the unique people with events during a particular time frame. I am expecting that all the rows be returned from my unique people table even if they don't have an event within the time frame. But this doesn't appear to be the case.
WITH DISTINCT_ATTENDING(ATTENDING) AS
(
SELECT DISTINCT ATTENDING
FROM PEOPLE
WHERE ATTENDING IS NOT NULL
), -- returns 62 records
EVENT_HISTORY(ATTENDING, TOTAL) AS
(
SELECT C.ATTENDING,
COUNT(C.ID)
FROM DISTINCT_ATTENDING D
LEFT JOIN PEOPLE C
ON C.ATTENDING = D.ATTENDING
AND TO_DATE(C.DATE, 'YYYYMMDD') < TO_DATE('20140101', 'YYYYMMDD')
GROUP BY C.ATTENDING
ORDER BY C.ATTENDING
)
SELECT * FROM EVENT_HISTORY; -- returns 49 rows
What am I doing wrong here?
Jonny
The problem is inthe column "C.ATTENDING", just change for "D.ATTENDING"
SELECT D.ATTENDING,
COUNT(C.ID)
FROM DISTINCT_ATTENDING D
LEFT JOIN PEOPLE C
ON C.ATTENDING = D.ATTENDING
AND TO_DATE(C.DATE, 'YYYYMMDD') < TO_DATE('20140101', 'YYYYMMDD')
GROUP BY D.ATTENDING
ORDER BY D.ATTENDING
Your query seems too complicated. I think the following does the same thing:
SELECT P.ATTENDING,
SUM(CASE WHEN TO_DATE(P.DATE, 'YYYYMMDD') < TO_DATE('20140101', 'YYYYMMDD')
THEN 1 ELSE 0 END)
FROM PEOPLE P
WHERE P.ATTENDING IS NOT NLL
GROUP BY P.ATTENDING
ORDER BY P.ATTENDING ;
Your problem is that you are aggregating by a column in the second table of a left join. This is NULL when there is no match.