Oracle Row Into Columns - sql

I am having 2 tables, Customers and Customer Contacts table.
Ex: Customers
Column:
Id
Customer Name
Contacts Table
Column
id
customer_id
contact_no
I need to fetch a record by below format.'
Customer Name, contact_no_1, contact_no_2 .... etc.
I'm using oracle 11g.

You can make use of either case expression or PIVOT to achieve the same.
Please refer to below links
Link1
Link2
Link3

You can use a ranking function (rank, dense_rank, row_number) to assign a nominal sequential number to each contact number for each customer:
select cus.id as customer_id,
cus.customer_name,
con.contact_no,
row_number() over (partition by cus.id order by con.id) as rn
from customers cus
left join contacts con on con.customer_id = cus.id
and then either use that in a pivot:
select *
from (
select cus.id as customer_id,
cus.customer_name,
con.contact_no,
row_number() over (partition by cus.id order by con.id) as rn
from customers cus
left join contacts con on con.customer_id = cus.id
)
pivot (max(contact_no) for (rn) in (1 as contact_no_1, 2 as contact_no_2,
3 as contact_no_3, 4 as contact_no_4, 5 as contact_no_5))
or with a manual pivot using aggregation:
select customer_id,
customer_name,
max(case when rn = 1 then contact_no end) as contact_no_1,
max(case when rn = 2 then contact_no end) as contact_no_2,
max(case when rn = 3 then contact_no end) as contact_no_3,
max(case when rn = 4 then contact_no end) as contact_no_4,
max(case when rn = 5 then contact_no end) as contact_no_5
from (
select cus.id as customer_id,
cus.customer_name,
con.contact_no,
row_number() over (partition by cus.id order by con.id) as rn
from customers cus
left join contacts con on con.customer_id = cus.id
)
group by customer_id, customer_name
db<>fiddle with some made-up data.
I've shown handling up to 5 contact numbers; you need to add as many in() values or case expressions as you need to handle whatever value of N you're comfortable limiting the output to. (If you decide you can't set a limit then you'll have to use a dynamic pivot, which is more complicated.)
I've included customer_id in case you can have two customer that happen to have the same name. You don't have to include that in the final projection; in the pivot version that means you will need to list all the columns you do want to include.

Related

How to retrieve the most frequent value of a column for a specific ID in a table

I'm trying to fetch the most frequent value from a SQLite 3 database table for each specific ID (which is the ID of a company). I have tried with GROUP BY and ORDER BY as well as with COUNT() function.
SELECT company_id, max(car)
FROM car_orders
GROUP by company_id
ORDER by max(car)
For a specific company_id (9) I am expecting 'Audi' to be in result but this is not the case as its 'Volkswagen' (which is wrong)
Similar to your attempts, consider joining two aggregates that calculates COUNT per car and company and MAX of same counter per company. Below uses CTE introduced in SQLite in version 3.8.3, released in February 2014.
WITH cnt AS (
SELECT company_id, car, COUNT(*) AS car_count
FROM car_orders
GROUP by company_id, car
),
max_cnt AS (
SELECT cnt.company_id, MAX(cnt.car_count) as max_count
FROM cnt
GROUP BY cnt.company_id
)
SELECT cnt.company_id, cnt.car
FROM cnt
INNER JOIN max_cnt
ON cnt.company_id = max_cnt.company_id
AND cnt.car_count = max_cnt.max_count
In the more recent versions of SQLite, you can use window functions:
SELECT cc.*
FROM (SELECT company_id, car, COUNT(*) as cnt,
ROW_NUMBER() OVER (PARTITION BY company_id ORDER BY COUNT(*) DESC) as seqnum
FROM car_orders
GROUP by company_id, car
) cc
WHERE seqnum = 1;
In earlier versions, it is a little more complicated:
WITH cc as (
SELECT company_id, car, COUNT(*) as cnt
FROM car_orders
GROUP by company_id, car
)
SELECT cc.*
FROM cc
WHERE cc.cnt = (SELECT MAX(cc2.cnt)
FROM cc cc2
WHERE cc2.company_id = cc.company_id
);

SQL Select Group By Min() - but select other

I want to select the ID of the Table Products with the lowest Price Grouped By Product.
ID Product Price
1 123 10
2 123 11
3 234 20
4 234 21
Which by logic would look like this:
SELECT
ID,
Min(Price)
FROM
Products
GROUP BY
Product
But I don't want to select the Price itself, just the ID.
Resulting in
1
3
EDIT: The DBMSes used are Firebird and Filemaker
You didn't specify your DBMS, so this is ANSI standard SQL:
select id
from (
select id,
row_number() over (partition by product order by price) as rn
from orders
) t
where rn = 1
order by id;
If your DBMS doesn't support window functions, you can do that with joining against a derived table:
select o.id
from orders o
join (
select product,
min(price) as min_price
from orders
group by product
) t on t.product = o.product and t.min_price = o.price;
Note that this will return a slightly different result then the first solution: if the minimum price for a product occurs more then once, all those IDs will be returned. The first solution will only return one of them. If you don't want that, you need to group again in the outer query:
select min(o.id)
from orders o
join (
select product,
min(price) as min_price
from orders
group by product
) t on t.product = o.product and t.min_price = o.price
group by o.product;
SELECT ID
FROM Products as A
where price = ( select Min(Price)
from Products as B
where B.Product = A.Product )
GROUP BY id
This will show the ID, which in this case is 3.

SQL Server : select only last record per customer from a join query

Assume I have these 3 tables :
The first 2 tables define customers of different types ,i.e second table has other columns which are not included in table 1 i just left them the same to save complexity.
The third table defines orders for both types of customers . Each customer has more than one orders
I want to select the last order for every customer, i.e the order with order_id 4 for customer 1 which was created on 23/12/2016 and the order with order_id 5 for customer 2 which was created on 26/12/2016
I tried something like this :
select *
from customertype1
left join order on order.customer_id = customertype1.customer_id
order by order_id desc;
But this gives me multiple records for every customer, as I have stated above I want only the last order for every customertype1.
If you want the last order for each customer, then you only need the orders table:
select o.*
from (select o.*,
row_number() over (partition by customer_id order by datecreated desc) as seqnum
from orders o
) o
where seqnum = 1;
If you want to include all customers, then you need to combine the two tables. Assuming they are mutually exclusive:
with c as (
select customer_id from customers1 union all
select customer_id from customers2
)
select o.*
from c left join
(select o.*,
row_number() over (partition by customer_id order by datecreated desc) as seqnum
from orders o
) o
on c.customer_id = o.customer_id and seqnum = 1;
A note about your data structure: You should have one table for all customers. You can then define a foreign key constraint between orders and customers. For the additional columns, you can have additional tables for the different types of customers.
Use ROW_NUMBER() and PARTITION BY.
ROW_NUMBER(): it will give sequence no to your each row
PARTITION BY: it will group your data by given column
When you use ROW_NUMBER() and PARTITION BY both together then first partition by group your records and then row_number give then sequence no by each group, so for each group you have start sequence from 1
Help Link: Example of ROW_NUMBER() and PARTITION BY
This is the general idea. You can work out the details.
with customers as
(select customer_id, customer_name
from table1
union
select customer_id, customer_name
from table2)
, lastOrder as
(select customer_id, max(order_id) maxOrderId
from orders
group by customer_id)
select *
from lastOrder join customers on lastOrder.Customer_id = customers.customer_id
join orders on order_id = maxOrderId

ORACLE SQL Return only duplicated values (not the original)

I have a database with the following info
Customer_id, plan_id, plan_start_dte,
Since some customer switch plans, there are customers with several duplicated customer_ids, but with different plan_start_dte. I'm trying to count how many times a day members switch to the premium plan from any other plan ( plan_id = 'premium').
That is, I'm trying to do roughly this: return all rows with duplicate customer_id, except for the original plan (min(plan_start_dte)), where plan_id = 'premium', and group them by plan_start_dte.
I'm able to get all duplicate records with their count:
with plan_counts as (
select c.*, count(*) over (partition by CUSTOMER_ID) ct
from CUSTOMERS c
)
select *
from plan_counts
where ct > 1
The other steps have me stuck. First I tried to select everything except the original plan:
SELECT CUSTOMERS c
where START_DTE not in (
select min(PLAN_START_DTE)
from CUSTOMERS i
where c.CUSTOMER_ID = i.CUSTOMER_ID
)
But this failed. If I can solve this I believe all I have to add is an additional condition where c.PLAN_ID = 'premium' and then group by date and do a count. Anyone have any ideas?
I think you want lag():
select c.*
from (select c.*,
lag(plan_id) over (partition by customer_id order by plan_start_date) as prev_plan_id
from customers c
) c
where prev_plan_id <> 'premium' and plan_id = 'premium';
I'm not sure what output you want. For the number of times this occurs per day:
select plan_start_date, count(*)
from (select c.*, lag(plan_id) over (partition by customer_id order by plan_start_date) as prev_plan_id
from customers c
) c
where prev_plan_id <> 'premium' and plan_id = 'premium'
group by plan_start_date
order by plan_start_date;

SQL query which puts columns from rows into one row

I have two tables:
Customer(cust_id, cust_name)
Refund (ref_id, cust_id, ref_date, ref_amount)
cust_id is the foreign key which points out to a Customer.
Each Customer can have several refunds. I want to get a list of customers and only two dates of refunds for each cutomer. Each customer and dates of his refunds must be in one row. I.e. I want to get the foolowing result:
(cust_name, ref_date1, ref_amount1, ref_date2, ref_amount2)
For example - 'John Smith', '06/06/2012', 500.0, '08/05/2014', 345.5
How can I get this?
Thanx!
(If it is important I use Oracle 11g)
The following gets the two most recent refunds:
select c.cust_name,
max(case when seqnum = 1 then ref_date end) as ref_date1,
max(case when seqnum = 1 then ref_amount end) as ref_amount1,
max(case when seqnum = 2 then ref_date end) as ref_date2,
max(case when seqnum = 2 then ref_amount end) as ref_amount2
from customer c join
(select r.*,
row_number() over (partition by cust_id order by ref_date desc) as seqnum
from refund r
) r
on c.cust_id = r.cust_id
where seqnum <= 2
group by c.cust_name;