SQL query which puts columns from rows into one row - sql

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;

Related

Oracle Row Into Columns

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.

(HANA SQL) Show multiple values in one row

I am trying to complete the following:
Old situation
What I want
For a fixed maximum number of target columns, you can use window functions and conditional aggregation:
select customer,
max(case when rn = 1 then order_date end) as order_date_1,
max(case when rn = 2 then order_date end) as order_date_2,
max(case when rn = 3 then order_date end) as order_date_3
from (
select t.*, row_number() over(partition by customer order by order_date) rn
from mytable t
) t
group by customer

Calculate lead-time between selected rows (SQL)

Given this table where we have users, the product that they used, and the first date that they used the product (I have also created a simple rank by user window). Note, each user will only have minimum 0 rows if they used nothing before, and 2 rows, if they used both products. There are only 3 products - cigars and beers.
How can I create a new view where each row is 1 user, the next column shows the first product, the next column shows the 2nd product, and the last column shows the lead-time b/w the first dates of use?
One method is conditional aggregation with row_number():
select user,
max(case when seqnum = 1 then product end) as product_1,
max(case when seqnum = 2 then product end) as product_2,
(max(case when seqnum = 2 then time_used end) -
max(case when seqnum = 1 then time_used end)
) as dif
from (select t.*,
row_number() over (partition by user order by time_used) as seqnum
from t
) t
group by user;
Date/time functions vary significantly across different databases. Not all support a simple -, so you might nee to adjust for your database.
Minus between dates may not work on each database
select
c1.user_id,
c1.first_product_used,
c2.second_product_used,
COALESCE(CAST((Cast(c2.second_date AS DATE) - Cast(c1.first_date AS DATE)) AS VARCHAR(20)), 'n/a') AS "leadtime_days"
from
(
select
user_id,
product_used AS first_product_used,
time_used AS first_date
from
check2
where
rank_of_use = 1
)c1
LEFT OUTER JOIN
(
select
user_id,
product_used AS second_product_used,
time_used AS second_date
from
check2
where
rank_of_use = 2
)c2
ON
c1.user_id = c2.user_id

SQL - Filter out only first record in SQL (Amazon Redshift)

I have this one query that I am using to pull data from one table that store customer data along with their feedback. However I have an issue where the same customer (cust_id) has more that one entry. How could I modify this to only return the first row (based on timestamp) and ignore all other records..
I am using Amazon redshift.
with q1 as
(select cust_id,
sum(case when response <= 6 then 1 else 0 end) as bad,
sum(case when response between 7 and 8 then 1 else 0 end) as good
from customers
group by cust_id
order by 1 DESC ,last_visit_datetime desc),
q2 as (select cust_id,rating as neg_rating,response as neg_response from customers
where rating is not null
order by neg_rating asc, last_visit_datetime desc )
select DISTINCT q1.cust_id,q1.good,q1.bad,q2.neg_response,q2.neg_rating
from q1 join q2 on q1.cust_id = q2.cust_id
Could anyone assist, thanks..
Use row_number to get one row per cust_id and then do the aggregation.
select cust_id,
sum(case when response <= 6 then 1 else 0 end) as bad,
sum(case when response between 7 and 8 then 1 else 0 end) as good
from (select c.*,row_number() over(partition by cust_id order by last_visit_datetime desc) as rnum
from customers c
) c
where rnum=1
group by cust_id

How to join a table to itself using rank multiple times?

I apologize if there is an existing solution, I tried searching quite a bit but couldnt find much.
Here's what I am trying to do:
I have a table that resembles the first image. I want it to look like the second image. Basically every time rnk_address = 1, I want that row's order_date to be moved into a separate column for that associated user_id.
I'm ultimately trying to calculate the average difference in order_date between all rnk_address = 1, by user_id.
I added a user_counter column using rank because I imagine I will need to do some time of join where i do user_rank = user_rank + 1? But not sure....
Original table:
End result:
You can use conditional aggregation:
select user_id, first_name,
max(case when seqnum = 1 then order_date end) as order_date_1,
max(case when seqnum = 2 then order_date end) as order_date_2,
max(case when seqnum = 3 then order_date end) as order_date_3
from (select t.*,
row_number() over (partition by user_id order by order_date) as seqnum
from t
where rnk_address = 1
) t
group by user_id, first_name;