I'm new with oracle queries and I would like to have some help as I'm totally stuck.
I have a Products table which contains the product id, price and product name:
And I would like to separate the price for the products into dollars and cents using select oracle query.
I don't know how to figure that but I tried the TRUNC function but I'm still can't get the cents values.
Can you help me please.
Here is the DB Table:
CREATE TABLE Products (
ProductID int,
Price varchar(255),
ProductName varchar(255)
);
INSERT INTO Products VALUES (1,5.17,'Apple');
INSERT INTO Products VALUES (2,6.527,'Orange');
INSERT INTO Products VALUES (3,7.12,'Watermelon');
INSERT INTO Products VALUES (4,3.25,'Pear');
INSERT INTO Products VALUES (5,4.123,'Grape');
Thanks for any help :)
Use TRUNC to get the dollar value and MOD (modulo) to get the cents value:
SELECT ProductID,
ProductName,
TRUNC( Price ) AS dollars,
MOD( Price, 1 ) * 100 AS cents
FROM Products
Which, for your sample data, outputs:
PRODUCTID | PRODUCTNAME | DOLLARS | CENTS
--------: | :---------- | ------: | ----:
1 | Apple | 5 | 17
2 | Orange | 6 | 52.7
3 | Watermelon | 7 | 12
4 | Pear | 3 | 25
5 | Grape | 4 | 12.3
db<>fiddle here
here is what you need.
select substr(price, 1, instr(price, '.')-1) dollars, substr(price, instr(price, '.')+1) cents, round(substr(price, instr(price, '.')),2)* 100 cents_rounded
from products
PRICE as VARHCAR2? Why? It should be a NUMBER as it is a numeric value. In that case:
SQL> desc products
Name Null? Type
----------------------------------------- -------- ----------------------------
PRODUCTID NUMBER(38)
PRICE NUMBER
PRODUCTNAME VARCHAR2(255)
SQL> SELECT productid,
2 productname,
3 price,
4 --
5 TRUNC (price) dollars,
6 (price - TRUNC (price)) * 100 cents
7 FROM products;
PRODUCTID PRODUCTNAM PRICE DOLLARS CENTS
---------- ---------- ---------- ---------- ----------
1 Apple 5,17 5 17
2 Orange 6,527 6 52,7
3 Watermelon 7,12 7 12
4 Pear 3,25 3 25
5 Grape 4,123 4 12,3
SQL>
Query:
Select productid,trunc(price) As Dollars, nvl(substr(price - trunc(price),2),0) as Cents, ProductName As Product From Products;
Output:
PRODUCTID
DOLLARS
CENTS
Product
1
5
17
Apple
2
6
527
Orange
3
7
12
Watermelon
4
3
25
Pear
5
4
123
Grape
Description:
Here I select the ProductId and then I use trunc function to remove any decimals (to have only the DOLLARS value) and after that I use nvl function to make like a replacement to null values (In case there are no cents in number) it will be replaced by 0 and the line: substr(price - trunc(price),2) that will subtract the full price from the DOLLARS value to get the cents.
Note:
You can solve this problem by different ways and other functions check this article for more informations: Link
create or replace FUNCTION METE(p_number VARCHAR, p_parent VARCHAR, p_child VARCHAR) RETURN VARCHAR
IS
v___left VARCHAR2(4000);
v___right VARCHAR2(4000);
v___number VARCHAR2(4000);
BEGIN
SELECT p_number INTO v___number FROM DUAL WHERE p_number LIKE '%.%';
IF v___number IS NOT NULL THEN
select rtrim((select rtrim(p_number,(select substr(p_number,(select instr(p_number,'.')from dual)+1) from dual)) from dual),'.')
into v___left
from dual;
select substr(p_number,(select instr(p_number,'.')from dual)+1)
INTO v___right
from dual;
RETURN NVL(v___left,'0')||' '||p_parent||' '||NVL(v___right,'0')||' '||p_child;
ELSIF v___number IS NULL THEN
select substr(p_number,(select instr(p_number,'.')from dual)+1)
INTO v___right
from dual;
RETURN NVL(v___right,'0')||' '||p_parent;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
select substr(p_number,(select instr(p_number,'.')from dual)+1)
INTO v___right
from dual;
RETURN NVL(v___right,'0')||' '||p_parent;
END;
select ProductID, mete(price,'Dollar','Cent') price, productname from Products;
Related
I have an exercise that says that I have to do this query: Names of the product categories (CategoryName) and total number of products for each of the categories
I have two tables:
-The first table is called "Categories" where the category of the products is found
-The second table is called "Products" and it contains the products
The primary key "Categoryid" of Categories is shared as a foreign with Products, so I think what to do is count how many products each id has and display the name on the left
I am going to leave two examples with the content of the two tables, since the two tables cannot be joined, but not how to count the number of products for each category
Table Categories:
| Categoryid | Categoryname ||
| -------- | ------------- ||
| 1 | Beverages ||
| 2 | Condiments ||
| 3 | Confections ||
Table Products:
| Productid | Productname | Categoryid ||
| -------- | ------------- | ---------- ||
| 1 | Chai | 1 ||
| 2 | Chang | 1 ||
| 3 | Tofu | 5 ||
How it should come out:
| CategoryName | TotalProducts||
| -------- | ----------- ||
| Beverages | 10 ||
| Condiments | 5 ||
| Confections | 3 ||
I don't know how to count the number of products for each category
i try this:
SELECT Categoryname COUNT(*)
FROM Categories JOIN Products ON Categories.Categoryid=Products.Categoryid;
Looks like outer join (so that you would display categories that don't have any products), counting products (not "generally" because you'd get false result; I'll show what I mean).
Sample data (your data is wrong; you can't have Tofu in category 5 if that category doesn't exist; foreign key constraint wouldn't allow it):
SQL> with
2 categories (categoryid, categoryname) as
3 (select 1, 'beverages' from dual union all
4 select 2, 'condiments' from dual union all
5 select 3, 'confections' from dual union all
6 select 5, 'category 5' from dual
7 ),
8 products (productid, productname, categoryid) as
9 (select 1, 'chai' , 1 from dual union all
10 select 2, 'chang', 1 from dual union all
11 select 3, 'tofu' , 5 from dual
12 )
Query: count products (line #14):
13 select c.categoryname,
14 count(p.productid) number_of_products
15 from categories c left join products p on p.categoryid = c.categoryid
16 group by c.categoryname;
CATEGORYNAME NUMBER_OF_PRODUCTS
-------------------- ------------------
beverages 2
category 5 1
condiments 0
confections 0
SQL>
If you used count(*) (line #14), you'd get wrong result as you'd count category itself:
13 select c.categoryname,
14 count(*) number_of_products
15 from categories c left join products p on p.categoryid = c.categoryid
16 group by c.categoryname;
CATEGORYNAME NUMBER_OF_PRODUCTS
-------------------- ------------------
beverages 2
category 5 1
condiments 1 --> no products in
confections 1 --> these two categories!
SQL>
However, if you aren't interested in categories that don't have any products, inner join and count(*) (as well as count(p.productid)) would do:
13 select c.categoryname,
14 count(*) number_of_products
15 from categories c join products p on p.categoryid = c.categoryid
16 group by c.categoryname;
CATEGORYNAME NUMBER_OF_PRODUCTS
-------------------- ------------------
beverages 2
category 5 1
SQL>
For each category I listed all the products and its count.
CREATE TABLE categories(
category_id, category_name) AS
SELECT 1, 'Beverages' FROM DUAL UNION ALL
SELECT 2, 'Condiments' FROM DUAL UNION ALL
SELECT 3, 'Confections' FROM DUAL;
CREATE TABLE products(
product_id, product_name, category_id) AS
SELECT 1, 'Chai',1 FROM DUAL UNION ALL
SELECT 2, 'Chang',1 FROM DUAL UNION ALL
SELECT 3, 'Tofu', 2 FROM DUAL;
select c.category_id,
c.category_name,
listagg(p.product_name,', ') within group(order by p.product_name) product_list,
count(p.product_name) cnt
from categories c,
products p
where c.category_id = p.category_id(+)
group by c.category_id,
c.category_name
order by c.category_id,
c.category_name
/
CATEGORY_ID CATEGORY_NA PRODUCT_LIST CNT
----------- ----------- -------------------- ----------
1 Beverages Chai, Chang 2
2 Condiments Tofu 1
3 Confections 0
I'm new to PL SQL and have to write a function, which has customer_id as an input and has to output a product_name of the best selling product for that customer_id.
The schema looks like this:
I found a lot of simple examples where it includes two tables, but I can't seem to find one where you have to do multiple joins and use a function, while selecting only the best selling product.
I could paste a lot of very bad code here and how I tried to approach this, but this seems to be a bit over my head for current knowledge, since I've been learning PL SQL for less than 3 days now and got this task.
With some sample data (minimal column set):
SQL> select * from products order by product_id;
PRODUCT_ID PRODUCT_NAME
---------- ----------------
1 BMW
2 Audi
SQL> select * From order_items;
PRODUCT_ID CUSTOM QUANTITY UNIT_PRICE
---------- ------ ---------- ----------
1 Little 100 1
1 Little 200 2
2 Foot 300 3
If we check some totals:
SQL> select o.product_id,
2 o.customer_id,
3 sum(o.quantity * o.unit_price) total
4 from order_items o
5 group by o.product_id, o.customer_id;
PRODUCT_ID CUSTOM TOTAL
---------- ------ ----------
2 Little 400
1 Little 100
2 Foot 900
SQL>
It says that
for customer Little, product 2 was sold with total = 400 - that's our choice for Little
for customer Little, product 1 was sold with total = 100
for customer Foot, product 2 was sold with total = 900 - that's our choice for Foot
Query might then look like this:
temp CTE calculates totals per each customer
rank_them CTE ranks them in descending order per each customer; row_number so that you get only one product, even if there are ties
finally, select the one that ranks as the highest
SQL> with
2 temp as
3 (select o.product_id,
4 o.customer_id,
5 sum(o.quantity * o.unit_price) total
6 from order_items o
7 group by o.product_id, o.customer_id
8 ),
9 rank_them as
10 (select t.customer_id,
11 t.product_id,
12 row_number() over (partition by t.customer_id order by t.total desc) rn
13 from temp t
14 )
15 select * From rank_them;
CUSTOM PRODUCT_ID RN
------ ---------- ----------
Foot 2 1 --> for Foot, product 2 ranks as the highest
Little 2 1 --> for Little, product 1 ranks as the highest
Little 1 2
SQL>
Moved to a function:
SQL> create or replace function f_product (par_customer_id in order_items.customer_id%type)
2 return products.product_name%type
3 is
4 retval products.product_name%type;
5 begin
6 with
7 temp as
8 (select o.product_id,
9 o.customer_id,
10 sum(o.quantity * o.unit_price) total
11 from order_items o
12 group by o.product_id, o.customer_id
13 ),
14 rank_them as
15 (select t.customer_id,
16 t.product_id,
17 row_number() over (partition by t.customer_id order by t.total desc) rn
18 from temp t
19 )
20 select p.product_name
21 into retval
22 from rank_them r join products p on p.product_id = r.product_id
23 where r.customer_id = par_customer_id
24 and r.rn = 1;
25
26 return retval;
27 end;
28 /
Function created.
SQL>
Testing:
SQL> select f_product ('Little') result from dual;
RESULT
--------------------------------------------------------------------------------
Audi
SQL> select f_product ('Foot') result from dual;
RESULT
--------------------------------------------------------------------------------
Audi
SQL>
Now, you can improve it so that you'd care about no data found issue (when customer didn't buy anything), ties (but you'd then return a collection or a refcursor instead of a scalar value) etc.
[EDIT] I'm sorry, ORDERS table has to be included into the temp CTE; your data model is correct, you don't have to do anything about it - my query was wrong (small screen + late hours issue; not a real excuse, just saying).
So:
with
temp as
(select i.product_id,
o.customer_id,
sum(i.quantity * i.unit_price) total
from order_items i join orders o on o.order_id = i.order_id
group by i.product_id, o.customer_id
),
The rest of my code is - otherwise - unmodified.
I have a column "Names" in the "Employee" table that has following values. The values either contain only single name (first, last, username) or Multiple names separated with semicolon (;). I need to search the values from that table either by first name or last name or username.
I have created a procedure but it is fetching only 1st,4th,5th records. Please let me know how to retrieve 2nd and 3rd records as well.
Firstname and lastname can be given by user with minimum of 2 characters length.
Username is given entire.
Employee:
ID Name Title
1 Andrea Warbutton (awr01) Manager
2 Claire Taylor (cta02);Mark Kites (mak03);Anitha Rooney (anr06) HOD;Supervisor;Business
3 Dave Rites (dar12);Jessica Simpson (jesi10) Lead;Analyst
4 Nick Ken (nik56) Product (Local,Regional)
5 Claire Pilkington (cpt09) Sales Owner
Code:
Create or replace empl (pm_firstname varchar2(100),
pm_lastname varchar2(100),
pm_username varchar2(100))
BEGIN
Select * from Employee
where Upper(Name) like Upper(pm_firstname ||'%'||) -- this will fetch 1st,4th,5th record
OR Upper(SUBSTR(Name, INSTR(Name),' '+1)) like Upper(pm_lastname ||'%'||) -- this will fetch 1st,4th,5th record
OR upper(REGEXP_SUBSTR(Name,'\((.+)\)',1,1,NULL,1)) = Upper(pm_username); -- -- this will fetch 1st,4th,5th record
END;
End empl ;
Please let me know how to retrieve 2nd and 3rd records as well.
Desired Output:
When searched with firstname = "Andrea", the output is below
ID Name Title
1 Andrea Warbutton (awr01) Manager
When searched with firstname = "Claire", the output is below
ID Name Title
2 Claire Taylor (cta02) HOD
5 Claire Pilkington (cpt09) Sales Owner
When searched with lastname = "Simps", the output is below
ID Name Title
3 Jessica Simpson (jesi10) Analyst
When searched with username = "mak03", the output is below
ID Name Title
2 Mark Kites (mak03) Supervisor
When searched with username = "nik56", the output is below
ID Name Title
4 Nick Ken (nik56) Product (Local,Regional)
with
x as (select id, name, '"'||replace(name, ';', '","')||'"' xml from employee),
n as (select id, name, column_value as cv from x, xmltable(xml))
select id,
trim(regexp_substr(cv, '(\S*)(\s)')) fname,
trim(regexp_substr(cv, '(\S*)(\s)', 1, 2)) lname,
regexp_substr(cv, '\((.+)\)', 1, 1, NULL, 1) uname
from n
Your task would be much easier if you normalize these data. Above query outputs:
ID FNAME LNAME UNAME
1 Andrea Warbutton awr01
2 Claire Taylor cta02
2 Mark Kites mak03
2 Anitha Rooney anr06
3 Dave Rites dar12
3 Jessica Simpson jesi10
4 Nick Ken nik56
5 Claire Pilkington cpt09
demo
Now you can search first, last, usernames however you want. First expression finds first word, then second and word between brackets.
Edit:
I posted the table structure with just ID and Name columns. However, I
have Titles column also in the same format separated with (semicolon).
In this case, How can I Normalize Titles as well along with Names
This query worked for provided examples:
with
x as (select id, name, '"'||replace(name, ';', '","')||'"' xmln,
'"'||replace(title, ';', '","')||'"' xmlt
from employee),
n1 as (select id, trim(xn.column_value) nm, rownum rn from x, xmltable(xmln) xn),
n2 as (select id, trim(xt.column_value) tt, rownum rn from x, xmltable(xmlt) xt)
select id, trim(regexp_substr(nm, '(\S*)(\s)')) fname,
trim(regexp_substr(nm, '(\S*)(\s)', 1, 2)) lname,
regexp_substr(nm, '\((.+)\)', 1, 1, NULL, 1) uname,
tt title
from n1 join n2 using (id, rn)
dbfiddle demo
Be careful however, because we cannot write ideal query. If you have entries like Benicio Del Toro, Mary Jo Catlett, Jean Claude Van Damme, it's impossible to write correct regexp. Sometimes second word is part of lastname, sometimes it is firstname, middlename etc.
The proper way is to modify table structure, divide rows, check results and put correct values in correct name columns. Now you have lists which are hard to search and every method may return wrong results.
No need for PL/SQL.
SQL> with temp as
2 (select id,
3 regexp_substr(name, '[^;]+', 1, column_value) name
4 from employee cross join
5 table(cast(multiset(select level from dual
6 connect by level <= regexp_count(name, ';') + 1
7 ) as sys.odcinumberlist))
8 )
9 select id, name
10 from temp
11 where instr(name, '&search_for_name') > 0;
Enter value for search_for_name: Claire
ID NAME
---------- ------------------------------
2 Claire Taylor (cta02)
5 Claire Pilkington (cpt09)
SQL> /
Enter value for search_for_name: mak03
ID NAME
---------- ------------------------------
2 Mark Kites (mak03)
SQL>
What does it do?
temp CTE splits semi-colon separated values into rows
final query uses a simple instr function which detects whether "rows" (extracted previously) contain value you're looking for
If it must be a function, that code can be reused. As you didn't say what exactly (which datatype, I mean) you want to return, I returned a string.
SQL> create or replace function f_search (par_what in varchar2)
2 return sys.odcivarchar2list
3 is
4 retval sys.odcivarchar2list;
5 begin
6 with temp as
7 (select id,
8 regexp_substr(name, '[^;]+', 1, column_value) name
9 from employee cross join
10 table(cast(multiset(select level from dual
11 connect by level <= regexp_count(name, ';') + 1
12 ) as sys.odcinumberlist))
13 )
14 select id ||' - '|| name
15 bulk collect into retval
16 from temp
17 where instr(name, par_what) > 0;
18
19 return retval;
20 end;
21 /
Function created.
SQL> select * from table(f_search('Andrea'));
COLUMN_VALUE
--------------------------------------------------------------------------------
1 - Andrea Warbutton (awr01)
SQL> select * from table(f_search('Claire'));
COLUMN_VALUE
--------------------------------------------------------------------------------
2 - Claire Taylor (cta02)
5 - Claire Pilkington (cpt09)
SQL>
I want to show a sales of a product and precious sale of that product also if that product is not sale till date than previous sale column should be null. My source table has id, name, sales_date,quantity and unit_price and resultant table should contain id,name,sales_Date,current_sale (which would contain sale on that day) and previous_sale.The previous sale is for individual product not same for all product.
Let's say you have these data in your table:
ID NAME SALES_DATE QUANTITY UNIT_PRICE
---- ----- ----------- -------- --------------
1 p1 2015-07-24 10 5,00
2 p2 2015-07-24 14 10,00
3 p1 2015-07-28 15 4,00
4 p2 2015-07-29 7 11,00
5 p3 2015-07-29 3 2,00
This query, using function lag(), generates output you described (plus column previous_sales_date):
select id, name, sales_date, unit_price*quantity current_sale,
lag(sales_date) over (partition by name order by sales_date) prev_date,
lag(unit_price*quantity) over (partition by name order by sales_date) prev_sale
from sales order by name, sales_date
SQLFiddle demo
I used partition by name, but if in input table "id" means "product id" it's better to use partition by id.
Output:
ID NAME SALES_DATE CURRENT_SALE PREV_DATE PREV_SALE
---- ----- ----------- ------------ ----------- ----------
1 p1 2015-07-24 50
3 p1 2015-07-28 60 2015-07-24 50
2 p2 2015-07-24 140
4 p2 2015-07-29 77 2015-07-24 140
5 p3 2015-07-29 6
Edit: If there are many entries for product per day you need some form of aggregation, most obvious is sum,
like in example below. You can also use min, max, avg.
select name, sales_date, sale current_sale, cnt,
lag(sales_date) over (partition by name order by sales_date) prev_date,
lag(sale) over (partition by name order by sales_date) prev_sale,
lag(cnt) over (partition by name order by sales_date) prev_cnt
from (
select name, trunc(sales_date) sales_date, sum(unit_price*quantity) sale, count(1) cnt
from sales group by name, trunc(sales_date)
)
order by name, sales_date
SQLFiddle demo
I also added columns cnt and prev_cnt - showing number of rows for that product in current and previous days.
I created a table with the following attributes :
TABLE "VENDORACCOUNT"
( "VEN_ACCOUNTID"
"VEN_REGNO"
"VEN_TXDATE"
"VEN_INVOICE_REFNO"
"TOTALAMOUNT"
"PAID_TOVEN"
"BALANCE"
)
I take the TOTALAMOUNT column value through a POPUP LOV from another table on the basis of its VEN_INVOICE_REFNO value. Here the scenario is that the TOTALAMOUNT column value is subtracted from the PAID_TOVEN column value. But the next time I select the TOTALAMOUNT value it does not show me the updated value. It shows me the old value as shown in the report below.
Query of Report:
select "VEN_ACCOUNTID",
"VEN_REGNO" ,
"VEN_TXDATE" ,
"VEN_INVOICE_REFNO" as ,
"TOTALAMOUNT" as ,
"PAID_TOVEN" as ,
TOTALAMOUNT-PAID_TOVEN as "Balance"
from "VENDORACCOUNT"
In the above report I want that whenever I do the second entry it should show me the subtracted or updated value ie 1800 instead of 2800 and 4550 instead of 9550 respectively. So the next time I can subtract the amount from 1800 and 4550.
I created this trigger
create or replace trigger "VENDORACCOUNT_T2"
BEFORE
insert or update or delete on "VENDORACCOUNT"
for each row
begin
DECLARE new_balance INT;
DECLARE new_total INT;
DECLARE new_paid INT;
SELECT balance INTO old_balance,
total INTO old_total,
PAID_TOVEN INTO new_paid
FROM vendoraccount
WHERE ven_regno = new.ven_regno
AND VEN_INVOICE_REFNO = new.VEN_INVOICE_REFNO;
UPDATE vendoraccount SET TOTALAMOUNT = old_total + old_balance - new_paid,
balance = TOTALAMOUNT - new_paid
WHERE VEN_REGNO= new.VEN_REGNO
AND VEN_INVOICE_REFNO = new.VEN_INVOICE_REFNO;
end;
and am getting this error:
ERROR: PLS-00103: 'Encountered the symbol "DECLARE" when expecting one
of the following: begin function pragma procedure subtype type current curs'
I think you don't need a trigger. You can create a view based on the below query and then create the RECORD GROUP for your LOV based on the VIEW. The query would look like-
select accid,regno,inv_refno,LAG(bal,1,totalamount) OVER (PARTITION BY regno ORDER BY accid) "TOTALAMOUNT", paid_toven, bal
from (with temp_data as
(select 2 accid,3 regno, 16 inv_refno, 2800 totalamount, 1000 paid_toven from dual
union
select 3 accid,3 regno, 16 inv_refno, 2800 totalamount, 2000 paid_toven from dual
union
select 4 accid,8 regno, 22 inv_refno, 9550 totalamount, 5000 paid_toven from dual
union
select 5 accid,8 regno, 22 inv_refno, 9550 totalamount, 5000 paid_toven from dual
union
select 6 accid,8 regno, 22 inv_refno, 9550 totalamount, 8000 paid_toven from dual)
select accid,regno,inv_refno,totalamount,paid_toven,totalamount-paid_toven bal
from temp_data);
The output is -
ACCID REGNO INV_REFNO TOTALAMOUNT PAID_TOVEN BAL
----- ----- --------- ----------- ---------- ---
2 3 16 2800 1000 1800
3 3 16 1800 2000 800
4 8 22 9550 5000 4550
5 8 22 4550 5000 4550
6 8 22 4550 8000 1550
So based on your table the query would be-
select accid,regno,inv_refno,LAG(bal,1,totalamount) OVER (PARTITION BY regno ORDER BY accid) "TOTALAMOUNT", paid_toven, bal
from (select accid,regno,inv_refno,totalamount,paid_toven,totalamount-paid_toven bal
from VENDORACCOUNT);
The query does pretty much what you want, but your sample data does not look right. This is actually one example of a running total.