Need column values to be dispersed among multiple columns - sql

I have the two tables as below :
SQL> select * from customer;
CUSTOMERID CUSTOMERNAME
---------- --------------
1 A
2 B
SQL> select * from cars;
CARID CUSTOMERID MODEL
---------- ---------- ----------
1 1 toyota
2 1 honda
3 2 suzuki
I need to write an SQL query which would give an output like below :
( Can we use PIVOT or UNPIVOT for this ? I am not sure ! )
CUSTOMERID CUSTOMERNAME Model1 Model2
---------- ------------- ------- --------
1 A toyota honda
2 B suzuki NULL

You can use window function & do conditional aggregation :
select customerid, customername,
max(case when seq = 1 then model end) as model1,
max(case when seq = 2 then model end) as model2
from (select c.*, cr.model,
row_number() over (partition by c.customerid order by cr.MODEL) as seq
from customer c inner join
cars cr
on cr.customerid = c.customerid
) t
group by customerid, customername;

If you have only two values, you can use aggregation:
select cu.customerid, cu.customername,
min(ca.model) as model1,
nullif(max(ca.model), min(ca.model)) as model2
from customers cu join
cars ca
on ca.customerid = cu.customerid
group by cu.customerid, cu.customername;
If you have an indeterminate number, I would recommend aggregating them into a string or array. That syntax depends on the database, but typical syntax is:
select cu.customerid, cu.customername,
listagg(ca.model, ',') within group (order by ca.model) as models
from customers cu join
cars ca
on ca.customerid = cu.customerid
group by cu.customerid, cu.customername;

Related

Selecting values in columns based on other columns

I have two tables, info and transactions.
info looks like this:
customer ID Postcode
1 ABC 123
2 DEF 456
and transactions looks like this:
customer ID day frequency
1 1/1/12 3
1 3/5/12 4
2 4/6/12 2
3 9/9/12 1
I want to know which day has the highest frequency for each postcode.
I know how to reference from two different tables but im not too sure how to reference multiple columns based on their values to other columns.
The output should be something like this:
customer ID postcode day frequency
1 ABC 123 3/5/12 4
2 DEF 456 4/6/12 2
3 GHI 789 9/9/12 1
and so on.
You can filter with a correlated subquery:
select
i.*,
t.day,
t.frequency
from info i
inner join transactions t on t.customerID = i.customerID
where t.frequency = (
select max(t.frequency)
from info i1
inner join transactions t1 on t1.customerID = i1.customerID
where i1.postcode = i.postcode
)
Or, if your RBDMS supports window functions, you can use rank():
select *
from (
select
i.*,
t.day,
t.frequency,
rank() over(partition by i.postcode order by t.frequency desc)
from info i
inner join transactions t on t.customerID = i.customerID
) t
where rn = 1

SQL: City wise,product wise latest order ID

I have tables:
tbl-city
id city
---------
1 A
2 B
3 C
tbl-orders
ord_id product_id city date
----------------------------------
1 1 1 12/3/18
2 1 2 13/3/18
3 2 3 12/4/18
4 1 3 14/4/18
5 3 2 11/2/18
6 1 1 15/1/18
7 2 3 15/4/28
I need to get all latest order Id from table orders
by city wise and product wise using date
Like this:
ord_id product_id city
---------------------------
1 1 1
2 1 2
7 2 3
5 3 2
How can I get this?
JOIN/INNER JOIN or any other way?
You can use correlated subquery :
select o.*
from orders o
where date = (select max(o1.date)
from orders o1
where o1.product_id = o.product_id and o1.city = o.city
);
If ord_id increases along with the date (as would be typical), then a simple aggregation works.
select product_id, city, max(ord_id)
from tbl_ord
group by product_id, city;
Otherwise, the typical method would use row_number():
select o.*
from (select o.*,
row_number() over (partition by product_id, city order by date desc) as seqnum
from tbl_ord o
) o
where seqnum = 1;
In older versions of MySQL, a correlated subquery is a good route. But if you want exactly one row per product and city, use ord_id:
select o.*
from tbl_ord o
where o.ord_id = (select o2.ord_id
from tbl_ord o2
where o2.product_id = o.product_id and o2.city = o.city
order by o2.date desc
limit 1
);

Use Count function inside conditional part of Case Expression

I have two tables with the following sample records in oracle database
1. staffs
inst_name name sid
ABC John 1
PQR Sam 2
ABC Tom 3
ABC Amit 4
PQR Jack 5
2. staffaccounts
sid account_no
1 4587
1 4588
2 4589
3 4581
3 4582
5 4583
5 4585
4 4586
Where I want the result like
inst_name account_type total
PQR SINGLE 1
ABC SINGLE 1
PQR DOUBLE 1
ABC DOUBLE 2
This can be achieved by a outer query, but I want to write a query where there is no outer query. Want to accomplish it in one single query.
SELECT
A .inst_name,
(
CASE COUNT (b.ac_no)
WHEN 1 THEN
'Single'
WHEN 2 THEN
'Double'
END
) account_type,
COUNT (A . NAME)
FROM
staffs A,
staffaccounts b
WHERE
A . s_id = b.s_id
GROUP BY
A .inst_name
The above query gives error ORA-00907: missing right parenthesis. Can it be done in single query or is outer query the only way out.
Oracle Version is 10g
May be something like this would work.
SELECT
A.inst_name,
CASE COUNT (b.account_no)
WHEN 1 THEN
'Single'
WHEN 2 THEN
'Double'
END account_type,
COUNT (A.name)
FROM
staffs A JOIN
staffaccounts b
ON
A.SID = b.sid
GROUP BY
A.inst_name , a.sid
ORDER BY 3;
You are grouping by inst_name, but this is not what you actually want, because you don't want a result row per inst_name, but per inst_name and account_type.
select
s.inst_name,
sa.account_type,
count(*) as total
from staffs s
join
(
select
sid,
case when count(*) = 1 then 'SINGLE' else 'DOUBLE' end as account_type
from staffaccounts
group by sid
having count(*) <= 2
) sa on sa.sid = s.sid
group by sa.account_type, s.inst_name
order by sa.account_type, s.inst_name;
You should learn how to properly use JOIN syntax. I prefer the explicit comparison syntax for CASE.
This may be what you want:
SELECT s.inst_name,
(CASE WHEN COUNT(sa.ac_no) = 1 THEN 'Single'
WHEN COUNT(sa.ac_no) = 2 THEN 'Double'
END) as account_type,
COUNT(*)
FROM staffs s JOIN
staffaccounts sa
ON s.SID = sa.sid
GROUP BY s.inst_name;
EDIT:
Now I see what you want:
SELECT s.inst_name,
(CASE WHEN cnt = 1 THEN 'Single'
WHEN cnt = 2 THEN 'Double'
END) as account_type,
COUNT(*)
FROM (SELECT s.*, COUNT(*) as cnt
FROM staffs s JOIN
staffaccounts sa
ON s.SID = sa.sid
GROUP BY s.id
) s
GROUP BY s.inst_name,
(CASE WHEN cnt = 1 THEN 'Single'
WHEN cnt = 2 THEN 'Double'
END);
I got only the way by using subquery but is in the easy way (more easier and readable) to achieve your requirement
SELECT inst_name, account_type, count(total) as total
FROM (
SELECT
a.inst_name,
CASE
WHEN COUNT (b.account_no) = 1 THEN 'Single'
WHEN COUNT (b.account_no) = 2 THEN 'Double'
END AS account_type,
COUNT (a.name) AS total
FROM staffs a
INNER JOIN staffaccounts b ON A . SID = b.sid
GROUP BY a.inst_name, a.sid) t GROUP BY inst_name, account_type
OUTPUT:
inst_name account_type total
ABC Double 2
PQR Double 1
ABC Single 1
PQR Single 1

Single SQL SELECT query for one to many relationship

This question is hard to explain without a example.
I have 2 tables, companies and employees, they have one-to-many relationship, one Company can attend many Employees.
Simplest table structure is shown here
Company
| id | name |
Employee
| id | name | company_id | join_date |
Now the question is:
How could I select the first 2 employees and show their joining date as column in company table?
So the result looks like this
| id | company_name | first_employee_join_at | second_employee_join_at |
Assuming there is a foreign key column company_id in the employee table:
with emps as (
select id, name, company_id,
row_number() over (partition by company_id order by join_date) as rn
from employee
)
select c.id, c.name as company_name,
e1.join_date as first_employee_join_at,
e2.join_date as second_employee_join_at
from company c
left join emps e1 on e1.company_id = c.id and e1.rn = 1
left join emps e2 on e2.company_id = c.id and e2.rn = 2;
This is not going to be terribly efficient though. A slightly more efficient version would use conditional aggregation:
with emps as (
select id, name, company_id,
row_number() over (partition by company_id order by join_date) as rn
from employee
)
select c.id, c.name as company_name,
max(e.join_date) filter (where rn = 1) as first_employee_join_at,
max(e.join_date) filter (where rn = 2) as second_employee_join_at
from company c
join emps e on e.company_id = c.id and e.rn in (1,2)
group by c.id, c.name;

SQL - Group By Distinct Values

My question, is there a faster way to the following query?
I'm using ORACLE 10g
Say i have a table Manufacturer and Car, and i want to count all occurrences of the column 'Car.Name'. here is How i'd do it:
SELECT manuf.Name, COUNT(car1.Name), COUNT(car2.Name), COUNT(car3.Name)
FROM Manufacturer manuf
LEFT JOIN (SELECT * FROM Car c where c.Name = 'Ferrari1') car1 ON manuf.PK = car1.ManufPK
LEFT JOIN (SELECT * FROM Car c where c.Name = 'Ferrari2') car2 ON manuf.PK = car2.ManufPK
LEFT JOIN (SELECT * FROM Car c where c.Name = 'Ferrari3') car3 ON manuf.PK = car3.ManufPK
GROUP BY manuf.Name
Wanted Results:
Manufacturer | Ferrari1 | Ferrari2 | Ferrari3
----------------------------------------------
Fiat | 1 | 0 | 5
Ford | 2 | 3 | 0
I tried this with few LEFT JOINs, and it worked fine. But when i added a lot (like 90+), it was ultra slow (more than 1 minute).
My question, is there a faster way to do this query?
If you are happy to see the cars counted down the page, try:
select m.Name manufacturer_name,
c.Name car_name,
count(*)
from Manufacturer m
left join Car c
on m.PK = c.ManufPK and c.Name in ('Ferrari1','Ferrari2','Ferrari3')
group by m.Name, c.Name
If you need to see individual cars across the page, try:
select m.Name manufacturer_name,
sum(case c.Name when 'Ferrari1' then 1 else 0 end) Ferrari1_Count,
sum(case c.Name when 'Ferrari2' then 1 else 0 end) Ferrari2_Count,
sum(case c.Name when 'Ferrari3' then 1 else 0 end) Ferrari3_Count
from Manufacturer m
left join Car c
on m.PK = c.ManufPK and c.Name in ('Ferrari1','Ferrari2','Ferrari3')
group by m.Name
SELECT manuf.Name, COUNT(DISTINCT c.Name)
FROM Manufacturer manuf
LEFT JOIN Car c ON manuf.PK = c.ManufPK
GROUP BY manuf.Name
OR depending on your needs
SELECT manuf.Name, c.Name, COUNT(*) Cnt
FROM Manufacturer manuf
LEFT JOIN Car c ON manuf.PK = c.ManufPK
GROUP BY manuf.Name, c.Name
PS: Your question is not very clear. Provide some wanted resultset to refine the answer
You can also try this:
SELECT manuf.Name
, car1.cnt AS Ferrari1
, car2.cnt AS Ferrari2
, car3.cnt AS Ferrari3
FROM
Manufacturer AS manuf
LEFT JOIN
( SELECT ManufPK, COUNT(*) AS cnt
FROM Car
WHERE Name = 'Ferrari1'
GROUP BY ManufPK
) AS car1
ON car1.ManufPK = manuf.PK
LEFT JOIN
( SELECT ManufPK, COUNT(*) AS cnt
FROM Car
WHERE Name = 'Ferrari2'
GROUP BY ManufPK
) AS car2
ON car2.ManufPK = manuf.PK
LEFT JOIN
( SELECT ManufPK, COUNT(*) AS cnt
FROM Car
WHERE Name = 'Ferrari3'
GROUP BY ManufPK
) AS car3
ON car3.ManufPK = manuf.PK
ORDER BY manuf.Name