DBMS - Oracle | return multiple max values for multiple tables / relations - sql

I need to return a list of customers who had ordered maximum number items. I have added the following and get some results but need to filter the query to display multiple maximum values.
DDL
CREATE TABLE Customer
(Cust_Num NUMBER(4) PRIMARY KEY,
Cust_LName VARCHAR2(10),
Cust_FName VARCHAR2(10),
Cust_Address VARCHAR2(20),
Cust_City VARCHAR2(12),
Cust_State VARCHAR2(2),
Cust_Zip VARCHAR2(5),
Cust_Referred NUMBER(4));
Create Table BookOrder
(BO_OrderNum NUMBER(4) PRIMARY KEY,
BO_CustNum NUMBER(4) REFERENCES Customer(Cust_Num),
BO_OrderDate DATE,
BO_ShipDate DATE,
BO_ShipStreet VARCHAR2(18),
BO_ShipCity VARCHAR2(15),
BO_ShipState VARCHAR2(2),
BO_ShipZip VARCHAR2(5));
CREATE TABLE BookOrderItem
(BOI_OrderNum NUMBER(4) NOT NULL REFERENCES BookOrder(BO_OrderNum),
BOI_ItemNum NUMBER(2) NOT NULL,
BOI_ISBN VARCHAR2(10) REFERENCES Book(Book_ISBN),
BOI_Qty NUMBER(3),
CONSTRAINT bookorderitem_pk PRIMARY KEY (BOI_OrderNum, BOI_ItemNum));
I have written the following DML:
SELECT C.CUST_LNAME, C.CUST_FNAME, BO.BO_CUSTNUM, BOI.BOI_ORDERNUM,
COUNT(BOI.BOI_ITEMNUM) AS Total_Items_Per_Order
FROM BookOrderItem BOI JOIN BookOrder BO ON BOI.BOI_OrderNum = BO.BO_OrderNum
JOIN Customer C ON C.Cust_Num = BO.BO_CustNum
GROUP BY C.Cust_LName, C.CUST_FName, BO.BO_CustNum, BOI.BOI_OrderNum
ORDER BY Total_Item_Per_Order DESC;
Which gives me the following results...
+------------+------------+------------+--------------+-----------------------+
| CUST_LNAME | CUST_FNAME | BO_CustNum | BOI_OrderNum | TOTAL_ITEMS_PER_ORDER |
+------------+------------+------------+--------------+-----------------------+
| NELSON | BECCA | 1017 | 1012 | 4 |
| GIANA | TAMMY | 1007 | 1007 | 4 |
| MORALES | BONITA | 1001 | 1003 | 3 |
| MORALES | BONITA | 1001 | 1018 | 2 |
| LUCAS | JAKE | 1010 | 1001 | 2 |
| GIRARD | CINDY | 1005 | 1009 | 2 |
| LEE | JASMINE | 1014 | 1013 | 1 |
| MONTIASA | GREG | 1018 | 1005 | 1 |
| MONTIASA | GREG | 1018 | 1019 | 1 |
| PIERSON | THOMAS | 1004 | 1008 | 1 |
| JONES | KENNETH | 1008 | 1020 | 1 |
| MCGOVERN | REESE | 1011 | 1002 | 1 |
| LUCAS | JAKE | 1010 | 1011 | 1 |
| FALAH | KENNETH | 1020 | 1015 | 1 |
| SMITH | JENNIFER | 1019 | 1010 | 1 |
| GIRARD | CINDY | 1005 | 1000 | 1 |
| SMITH | LEILA | 1003 | 1006 | 1 |
| GIANA | TAMMY | 1007 | 1014 | 1 |
| FALAH | KENNETH | 1020 | 1004 | 1 |
| SMITH | LEILA | 1003 | 1016 | 1 |
| SCHELL | STEVE | 1015 | 1017 | 1 |
+------------+------------+------------+--------------+-----------------------+
As per this screenshot...

To only keep the first row(s) you'd use FETCH FIRST n ROW(s) ONLY in Oracle 12c. To consider ties, you'd replace ONLY by WITH TIES:
...
ORDER BY Total_Item_Per_Order DESC
FETCH FIRST 1 ROW WITH TIES;

Please try the following...
SELECT Cust_LName,
Cust_FName,
Cust_Num AS Cust_Num,
BO_OrderNum AS Order_Num,
BOI_ItemNum AS Item_Num,
Max_Qty_Per_Order AS Max_Qty_Per_Order
FROM ( SELECT BOI_OrderNum AS Order_Num,
MAX( BOI_Qty ) AS Max_Qty_Per_Order
FROM BookOrderItem
GROUP BY BOI_OrderNum
) Max_Qty_Per_Order_Finder
JOIN BookOrderItem ON BookOrderItem.BOI_OrderNum = Max_Qty_Per_Order_Finder.Order_Num
AND BookOrderItem.BOI_Qty = Max_Qty_Per_Order_Finder.Max_Qty_Per_Order
JOIN BookOrder ON Max_Qty_Per_Order_Finder.Order_Num = BookOrder.BO_OrderNum
JOIN Customer Customer ON Customer.Cust_Num = BookOrder.BO_CustNum
ORDER BY Max_Qty_Per_Order DESC,
BO_OrderNum,
BOI_Item_Num;
This statement starts by creating a list of Order Numbers and the largest value of BOI_Qty associated with each.
The results of this subquery will be joined to BookOrderItem in such a way that only those records from BookOrderItem that have the largest value of BOI_Qty for their BookOrder will be returned. If more than one BookOrderItem for a particular BookOrder has the maximum value of BOI_Qty for that BookOrder, then each such record will be retained.
The resulting dataset is then joined to BookOrder so that the value of BO_CustNum for that BookOrder may be retrieved and be used to join the dataset to Customer, allowing for the Name of a Customer associated with each BookOrder to be retrieved.
The desired fields for each record in our final dataset are then retrieved and sorted.
Please note that I have included the field BOI_ItemNum in the selected fields as you asked for each record that has the maximum value of BOI_Qty to be returned. Without it such records would appear to be repeated. Including BOI_ItemNum will allow you to identify each record returned.
If you have any questions or comments, then please feel free to post a Comment accordingly.

Related

How do I insert multiple rows from one table into a struct column of a single row of another table?

I have 2 source tables at the moment.
Table #1: sourceTableMain
|EmployeeNumber| DepartmentNumber | CostCenterNumber |
| -------------| ---------------- |------------------|
| 1 | 100 | 1001 |
| 2 | 200 | 1001 |
| 3 | 100 | 1002 |
Table #2: sourceTableEmployee
|EmployeeNumber| EmployeeFirstName | EmployeeLastName | EmployeeAddress |
| -------------| ---------------- |------------------|---------------- |
| 1 | Michael | Scott | 110 ABC Ln |
| 1 | Michael | Scott | 450 XYZ Ln |
| 2 | Dwight | Schrute | 321 PQR St |
| 3 | Jim | Halpert | 678 LMN Blvd |
I am trying to insert the combine the rows into a 3rd table named targetTableCombined which has the following schema:
FieldName
Type
Mode
employeeNumber
INTEGER
NULLABLE
employeeDetails
(struct)
RECORD
REPEATED
employeeFirstName
STRING
NULLABLE
employeeLastName
STRING
NULLABLE
employeeAddress
STRING
NULLABLE
Within the target table (targetTableCombined), I am trying to make sure that for each employeeNumber, all of the First Names, Last Names and Addresses are repeated under a single struct array. For example, EmployeeNumber 1 should have only 1 row in the target table, with the first name, last name and different addresses as part of the second column (struct), each in a separate row.
I wrote an insert script to do this, but I am going wrong:
insert into `dev.try_sbx.targetTableCombined`
select
main.employeeNumber,
array(
select as struct
emp.employeeFirstName,
emp.employeeLastName,
emp.employeeAddress
)
from
`dev.try_sbx.sourceTableMain` as main
inner join `dev.try_sbx.sourceTableEmployee` as emp
on main.EmployeeNumber = emp.EmployeeNumber;
This is the result I am getting when running the query above:
| EmployeeNumber | EmployeeDetails |
| ------------- | ------------------------------ |
| 1 | [Michael, Scott, 110 ABC Ln] |
| 1 | [Michael, Scott, 450 XYZ Ln] |
| 2 | [Dwight, Schrute, 321 PQR St] |
| 3 | [Jim, Halpert, 678 LMN Blvd] |
(Sorry about not being able to share screenshots - I don't have enough rep. But to elaborate, I am expecting only 3 rows on the insert (employee 1 should have had a single array containing both addresses). I am instead, getting 4 rows after the insert.)
Where am I going wrong with my script?
It's because ARRAY() is not an aggregation function. You should ARRAY_AGG() along with GROUP BY to group details for each employee into an array.
SELECT EmployeeNumber,
ARRAY_AGG((SELECT AS STRUCT EmployeeFirstName, EmployeeLastName, EmployeeAddress)) AS employeeDetails
FROM `dev.try_sbx.sourceTableEmployee`
GROUP BY 1;
More preferred way is :
SELECT EmployeeNumber,
ARRAY_AGG(STRUCT(EmployeeFirstName, EmployeeLastName, EmployeeAddress)) AS employeeDetails
FROM `dev.try_sbx.sourceTableEmployee`
GROUP BY 1;
output:

SQL - joining 3 tables and choosing newest logged entry per id

I got rather complicated riddle to solve. So far I'm unlocky.
I got 3 tables which I need to join to get the result.
Most important is that I need highest h_id per p_id. h_id is uniqe entry in log history. And I need newest one for given point (p_id -> num).
Apart from that I need ext and name as well.
history
+----------------+---------+--------+
| h_id | p_id | str_id |
+----------------+---------+--------+
| 1 | 1 | 11 |
| 2 | 5 | 15 |
| 3 | 5 | 23 |
| 4 | 1 | 62 |
+----------------+---------+--------+
point
+----------------+---------+
| p_id | num |
+----------------+---------+
| 1 | 4564 |
| 5 | 3453 |
+----------------+---------+
street
+----------------+---------+-------------+
| str_id | ext | name |
+----------------+---------+-------------+
| 15 | | Mein st. 33 | - bad name
| 11 | | eck st. 42 | - bad name
| 62 | abc | Main st. 33 |
| 23 | efg | Back st. 42 |
+----------------+---------+-------------+
EXPECTED RESULT
+----------------+---------+-------------+-----+
| num | ext | name |h_id |
+----------------+---------+-------------+-----+
| 3453 | efg | Back st. 42 | 3 |
| 4564 | abc | Main st. 33 | 4 |
+----------------+---------+-------------+-----+
I'm using Oracle SQL. Tried using query below but result is not true.
SELECT num, max(name), max(ext), MAX(h_id) maxm FROM history
INNER JOIN street on street.str_id = history._str_id
INNER JOIN point on point.p_id = history.p_id
GROUP BY point.num
In Oracle, you can use keep:
SELECT p.num,
MAX(h.h_id) as maxm,
MAX(s.name) KEEP (DENSE_RANK FIRST ORDER BY h.h_id DESC) as name,
MAX(s.ext) KEEP (DENSE_RANK FIRST ORDER BY h.h_id DESC) as ext
FROM history h INNER JOIN
street s
ON s.str_id = h._str_id INNER JOIN
point p
ON p.p_id = h.p_id
GROUP BY p.num;
The keep syntax allows you to do "first()" and "last()" for aggregations.

how to view data from different tables

I have 4 tables : tbl_info, tbl_owner, tbl_accounts, tbl_billing
--tbl_info: information
info_id | fname | lname
10 | ron | lum
--tbl_owner: owner
own_id | owner_info_id |property_type
01 | 10 | land
--tbl_all_property: landfindings
property_id | property_owner_id | owner_id | OR_no
1 | 101 | 10 | 987
2 | 101 | 10 | 874
3 | 101 | 10 | 875
--tbl_billing: billing
bill_id | status | total | property_id
1 | not paid | 100 | 1
my question is how to view the value of "fname,lname,status,total" when I enter ORNo = '875'
since the 3 OR_no was owned by the same owner.
select a.fname,
a.lname,
d.status,
d.total
from tbl_info a
inner join tbl_owner b
on a.info_id=b.owner_info_id
inner join tbl_all_property c
on c.owner_id=b.owner_info_id
inner join tbl_billing d
on d.property_id=c.property_id
where c.OR_no='875'

How can I make a pivot table with 2 or more tables

I have 3 tables, and I want to make a pivot table:
CERTIFICATIONS | data
-----------------------------------
ID_CERT | 1
DESCRIPTION | Writer
STATUS | A
PERSONAL_CERT | DATA
------------------------------------
PNUMBER | 806
ID_CERT | 1
LEVEL | 2
SCORE | 8.5
PERSONAL | DATA
-------------------------------------
PNUMBER | 806
NAME | Ralph
I need to show a table like this:
DESCRIPTION | LEVEL 1 | LEVEL 2 | LEVEL3 | LEVEL4
-------
Writer | NULL | 8.5 | NULL | NULL
I need the table PERSONAL to show the data for a specific employee with "where" and "Like"

Fixing the payout issue

I am re-posting my original question with edits, as that question was answered and best answer chosen.
Payments comes from our supplier which goes towards the accounts and the reps get paid based on which account got how much.
Customers Table (Usage is kwH)
+----+----------+------------+----------+----------+----------+-------+-------+
| ID | Customer | Account_no | Meter_no | Supplier | Active | Usage | Repid |
+----+----------+------------+----------+----------+----------+-------+-------+
| 1 | Joe | 123 | 111 | NSTAR | active | 20 | 100 |
| 2 | Joe | 123 | 222 | NSTAR | active | 30 | 100 |
| 3 | Joe | 123 | 150 | NSTAR | inactive | 60 | 100 |
| 4 | Sam | 456 | 352 | SEP | active | 50 | 100 |
| 5 | Jill | 789 | 222 | FES | active | 40 | 200 |
| 6 | Mike | 883 | 150 | ABB | inactive | 40 | 200 |
+----+----------+------------+----------+----------+----------+-------+-------+
Payment_Receive (table)
+------------+----------+-------------+-------------+
| Account_no | Supplier | Amount_paid | PaymentDate |
+------------+----------+-------------+-------------+
| 123 | NSTAR | 20 | 2011-11-01 |
| 456 | SEP | 40 | 2011-11-01 |
| 456 | SEP | -40 | 2011-11-01 |
| 456 | SEP | 40 | 2011-11-01 |
| 789 | FES | 50 | 2011-11-01 |
| 883 | ABB | 30 | 2011-11-01 |
+------------+----------+-------------+-------------+
The two tables are used for rep payout. Payment are recieved for each account, they are matched with our customers based on Account_No and Supplier. We do not have control over the payment_table because it comes from outside. This creates certain problems because we can not do one-to-one match between the two tables. Leaving that aside, I would like to have payout calculated for RepID = 100 with certain criteria. This is the output I would like to see for RepId = 100
+------------+----------+-------------+-------------+-------------+
| Account_no | Supplier | Amount_paid | Usage | PaymentDate |
+------------+----------+-------------+-------------+-------------+
| 123 | NSTAR | 20 | 60* | 2011-11-01 |
| 456 | SEP | 40 | 50 | 2011-11-01 |
| 456 | SEP | -40 | 40 | 2011-11-01 |
| 456 | SEP | 40 | 40 | 2011-11-01 |
+------------+----------+-------------+-------------+-------------+
Note here that
Account_no 123 exists thrice in customers table, it must show one time in rep payout
3 amounts were paid to account_no 456, all the three must show in the report
*60 = Notice that there are 2 active records (and one inactive). This could be the sum of the two active. But any other value is acceptable if that makes the query easy (for greater of the two or one, not the other)
Note that Usage column must appear in the output table, This is the column that creates problem for me. If I dont include this everything works fine.
The point with Usage column, if I have two records for same customer having same Account_No and Supplier but different usage, that makes the two records distinct when I include usage column. Therefore distinct does not work to remove this duplicate.
Reports are calculated on Monthly basis
Script for the question
create database testcase
go
use testcase
go
create table customers (
id int not null primary key identity,
customer_name varchar(25),
account_no int,
meter_no int,
supplier varchar(20),
active varchar(20),
usage int,
repid int
)
create table payments_received (
account_no int,
supplier varchar(20),
amount_paid float,
paymentdate smalldatetime
)
insert into customers values('Joe',123, 111,'NSTAR','active',20,100)
insert into customers values('Joe',123, 222,'NSTAR','active',30, 100)
insert into customers values('Joe',123, 150,'NSTAR','inactive',60,100)
insert into customers values('Sam',456, 352,'SEP','active',40,100)
insert into customers values('Jill',789, 222,'FES','active',40,200)
insert into customers values('Mike',883, 150,'ABB','inactive',40,200)
select * from customers
insert into payments_received values(123,'NSTAR',20,'2011-11-01')
insert into payments_received values(456,'SEP',40,'2011-11-01')
insert into payments_received values(456,'SEP',-40,'2011-11-01')
insert into payments_received values(456,'SEP',40,'2011-11-01')
insert into payments_received values(789,'FES',50,'2011-11-01')
insert into payments_received values(883,'ABB',30,'2011-11-01')
select * from payments_received
How about this:
CREATE VIEW v_customers_by_rep
AS
SELECT SUM(USAGE) AS USAGE ,
REPID ,
CAST(account_no AS VARCHAR) + '_' + Supplier AS UniqueId
FROM customers
GROUP BY CAST(account_no AS VARCHAR) + '_' + Supplier ,
REPID
GO
DECLARE
#repid INT
SET #repid = 100
SELECT pr.* ,
u.Usage
FROM payments_received pr INNER JOIN v_customers_by_rep u
ON CAST(pr.account_no AS VARCHAR) + '_' + pr.Supplier = u.UniqueId
WHERE u.repid = #repid
You could also eliminate inactive records in the view if desired.