How can I use a specific value of a field 'ranking' from table 'course' by 'course_id' in a function? I need to return 1 if the value given by a parameter is higher and 0 if the value is lower. So, I need to get the data from the table somehow based on course_id as a parameter.
CREATE OR ALTER FUNCTION dbo.f_rank (#course_id INT, #user_ranking INT)
RETURNS INT
AS
BEGIN
RETURN
CASE
WHEN #course_id.ranking > #user_ranking THEN 1
ELSE 0
END
END
After the function returns 0 or 1 I need to display:
If on call function returns 1 then display ‘Ranking of <course_name> is above score’, else ‘Ranking of <course_name> is below score’.
Sample data:
course_id | course_name | ranking
1 | GIT | 10
2 | CSS | 2
3 | C++ | 6
I need to compare a ranking of course_id = 1 for example which is 10 with the random number given as a parameter. course_id is also given as a parameter.
For example:
If the user chooses as input params (course_id = 1 and user_ranking = 5)
Expected result:
'Ranking of GIT is above score' - if function returns 1
'Ranking of GIT is below score' - if function returns 0
I assume that you probably want something like this (no expected results were given on request):
--Note you will have to DROP your old function if already exists as a scalar function first.
--You cannot ALTER a scalar function to be a table function.
CREATE OR ALTER FUNCTION dbo.f_rank (#course_id INT, #user_ranking INT)
RETURNS table
AS RETURN
SELECT CONVERT(bit, CASE WHEN C.ranking > #user_ranking THEN 1 ELSE 0 END) AS SomeColumnAlias --Obviusly give this a proper name
FROM dbo.Course C
WHERE C.Course_id = #course_id;
GO
As I mentioned, I use an inline table value function, as this will likely be more performant, and you don't mention your version of SQL Server (so don't know if you are on 2019, and so could use an inlineable scalar function).
Related
I'm creating a new table and carrying over several columns from a previous table. One of the new fields that I need to create is a flag that will have values 0 or 1 and value needs to be determined based on 6 previous fields in the table.
The 6 previous columns have preexisting values of 0 or 1 stored for each one. This new field needs to check whether any of the 6 columns have 1 and if so set the flag to 0. If there is 0 in all 6 fields then set itself to 1.
Hopefully this makes sense. How can I get this done in oracle? I assume a case statement and some sort of forloop?
You can use greatest() function: GREATEST
create table t_new
as
select
case when greatest(c1,c2,c3,c4,c5,c6)=1 -- at least one of them contains 1
then 0
else 1
end c_new
from t_old;
Or even shorter:
create table t_new
as
select
1-greatest(c1,c2,c3,c4,c5,c6) as c_new
from t_old;
In case of greatest = 1, (1-1)=0, otherwise (1-0)=1
You can use a virtual column with a case expression; something like:
flag number generated always as (
case when val_1 + val_2 + val_3 + val_4 + val_5 + val_6 = 0 then 1 else 0 end
) virtual
db<>fiddle
or the same thing with greatest() as #Sayan suggested.
Using a virtual column means the flag will be right for newly-inserted rows, and if any of the other values are updated; you won't have to recalculate or update the flag column manually.
I've assumed the other six columns can't be null and are constrained to only be 0 or 1, as the question suggests. If they can be null you can add nvl() or coalesce() to each term in the calculation.
Version B C D output Account number Balance
----------------------------------------------------------------
2 1283 1303 0 4071 1 10
2 1283 1304 0 4072 2 20
3 1283 4068 1303 4071 1 30
3 1283 4069 1304 4072 4 40
4 1283 4071 4068 4071 5 -50
4 1283 4072 4069 4072 2 90
Version,B,C,D are columns present in all_details table.Column "Output" is the desired output i wish to achieve and i wish to store all the above columns in a table until output
How in the first line of Output i placed 4071 is
1) i took 1303 in column C and then looked into column D
2) then it is again referring to 4068 in column C
3) Then i took 4068 and it is refering to 4071 in column C its like a linkage
I'm using column B as it is related information to other columns.
i need a another column output so that i can identify related links and sum up balances.For example
I'll sum up related links 1303,4068,4071 balances Group by Output i'll get 10+40 = 50 for account 1 and -50 for account 5 corresponding to 4071
So from what I am understanding, you need to have something that recursively finds the last linked number, for a given number. These links exists between your Column C and Column D.
I am assuming that Column B is a grouping type number, but I am certain that you might be able to figure out how to adjust the function to return what you need it to return.
What you will need to do is build a SQL function that will iterate through your table and follow the links until it can find no more links. Details and example follow below as to how one can do that.
So firstly the building up of the sample data, as you presented them (NOTE this creates a table on your database, be careful!)
-- Building testing data
if exists (select 1
from sys.objects
where name = 'versionhistory'
and type = 'U')
begin
drop table versionhistory
end
create table versionhistory
( versionno int,
colB int,
colC int, -- Current Value
colD int ) -- Previous value
insert versionhistory
( versionno,
colB,
colC,
colD )
values -- B C D
( 2, 1283, 1303, 0),
( 2, 1283, 1304, 0),
( 3, 1283, 4068, 1303),
( 3, 1283, 4069, 1304),
( 4, 1283, 4071, 4068),
( 4, 1283, 4072, 4069)
go
Now we need to create the function that will iterate through the tables records, follow the links between the two columns until it can find no more links, then return the last linked value.
-- Create the function that will get the last entry for a give number
if exists (select 1 from sys.objects where name = 'f_get_last_ver' and type = 'FN')
begin
drop function f_get_last_ver
end
go
create function f_get_last_ver
( #colB int,
#colC int )
returns int
as
begin
declare #nextColC int,
#lastColC int
-- Initial check if there is a 'next' version
select #nextColC = isnull((select vh.colC
from versionhistory vh
where vh.colB = #colB
and vh.colD = #colC), 0)
-- This will handle the loop until there are no more entries linked
while isnull(#nextColC, 0) <> 0
begin
-- Store our last value for return purposes
select #lastColC = #nextColC
-- Get our next version number that is linked
select #nextColC = isnull((select vh.colC
from versionhistory vh
where vh.colB = #colB
and vh.colD = #nextColC), 0)
end
-- Return our last value, otherwise if no linkage was found, return the input
return isnull(#lastColC, #colC)
end
go
And finally here you have the usage of the function.
-- Example usage
select dbo.f_get_last_ver(1283, 1303), -- returns 4071
dbo.f_get_last_ver(1283, 1304) -- returns 4072
Remember to clean up your database when done testing/experimenting with this
I hope that the comments inside the function will explain enough of what is happening, but if something is unclear, ask away.
P.S. Please rename your columns and variables in your actual code into more meaningful column names and variables, since B, C, D doesn't really explain what they are used for.
I have a query being executed every X milliseconds. As a part of result I would like to have alternating true/false flag. This flag should change whenever the query is executed again.
Example
Sample query: select 1, <<boolean alternator>>;
1st execution returns: 1 | true
2nd execution returns: 1 | false
3rd execution returns: 1 | true
and so on. It does not matter if it returns true or false for the first time.
For cases when X is odd number of seconds I have the following solution:
select
mod(right(extract(epoch from current_timestamp)::int::varchar,1)::int, 2) = 0
as alternator
This extracts last digit from epoch and then test if this is even number. Because X is defined as odd number, this test will alternate from one execution to another.
What would work in the same way when X is different - even or not in whole seconds? I would like to make it work for X like 500ms, 1200ms, 2000ms, ...
Note: I plan to use this with PostgreSQL.
I suggest a dedicated SEQUENCE.
CREATE SEQUENCE tf_seq MINVALUE 0 MAXVALUE 1 START 0 CYCLE;
Each call with nextval() returns 0 / 1 alternating. You can cast to boolean:
0::bool = FALSE
1::bool = TRUE
So:
SELECT nextval('tf_seq'::regclass)::int::bool;
To keep other roles from messing with the state of the sequence, only
GRANT USAGE ON SEQUENCE tf_seq TO $dedicated_role;. Run your query as that role or create a function with SECURITY DEFINER and ALTER FUNCTION foo() OWNER TO $dedicated_role;
Or, simpler yet, just make it the column default and completely ignore it in your inserts:
ALTER TABLE foo ALTER COLUMN bool_col
SET DEFAULT nextval('tf_seq'::regclass)::int::bool;
You need to grant USAGE on the sequence to roles that can insert.
Every next row gets the flipped value automatically.
The usual notes for sequences apply. Like, if you roll back an INSERT, the sequence stays flipped. Sequence states are never rolled back.
A temporary table can save the boolean state:
create temporary table t (b boolean);
insert into t (b) values (true);
with u as (
update t
set b = not b
returning b
)
select 1, b
from t;
I would do the same (boolean flipping with not b) with a pltcl function.
Advantage: variable is cached in your session tcl interpreter. Disadvantage: Any pl??? function has a call overhead.
Test table, insert some values:
strobel=# create table test (i integer);
CREATE TABLE
strobel=# insert into test (select * from generate_series(1, 10));
INSERT 0 10
The function:
create or replace function flipper () returns boolean as $$
if {![info exists ::flag]} {set ::flag false}
return [set ::flag [expr {! $::flag}]]
$$ language pltcl volatile;
The test:
strobel=# select *, flipper() from test;
i | flipper
----+---------
1 | t
2 | f
3 | t
4 | f
5 | t
I have a hierarchical table of Regions and sub-regions, and I need to list a tree of regions and sub-regions (which is easy), but also, I need a column that displays, for each region, all the ids of it's sub regions.
For example:
id name superiorId
-------------------------------
1 RJ NULL
2 Tijuca 1
3 Leblon 1
4 Gavea 2
5 Humaita 2
6 Barra 4
I need the result to be something like:
id name superiorId sub-regions
-----------------------------------------
1 RJ NULL 2,3,4,5,6
2 Tijuca 1 4,5,6
3 Leblon 1 null
4 Gavea 2 4
5 Humaita 2 null
6 Barra 4 null
I have done that by creating a function that retrieves a STUFF() of a region row,
but when I'm selecting all regions from a country, for example, the query becomes really, really slow, since I execute the function to get the region sons for each region.
Does anybody know how to get that in an optimized way?
The function that "retrieves all the ids as a row" is:
I meant that the function returns all the sub-region's ids as a string, separated by a comma.
The function is:
CREATE FUNCTION getSubRegions (#RegionId int)
RETURNS TABLE
AS
RETURN(
select stuff((SELECT CAST( wine_reg.wine_reg_id as varchar)+','
from (select wine_reg_id
, wine_reg_name
, wine_region_superior
from wine_region as t1
where wine_region_superior = #RegionId
or exists
( select *
from wine_region as t2
where wine_reg_id = t1.wine_region_superior
and (
wine_region_superior = #RegionId
)
) ) wine_reg
ORDER BY wine_reg.wine_reg_name ASC for XML path('')),1,0,'')as Sons)
GO
When we used to make these concatenated lists in the database we took a similar approach to what you are doing at first
then when we looked for speed
we made them into CLR functions
http://msdn.microsoft.com/en-US/library/a8s4s5dz(v=VS.90).aspx
and now our database is only responsible for storing and retrieving data
this sort of thing will be in our data layer in the application
I must apply a certain transformation fn(argument). Here argument is equal to value, but not when it is negative. When you get a first negative value, then you "wait" until it sums up with consecutive values and this sum becomes positive. Then you do fn(argument). See the table I want to get:
value argument
---------------------
2 2
3 3
-10 0
4 0
3 0
10 7
1 1
I could have summed all values and apply fn to the sum, but fn can be different for different rows and it is essential to know the row number to choose a concrete fn.
As want a Postgres SQL solution, looks like window functions fit, but I am not experienced enough to write expression that does that yet. In fact, I am new to "thinking in sql", unfortunately. I guess that can be easily done in an imperative way, but I do not want to write a stored procedure yet.
I suppose I'm late, but this may help someone:
select
value,
greatest(0, value) as argument
from your_table;
This doesn't really fit any of the predefined aggregation functions. You probably need to write your own. Note that in postgresql, aggregate functions can be used as window functions, and in fact that is the only way to write window functions in anything other than C, as of 9.0.
You can write a function that tracks the state of "summing" the values, except that it always returns the input value if the current "sum" is positive, and just keeps adding when the "sum" is negative. Then you simply need to take the greater of either this sum or zero. To whit:
-- accumulator function: first arg is state, second arg is input
create or replace function ouraggfunc(int, int)
returns int immutable language plpgsql as $$
begin
raise info 'ouraggfunc: %, %', $1, $2; -- to help you see what's going on
-- get started by returning the first value ($1 is null - no state - first row)
if $1 is null then
return $2;
end if;
-- if our state is negative, we're summing until it becomes positive
-- otherwise, we're just returning the input
if $1 < 0 then
return $1 + $2;
else
return $2;
end if;
end;
$$;
You need to create an aggregate function to invoke this accumulator:
create aggregate ouragg(basetype = int, sfunc = ouraggfunc, stype = int);
This defines that the aggregate takes integers as input and stores its state as an integer.
I copied your example into a table:
steve#steve#[local] =# create table t(id serial primary key, value int not null, argument int not null);
NOTICE: CREATE TABLE will create implicit sequence "t_id_seq" for serial column "t.id"
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t_pkey" for table "t"
CREATE TABLE
steve#steve#[local] =# copy t(value, argument) from stdin;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 2 2
>> 3 3
>> -10 0
>> 4 0
>> 3 0
>> 10 7
>> 1 1
>> \.
And you can now have those values produced by using the aggregate function with a window clause:
steve#steve#[local] =# select value, argument, ouragg(value) over(order by id) from t;
INFO: ouraggfunc: <NULL>, 2
INFO: ouraggfunc: 2, 3
INFO: ouraggfunc: 3, -10
INFO: ouraggfunc: -10, 4
INFO: ouraggfunc: -6, 3
INFO: ouraggfunc: -3, 10
INFO: ouraggfunc: 7, 1
value | argument | ouragg
-------+----------+--------
2 | 2 | 2
3 | 3 | 3
-10 | 0 | -10
4 | 0 | -6
3 | 0 | -3
10 | 7 | 7
1 | 1 | 1
(7 rows)
So as you can see, the final step is that you need to take the output of the function if it is positive, or zero. This can be done by wrapping the query, or writing a function to do that:
create function positive(int) returns int immutable strict language sql as
$$ select case when $1 > 0 then $1 else 0 end $$;
and now:
select value, argument, positive(ouragg(value) over(order by id)) as raw_agg from t
This produces the arguments for the function that you specified in the question.