Summarizing redundant SQL information - sql

Given (Oracle 11g):
MANUFACTURER
------------
ID
NAME
CARS
---------
CAR_ID
MANUFACTURER_ID
NAME
PARTS
---------
PART_ID
CAR_ID
PART_NAME
Is it possible from SQL to get a listing of parts by car and manufacturer in a single query without repeating the redundant data on each row?
Something like:
FORD ESCORT Windshield Wiper
Horn
Steering Wheel
F-150 Windshield Wiper
Horn
Bed Liner
TOYOTA CAMRY Floor Mat
Door Handle
CIVIC Headlight
Horn
Or does something like this require application-level logic and/or use of reporting features. I've tried numerous queries, but gotten nothing close thus far.

Try the lag function like this:
WITH manufacturer AS (
SELECT 1 manufacturer_id, 'FORD' NAME FROM dual
UNION ALL SELECT 2, 'TOYOTA' FROM dual)
, CAR AS (
SELECT 1 car_id, 1 manufacturer_id, 'ESCORT' AS name FROM dual
UNION ALL SELECT 2, 1, 'F-150' FROM dual
UNION ALL SELECT 3, 2, 'CAMRY' FROM dual
UNION ALL SELECT 4, 2, 'CIVIC' FROM dual)
, part AS (
SELECT 1 AS part_id, 1 AS car_id, 'Windshield Wiper' AS part_name FROM dual
UNION ALL SELECT 2, 1, 'Horn' FROM dual
UNION ALL SELECT 3, 1, 'Steering Wheel' FROM dual
UNION ALL SELECT 4, 2, 'Windshield Wiper' FROM dual
UNION ALL SELECT 5, 2, 'Horn' FROM dual
UNION ALL SELECT 6, 2, 'Bed Liner' FROM dual
UNION ALL SELECT 7, 3, 'Floor Mat' FROM dual
UNION ALL SELECT 8, 3, 'Door Handle' FROM dual
UNION ALL SELECT 9, 4, 'Headlight' FROM dual
UNION ALL SELECT 10, 4, 'Horn' FROM dual)
SELECT case lag (m.name) over (order by p.part_id)
when m.name then null
else m.name
end as manufcturer,
case lag (c.name) over (order by p.part_id)
when c.name then null
else c.name
end as carname,
p.part_name
FROM manufacturer m INNER JOIN car c ON m.manufacturer_id = c.manufacturer_id
INNER JOIN part p ON p.car_id = c.car_ID
;
OUTPUT:
MANUFACTURER CARNAME PART_NAME
------------- --------- -----------------
FORD ESCORT Windshield Wiper
Horn
Steering Wheel
F-150 Windshield Wiper
Horn
Bed Liner
TOYOTA CAMRY Floor Mat
Door Handle
CIVIC Headlight
Horn

The natural way to get the result is:
select m.name as manufacturer_name, c.name as car_name, p.name as part_name
from manufacturer m join
cars c
on c.manufacturer_id = m.id join
parts p
on p.car_id = c.car_id;
This will be in a format where all the cells are filled in the table (so 'Ford' will be in the first few rows of the table).
If you only want the first appearance of each name, you can use row_number() (and be sure to sort the results in the end):
select (case when m_seqnum = 1 then manufacturer_name else '' end) as manufacturer_name,
(case when c_seqnum = 1 then car_name else '' end) as car_name,
part_name
from (select m.name as manufacturer_name, c.name as car_name, p.name as part_name,
row_number() over (partition by m.name order by c.name, p.name) as m_seqnum,
row_number() over (partition by m.name, c.name order by p.name) as c_seqnum
from manufacturer m join
cars c
on c.manufacturer_id = m.id join
parts p
on p.car_id = c.car_id
) mcp
order by manufacturer_name, car_name, part_name;

Related

Add country name for each medals columns for this particular Query. maximum medals each medal group with country name?

with t1 as
(select distinct oh.games,oh.noc,region as countrys from olympics_history oh inner join olympics_history_noc_regions hnr
on hnr.noc = oh.noc order by games),
t2 as
(select games,noc,count(medal) as gold_medals from olympics_history
where medal like '%Gold%'
group by noc,games
order by games),
t3 as
(select games,noc,count(medal) as Silver_medals from olympics_history
where medal like '%Silver%'
group by noc,games
order by games),
t4 as
(select games,noc,count(medal) as Bronze_medals from olympics_history
where medal like '%Bronze%'
group by noc,games
order by games),
t5 as
(select t1.games,countrys,gold_medals,Silver_medals,Bronze_medals
from t1 inner join t2 on (t1.noc = t2.noc and t1.games = t2.games)
inner join t3 on (t2.noc = t3.noc and t2.games = t3.games)
inner join t4 on (t4.noc = t3.noc and t4.games = t3.games)
order by games,countrys)
select games,max(gold_medals)as max_gold,max(silver_medals) as max_gold,max(bronze_medals) as max_bronze from t5
group by games
order by games
before last query i got out like this
enter image description here
My output
enter image description here
Actual output needed
enter image description here
Im using Oracle database
my questing is - take a example in max_gold column have value of 25. that 25 gold value belong to germany. so i need output like germany-25 in max_gold column. that values group by games (ex - 1896 Summer,1900 Summer,1904 Summer). in second column You have 18.
There's no sample data so I made up my own.
I guess you don't need that many CTEs; one should do (temp in my example) as it fetches all medals "at once", while the rank analytic function ranks them (so rnk = 1 represents the top countries.
Why rank and not row_number? Because of ties - what if two (or more) countries have the same number of medals? That's also why final query utilizes listagg aggregate (and not e.g. min or max).
OK, here we go.
Sample data:
SQL> with
2 olympics_history (games, medal, region) as
3 (select 1896, 'Gold' , 'Austria' from dual union all
4 select 1896, 'Gold' , 'Austria' from dual union all
5 select 1896, 'Gold' , 'Belgium' from dual union all
6 select 1896, 'Silver', 'Germany' from dual union all
7 select 1896, 'Bronze', 'Austria' from dual union all
8 select 1896, 'Bronze', 'Austria' from dual union all
9 select 1896, 'Bronze', 'Canada' from dual union all
10 --
11 select 1900, 'Gold' , 'Germany' from dual union all
12 select 1900, 'Gold' , 'UK' from dual union all
13 select 1900, 'Silver', 'France' from dual union all
14 select 1900, 'Silver', 'France' from dual union all
15 select 1900, 'Silver', 'France' from dual union all
16 select 1900, 'Silver', 'Greece' from dual
17 ),
Query begins here:
18 temp as
19 (select games, medal, region, count(*) cnt,
20 rank() over (partition by games, medal order by count(*) desc) rnk
21 from olympics_history
22 group by games, medal, region
23 )
24 select games,
25 listagg(case when medal = 'Gold' then region || ' - ' || cnt end, ', ') within group (order by region) as gold,
26 listagg(case when medal = 'Silver' then region || ' - ' || cnt end, ', ') within group (order by region) as silver,
27 listagg(case when medal = 'Bronze' then region || ' - ' || cnt end, ', ') within group (order by region) as bronze
28 from temp
29 where rnk = 1
30 group by games;
GAMES GOLD SILVER BRONZE
---------- -------------------- -------------------- --------------------
1896 Austria - 2 Germany - 1 Austria - 2
1900 Germany - 1, UK - 1 France - 3
SQL>

Get column values separated by semi colon

I have two tables in plsql
tblStudent:-
StudentId Name .........
1 A
2 B
tblDept:-
DeptId DeptName StudentId
1 Dep Aero 1
2 IT 1
3 Dep Maths 1
4 Dep Chemistry 2
I want to get studentId, with all its departments which starts with 'Dep' separated by semi colon, If i pass where StudentId = 1 in SELECT result should look like
StudentId DeptName
1 Dep Aero;Dep Maths
any help please?
You may use LISTAGG to concat and LIKE to filter the records.
SELECT studentid,
LISTAGG(deptname,';') WITHIN GROUP(
ORDER BY deptid
) as deptname
FROM t
WHERE deptname LIKE 'Dep%'
GROUP BY studentid;
If you want an empty list of departments when a student has no entries then you can use an outer join between the two tables, e.g.:
select s.studentid,
listagg(d.deptname, ';') within group (order by d.deptname) as deptnames
from tblstudent s
left join tbldept d on d.studentid = s.studentid
and deptname like 'Dep%'
group by s.studentid;
Demo using CTEs for your sample data, including a third student ID with no matching departments:
-- CTEs for sample data
with tblstudent (studentid, name) as (
select 1, 'A' from dual
union all select 2, 'B' from dual
union all select 3, 'C' from dual
),
tbldept (deptid, deptname, studentid) as (
select 1, 'Dep Aero', 1 from dual
union all select 2, 'IT', 1 from dual
union all select 3, 'Dep Maths', 1 from dual
union all select 4, 'Dep Chemistry', 2 from dual
)
-- actual query
select s.studentid,
listagg(d.deptname, ';') within group (order by d.deptname) as deptnames
from tblstudent s
left join tbldept d on d.studentid = s.studentid
and deptname like 'Dep%'
group by s.studentid;
STUDENTID DEPTNAMES
---------- ------------------------------
1 Dep Aero;Dep Maths
2 Dep Chemistry
3
Your data model looks odd though; you should probably have a department table which only has the department IDs and names, and then another tables that links each student to all of their departments - something like (in CTE form again):
-- CTEs for sample data
with tblstudent (studentid, name) as (
select 1, 'A' from dual
union all select 2, 'B' from dual
union all select 3, 'C' from dual
),
tbldept (deptid, deptname) as (
select 1, 'Dep Aero' from dual
union all select 2, 'IT' from dual
union all select 3, 'Dep Maths' from dual
union all select 4, 'Dep Chemistry' from dual
),
tblstudentdept (studentid, deptid) as (
select 1, 1 from dual
union all select 1, 2 from dual
union all select 1, 3 from dual
union all select 2, 4 from dual
)
-- actual query
select s.studentid,
listagg(d.deptname, ';') within group (order by d.deptname) as deptnames
from tblstudent s
left join tblstudentdept sd on sd.studentid = s.studentid
left join tbldept d on d.deptid = sd.deptid
and deptname like 'Dep%'
group by s.studentid;
STUDENTID DEPTNAMES
---------- ------------------------------
1 Dep Aero;Dep Maths
2 Dep Chemistry
3
Either way, if you only want to see a single student's results when add that as a where clause, right before the group by:
...
where s.studentid = 1
group by s.studentid;
Try this GROUP_CONCAT -
SELECT stud2.studentId,
CAST((SELECT GROUP_CONCAT(CONCAT(dep.depName,'; ') FROM tblDept dep
INNER JOIN tblStudent stud ON (stud.DeptId = dep.DeptId)))
FROM tblStudent stud2
No join is necessary for your query. If you want to do this for a particular student id:
select listagg(d.DeptName, ';') within group (order by d.DeptName)
from tblDept d
where d.studentid = :studentid and
d.DeptName like 'Dep%';
you can alo use this:
select
st.studentid,
listagg(d.DeptName,';') within group( order by d.DeptName )
From tblStudent st
join tblDept d on d.studentid = st.studentid
where DeptName like 'Dep%'
group by st.studentid
sqlFiddle

Order by with a condition

I have two tables, Products and Category. each product is related to a specific Category, and has an Expiry_Date which may be NULL value.
I want to query all productsordered by the soonest Expiry_Date first. Null-Expiry_date products are ordered last for a Category with specific name, like Food.
UPDATED (sample data below):
Product table:
Category Table:
Results:
This isn't just about ordering, you want to exclude some rows with null dates but include others based on the category name; and then order what's left:
select p.prod_id, p.name, p.expiry_date, c.cat_id
from product p
join category c on c.cat_id = p.cat_id
where (c.name = 'Food' or p.expiry_date is not null)
order by p.expiry_date desc nulls last;
The where clause excludes products will null expiry dates, unless they are in the category called 'Food'. The order-by is then straightforward, though as you want it in descending date order you need to specify nulls last to get those... er... last.
Demo with your sample data in CTEs:
with product (prod_id, name, expiry_date, cat_id) as (
select 1, 'NAME1', date '2018-01-10', 1 from dual
union all select 2, 'NAME2', date '2018-01-11', 2 from dual
union all select 3, 'NAME3', date '2018-01-12', 3 from dual
union all select 4, 'NAME4', null, 1 from dual
union all select 5, 'NAME5', null, 2 from dual
union all select 6, 'NAME6', date '2018-01-13', 2 from dual
union all select 7, 'NAME7', date '2018-01-14', 2 from dual
union all select 8, 'NAME8', null, 3 from dual
),
category (cat_id, name) as (
select 1, 'Food' from dual
union all select 2, 'Food1' from dual
union all select 3, 'Food2' from dual
)
select p.prod_id, p.name, p.expiry_date, c.cat_id
from product p
join category c on c.cat_id = p.cat_id
where (c.name = 'Food' or p.expiry_date is not null)
order by p.expiry_date desc nulls last;
PROD_ID NAME EXPIRY_DAT CAT_ID
---------- ----- ---------- ----------
7 NAME7 2018-01-14 2
6 NAME6 2018-01-13 2
3 NAME3 2018-01-12 3
2 NAME2 2018-01-11 2
1 NAME1 2018-01-10 1
4 NAME4 1
If I understand correctly, just use nulls last in the order by:
select c.category, p.*
from products p join
category c
on p.? = c.? -- whatever the join keys are
order by category, expiry_date nulls last

HAVING COUNT() not giving out the expected result

Attempting to get duplicate values of produce.prod_id using distinct, having clauses.but not giving out the expected result.
Here's my data:
PRODUCE
--------- -----------
PROD_ID PROD_NAME
--------- -----------
1 APPLES
2 PEARS
3 ORANGES
4 BANANAS
5 PEACHES
BUYERS
---------- ------------
BUYER_ID BUYER_NAME
---------- --------------
1 ADAM BARR
2 SEAN CHAI
3 EVA CORETS
4 ERIN O`MELIA
SALES
---------- --------- ------
BUYER_ID PROD_ID QTY
---------- --------- ------
1 2 15
1 3 5
4 1 37
3 5 11
4 3 1005
and here's my code:
select produce.prod_name,
buyer.BUYER_NAME,
SALES.PROD_ID
from produce
inner join SALES on produce.PROD_ID = SALES.PROD_ID
inner join buyer on SALES.BUYER_ID = buyer.BUYER_ID
group by produce.prod_name,
buyer.BUYER_NAME,
SALES.PROD_ID
having count(SALES.PROD_ID) > 1;
Expected result:
PROD_Name Buyer_Name
----------- --------------
Oranges ADAM BARR
Oranges ERIN O`MELIA
You need to use analytical function for your requirement
WITH PRODUCE(PROD_ID, PROD_NAME) AS (
SELECT 1, 'APPLES' from dual union all
select 2, 'PEARS' from dual union all
select 3, 'ORANGES' from dual union all
select 4, 'BANANAS' from dual union all
select 5, 'PEACHES' from dual),
BUYERS (BUYER_ID, BUYER_NAME) as (
select 1, 'ADAM BARR' from dual union all
select 2, 'SEAN CHAI' from dual union all
select 3, 'EVA CORETS' from dual union all
select 4, 'ERIN O`MELIA' from dual),
SALES(BUYER_ID, PROD_ID, QTY) as (
select 1, 2, 15 from dual union all
select 1, 3, 5 from dual union all
select 4, 1, 37 from dual union all
select 3, 5, 11 from dual union all
select 4, 3, 1005 from dual),
-- End of data preparation
TABLE_ AS (
SELECT produce.prod_name,
buyers.buyer_name,
sales.prod_id,
COUNT(1) OVER (PARTITION BY sales.prod_id) p_count
FROM produce
INNER JOIN sales
ON produce.prod_id = sales.prod_id
INNER JOIN buyers
ON sales.buyer_id = buyers.buyer_id)
SELECT prod_name, buyer_name, prod_id
FROM table_
WHERE p_count > 1;
Output:
| PROD_NAME | BUYER_NAME | PROD_ID |
|-----------|--------------|---------|
| ORANGES | ERIN O`MELIA | 3 |
| ORANGES | ADAM BARR | 3 |
Update: You simplified query would be:
With TABLE_ AS (
SELECT produce.prod_name,
buyers.buyer_name,
sales.prod_id,
COUNT(1) OVER (PARTITION BY sales.prod_id) p_count
FROM produce
INNER JOIN sales
ON produce.prod_id = sales.prod_id
INNER JOIN buyers
ON sales.buyer_id = buyers.buyer_id)
SELECT prod_name, buyer_name, prod_id
FROM table_
WHERE p_count > 1;
you don't need distinct. you only need group by and having. you also cant group by buyer_name to get a count > 1. that is where the issue lies, you can to do this as a set of nested queries
select dupes.prod_name,
buyers.buyer_name
from (
select produce.prod_name,
SALES.PROD_ID,
count(SALES.PROD_ID)
from produce
inner join SALES on produce.PROD_ID=SALES.PROD_ID
group by produce.prod_name,
SALES.PROD_ID
having count(*)>1
) as dupes
inner join sales on sales.prod_id = dupes.prod_id
inner join buyers on buyers.buyer_id = sales.buyer_id
Here's my, hopefully, simple answer.
Here's my SQL Fiddle scratch pad as well. http://sqlfiddle.com/#!9/d80f6/3
select P.Prod_Name, Buyer_Name
from Sales S
join
(select prod_id, count(prod_id) as ActSales
from Sales group by prod_id having count(prod_id) > 1) SQ
on S.Prod_Id = SQ.Prod_Id
join Produce P
on S.Prod_Id = P.Prod_Id
join Buyers B
on S.Buyer_Id = B.Buyer_Id

select multiple columns from mutiple tables

Example.
I have two tables
CAR
id name
1 bmw
2 fiat
Car_info
car_id field_name country
1 year 2000
1 country germany
2 year 1988
2 country italy
How in one select query I can get this?
id name year country
1 bmw 2000 germany
2 fiat 1988 italy
Ideally, you should have a column in the car_info table specifying what type of information is being displayed on that line. i.e. Type 1 is the year, type 2 is the country.
You can get round this by joining on to the car_info table twice specifying an 'AND' condition on the join, like so:
SELECT car.ID, car.Name, ci1.country as [YEAR], ci2.country as COUNTRY
FROM car
INNER JOIN car_info AS ci1 ON Car.ID = ci1.car_id AND ci1.field_name = 'year'
INNER JOIN car_info AS ci2 ON Car.ID = ci2.car_id AND ci2.field_name = 'country'
Try this :
You can use PIVOT and then join with the Car table
with cte as
(
Select car_id,[year],[country]
from
(Select car_id,field_name,countryName
from car_info
)pv
pivot
(
max(countryName)
FOR field_name IN ([year],[country])
)pvt
)
Select c.name,ct.year,ct.country
from car c inner join cte ct
on ct.car_id=c.id
Demo in SQL Fiddle
WITH car AS (
SELECT 1 AS id, 'BMW' AS name FROM dual
UNION ALL
SELECT 2, 'Fiat' FROM dual
)
, car_info AS (
SELECT 1 AS car_id, 'Year' AS field_name, '2000' AS field_val FROM dual
UNION ALL
SELECT 1, 'Country', 'Germany' FROM dual
UNION ALL
SELECT 2, 'Year', '1988' FROM dual
UNION ALL
SELECT 2, 'Country', 'Italy' FROM dual
)
SELECT c.*, car_year.field_val AS yr, car_country.field_val AS country
FROM car c
JOIN car_info car_year ON c.id = car_year.car_id AND car_year.field_name = 'Year'
JOIN car_info car_country ON c.id = car_country.car_id AND car_country.field_name = 'Country'
;