I have the following code and results in sas. It does sort descending by subtotals however I need the report to order descending by officer within the same subtotal. Code enclosed. How would I do this
data have;
format setup_date date9.;
infile datalines;
input setup_date date9. Division $ Officer $ cnt;
return;
datalines;
1Jun2018 Central Smith 1
10Jun2018 Central Smith 1
10Jul2018 Central Smith 1
20Jun2018 Central Smith 1
11Jun2018 Central Shelton 1
1May2018 Central Baldwin 1
16May2018 Central Stone 1
12May2018 Central Grant 1
14May2018 Central Grant 1
1Sep2018 Central Jones 1
11Apr2019 Atlantic James 1
17Apr2019 Atlantic James 1
19Apr2019 Atlantic Smith 1
1Feb2019 Atlantic Doglass 1
14Feb2019 Atlantic Shane 1
15Feb2019 Atlantic Shane 1
16Feb2019 Atlantic Shane 1
17Feb2019 Atlantic Shane 1
;run;
proc sql;
create table report_data as
select *,
sum(cnt) as div_cnt_sum
from have
group by division;
;quit;
proc report data=report_data wrap style(summary)=Header;
columns div_cnt_sum Division WiderDivision Officer setup_date cnt ;
define div_cnt_sum / group descending noprint ;
define Division / group noprint;
define WiderDivision / 'Division' computed;
define Officer /group ;
define setup_date / across format=year4. order=internal '';
define cnt / sum f=comma6. 'Row Tot';
break after Division / summarize;
rbreak after / summarize;
compute WiderDivision / character length=25; /* specify the widerness */
WiderDivision = Division;
endcomp;
compute after;
WiderDivision='Grand Total';
endcomp;
run;
I want to keep the overall Division and sort descending by officer
You had to precompute the division sum in order to order by the group sum. Now you want to additionally order by the group sum over officer within division. In order (pun intended) to do so you will need to follow the same approach. Precompute the sums of each division officer by group combination and use them in another noprint column of the report.
Example:
data have;
format setup_date date9.;
infile datalines;
input setup_date date9. Division $ Officer $ cnt;
datalines;
1Jun2018 Central Smith 1
10Jun2018 Central Smith 1
10Jul2018 Central Smith 1
20Jun2018 Central Smith 1
11Jun2018 Central Shelton 1
1May2018 Central Baldwin 1
16May2018 Central Stone 1
12May2018 Central Grant 1
14May2018 Central Grant 1
1Sep2018 Central Jones 1
11Apr2019 Atlantic James 1
17Apr2019 Atlantic James 1
19Apr2019 Atlantic Smith 1
1Feb2019 Atlantic Doglass 1
14Feb2019 Atlantic Shane 1
15Feb2019 Atlantic Shane 1
16Feb2019 Atlantic Shane 1
17Feb2019 Atlantic Shane 1
;
* nested queries for computing sums of two different by groups;
* grouping by division is the nested sub-query and a new outer scope
* computes the sums for division,officer;
proc sql;
create table report_data as
select *,
sum(cnt) as div_off_cnt_sum
from
(
select *,
sum(cnt) as div_cnt_sum
from have
group by division
)
group by division, officer
;
quit;
proc report data=report_data wrap style(summary)=Header;
columns
div_cnt_sum /* to be used for ordering by group sum */
Division
WiderDivision
div_off_cnt_sum /* officer within division sum(cnt) */
Officer
setup_date
cnt
;
/* precomputed group sums for custom ordering by totals when /across in effect */
define div_cnt_sum / group descending noprint;
define div_off_cnt_sum / group descending noprint;
define Division / group noprint;
define WiderDivision / 'Division' computed;
define Officer / group descending;
define setup_date / across format=year4. order=internal '';
define cnt / sum f=comma6. 'Row Tot';
break after Division / summarize;
rbreak after / summarize;
compute WiderDivision / character length=25;
WiderDivision = Division;
endcomp;
compute after;
WiderDivision='Grand Total';
endcomp;
run;
Related
How do I select a random row from the database based on the probability chance assigned to each row.
Example:
Make Chance Value
ALFA ROMEO 0.0024 20000
AUDI 0.0338 35000
BMW 0.0376 40000
CHEVROLET 0.0087 15000
CITROEN 0.016 15000
........
How do I select random make name and its value based on the probability it has to be chosen.
Would a combination of rand() and ORDER BY work? If so what is the best way to do this?
You can do this by using rand() and then using a cumulative sum. Assuming they add up to 100%:
select t.*
from (select t.*, (#cumep := #cumep + chance) as cumep
from t cross join
(select #cumep := 0, #r := rand()) params
) t
where #r between cumep - chance and cumep
limit 1;
Notes:
rand() is called once in a subquery to initialize a variable. Multiple calls to rand() are not desirable.
There is a remote chance that the random number will be exactly on the boundary between two values. The limit 1 arbitrarily chooses 1.
This could be made more efficient by stopping the subquery when cumep > #r.
The values do not have to be in any particular order.
This can be modified to handle chances where the sum is not equal to 1, but that would be another question.
I was hoping if you can help me.
I am in a situation where I first need to do a Count Distinct of Funds and Group By Policies. Once I have done that, I need to put the count of policies in Range of Number of Funds.
This is the data available, where you can see the policies and different funds linked to it
**PolicyNum Fund**
1201 AB
1202 AC
1203 AB
1203 AC
1203 AD
1204 AB
1204 BC
1204 AC
1204 AD
1204 AE
1204 AF
Now I need to do a Count Distinct of Fund Grouped by Policy.
I have used this query to do that:
select fv, policy, count(distinct fv.fund)
from policy_fund fv
group by fv.policy
order by count(distinct fv.fund) desc
After using the above code, the following would come up
This is a view where you can see the number of funds linked to each policy
**Policy No. of Funds**
1201 1
1202 1
1203 3
1204 6
Now, the problem part, I want to reach to this, which is the Range of Number of Funds and how many policies fall under that range of funds:
Help required to achieve this view of Range of Number of Funds and how many policies are present in each range
**Range of Number of funds Number of policies**
0 to 1 2
2 to 3 1
4 to 5 0
5 to 6 1
You can left join your query to a derived table defining the ranges on the count being in the range and then group by the range and count the policies.
SELECT r.l || ' to ' || r.u "RANGE",
count(p) "COUNT"
FROM (SELECT 0 l,
1 u
FROM dual
UNION ALL
SELECT 2 l,
3 u
FROM dual
UNION ALL
SELECT 4 l,
5 u
FROM dual
UNION ALL
SELECT 6 l,
7 u
FROM dual) r
LEFT JOIN (SELECT fv.policy p,
count(distinct fv.fund) cof
FROM policy_fund fv
GROUP BY fv.policy) fpp
ON fpp.cof >= r.l
AND fpp.cof <= r.u
GROUP BY r.l,
r.u
ORDER BY r.l,
r.u;
db<>fiddle
I'm searching for a measure to utilize within SSAS Tabular model that will me to perform dynamic ranking that will automatically update the associated rank value based on filters and slicer values that are applied.
I am not in this kind of scearios : PowerPivot DAX - Dynamic Ranking Per Group (Min Per Group)
The difference is the following, my data are not in the same table :
I have a fact table like this :
-------------------------------------------------------------------------------------
ClientID | ProductID | Transaction Date | Sales
------------------------------------------------------------------------------------
C1 P3 1/1/2012 $100
C2 P1 8/1/2012 $150
C3 P4 9/1/2012 $200
C1 P2 3/5/2012 $315
C2 P2 9/5/2012 $50
C3 P2 12/9/2012 $50
------------------------------------------------------------------------------------
A Customer table
-------------------------------------------------------------------------------------
ClientID | ClientCountry |
C1 France
C2 France
C3 Germany
------------------------------------------------------------------------------------
...and also a Product table
-------------------------------------------------------------------------------------
ProductID | ProductSubCategory |
P1 SB1
P2 SB1
P3 SB2
P4 SB3
------------------------------------------------------------------------------------
So here is my visualization pivot table :
-------------------------------------------------------------------------------------
ProductSubCategory | Sales
SB1 565 (150 + 315 + 50 + 50)
SB2 100
SB3 200
And the measure I'm looking for should perform like this :
-------------------------------------------------------------------------------------
ProductSubCategory | Sales | Rank
SB1 565 (150 + 315 + 50 + 50) 1
SB2 100 3
SB3 200 2
...simple, I browse my cube into Excel, put the ProductSubCategory in line, sum of Sales and expect my measure gives me correct ranking by ProductSubCategory.
Now, scenario also includes using a slicer on ClientCountry.
So when I select 'France', I expect my measure gives me an adapted ranking, only including ProductSubCategory for Clients living in France (so C1 and C2).
I tried a lot of solutions but without any result. Has anyone and idea with this kind of scenario ?
I greatly appreciate your help with this!
Thank's all
For example if you take the following example into consideration.
100.00 - Original Number
33.33 - 1st divided by 3
33.33 - 2nd divided by 3
33.33 - 3rd divided by 3
99.99 - Is the sum of the 3 division outcomes
But i want it to match the original 100.00
One way that i saw it could be done was by taking the original number minus the first two divisions and the result would be my third number. Now if i take those 3 numbers i get my original number.
100.00 - Original Number
33.33 - 1st divided by 3
33.33 - 2nd divided by 3
33.34 - 3rd number
100.00 - Which gives me my original number correctly. (33.33+33.33+33.34 = 100.00)
Is there a formula for this either in Oracle PL/SQL or a function or something that could be implemented?
Thanks in advance!
This version takes precision as a parameter as well:
with q as (select 100 as val, 3 as parts, 2 as prec from dual)
select rownum as no
,case when rownum = parts
then val - round(val / parts, prec) * (parts - 1)
else round(val / parts, prec)
end v
from q
connect by level <= parts
no v
=== =====
1 33.33
2 33.33
3 33.34
For example, if you want to split the value among the number of days in the current month, you can do this:
with q as (select 100 as val
,extract(day from last_day(sysdate)) as parts
,2 as prec from dual)
select rownum as no
,case when rownum = parts
then val - round(val / parts, prec) * (parts - 1)
else round(val / parts, prec)
end v
from q
connect by level <= parts;
1 3.33
2 3.33
3 3.33
4 3.33
...
27 3.33
28 3.33
29 3.33
30 3.43
To apportion the value amongst each month, weighted by the number of days in each month, you could do this instead (change the level <= 3 to change the number of months it is calculated for):
with q as (
select add_months(date '2013-07-01', rownum-1) the_month
,extract(day from last_day(add_months(date '2013-07-01', rownum-1)))
as days_in_month
,100 as val
,2 as prec
from dual
connect by level <= 3)
,q2 as (
select the_month, val, prec
,round(val * days_in_month
/ sum(days_in_month) over (), prec)
as apportioned
,row_number() over (order by the_month desc)
as reverse_rn
from q)
select the_month
,case when reverse_rn = 1
then val - sum(apportioned) over (order by the_month
rows between unbounded preceding and 1 preceding)
else apportioned
end as portion
from q2;
01/JUL/13 33.7
01/AUG/13 33.7
01/SEP/13 32.6
Use rational numbers. You could store the numbers as fractions rather than simple values. That's the only way to assure that the quantity is truly split in 3, and that it adds up to the original number. Sure you can do something hacky with rounding and remainders, as long as you don't care that the portions are not exactly split in 3.
The "algorithm" is simply that
100/3 + 100/3 + 100/3 == 300/3 == 100
Store both the numerator and the denominator in separate fields, then add the numerators. You can always convert to floating point when you display the values.
The Oracle docs even have a nice example of how to implement it:
CREATE TYPE rational_type AS OBJECT
( numerator INTEGER,
denominator INTEGER,
MAP MEMBER FUNCTION rat_to_real RETURN REAL,
MEMBER PROCEDURE normalize,
MEMBER FUNCTION plus (x rational_type)
RETURN rational_type);
Here is a parameterized SQL version
SELECT COUNT (*), grp
FROM (WITH input AS (SELECT 100 p_number, 3 p_buckets FROM DUAL),
data
AS ( SELECT LEVEL id, (p_number / p_buckets) group_size
FROM input
CONNECT BY LEVEL <= p_number)
SELECT id, CEIL (ROW_NUMBER () OVER (ORDER BY id) / group_size) grp
FROM data)
GROUP BY grp
output:
COUNT(*) GRP
33 1
33 2
34 3
If you edit the input parameters (p_number and p_buckets) the SQL essentially distributes p_number as evenly as possible among the # of buckets requested (p_buckets).
I've solved this problem yesterday by subtracting 2 of 3 parts from the starting number, e.g. 100 - 33.33 - 33.33 = 33.34 and the result of summing it up is still 100.
I have table with some positive integer numbers
n
----
1
2
5
10
For each row of this table I want values cos(cos(...cos(0)..)) (cos is applied n times) to be calculated by means of SQL statement (PL/SQL stored procedures and functions are not allowed):
n coscos
--- --------
1 1
2 0.540302305868
5 0.793480358743
10 0.731404042423
I can do this in Oracle 11g by using recursive queries.
Is it possible to do the same in Oracle 10g ?
The MODEL clause can solve this:
Test data:
create table test1(n number unique);
insert into test1 select * from table(sys.odcinumberlist(1,2,5,10));
commit;
Query:
--The last row for each N has the final coscos value.
select n, coscos
from
(
--Set each value to the cos() of the previous value.
select * from
(
--Each value of N has N rows, with value rownumber from 1 to N.
select n, rownumber
from
(
--Maximum number of rows needed (the largest number in the table)
select level rownumber
from dual
connect by level <= (select max(n) from test1)
) max_rows
cross join test1
where max_rows.rownumber <= test1.n
order by n, rownumber
) n_to_rows
model
partition by (n)
dimension by (rownumber)
measures (0 as coscos)
(
coscos[1] = cos(0),
coscos[rownumber > 1] = cos(coscos[cv(rownumber)-1])
)
)
where n = rownumber
order by n;
Results:
N COSCOS
1 1
2 0.54030230586814
5 0.793480358742566
10 0.73140404242251
Let the holy wars begin:
Is this query a good idea? I wouldn't run this query in production, but hopefully it is a useful demonstration that any problem can be solved with SQL.
I've seen literally thousands of hours wasted because people are afraid to use SQL. If you're heavily using a database it is foolish to not use SQL as your primary programming language. It's good to occasionally spend a few hours to test the limits of SQL. A few strange queries is a small price to pay to avoid the disastrous row-by-row processing mindset that infects many database programmers.
Using WITH FUNCTION(Oracle 12c):
WITH FUNCTION coscos(n INT) RETURN NUMBER IS
BEGIN
IF n > 1
THEN RETURN cos(coscos(n-1));
ELSE RETURN cos(0);
END IF;
END;
SELECT n, coscos(n)
FROM t;
db<>fiddle demo
Output:
+-----+-------------------------------------------+
| N | COSCOS(N) |
+-----+-------------------------------------------+
| 1 | 1 |
| 2 | .5403023058681397174009366074429766037354 |
| 5 | .793480358742565591826054230990284002387 |
| 10 | .7314040424225098582924268769524825209688 |
+-----+-------------------------------------------+