Results of SQL grouping not as intended - sql

Intro Details:
I have an issue with a query for Oracle SQL Developer using client 12c. I've researched several other questions on SO as well as searched Google for an answer to this, but ultimately the answers to all have been to include all columns without aggregate functions in the GROUP BY clause.
What I want is to get a single result for each category (PG.CAT) per year, week, and day (FD.YEAR, FD.WEEK, and FD.DT respectively). I want to sum the units, hours, errors (GE.QTY), and total hours. I also perform multiplication and division on two columns and join up to four other tables.
Query:
`SELECT
FD.YEAR,
FD.WEEK,
PG.DT,
PG.CAT,
SUM(PG.UNITS) AS UNITS,
SUM(PG.HOURS) AS HOURS,
PM.MTM,
PM.MTM * PG.HOURS AS ADJ_MTM,
(PG.UNITS / (PM.MTM * PG.HOURS)) AS PERC_STANDARD,
SUM(CASE WHEN GE.QTY IS NULL THEN 0 ELSE GE.QTY END) AS QTY,
SUM(WH.TOTALHOURS) AS TOTALHOURS
FROM
PROD_GUNS PG
INNER JOIN PROD_MTM PM
ON PG.CAT = PM.CATEGORY
AND PM.DEPTNO = '018'
AND PG.DT BETWEEN PM.START_DT AND PM.END_DT
INNER JOIN FISCAL_DATES_DAYS FD
ON PG.DT = FD.DT
LEFT OUTER JOIN PROD_GUNS_ERRORS GE
ON PG.EID = GE.EID
AND PG.DT = GE.DT
INNER JOIN WEEKLY_HOURS WH
ON FD.WEEK = WH.DT_WEEK
AND FD.YEAR = WH.DT_YEAR
AND PG.EID = WH.EEXX31
GROUP BY
FD.YEAR,
FD.WEEK,
PG.DT,
PG.CAT,
PM.MTM,
PM.MTM * PG.HOURS,
(PG.UNITS / ( PM.MTM * PG.HOURS))
HAVING
FD.YEAR = '2015'
AND FD.WEEK = '1'
AND PG.DT = '29-DEC-14'
AND PG.CAT = 'Picking'
ORDER BY
PG.DT;`
Actual Result:
2015 1 29-DEC-14 Picking 46 0.5 68 34 1.35294117647058823529411764705882352941 0 32.21
2015 1 29-DEC-14 Picking 831 7.72 68 524.96 1.58297775068576653459311185614142029869 0 29.35
Intended Result:
2015 1 20-Dec-14 Picking 877 8.22 68 558.96 1.21654501216545 0 61.59
Question:
With the aggregates and grouping that I have above, why would this not be giving me the intended result? Thank you all in advance for any guidance provided.

Try to SUM/AVG (depending on what you need) PM.MTM * PG.HOURS AS ADJ_MTM and (PG.UNITS / (PM.MTM * PG.HOURS)) AS PERC_STANDARD, not group by them:
SELECT
FD.YEAR,
FD.WEEK,
PG.DT,
PG.CAT,
SUM(PG.UNITS) AS UNITS,
SUM(PG.HOURS) AS HOURS,
PM.MTM,
SUM(PM.MTM * PG.HOURS )AS ADJ_MTM,
SUM((PG.UNITS / (PM.MTM * PG.HOURS))) AS PERC_STANDARD,
SUM(CASE WHEN GE.QTY IS NULL THEN 0 ELSE GE.QTY END) AS QTY,
SUM(WH.TOTALHOURS) AS TOTALHOURS
FROM
PROD_GUNS PG
INNER JOIN PROD_MTM PM
ON PG.CAT = PM.CATEGORY
AND PM.DEPTNO = '018'
AND PG.DT BETWEEN PM.START_DT AND PM.END_DT
INNER JOIN FISCAL_DATES_DAYS FD
ON PG.DT = FD.DT
LEFT OUTER JOIN PROD_GUNS_ERRORS GE
ON PG.EID = GE.EID
AND PG.DT = GE.DT
INNER JOIN WEEKLY_HOURS WH
ON FD.WEEK = WH.DT_WEEK
AND FD.YEAR = WH.DT_YEAR
AND PG.EID = WH.EEXX31
GROUP BY
FD.YEAR,
FD.WEEK,
PG.DT,
PG.CAT,
PM.MTM
HAVING
FD.YEAR = '2015'
AND FD.WEEK = '1'
AND PG.DT = '29-DEC-14'
AND PG.CAT = 'Picking'
ORDER BY
PG.DT;

Related

How to force postgres to return 0 even if there are no rows matching query, using coalesce, group by and join

I've been trying hopelessly to get the following SQL statement to return the query results and default to 0 if there are no rows matching the query.
This is the intended result:
vol | year
-------+------
0 | 2018
Instead I get:
vol | year
-----+------
(0 rows)
Here is the sql statement:
select coalesce(vol,0) as vol, year
from (select sum(vol) as vol, year
from schema.fact_data
join schema.period_data
on schema.fact_data.period_tag = schema.period_data.tag
join schema.product_data
on schema.fact_data.product_tag =
schema.product_data.tag
join schema.market_data
on schema.fact_data.market_tag = schema.market_data.tag
where "retailer"='MadeUpRetailer'
and "product_tag"='FakeProductTag'
and "year"='2018' group by year
) as DerivedTable;
I know the query works because it returns data when there is data. Just doesn't default to 0 as intended...
Any help in finding why this is the case would be much appreciated!
Using your subquery DerivedTable, you could write:
SELECT coalesce(DerivedTable.vol, 0) AS vol,
y.year
FROM (VALUES ('2018'::text)) AS y(year)
LEFT JOIN (SELECT ...) AS DerivedTable
ON DerivedTable.year = y.year;
Remove the GROUP BY (and the outer query):
select 2018 as year, coalesce(sum(vol), 0) as vol
from schema.fact_data f join
schema.period_data p
on f.period_tag = p.tag join
schema.product_data pr
on f.product_tag = pr.tag join
schema.market_data m
on fd.market_tag = m.tag
where "retailer" = 'MadeUpRetailer' and
"product_tag" = 'FakeProductTag' and
"year" = '2018';
An aggregation query with no GROUP BY always returns exactly one row, so this should do what you want.
EDIT:
The query would look something like this:
select v.yyyy as year, coalesce(sum(vol), 0) as vol
from (values (2018), (2019)) v(yyyy) left join
schema.fact_data f
on f.year = v.yyyy left join -- this is just an example. I have no idea where year is coming from
schema.period_data p
on f.period_tag = p.tag left join
schema.product_data pr
on f.product_tag = pr.tag left join
schema.market_data m
on fd.market_tag = m.tag
group by v.yyyy
However, you have to move the where conditions to the appropriate on clauses. I have no idea where the columns are coming from.
From the code you posted it is not clear in which table you have the year column.
You can use UNION to fetch just 1 row in case there are no rows in that table for the year 2018 like this:
select sum(vol) as vol, year
from schema.fact_data innrt join schema.period_data
on schema.fact_data.period_tag = schema.period_data.tag
inner join schema.product_data
on schema.fact_data.product_tag = schema.product_data.tag
inner join schema.market_data
on schema.fact_data.market_tag = schema.market_data.tag
where
"retailer"='MadeUpRetailer' and
"product_tag"='FakeProductTag' and
"year"='2018'
group by "year"
union
select 0 as vol, '2018' as year
where not exists (
select 1 from tablename where "year" = '2018'
)
In case there are rows for the year 2018, then nothing will be fetched by the 2nd query,

altering query in db2 to fix count from a join

I'm getting an aggregated count of records for orders and I'm getting the expected count on this basic query:
SELECT
count(*) as sales_180,
180/count(*) as velocity
FROM custgroup g
WHERE g.cstnoc = 10617
AND g.framec = 4847
AND g.covr1c = 1763
AND g.colr1c = 29
AND date(substr(g.extd1d,1,4)||'-'||substr(g.EXTD1d,5,2)||'-'||substr(g.EXTD1d,7,2) ) between current_Date - 180 DAY AND current_Date
But as soon as I add back in my joins and joined values then my count goes from 1 (which it should be) to over 200. All I need from these joins is the customer ID and the manager number. so even if my count is high, I'm basically just trying to say "for this cstnoc, give me the slsupr and xlsno"
How can I perform this below query without affecting the count? I only want my count (sales_180 and velocity) coming from the custgroup table based on my where clause, but I then just want one value of the xcstno and xslsno based on the cstnoc.
SELECT
count(*) as sales_180,
180/count(*) as velocity,
c.xslsno as CustID,
cr.slsupr as Manager
FROM custgroup g
inner join customers c
on g.cstnoc = c.xcstno
inner join managers cr
on c.xslsno = cr.xslsno
WHERE g.cstnoc = 10617
AND g.framec = 4847
AND g.covr1c = 1763
AND g.colr1c = 29
AND date(substr(g.extd1d,1,4)||'-'||substr(g.EXTD1d,5,2)||'-'||substr(g.EXTD1d,7,2) ) between current_Date - 180 DAY AND current_Date
GROUP BY c.xslsno, cr.slsupr
You are producing multiple rows when joining, so your count is now counting all the resulting rows with all that [unintended] multiplicity.
The solution? Use a table expression to pre-compute your count, and then you can join it to the other tables, as in:
select
g2.sales_180,
g2.velocity,
c.xslsno as CustID,
cr.slsupr as Manager
from customers c
join managers cr on c.xslsno = cr.xslsno
join ( -- here the Table Expression starts
SELECT
count(*) as sales_180,
180/count(*) as velocity
FROM custgroup g
WHERE g.cstnoc = 10617
AND g.framec = 4847
AND g.covr1c = 1763
AND g.colr1c = 29
AND date(substr(g.extd1d,1,4)||'-'||substr(g.EXTD1d,5,2)
||'-'||substr(g.EXTD1d,7,2) )
between current_Date - 180 DAY AND current_Date
) g2 on g2.cstnoc = c.xcstno
You can also use a Common Table Expression (CTE) that will produce the same result:
with g2 as (
SELECT
count(*) as sales_180,
180/count(*) as velocity
FROM custgroup g
WHERE g.cstnoc = 10617
AND g.framec = 4847
AND g.covr1c = 1763
AND g.colr1c = 29
AND date(substr(g.extd1d,1,4)||'-'||substr(g.EXTD1d,5,2)
||'-'||substr(g.EXTD1d,7,2) )
between current_Date - 180 DAY AND current_Date
)
select
g2.sales_180,
g2.velocity,
c.xslsno as CustID,
cr.slsupr as Manager
from customers c
join managers cr on c.xslsno = cr.xslsno
join g2 on g2.cstnoc = c.xcstno

How can I return 3 COUNT columns from the same table when JOIN with other tables as well?

I would like to know how I can return several COUNTs for the following:
I have 27 work sites, each site has an employee with a contract, I have wrote a script to return one actual column of data (Work sites, 27 in total), I then added a COUNT to count how many contracts/staff I have in each site. I have had to use 3 tables to get the data.
what I would like to do now is add two more columns, one that shows how many contracts I have "Under 35 hours" and one that shows how many I have "Over 35 hours"
This is what I have to return site names and total contracted hours:
SELECT
LOCATION.LocationName,
COUNT (EB_MINMAX_VIEW.UnitQuan) AS 'Total Contracts'
FROM
LOCATION
JOIN
eb_view on eb_view.locationcounter = location.locationcounter
JOIN
EB_MINMAX_VIEW on EB_MINMAX_VIEW.ebcounter = eb_view.ebcounter
GROUP BY
LOCATION.LocationName
Then if i want to return contacts under 35 hours then i have to write this:
SELECT
LOCATION.LocationName,
COUNT (EB_MINMAX_VIEW.UnitQuan) AS 'Total Contracts'
FROM
LOCATION
JOIN
eb_view on eb_view.locationcounter = location.locationcounter
JOIN
EB_MINMAX_VIEW on EB_MINMAX_VIEW.ebcounter = eb_view.ebcounter
WHERE
UnitQuan < 35
GROUP BY
LOCATION.LocationName
and this will give me the number of contracts less than 35 for all sites, but I want to include this in the final table i.e. site name, number of total contracts per site, number of contrast < 35 for all sites, and a column for number of contracts > 35 for each site.
Maybe this helps (I didn't test it):
SELECT s1.LocationName,
s1.TotalContracts AS cntAll,
s2.TotalContracts AS cntLess35,
s3.TotalContracts AS cntGreater35
FROM(SELECT LOCATION.LocationName,
COUNT(EB_MINMAX_VIEW.UnitQuan) TotalContracts
FROM LOCATION
JOIN eb_view ON eb_view.locationcounter = location.locationcounter
JOIN EB_MINMAX_VIEW on EB_MINMAX_VIEW.ebcounter = eb_view.ebcounter
GROUP
BY LOCATION.LocationName
) s1
LEFT
JOIN(SELECT LOCATION.LocationName,
COUNT(EB_MINMAX_VIEW.UnitQuan) TotalContracts
FROM LOCATION
JOIN eb_view ON eb_view.locationcounter = location.locationcounter
JOIN EB_MINMAX_VIEW on EB_MINMAX_VIEW.ebcounter = eb_view.ebcounter
WHERE UnitQuan < 35
GROUP
BY LOCATION.LocationName
) s2
ON s1.LocationName = s2.LocationName
LEFT
JOIN(SELECT LOCATION.LocationName,
COUNT(EB_MINMAX_VIEW.UnitQuan) TotalContracts
FROM LOCATION
JOIN eb_view ON eb_view.locationcounter = location.locationcounter
JOIN EB_MINMAX_VIEW on EB_MINMAX_VIEW.ebcounter = eb_view.ebcounter
WHERE UnitQuan > 35
GROUP
BY LOCATION.LocationName
) s3
ON s1.LocationName = s3.LocationName
Another alternative:
SELECT LOCATION.LocationName,
SUM(all),
SUM(less35),
SUM(greater35)
FROM(
SELECT LOCATION.LocationName,
1 AS all,
CASE WHEN UnitQuan < 35 THEN 1 ELSE 0 END less35,
CASE WHEN UnitQuan > 35 THEN 1 ELSE 0 END greater35
FROM LOCATION
JOIN eb_view ON eb_view.locationcounter = location.locationcounter
JOIN EB_MINMAX_VIEW on EB_MINMAX_VIEW.ebcounter = eb_view.ebcounter
)
GROUP BY LOCATION.LocationName
This is a simpler form of DirkNM's query:
SELECT l.LocationName,
count(*) AS all,
SUM(CASE WHEN ebmm.UnitQuan < 35 THEN 1 ELSE 0 END) as less35,
SUM(CASE WHEN ebmm.UnitQuan > 35 THEN 1 ELSE 0 END) greater35
FROM LOCATION JOIN
eb_view eb
ON eb.locationcounter = l.locationcounter JOIN
EB_MINMAX_VIEW ebmm
on ebmm.ebcounter = eb.ebcounter
GROUP BY l.LocationName

Null/0 Answers Not Appearing

Another Question for Today :)
Wrote a query that works perfectly - except I want it to show NULL/0 values as 0 - and - that isn't happening. I tried approaching this two ways:
First I used isnull()
Select isnull(Count(*),0) as Total ,
z.zname
From STable s ,
SLTable sl ,
ZTable z ,
SETable se ,
SEETable see ,
SEGTable seg
Where s.sID = sl.sID
and sl.zID = z.zID
and s.sID = se.sID
and se.etID = see.etID
and see.segID = seg.segID
and see.segID = 3
Group By z.zname
order by z.zname
Is doesn't seem to give me the Null/0 values
Then I tried using a sum/case approach
Select sum(case when see.segID <> 3 then 0 else 1 end) as Total ,
z.zname
From STable s ,
SLTable sl ,
Table z ,
SETable se ,
SEETable see ,
SEGTable seg
Where s.sID = sl.sID
and sl.zID = z.zID
and s.sID = se.sID
and se.etID = see.etID
and see.segID = seg.segID
and see.segID = 3
Group By z.zname
order by z.zname
And still no 0 values - so now I'm stumped :(
Well, it's unclear what you're actually trying to find. However, a big part of your problem is that you're using old-school, pre-ISO/ANSI join syntax. If you refactor your join to use modern join syntax, you'll get a query that looks somethinn (a lot, actually) like this:
select zName = z.zname ,
Total = count(*)
From ZTable z
join SLTable sl on sl.zID = z.zID
join STable s on s.sID = sl.sID
join SETable se on se.sID = s.sID
join SEETable see on see.etID = se.etID
and see.segID = 3
join SEGTable seg on seg.segID = see.segID
Group By z.zname
order by z.zname
I suspect that what you want to get is a list of all zNames and their respect counts of having segID = 3. Since you are using inner joins, you'll only ever see zNames that have a match. What you can do is something like this:
select zName = z.zname ,
Total = sum(case see.segID when 3 then 1 else 0 end)
from ZTable z
left join SLTable sl on sl.zID = z.zID
left join STable s on s.sID = sl.sID
left join SETable se on se.sID = s.sID
left join SEETable see on see.etID = se.etID
group By z.zname
order by z.zname
The above will return every row from zTable at least once, with null values for the columns of any table for which no match was found. Then we group it and count the rows where segID is 3.
Actually just to semi answer my own question - just realized that where - see.segID-3 - so it'll only turn out results with segID = 3 - so it cannot NOT have 3 - right?
But Im specifically looking for segIDs as 3 - just, if there is nothing there then display 0
Here's what I am assuming you need changed
Select
Count(*) as Total,
z.zname
From STable s, SLTable sl, ZTable z, SETable se, SEETable see, SEGTable seg
Where s.sID=sl.sID
and sl.zID=z.zID
and s.sID=se.sID
and se.etID=see.etID
and see.segID=seg.segID
and see.segID=3
AND TABLE.YOURCOLUMN IS NOT NULL -- THESE ARE
AND TABLE.YOURCOLUMN <> 0 -- NEW
GROUP BY z.zname
ORDER BY z.zname
In your Query
isnull(Count(*),0) as Total,
Count will never return null, it will be numeric values ranging from 0 to n.
So your isnull condition will never get satisfy.
You can simply write select count() as total .. instead of isnull(Count(),0) and it will return 0 when ever it will find no rows in table.

Rollup / recursive addition SQL Server 2008

I have a query with rollup that outputs data like (the query is a little busy, but I can post if necessary)
range subCounts Counts percent
1-9 3 100 3.0
10-19 13 100 13.0
20-29 30 100 33.0
30-39 74 100 74.0
NULL 100 100 100.0
How is it possible to keep a running summation total of percent? Say I need to find the bottom 15 percentile, in this case 3+13=16 so I would like for the last row to be returned read
range subCounts counts percent
10-19 13 100 13.0
EDIT1: here the query
select '$'+cast(+bin*10000 + ' ' as varchar(10)) + '-' + cast(bin*10000+9999 as varchar(10)) as bins,
count(*) as numbers,
(select count(distinct patient.patientid) from patient
inner join tblclaims on patient.patientid = tblclaims.patientid
and patient.admissiondate = tblclaims.admissiondate
and patient.dischargedate = tblclaims.dischargedate
inner join tblhospitals on tblhospitals.hospitalnpi = patient.hospitalnpi
where (tblhospitals.hospitalname = 'X')
) as Totals
, round(100*count(*)/cast((select count(distinct patient.patientid) from patient
inner join tblclaims on patient.patientid = tblclaims.patientid
and patient.admissiondate = tblclaims.admissiondate
and patient.dischargedate = tblclaims.dischargedate
inner join tblhospitals on tblhospitals.hospitalnpi = patient.hospitalnpi
where (tblhospitals.hospitalname = 'X')) as float),2) as binsPercent
from
(
select tblclaims.patientid, sum(claimsmedicarepaid) as TotalCosts,
cast(sum(claimsmedicarePaid)/10000 as int) as bin
from tblclaims inner join patient on patient.patientid = tblclaims.patientid
and patient.admissiondate = tblclaims.admissiondate
and patient.dischargedate = tblclaims.dischargedate
inner join tblhospitals on patient.hospitalnpi = tblhospitals.hospitalnpi
where tblhospitals.hospitalname = 'X'
group by tblclaims.patientid
) as t
group by bin with rollup
OK, so for whomever might use this for reference I figured out what I needed to do.
I added row_number() over(bin) as rownum to the query and saved all of this as a view.
Then I used
SELECT *,
SUM(t2.binspercent) AS SUM
FROM t t1
INNER JOIN t t2 ON t1.rownum >= t2.rownum
GROUP BY t1.rownum,
t1.bins, t1.numbers, t1.uktotal, t1.binspercent
ORDER BY t1.rownum
by joining t1.rownum >=t2.rownum you can get the rolling count sort of thing.
This isn't exactly what i was looking for, but it's on the same track:
http://blog.tallan.com/2011/12/08/sql-server-2012-windowing-functions-part-1-of-2-running-and-sliding-aggregates/ and http://blog.tallan.com/2011/12/19/sql-server-2012-windowing-functions-part-2-of-2-new-analytic-functions/ - check out PERCENT_RANK
CUME_DIST
PERCENTILE_CONT
PERCENTILE_DISC
Sorry for the lame answer