Turning PIVOT query into table function - sql

Following up on BigQuery pivot on more fields, based on the suggestion by the genius Mikhail, I was wondering if the following 'pivot-like functionality' could be turned into a table function. Here would be an example:
The query from the currently accepted answer is:
select
(case when grp_set & 1 > 0 then Reseller end) as Reseller,
(case when grp_set & 2 > 0 then ProductGroup end) as ProductGroup,
(case when grp_set & 4 > 0 then Product end) as Product,
(case when grp_set & 8 > 0 then Year end) as Year,
(case when grp_set & 16 > 0 then Quarter end) as Quarter,
(case when grp_set & 32 > 0 then Product_Info end) as Product_Info,
sum(Revenue) as Revenue,
sum(Units) as Units
from `first-outlet-750.biengine_tutorial.Product`, unnest(generate_array(1, 64)) grp_set
where Year IN (2020) and Quarter in ('Q1', 'Q2')
group by 1, 2, 3, 4, 5, 6
having not (Quarter is null and Product_Info is not null)
and not (Year is null and Quarter is not null)
and not (ProductGroup is null and Product is not null)
order by 1, 2, 3, 4 , 5, 6
And I'm wondering if a table function could be created along the lines of:
PIVOT(
[row_agg1, row_agg2, ...],
[col_agg1, col_agg2, ...],
[agg_val1, agg_val2, ...]
)
So the above query could hopefully be translated into something like:
SELECT
*
FROM
PIVOT(
[Reseller, ProductGroup, Product], -- rows
[Year, Quarter, ProductInfo], -- cols
[SUM(Revenue), SUM(Units)] -- vals
)
A few things that I think might be a bit tricky:
Where does the actual table go in the FROM clause?
How would aliases work? For example, what if we wanted SUM(Revenue) to show up as TotalRev -- how to easily be able to refer to that in the main SELECT clause to be able to alias it if not just doing a SELECT * ?

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%

How to get and unite spesific amount using where clause and make this 3 times in one table

SELECT OfferSK, DateSK, UsedAmount, PaidAmount, ChargedAmount
FROM
(SELECT OfferSK, DateSK, Amount as UsedAmount
FROM dwh.FactExtraExpenses
where FinOperationSK = 2
UNION ALL
SELECT OfferSK, DateSK, Amount as PaidAmount
FROM dwh.FactExtraExpenses
where FinOperationSK = 1
UNION ALL
SELECT OfferSK, DateSK, Amount as ChargedAmount
FROM dwh.FactExtraExpenses
where FinOperationSK in (3, 4, 5, 6, 7, 10)) FCP
order by DateSK
Hello, in one column (Amount) in one table (FactExtraExpenses) I have amounts for different cases like used amount (FinOperationSK = 2) or paid amount(FinOperationSK = 1) or some fee/interst (FinOperationSK in (3,4,5,6,7,10)). Im trying to take all in one table, ordered by day by day (DateSK) for every customer (OfferSK). So i do something but is not ok :) Can you help me?
I think you want conditional aggregation:
select OfferSK, DateSK,
sum(case when FinOperationSK = 2 then Amount else 0 end) as UsedAmount,
sum(case when FinOperationSK = 1 then Amount else 0 end) as PaidAmount,
sum(case when in (3, 4, 5, 6, 7, 10) then Amount else 0 end) as ChargedAmount
from dwh.FactExtraExpenses
group by OfferSK, DateSK;

Trying to get data for every 6 months

I am running SQL Server and trying to get data for every 6 months.
Here is my query but it is for year, I want it to be every 6 months:
SELECT
DISTINCT YEAR(datein) AS 'Year',
COUNT(*) AS 'Total'
FROM
users
GROUP BY
YEAR(datein)
I want the column value should appear as: MONTH/YEAR
Use month() or quarter() and some arithmetic. Here is one way:
select YEAR(datein) as Year, FLOOR((MONTH(datein) - 1) / 6) as Year_Part,
COUNT(*) as Total
from users
group by YEAR(datein), FLOOR((MONTH(datein) - 1) / 6);
Or, you can put this into two columns:
select year(datein) as year,
sum(case when month(datein) <= 6 then 1 else 0 end) as total_half_1,
sum(case when month(datein) > 6 then 1 else 0 end) as total_half_2
from users
group by year(datein)
order by year(datein);

Group By Creates Duplicate Rows

I am using Oracle sql to create a sample data GridView and run into a very basic issue. So here it's, I've to organize data month-wise, say no of employees in a month based on a status column. So status = 0; Jan1 and status > 0; Jan2. I am not elaborating anything else as it has already a built-in view and that's what I've to use to make it work. So here is the query that I am using and the sample output that works fine except one:
SELECT DISTINCT SYEAR, DEPT_NAME,
--Month-wise data - Starts
DECODE ( upper((MONTHNAMESHORT)), 'JAN', NVL((FirstLetter), 0), NULL) "JAN1" ,
DECODE ( upper((MONTHNAMESHORT)), 'JAN', NVL((SecondLetter), 0), NULL) "JAN2",
DECODE ( upper((MONTHNAMESHORT)), 'FEB', NVL((FirstLetter), 0), NULL) "FEB1" ,
DECODE ( upper((MONTHNAMESHORT)), 'FEB', NVL((SecondLetter), 0), NULL) "FEB2"
--Month-wise data - Ends
FROM
--Sub-query - starts
(SELECT DISTINCT VWWEBLETTERSTATUS2.SYEAR, MONTHRANK.MONTHNAMESHORT,VWWEBLETTERSTATUS2.DEPT_NAME,
nvl(fnfirstletter(DEPT_NAME,upper(MONTHRANK.MONTHNAMESHORT),VWWEBLETTERSTATUS2.SYEAR),0) FirstLetter,
nvl(fnSecondLetter(DEPT_NAME,upper(MONTHRANK.MONTHNAMESHORT),VWWEBLETTERSTATUS2.SYEAR),0) SecondLetter,MONTHRANK.RANK
FROM
MONTHRANK,VWWEBLETTERSTATUS2 where VWWEBLETTERSTATUS2.SYEAR = '2018' AND
nvl(fnfirstletter(DEPT_NAME,upper(MONTHRANK.MONTHNAMESHORT),VWWEBLETTERSTATUS2.SYEAR), 0) <> 0 AND
nvl(fnSecondLetter(DEPT_NAME,upper(MONTHRANK.MONTHNAMESHORT),VWWEBLETTERSTATUS2.SYEAR), 0) <> 0
order by DEPT_NAME, rank) q
--Sub-query - Ends
GROUP BY SYEAR, (MONTHNAMESHORT), DEPT_NAME; --Issue here - For the month-wise group by
Output
Year Dept Jan1 Jan2 Feb1 Feb2
2018 UNIT-I3 93 87
2018 UNIT-I5 62 66
2018 QA 0 0
2018 UNIT-I5 87 66
Here for the GROUP BY (MONTHNAMESHORT) clause, it creates duplicate rows for the department and that specific year. Say when Unit-I5 has data for both the months, it creates separate rows though it should be in a single row.
Any way to overcome the issue keeping the same thing, just an alternate for the GROUP BY?
Update 1: Even tried this one, but didn't work
SUM(CASE WHEN Q.MONTHNAMESHORT = 'JAN' THEN Q.FirstLetter ELSE 0 END) "JAN1",
SUM(CASE WHEN Q.MONTHNAMESHORT = 'JAN' THEN Q.SecondLetter ELSE 0 END) "JAN2"
N.B: FirstLetter and SecondLetter are counted in the view.
SELECT DISTINCT is almost never appropriate with GROUP BY.
Your problem is that you are including (MONTHNAMESHORT) in the GROUP BY.
Your query is very difficult to decipher. But it should look something like this:
SELECT SYEAR, DEPT_NAME,
SUM(CASE WHEN upper(MONTHNAMESHORT) = 'JAN' THEN FirstLetter END) as "JAN1" ,
SUM(CASE WHEN upper(MONTHNAMESHORT) = 'JAN' THEN SecondLetter END) as "JAN2" ,
SUM(CASE WHEN upper(MONTHNAMESHORT) = 'FEB' THEN FirstLetter END) as "FEB1" ,
SUM(CASE WHEN upper(MONTHNAMESHORT) = 'FEB' THEN SecondLetter END) as "FEB2"
FROM . . .
GROUP BY SYEAR, DEPT_NAME;

SQL Efficiency on Date Range or Separate Tables

I'm calculating historical amount from a table in years(ex. 2015-2016, 2014-2015, etc.) I would like to seek expertise if its more efficient to do it in one batch or repeat the query multiple times filtered by the date required.
Thanks in advance!
OPTION 1:
select
id,
sum(case when year(getdate()) - year(txndate) between 5 and 6 then amt else 0 end) as amt_6_5,
...
sum(case when year(getdate()) - year(txndate) between 0 and 1 then amt else 0 end) as amt_1_0,
from
mytable
group by
id
OPTION 2:
select
id, sum(amt) as amt_6_5
from
mytable
group by
id
where
year(getdate()) - year(txndate) between 5 and 6
...
select
id, sum(amt) as amt_1_0
from
mytable
group by
id
where
year(getdate()) - year(txndate) between 0 and 1
1.
Unless you have resources issues I would go with the CASE version.
Although it has no impact on the results, filtering on the requested period in the WHERE clause might have a significant performance advantage.
2. Your period definition creates overlapping.
select id
,sum(case when year(getdate()) - year(txndate) = 6 then amt else 0 end) as amt_6
-- ...
,sum(case when year(getdate()) - year(txndate) = 0 then amt else 0 end) as amt_0
where txndate >= dateadd(year, datediff(year,0, getDate())-6, 0)
from mytable
group by id
This may be help you,
WITH CTE
AS
(
SELECT id,
(CASE WHEN year(getdate()) - year(txndate) BETWEEN 5 AND 6 THEN 'year_5-6'
WHEN year(getdate()) - year(txndate) BETWEEN 4 AND 5 THEN 'year_4-5'
...
END) AS my_year,
amt
FROM mytable
)
SELECT id,my_year,sum(amt)
FROM CTE
GROUP BY id,my_year
Here, inside the CTE, just assigned a proper year_tag for each records (based on your conditions), after that select a summary for the CTE grouped by that year_tag.