Is it possible to perform a bitwise group function? - sql

I have a field in a table which contains bitwise flags. Let's say for the sake of example there are three flags: 4 => read, 2 => write, 1 => execute and the table looks like this*:
user_id | file | permissions
-----------+--------+---------------
1 | a.txt | 6 ( <-- 6 = 4 + 2 = read + write)
1 | b.txt | 4 ( <-- 4 = 4 = read)
2 | a.txt | 4
2 | c.exe | 1 ( <-- 1 = execute)
I'm interested to find all users who have a particular flag set (eg: write) on ANY record. To do this in one query, I figured that if you OR'd all the user's permissions together you'd get a single value which is the "sum total" of their permissions:
user_id | all_perms
-----------+-------------
1 | 6 (<-- 6 | 4 = 6)
2 | 5 (<-- 4 | 1 = 5)
*My actual table isn't to do with files or file permissions, 'tis but an example
Is there a way I could perform this in one statement? The way I see it, it's very similar to a normal aggregate function with GROUP BY:
SELECT user_id, SUM(permissions) as all_perms
FROM permissions
GROUP BY user_id
...but obviously, some magical "bitwise-or" function instead of SUM. Anyone know of anything like that?
(And for bonus points, does it work in oracle?)

MySQL:
SELECT user_id, BIT_OR(permissions) as all_perms
FROM permissions
GROUP BY user_id

Ah, another one of those questions where I find the answer 5 minutes after asking... Accepted answer will go to the MySQL implementation though...
Here's how to do it with Oracle, as I discovered on Radino's blog
You create an object...
CREATE OR REPLACE TYPE bitor_impl AS OBJECT
(
bitor NUMBER,
STATIC FUNCTION ODCIAggregateInitialize(ctx IN OUT bitor_impl) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateIterate(SELF IN OUT bitor_impl,
VALUE IN NUMBER) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateMerge(SELF IN OUT bitor_impl,
ctx2 IN bitor_impl) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateTerminate(SELF IN OUT bitor_impl,
returnvalue OUT NUMBER,
flags IN NUMBER) RETURN NUMBER
)
/
CREATE OR REPLACE TYPE BODY bitor_impl IS
STATIC FUNCTION ODCIAggregateInitialize(ctx IN OUT bitor_impl) RETURN NUMBER IS
BEGIN
ctx := bitor_impl(0);
RETURN ODCIConst.Success;
END ODCIAggregateInitialize;
MEMBER FUNCTION ODCIAggregateIterate(SELF IN OUT bitor_impl,
VALUE IN NUMBER) RETURN NUMBER IS
BEGIN
SELF.bitor := SELF.bitor + VALUE - bitand(SELF.bitor, VALUE);
RETURN ODCIConst.Success;
END ODCIAggregateIterate;
MEMBER FUNCTION ODCIAggregateMerge(SELF IN OUT bitor_impl,
ctx2 IN bitor_impl) RETURN NUMBER IS
BEGIN
SELF.bitor := SELF.bitor + ctx2.bitor - bitand(SELF.bitor, ctx2.bitor);
RETURN ODCIConst.Success;
END ODCIAggregateMerge;
MEMBER FUNCTION ODCIAggregateTerminate(SELF IN OUT bitor_impl,
returnvalue OUT NUMBER,
flags IN NUMBER) RETURN NUMBER IS
BEGIN
returnvalue := SELF.bitor;
RETURN ODCIConst.Success;
END ODCIAggregateTerminate;
END;
/
...and then define your own aggregate function
CREATE OR REPLACE FUNCTION bitoragg(x IN NUMBER) RETURN NUMBER
PARALLEL_ENABLE
AGGREGATE USING bitor_impl;
/
Usage:
SELECT user_id, bitoragg(permissions) FROM perms GROUP BY user_id

And you can do a bitwise or with...
FUNCTION BITOR(x IN NUMBER, y IN NUMBER)
RETURN NUMBER
AS
BEGIN
RETURN x + y - BITAND(x,y);
END;

You would need to know the possible permission components (1, 2 and 4) apriori (thus harder to maintain), but this is pretty simple and would work:
SELECT user_id,
MAX(BITAND(permissions, 1)) +
MAX(BITAND(permissions, 2)) +
MAX(BITAND(permissions, 4)) all_perms
FROM permissions
GROUP BY user_id

I'm interested to find all users who
have a particular flag set (eg: write)
on ANY record
What's wrong with simply
SELECT DISTINCT User_ID
FROM Permissions
WHERE permissions & 2 = 2

Related

custom aggregate function in postgres return NULL value

in order to create a more complex custom aggregate function, i followed first this amazing tutorial.
Here the data i use :
create table entries(
id serial primary key,
amount float8 not null
);
select setseed(0);
insert into entries(amount)
select (2000 * random()) - 1000
from generate_series(1, 1000000);
So I have this table :
id | amount | running_total
---------+-----------------------+--------------------
1 | -462.016298435628 | -462.016298435628
2 | 162.440904416144 | -299.575394019485
3 | -820.292402990162 | -1119.86779700965
4 | -866.230697371066 | -1986.09849438071
5 | -495.30001822859 | -2481.3985126093
6 | 772.393747232854 | -1709.00476537645
7 | -323.866365477443 | -2032.87113085389
8 | -856.917716562748 | -2889.78884741664
9 | 285.323366522789 | -2604.46548089385
10 | -867.916810326278 | -3472.38229122013
-- snip --
And I would like the max of the running_total column
(I know I can do do it without a new aggregate function, but it's for the demonstration)
So i've made this aggregate function
create or replace function grt_sfunc(agg_state point, el float8)
returns point
immutable
language plpgsql
as $$
declare
greatest_sum float8;
current_sum float8;
begin
current_sum := agg_state[0] + el;
greatest_sum := 40;
/*if agg_state[1] < current_sum then
greatest_sum := current_sum;
else
greatest_sum := agg_state[1];
end if;*/
return point(current_sum, greatest_sum);
/*return point(3.14159, 0);*/
end;
$$;
create or replace function grt_finalfunc(agg_state point)
returns float8
immutable
strict
language plpgsql
as $$
begin
return agg_state[0];
end;
$$;
create or replace aggregate greatest_running_total (float8)
(
sfunc = grt_sfunc,
stype = point,
finalfunc = grt_finalfunc
);
Normally it sould work, but in the end, it gives me a null result :
select greatest_running_total(amount order by id asc)
from entries;
id | running_total
---------+---------------
1 | [NULL]
I tried to change the type of the data, to check the 2 first aggregate functions separately, they are working well. Does someone could help me find a solution please ? :)
Thank you very much !
You need to set a non-NULL initcond for the aggregate. Presumably that would be (0,0), or maybe negative very large numbers for each? Or manually check for the agg_state being NULL.
Also, it seems like your grt_finalfunc should be returning subscript [1], not [0].
So, the solution was to add an initial condition. Indeed, without initial condition, the first value is considered as NULL :D (thank you #jjanes and #The Impaler)
So I corrected ma aggregated function :
create or replace aggregate greatest_running_total (float8)
(
sfunc = grt_sfunc,
stype = point,
finalfunc = grt_finalfunc,
initcond = '(0,0)'
);
And, indeed SQL indexes its tables from 1 and not from 0... Here was my second mistake,
Thank you very much !!

check if two values are present in a table with plsql in oracle sql

I'm trying to create a procedure, that checks if two values are present in a table.
The logic is as follows: Create a function called get_authority. This function takes two parameters (found in the account_owner table): cust_id and acc_id, and returns 1 (one), if the customer has the right to make withdrawals from the account, or 0 (zero), if the customer doesn't have any authority to the account. I'm writing plsql and using oracle live sql. I can't figure out how to handle the scenario where a customer has two accounts!
account_owner is seen here:
create or replace function get_authority(
p_cust_id in account_owner.cust_id%type,
p_acc_id in account_owner.acc_id%type
)
return varchar2
as
v_return number(1);
v_acc_id account_owner.acc_id%type;
v_cust_id account_owner.cust_id%type;
begin
for v_ in (select account_owner.cust_id,
account_owner.acc_id
from account_owner
where p_cust_id = cust_id)
LOOP
if p_cust_id = v_cust_id and p_acc_id = v_acc_id then
v_return := v_return + 1;
else
v_return := v_return + 0;
end if;
return v_return;
END LOOP;
end;
/
When I check for the cust_id I get the return 0 - but it should be 1??
select get_authority('650707-1111',123) from dual;
return:
GET_AUTHORITY('650707-1111',123)
0
What do I do wrong?
You got 0? How come; should be NULL.
v_return number(1);
so it is initially NULL. Later on, you're adding "something" to it, but - adding anything to NULL will be NULL:
SQL> select 25 + null as result from dual;
RESULT
----------
SQL>
Therefore, set its default value to 0 (zero):
v_return number(1) := 0;
Also, you declared two additional variables:
v_acc_id account_owner.acc_id%type;
v_cust_id account_owner.cust_id%type;
Then you compare them to values passed as parameters; as they are NULL, ELSE is executed.
Furthermore, there's a loop, but you don't do anything with it. If you meant that this:
for v_ in (select account_owner.cust_id,
(rewritten as for v_ in (select cust_id) evaluates to v_cust_id - it does not. Cursor variables are referred to as v_.cust_id (note the dot in between).
Also, if there's only one row per p_cust_id and p_acc_id, why do you use cursor FOR loop at all? To avoid no_data_found or too_many_rows? I wouldn't do that; yes, it fixes such "errors", but is confusing. You'd rather properly handle exceptions.
Here's what you might have done:
Sample data:
SQL> select * From account_owner;
ACCOW_ID CUST_ID ACC_ID
---------- ----------- ----------
1 650707-1111 123
2 560126-1148 123
3 650707-1111 5899
Function; if there are more rows per parameters' combination, max function will make sure that too_many_rows is avoided (as it bothers you). You don't really care what it returns - important is that select returns anything to prove that authority exists for that account.
SQL> create or replace function get_authority
2 (p_cust_id in account_owner.cust_id%type,
3 p_acc_id in account_owner.acc_id%type
4 )
5 return number
6 is
7 l_accow_id account_owner.accow_id%type;
8 begin
9 select max(o.accow_id)
10 into l_accow_id
11 from account_owner o
12 where o.cust_id = p_cust_id
13 and o.acc_id = p_acc_id;
14
15 return case when l_accow_id is not null then 1
16 else 0
17 end;
18 end;
19 /
Function created.
Testing:
SQL> select get_authority('650707-1111', 123) res_1,
2 get_authority('650707-1111', 5899) res_2
3 from dual;
RES_1 RES_2
---------- ----------
1 1
SQL>

Function returns max of whole table instead of someting particular - PLSQL

I got a PLSQL question related to my output.
Suppose i have a table like this:
Rates:
Company | Country | Level
AA | US | 5
BB | UK | 4
CC | FRANCE | 2
DD | FRANCE | 3
EE | US | 4
FF | UK | 5
I need to create a FUNCTION that gets as parameter the country and provides me it's maximal level.
this is what i wrote:
CREATE OR REPLACE
FUNCTION getMaxLevel(Country VARCHAR2)
RETURN NUMBER
IS
MaxLevel NUMBER;
BEGIN
SELECT max(R.Level) INTO MaxLevel
FROM Rates R
WHERE R.country=Country;
RETURN MaxLevel;
END getMaxLevel;
/
/*CALL TO CHECK*/
DECLARE
X NUMBER;
BEGIN
X:=getMaxLevel('FRANCE');
dbms_output.put_line(X);
END;
and my output is the maximal rate of whole Rates table which here for instance is 6, but i needed to get 3.
Will be thankful for finding my bug :)
Thanks!
You problem is the parameter name. I would recommend prefixing it with something like in:
CREATE OR REPLACE
FUNCTION getMaxLevel(
in_Country VARCHAR2
)
RETURN NUMBER IS
MaxLevel NUMBER;
BEGIN
SELECT max(R.Level) INTO MaxDegree
FROM Rates R
WHERE R.country = in_Country;
RETURN MaxLevel;
END getMaxLevel;
/
You think that Country in your expression:
R.country = Country
is referring to the parameter. But that is not how SQL scoping rules work. It is referring to R.country -- hence the non-sensical result.
You should give your parameter another name that the name of the column in the table, otherwise the condition in the WHERE clause is ambiguous.
In condition R.country = Country, the database thinks that the second Country refers to the (unqualified) column name, not to the parameter. This condition is always true (unless Country is null), and the query ends up returning the maximum level from the whole table.
CREATE OR REPLACE
FUNCTION getMaxLevel(pCountry VARCHAR2)
RETURN NUMBER
IS
MaxLevel NUMBER;
BEGIN
SELECT max(R.Level) INTO MaxLevel
FROM Rates R
WHERE R.country = pCountry;
RETURN MaxLevel;
END getMaxLevel;
/
/*CALL TO CHECK*/
DECLARE
X NUMBER;
BEGIN
X:=getMaxLevel('FRANCE');
dbms_output.put_line(X);
END;
Side note: there is a typo in the query, you meant SELECT .. INT0 MaxLevel instead of SELECT .. INT0 MaxDegree.
You forgot tо prefix the function parameter, this is the name of the function itself.
Rename the column level in the table (e.g. lvl), it is the key word.
Consider this working example:
create or replace function getMaxLevel (Country varchar2) return number is
begin
for r in (
select max (r.Lvl) MaxLevel
from Rates r
where R.country=getMaxLevel.Country
) loop return r.MaxLevel;
end loop;
raise program_error;
end getMaxLevel;
/
var x number
exec :x := getMaxLevel ('FRANCE');
X
-
3

Result from pipelined function, always will sorted as "written", or not?

Needed get renumbered result set, for example:
CREATE OR REPLACE TYPE nums_list IS TABLE OF NUMBER;
CREATE OR REPLACE FUNCTION generate_series(from_n INTEGER, to_n INTEGER, cycle_max INTEGER)
RETURN nums_list PIPELINED AS
cycle_iteration INTEGER := from_n;
BEGIN
FOR i IN from_n..to_n LOOP
PIPE ROW( cycle_iteration );
cycle_iteration := cycle_iteration + 1;
IF cycle_iteration > cycle_max THEN
cycle_iteration := from_n;
END IF;
END LOOP;
RETURN;
END;
SELECT * FROM TABLE(generate_series(1,10,3));
Question is: there is guarantee, that oracle always will return result in that order? :
1
2
3
1
2
3
1
2
3
1
or maybe sometimes result will unexpected ordered, like this:
1
1
1
1
2
2
....
?
Pipelining negates the need to build huge collections by piping rows
out of the function as they are created, saving memory and allowing
subsequent processing to start before all the rows are generated
pipelined-table-functions
This means, it will start processing the rows before get fetched completely and that's why you are seeing unpredictable order.

ORACLE: Parameter reference in WHERE doesn't work

I have created a simple static function in oracle 10g to get the reference of an object based on his pk.
STATIC FUNCTION getRef(nome IN VARCHAR2)
RETURN REF folder_typ IS
fl_r REF folder_typ := null;
BEGIN
SELECT REF(fl)
INTO fl_r
FROM folder_tab fl
WHERE fl.nome = nome;
RETURN fl_r;
END getRef;
This gives me an error because it could't fetch a row.
If insted of WHERE fl.nome = nome; I write WHERE fl.nome = 'folder1'; -- it works.
I think im not using the parameter in the right way.
How can I use it?
This is a scoping problem.
Here is my version of your set-up, extrapolated from what you posted. Note that the parameter name is different from the attribute name.
create or replace type folder_t as object
(name varchar2(128))
/
create table folders of folder_t
/
create or replace function getRef
(nome in varchar2)
return ref folder_t
is
fl_r REF folder_t;
begin
select ref(fl)
into fl_r
from folders fl
where fl.name = nome;
return fl_r;
end getRef;
/
As you can see, given this test data ...
SQL> insert into folders values (folder_t('temp'))
2 /
1 row created.
SQL> insert into folders values (folder_t('work'))
2 /
1 row created.
SQL>
... I can query the two REFs:
SQL> select getRef('temp') from dual
2 /
GETREF('TEMP')
--------------------------------------------------------------------------------
00002802091051432318864AF594741916D743E1291CF597373A4F4D7A93F159DA53A73FC0010372
2D0000
SQL> select getRef('work') from dual
2 /
GETREF('WORK')
--------------------------------------------------------------------------------
0000280209F31778C18D5740FBA0CB90929E1B6FBD1CF597373A4F4D7A93F159DA53A73FC0010372
2D0001
SQL>
But, if I change the function declaration so the parameter name is the same as the attribute name, this happens:
SQL> create or replace function getRef
2 (name in varchar2)
3 return ref folder_t
4 is
5 fl_r REF folder_t;
6 begin
7 select ref(fl)
8 into fl_r
9 from folders fl
10 where fl.name = name;
11
12 return fl_r;
13 end getRef;
14 /
Function created.
SQL> select getRef('temp') from dual
2 /
GETREF('TEMP')
------------------------------------------------------------
SQL>
The SQL engine applies scope from the table outwards. Because the unqualified NAME matches a column on the table it doesn't check to see that whether there is a parameter of that name too. This is why it is a good idea to give the parameters a distinct name. Persoanlly I favour the practice of prefixing parameters with a P_, so there's no chance of the paremeter clashing with local variables or object names.
Best practice is to always provide the scope for all variables, not just for those from table aliases:
FUNCTION getRef(nome IN VARCHAR2)
RETURN REF folder_typ IS
fl_r REF folder_typ := null;
BEGIN
SELECT REF(fl)
INTO fl_r
FROM folder_tab fl
WHERE fl.nome = getRef.nome;
RETURN fl_r;
END getRef;