Distribute budget over for ranked components in SQL - sql

Assume I have a budget of $10 (any integer) and I want to distribute it over records which have rank field with varying needs. Example:
rank Req. Fulfilled?
1 $3 Y
2 $4 Y
3 $2 Y
4 $3 N
Those ranks from 1 to 3 should be fulfilled because they are within budget. whereas, the one ranked 4 should not.
I want an SQL query to solve that.
Below is my initial script:
CREATE TABLE budget (
id VARCHAR (32),
budget INTEGER,
PRIMARY KEY (id));
CREATE TABLE component (
id VARCHAR (32),
rank INTEGER,
req INTEGER,
satisfied BOOLEAN,
PRIMARY KEY (id));
INSERT INTO budget (id,budget) VALUES ('1',10);
INSERT INTO component (id,rank,req) VALUES ('1',1,3);
INSERT INTO component (id,rank,req) VALUES ('2',2,4);
INSERT INTO component (id,rank,req) VALUES ('3',3,2);
INSERT INTO component (id,rank,req) VALUES ('4',4,3);
Thanks in advance for your help.
Lee

Well, the example you gave is fairly easy:
select rank, req,
sum(req) over(order by rank) < (select budget from budget where id = '1')
as fulfilled
from component
But this doesn't take into account:
there are 2 units left over from the budget that could be allocated to a further component with a lower requirement
budget is allocated to components rank-first, not sure that's what you meant
So if there was a component (id=5, rank=5, req=2) and that should be fulfilled, this isn't sufficient.
TBH I suspect a function to do the allocation is the best bet- should be quite easy to simply run through the result of a query ordered by "rank asc" and update the fulfilled column according to the current state.

Related

How to find rows with max value without aggregate functions SQLPLUS Oracle11g

I'm trying to find rows with max credit on my table ,
CREATE TABLE Course(
CourseNr INTEGER,
CourseTitel VARCHAR(60),
CourseTyp VARCHAR(10),
CourselenghtDECIMAL,
Credit DECIMAL,
PRIMARY KEY (CourseNr)
);
and there is more than one courses with max value. I dont want to use any default functions for that, any ideas?
Presumably, you want the rows with the maximum credit. A common method is to find any rows that have no larger credit:
select c.*
from course c
where c.credit >= all (select c2.credit from course c2);
Get the rows with Credit for which there don't exist any rows with greater Credit:
SELECT
c.*
FROM Course c
WHERE
NOT EXISTS (
SELECT 1 FROM Course WHERE Credit > c.Credit
)

How to select max price using multiple query

i have create tow table, table and data inserting value are given below :
create table product (
pid number(10) primary key,
pname varchar2(30)
);
INSERT INTO product values(100,'Apple');
INSERT INTO product values(101,'Banana');
INSERT INTO product values(102,'Pinaple');
INSERT INTO product values(103,'Orange');
create table purchase(
invid number(10) primary key,
pid number(10),
pprice number(10)
);
alter table purchase add(constraint pid_fk FOREIGN KEY (pid) references product(pid));
INSERT INTO purchase values(10,101,30);
INSERT INTO purchase values(11,103,35);
INSERT INTO purchase values(12,103,9);
INSERT INTO purchase values(13,103,22);
INSERT INTO purchase values(14,101,12);
now i have select table purchase show give data
invid pid pprice
10 101 30
11 103 35
12 103 9
13 103 22
14 101 12
now i want to be last insert price pid=103
now need show pprice=22
i have all ready done the task using two query such as:
select max(invid) from purchase where pid=103;
result is 22 and running next statement
select max(pprice) from purchase where invid=13;
show result =22
i want to show result 22 using running one statment,
Probably the most direct way is to use a subquery:
SELECT MAX(pprice)
FROM purchase
WHERE invid = (SELECT MAX(invid) FROM purchase WHERE pid = 103)
Side note, you don't need to MAX() pprice based on your sample data, but I left it that way in case you need it for your real data set.
The aggregate FIRST/LAST function is made exactly for this kind of task. Unfortunately it seems the vast majority of developers don't use it - either because they are not aware of its existence, or because they don't understand how it works, or for who knows what other reason.
https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions056.htm
The function allows for the case when the max(invid) (in your model) is not unique; in which case you must decide WHICH pprice to select. You can select the max, or min, or average pprice (or the sum for that matter), from all those that have pid = 103 and the max value in the invid column. Of course, if the max(invid) is unique (as it is in your problem), then all these functions will return just the pprice associated to that invid; but you must still use one of these aggregate functions, since that uniqueness is not known at parsing time, it is only known at execution time. min is conventionally used in this manner. So:
select min(pprice) keep (dense_rank last order by invid) as pprice
from ........
where pid = 103
;

Row returned by a query with ROWNUM and PRIMARY KEY column

I'm reading about 1 of the oracle pseudocolumns i.e
ROWNUM: which return number indicating the order in which oracle return the row.
I have encountered some behavior here,
Example used
1. Create Script:
CREATE TABLE CUSTOMERS(
ID INT NOT NULL,
NAME VARCHAR (20) NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR (25) ,
SALARY DECIMAL (18, 2),
PRIMARY KEY (ID)
);
2 Insert Script:
INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY)
VALUES (2, 'Max', 22, 'India', 4500.00 );
INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY)
VALUES (1, 'Maths', 22, 'US', 4500.00 );
select * from CUSTOMERS; executed in the same order as inserted above,
ID NAME AGE ADDRESS SALARY
------ ---- --- ------- ------
2 Max 22 India 4500.00
1 Math 22 US 4500.00
Here if I run the select query,
select
rownum,
customers.ID
from customers;
I get below output:
ROWNUW ID
------ --
1 1
2 2
Here ID = 2 is inserted first but oracle returns in the 2nd row,
But if you include any other column from a table with ROWNUM and PK like
select
rownum,
customers.ID,customers.Name
from customers;
I get correct output (Correct inserted order) :
ROWNUW ID NAME
------ -- ----
1 2 Max
2 1 Math
If run query without Name column i.e only ROWNUW (pseudocolumns) and PK (table column)
We get this,
ROWNUW ID
------ --
1 1
2 2
My Question is Why is that ID=2 is not first returned row.
if I query any table column with ROWNUM, I get the result back based on insertion order.
Example below
select
rownum,
(NAME / AGE / ADDRESS SALARY) any one of these columns
From CUSTOMERS
But if use ROWNUM with ID (Primary Key column) insertion order is not working. why is this behavior with the only Primary Key column?
ROWNUM values depends on how oracle access the resultset. Once resultset is fetched, rownum is assigned to the rows.
As there is no guarantee in what order the data is returned, there is no guarantee in what order rownum is assigned.
Maybe if you try running both of your queries without rownum, you might get the rows swapped as you showed, or they might not.
Rownum will always be in order of 1,2,3,4 .. no matter what you are getting in result set. It is always calculated after result set is returned from query.
Now, you have ID column as PRIMARY KEY which makes it eligible for default clustered index creation in database. So when you select only PRIMARY column, it will be sorted as it was stored in DB that way. Index created as a result of PRIMARY key creation is sorted in ASC which is default.
Here if you select only PRIMARY KEY column i.e. "ID", Database is returning it in sorted order and ROWNUM is writing 1 & 2 to it.
But if you are selecting some extra non-PRIMARY KEY as well then it is totally upto oracle, whichever way it finds data faster. Sometimes it will return data from cache memory. If not it will read disk and fetch data. While reading datafile, there are multiple things which will differ machine to machine i.e is your table partitioned? is your table sharing tablespace ? what organization you are using in table ? blah blah....
Bottom line is, don't trust ROWNUM. It gives 1,2,3.. after you have applied your brain to the query.

Displaying entire row of max() value from nested table

My table CUSTOMER_TABLE has a nested table of references toward ACCOUNT_TABLE. Each account in ACCOUNT_TABLE has a reference toward a branch: branch_ref.
CREATE TYPE account AS object(
accid integer,
acctype varchar2(15),
balance number,
rate number,
overdraft_limit integer,
branch_ref ref branch,
opendate date
) final;
CREATE TYPE customer as object(
custid integer,
infos ref type_person,
accounts accounts_list
);
create type branch under elementary_infos(
bid integer
) final;
All tables are inherited from these object types.
I want to select the account with the highest balance per branch. I arrived to do that with this query:
select MAX(value(a).balance), value(a).branch_ref.bid
from customer_table c, table(c.accounts) a
group by value(a).branch_ref.bid
order by value(a).branch_ref.bid;
Which returns:
MAX(VALUE(A).BALANCE) VALUE(A).BRANCH_REF.BID
--------------------------------------- ---------------------------------------
176318.88 0
192678.14 1
190488.19 2
196433.93 3
182909.84 4
However, how to select as well others attribues from the max accounts displayed ? I would like to display the name of the owner plus the customer's id. The id is directly an attribute of customer. But the name is stored with a reference toward person_table. So I have to select as well c.id & deref(c.infos).names.surname.
How to select these other attributes with my MAX() query ?
Thank you
I generally use analytic functions to achieve that kind of functionality. With analytic functions, you can add aggregate columns to your query without losing the original rows. In your particular case it would be something like:
select
-- select interesting fields
accid,
acctype,
balance,
rate,
overdraft_limit,
branch_ref,
opendate,
max_balance
from (
select
-- propagate original fields to outer query
value(a).accid accid,
value(a).acctype acctype,
value(a).balance balance,
value(a).rate rate,
value(a).overdraft_limit overdraft_limit,
value(a).branch_ref branch_ref,
value(a).opendate opendate,
-- add max(balance) of our branch_ref to the row
max(value(a).balance) over (partition by value(a).branch_ref.bid) max_balance
from customer_table c, table(c.accounts) a
) data
where
-- we are only interested in rows with balance equal to the max
-- (NOTE: there might be more than one, you should fine tune the filtering!)
data.balance = data.max_balance
-- order by branch
order by data.branch_ref.bid;
I don't have any Oracle instance available right now to test this, but that would be the idea, unless there is some kind of incompatibility between analytic functions and collection columns, you should be able to have that working with little effort.

Factor (string) to Numeric in PostgreSQL

Similar to this, is it possible to convert a String field to Numeric in PostgreSQL. For instance,
create table test (name text);
insert into test (name) values ('amy');
insert into test (name) values ('bob');
insert into test (name) values ('bob');
insert into test (name) values ('celia');
and add a field that is
name | num
-------+-----
amy | 1
bob | 2
bob | 2
celia | 3
The most effective "hash"-function of all is a serial primary key - giving you a unique number like you wished for in the question.
I also deal with duplicates in this demo:
CREATE TEMP TABLE string (
string_id serial PRIMARY KEY
,string text NOT NULL UNIQUE -- no dupes
,ct int NOT NULL DEFAULT 1 -- count instead of dupe rows
);
Then you would enter new strings like this:
(Data-modifying CTE requires PostgreSQL 9.1 or later.)
WITH x AS (SELECT 'abc'::text AS nu)
, y AS (
UPDATE string s
SET ct = ct + 1
FROM x
WHERE s.string = x.nu
RETURNING TRUE
)
INSERT INTO string (string)
SELECT nu
FROM x
WHERE NOT EXISTS (SELECT 1 FROM y);
If the string nu already exists, the count (ct) is increased by 1. If not, a new row is inserted, starting with a count of 1.
The UNIQUE also adds an index on the column string.string automatically, which leads to optimal performance for this query.
Add additional logic (triggers ?) for UPDATE / DELETE to make this bullet-proof - if needed.
Note, there is a super-tiny race condition here, if two concurrent transactions try to add the same string at the same moment in time. To be absolutely sure, you could use SERIALIZABLE transactions. More info and links under this this related question.
Live demo at sqlfiddle.
How 'bout a hash, such as md5, of name?
create table test (name text, hash text);
-- later
update test set hash = md5(name);
If you need to convert that md5 text to a number: Hashing a String to a Numeric Value in PostgresSQL
If they are all single characters, you could do this:
ALTER TABLE test ADD COLUMN num int;
UPDATE test SET num = ascii(name);
Though that would only return the character for the first letter if the string was more than a single character.
The exact case shown in your request can be produced with the dense_rank window function:
regress=# SELECT name, dense_rank() OVER (ORDER BY name) FROM test;
name | dense_rank
-------+------------
amy | 1
bob | 2
bob | 2
celia | 3
(4 rows)
so if you were adding a number for each row, you'd be able to do something like:
ALTER TABLE test ADD COLUMN some_num integer;
WITH gen(gen_name, gen_num) AS
(SELECT name, dense_rank() OVER (ORDER BY name) FROM test GROUP BY name)
UPDATE test SET some_num = gen_num FROM gen WHERE name = gen_name;
ALTER TABLE test ALTER COLUMN some_num SET NOT NULL;
however I think it's much more sensible to use a hash or to assign generated keys. I'm just showing that your example can be achieved.
The biggest problem with this approach is that inserting new data is a pain. It's a ranking (like your example shows) so if you INSERT INTO test (name) VALUES ('billy'); then the ranking changes.