How to make multiple aggregate function without break / divided the fields - sql

Is it possible to make an aggregate function without break / divided the grouping fields? I make a query but it will divided into duplicate value in the first field, here is my query:
SELECT TOP 5 empname AS 'EMP Name',
SUM (CASE WHEN prod = 'P' THEN 1 ELSE 0 END) AS 'Count of Prod',
COUNT (prod) AS 'Total Account',
FORMAT (COALESCE (SUM (CASE WHEN prod = 'P' THEN 1 ELSE 0 END) / COUNT (prod), 0), 'P') AS '% Prod',
DATEDIFF(DAY, t_start, t_end) as 'Duration Trip'
FROM Sampletable
WHERE empname NOT IN ('NA') AND
empname IS NOT NULL AND
t_end IS NOT NULL
GROUP BY empname,
prod,
t_end,
t_start
ORDER BY [Count of Prod] DESC
My expected result:
Emp. Name
Count of Prod
Total Account
% Prod
Duration Trip
Emp.1
62
63
98,41%
30
Emp.2
45
48
93,75%
28
Emp.3
20
22
90,91%
25
Emp.4
20
24
83,33%
22
Emp.5
15
19
78,95%
20
Thank you in advance.

If you want one row per empname, then that should be the only column in the group by (or perhaps other columns that describe each employee without multiplying the number of rows).
That suggests something like this:
SELECT TOP 5 empname,
SUM(CASE WHEN prod = 'P' THEN 1 ELSE 0 END) AS prod_count,
COUNT prod) AS Total_Account,
FORMAT(AVG(CASE WHEN prod = 'P' THEN 1.0 ELSE 0 END), '%P') AS prod_ratio,
SUM(DATEDIFF(DAY, t_start, t_end)) as trip_duration
FROM Sampletable
WHERE empname NOT IN ('NA') AND
empname IS NOT NULL AND
t_end IS NOT NULL
GROUP BY empname
ORDER BY prod_count DESC;
Note some of the changes to the query:
The column aliases are simplified so no escape characters are needed.
The GROUP BY has only empname.
The logic for the proportion of products is simplified using AVG().
I assume that the trip duration should be a sum of the individual durations.
I did not remove it, but empname IS NOT NULL is redundant, because the NOT IN handles this.

Related

Oracle APex calculate profit on rendering

I have an interactive grid with fixed rows and need to calculate the formula on pre-rendering.
So the source query is:
select kpi,monthly,yearly from kpi where project_id = :P1_PROJECT_ID;
I need to modify this so that the row where kpi='Gross' is calculated on rendering.
It looks in the grid like:
Expected result:(Gross=Profit/Loss)
I am trying to write sql query but it doesn;t work.
Despite data it returns null.
What am i doing wrong here?
select kpi,
case when KPI='Gross'
then to_char(case when KPI='Profit' then to_number(replace(nvl(monthly,0),',','')) end /
case when KPI='Loss' then to_number(replace(nvl(monthly,0),',','')) end ,'999,999,999,999')
else to_char( monthly,'999,999,999,999') end as monthly,
case when KPI='Gross'
then to_char(case when KPI='Profit' then to_number(replace(nvl(yearly,0),',','')) end /
case when KPI='Loss' then to_number(replace(nvl(yearly,0),',','')) end ,'999,999,999,999')
else to_char( yearly,'999,999,999,999') end as yearly,
from kpi where project_id = :P1_PROJECT_ID;
To_char is used to display values as comma separated.
So it would when KPI=Gross, it will divide the columns where kpi=profit by kpi=Loss and dispaly result.
Also the result in the row where KPI=Gros should also have % concatenated.
Apex 20.2
How can this be achieved?
You were close, but you need to use window functions. Without using window functions, the query will not look at the other rows in the case statements to calculate the GROSS column.
The query below is how to properly calculate the GROSS using window functions. I have added ROUND to round the gross to an integer, but you can remove that if you want the decimal points.
WITH
kpi (pk,
kpi,
monthly,
yearly,
project_id)
AS
(SELECT 1, 'Revenue', 60000, 2000000, 1 FROM DUAL
UNION ALL
SELECT 2, 'Profit', 20, 30, 1 FROM DUAL
UNION ALL
SELECT 3, 'Loss', 10, 50, 1 FROM DUAL
UNION ALL
SELECT 4, 'Gross', NULL, NULL, 1 FROM DUAL)
SELECT k.kpi,
CASE k.kpi
WHEN 'Gross'
THEN
ROUND (
SUM (CASE k.kpi WHEN 'Profit' THEN k.monthly ELSE 0 END)
OVER (PARTITION BY project_id)
/ SUM (CASE k.kpi WHEN 'Loss' THEN k.monthly ELSE 0 END)
OVER (PARTITION BY project_id))
|| '%'
ELSE
TO_CHAR (k.monthly)
END AS monthly,
CASE k.kpi
WHEN 'Gross'
THEN
ROUND (
SUM (CASE k.kpi WHEN 'Profit' THEN k.yearly ELSE 0 END)
OVER (PARTITION BY project_id)
/ SUM (CASE k.kpi WHEN 'Loss' THEN k.yearly ELSE 0 END)
OVER (PARTITION BY project_id))
|| '%'
ELSE
TO_CHAR (k.yearly)
END AS yearly
FROM kpi k
WHERE project_id = 1;
KPI MONTHLY YEARLY
__________ __________ __________
Revenue 60000 2000000
Profit 20 30
Loss 10 50
Gross 2% 1%

Need to calculate Year wise sum in the desired output format below, but I am getting column wise data

I just need to know that how can I get the row wise- year totals.
Here is my code and need to get the below expected result :
SELECT /*+parallel(v,32) +parallel(mm,32) +parallel(c,32) */
'BUS' VEH_TYPE,
MM.MAKE_MO,
SUM (CASE WHEN SUBSTR (V.YEAR_Val, 1, 4) = '1990' THEN 1 ELSE 0 END)
AS FY1990,
SUM (CASE WHEN SUBSTR (V.YEAR_VAL, 1, 4) = '1991' THEN 1 ELSE 0 END)
AS FY1991,
SUM (CASE WHEN SUBSTR (V.YEAR_VAL, 1, 4) = '1992' THEN 1 ELSE 0 END)
AS FY1992,
SUM (CASE WHEN SUBSTR (V.YEAR_VAL, 1, 4) = '1993' THEN 1 ELSE 0 END)
AS FY1993,
SUM (CASE WHEN SUBSTR (V.YEAR_VAL, 1, 4) = '1994' THEN 1 ELSE 0 END)
AS FY1994,
SUM (CASE WHEN SUBSTR (V.YEAR_VAL, 1, 4) = '1995' THEN 1 ELSE 0 END)
AS FY1995,
COUNT (MM.MAKE_MO) Total_Count
FROM VEHICLE V, MAKE mm, CATEGORY c
WHERE C.MAKE_MO = mm.MAKE_MO
AND c.CATEGORY_CD = v.CATEGORY_CD
AND V.p_MONTH BETWEEN '199001' AND '199501' --using this as data is huge
AND C.TYPE_CODE=01
AND MM.VEH_CODE='B'
GROUP BY 'BUS,'MM.MAKE_MO, V.YEAR_Val
ORDER BY V.YEAR_Val;
This is the Expected Result :
VEH TYPE YEAR MODEL TOTAL
------- ----- --------
Bus 1990 7,808,658
Bus 1991 5,474,809
Bus 1992 54,839,221
Bus 1993 54,680,000
Bus 1994 15,000,000
Bus 1995 17,899,668
This was way too long for a comment. After unwinding this query, it appears that you are simply trying to get a count of 'Bus' vehicles by model year (with ALL vehicles in the VEHICLE table are being labeled 'Bus'). If that's the case, you can simplify your query a lot.
SELECT VEH_TYPE
, theYear AS YEAR_MODEL
, COUNT(*) AS TOTAL
FROM (
SELECT 'BUS' AS VEH_TYPE
, SUBSTR(V.YEAR_Val,1,4) AS theYear
FROM VEHICLE V
INNER JOIN CATEGORY c ON v.CATEGORY_CD = c.CATEGORY_CD
AND C.TYPE_CODE='01'
INNER JOIN MAKE mm ON c.MAKE_MO = mm.MAKE_MO
AND MM.VEH_CODE='B'
WHERE V.p_MONTH BETWEEN '199001' AND '199501'
) s1
WHERE theYear IN ('1990','1991','1992','1993','1994','1995')
GROUP BY theYear
You may not need the WHERE clause if your vehicle years are only 1990-1995. And your JOINed tables don't appear to be doing anything other than limiting your VEHICLE table if there are no CATEGORY_CD matches in VEHICLE and CATEGORY, and if there are no MAKE_MO matches in MAKE and CATEGORY. If you were filtering for a specific column from CATEGORY or MAKE, or you had orphaned records in VEHICLE, then you'd want to add those JOINs back in. Otherwise, I don't think they're really doing anything.
Note that SUBSTR(V.YEAR_Val,1,4) will exclude any index you have on V.YEAR_Val, and if that's a column you need regularly, or if there are a lot of VEHICLE records, you may need to split that value into its own column.

Multiple Queries in different table

(Also posted here.)
So I have two tables, one is invalid table and the other is valid table.
valid table:
id
status
date
invalid table:
id
status
date
I have to produce a report with this output:
date on-time late total valid invalid1 invalid2 total rate
--------- ------- ---- ----- ----- -------- -------- ----- ----
9/10/2011 4 10 14 3 3 3 6
date: common fields on the 2 tables, field to group by, how many records on that day has
on-time: count of all the id on the valid table
late: count of all the records(id) on the invalid table
total: total of on-time and late
valid: count of id on the valid table with the "valid" status
invalid1: count of id on the invalid table with "invalid1" status
invalid2: count of id on the invalid table with "invalid2" status
total: total of valid, invalid1, invalid2
rate: average of totals
It's basically multiple queries with different table. How can I achieve it?
Someting like this?
SELECT
*,
(result.total + result._total) / 2 AS rate
FROM (
SELECT
date,
SUM(CASE WHEN data.valid = 1 THEN 1 ELSE 0 END) AS ontime,
SUM(CASE WHEN data.valid = 0 THEN 1 ELSE 0 END) AS late,
COUNT(*) AS total,
SUM(CASE WHEN data.valid = 1 AND data.status = 'valid' THEN 1 ELSE 0 END) AS valid,
SUM(CASE WHEN data.valid = 0 AND data.status = 'invalid1' THEN 1 ELSE 0 END) AS invalid1,
SUM(CASE WHEN data.valid = 0 AND data.status = 'invalid2' THEN 1 ELSE 0 END) AS invalid2,
SUM(CASE WHEN data.status IN ('valid', 'invalid', 'invalid2') THEN 1 ELSE 0 END) AS _total
FROM (
SELECT
date,
status,
valid = 1
FROM
Valid
UNION ALL
SELECT
date,
status,
valid = 0
FROM
InValid ) AS data
GROUP BY
date) AS result
SELECT date, ontime, late, ontime+late total, valid, invalid1, invalid2, valid+invalid1+invalid2 total
FROM
(SELECT date,
COUNT(*) late,
COUNT(IIF(status = 'invalid1', 1, NULL)) invalid1,
COUNT(IIF(status = 'invalid2', 1, NULL)) invalid2,
FROM invalid
GROUP BY date
) JOIN (
SELECT date,
COUNT(*) ontime,
COUNT(IIF(status = 'valud', 1, NULL)) valid,
FROM valid
GROUP BY date
) USING (date)
First of all, it seems that you are holding exactly the same information in 2 tables - I would recommend merging those tables together and add an additional boolean column called valid to hold the info related to validity of the record.
The query on your existent DB structure might look something like this:
SELECT unioned.* FROM (
( SELECT v.date AS date, v.status AS status, v.id AS id, COUNT(id) AS valid, 0 AS invalid1, 0 AS invalid2 FROM valid v GROUP BY v.date)
UNION
( SELECT i1.date AS date, i1.status AS status, i1.id AS id, 0 AS valid, COUNT(i1.id) AS invalid1, 0 AS invalid2 FROM invalid1 i1 GROUP BY i1.date)
UNION
( SELECT i2.date AS date, i2.status AS status, i2.id AS id, 0 AS valid, 0 AS invalid1, COUNT(i.id) AS invalid2 FROM invalid1 i1 GROUP BY i1.date)
) AS unioned GROUP BY unioned.date

change rows to columns and count

how to calculate count based on rows?
SOURCE TABLE
each employee can take 2 days off
Employee-----First_Day_Off-----Second_Day_Off
1------------10/21/2009--------12/6/2009
2------------09/3/2009--------12/6/2009
3------------09/3/2009--------NULL
4
5
.
.
.
Now i need a table that shows the dates and number of people taking off on that day
Date---------First_Day_Off-------Second_Day_Off
10/21/2009---1-------------------0
12/06/2009---1--------------------1
09/3/2009----2--------------------0
Any ideas?
Oracle 9i+, using Subquery Factoring (WITH):
WITH sample AS (
SELECT a.employee,
a.first_day_off AS day_off,
1 AS day_number
FROM YOUR_TABLE a
WHERE a.first_day_off IS NOT NULL
UNION ALL
SELECT b.employee,
b.second_day_off,
2 AS day_number
FROM YOUR_TABLE b
WHERE b.second_day_off IS NOT NULL)
SELECT s.day_off AS date,
SUM(CASE WHEN s.day_number = 1 THEN 1 ELSE 0 END) AS first_day_off,
SUM(CASE WHEN s.day_number = 2 THEN 1 ELSE 0 END) AS second_day_off
FROM sample s
GROUP BY s.day_off
Non Subquery Version
SELECT s.day_off AS date,
SUM(CASE WHEN s.day_number = 1 THEN 1 ELSE 0 END) AS first_day_off,
SUM(CASE WHEN s.day_number = 2 THEN 1 ELSE 0 END) AS second_day_off
FROM (SELECT a.employee,
a.first_day_off AS day_off,
1 AS day_number
FROM YOUR_TABLE a
WHERE a.first_day_off IS NOT NULL
UNION ALL
SELECT b.employee,
b.second_day_off,
2 AS day_number
FROM YOUR_TABLE b
WHERE b.second_day_off IS NOT NULL) s
GROUP BY s.day_off
It is a bit awkward to handle these queries, since you have days off stored in different columns. A better layout would be to have something like
EMPLOYEE_ID DAY_OFF
Then you would have multiple rows if an employee took multiple days off
EMPLOYEE_ID DAY_OFF
1 10/21/2009
1 12/6/2009
2 09/3/2009
2 12/6/2009
3 09/3/2009
...
In that case, you could find out how many days off each person took by using the following query:
SELECT EMPLOYEE_ID, COUNT(*) AS NUM_DAYS_OFF FROM DAYS_OFF_TABLE GROUP BY EMPLOYEE_ID
And the number of people who took days off on each date like this:
SELECT DAY_OFF, COUNT(*) AS NUM_PEOPLE FROM DAYS_OFF_TABLE GROUP BY DAY_OFF
But I digress...
You can try to use an SQL CASE statement to help with this:
SELECT Employee, CASE
WHEN First_Day_Off is NULL AND Second_Day_Off is NULL THEN 0
WHEN First_Day_Off is NOT NULL AND Second_Day_Off is NULL THEN 1
WHEN First_Day_Off is NULL AND Second_Day_Off is NOT NULL THEN 1
ELSE 2
END AS NUM_DAYS_OFF
FROM DAYS_OFF_TABLE
(note that you may need to change around the syntax slightly depending on your database.
Getting dates and number of people who took off on that day might be more complicated.
I don't know if this would work, but you can try it:
SELECT
Date_Off,
COUNT(*) AS Num_People
FROM
(SELECT
First_Day_Off, COUNT(*) AS Num_People FROM DAYS_OFF_TABLE WHERE First_Day_Off IS NOT NULL GROUP BY First_Day_Off
UNION
SELECT Second_Day_Off, COUNT(*) AS Num_People FROM DAYS_OFF_TABLE WHERE Second_Day_Off IS NOT NULL GROUP BY Second_Day_Off)
GROUP BY
Num_People

Can I combine my two SQLite SELECT statements into one?

I have a SQLite table called posts. An example is shown below. I would like to calculate the monthly income and expenses.
accId date text amount balance
---------- ---------- ------------------------ ---------- ----------
1 2008-03-25 Ex1 -64.9 3747.56
1 2008-03-25 Shop2 -91.85 3655.71
1 2008-03-26 Benny's -100.0 3555.71
For the income I have this query:
SELECT SUBSTR(date, 0,7) "month", total(amount) "income" FROM posts
WHERE amount > 0 GROUP BY month ORDER BY date;
It works fine:
month income
---------- ----------
2007-05 4877.0
2007-06 8750.5
2007-07 8471.0
2007-08 5503.0
Now I need the expenses and I could of cause just repeat the first statement with the condition amount < 0, but I am wondering if there is an elegant way to get both income and expenses in one query?
Try something like this
select substr(date, 0,7) "Month",
total(case when a > 0 then a else 0 end) "Income",
total(case when a < 0 then a else 0 end) "Expenses"
from posts
group by month
Look into the UNION statement (bottom of the link). This will let you combine the results of two queries, generally in the form:
<SELECT_STATEMENT_1> UNION <SELECT_STATEMENT_2>
Not sure if SQL Lite supports CASE statements, but if it does you could do something like this.
SELECT SUBSTR(date, 0,7) "month"
, total(CASE WHEN Amount > 0 THEN Amount ELSE 0 END) "income"
, -1 * total(CASE WHEN Amount < 0 THEN Amount ELSE 0 END) "expenses"
FROM posts
GROUP BY month
ORDER BY date;