Oracle 10g
My requirements are to:
Select each department
Select each individual item per department (each item get's its own row, but combine if duplicates)
Select each color per distinct department AND item (if duplicate, select the lowest number)
Select each user per distinct department AND item (aggregate if multiple)
DB data
Department Item_List Color User
Research Item 1 1. Blue John
Research Item 1;Item 2 2. Blue Mike
Research Item 1;Item 2; Item 3 1. Red Steve
Research Item 2 1. Purple John
Research Item 1;Item 4 2. Red Bill
Ops Item 1;Item 2 3. Silver John
Ops Item 1;Item 3 3. Silver Mike
Ops Item 4 4. Yellow Mark
Expected Results
Department Item_List Color User
Research Item 1 1. Blue John, Mike
Research Item 2 1. Blue Mike
Research Item 1 1. Red Steve, Bill
Research Item 2 1. Red Steve
Research Item 3 1. Red Steve
Research Item 2 1. Purple John
Research Item 4 1. Red Bill
Ops Item 1 3. Silver John, Mike
Ops Item 2 3. Silver John
Ops Item 3 3 Silver Mike
Ops Item 4 4. Yellow Mark
I am using the following SQL, but it is not working:
with data as
(
select
DEPARTMENT,
ITEM_LIST,
(length(ITEM_LIST)-length(replace(ITEM_LIST,';','')))+1 cnt,
MIN(Color) as Color,
wm_concat(USER) as USER
from DataBase_Table
Group by
DEPARTMENT,
ITEM_LIST
)
select
DEPARTMENT
ITEM_LIST,
Color,
User
from
(
select distinct
DEPARTMENT,
ltrim(regexp_substr(ITEM_LIST,'[^;]+',1,level)) ITEM_LIST,
Color,
level,
User
from data
connect by level <= cnt
order by DEPARTMENT
)
;
with
t1 as (
select
department,
trim(regexp_substr(item_list, '[^;]+', 1, occ)) as item,
to_number(regexp_substr(color, '^\d+')) as color_no,
regexp_replace(color, '^\d+(.*)$', '\1') as color_name,
user_
from
(
select level as occ from dual
connect by level <= (select max(length(item_list)) from source)
),
source
where regexp_substr(item_list, '[^;]+', 1, occ) is not null
)
select
department,
item,
color,
listagg(user_, ',') within group (order by user_)
from
(
select
min(color_no)||color_name as color,
color_name
from t1
group by color_name
)
natural join t1
group by
department,
item,
color
order by 1 desc, 2, 3
SQLFiddle
In Oracle 10 you should use
wm_concat(distinct user_)
instead of
listagg(user_, ',') within group (order by user_)
Related
I have a table in SQLite named Clients:
ID
Name
Hobbies
SubHobbies
1
Anne
sports, fashion
shoes, swimming
2
Max
eating, automative
desserts, cars
I split the Hobbies column using Common Table Expressions (with split() as) :
ID
Name
Hobbies
SubHobbies
1
Anne
sports
shoes, swimming
1
Anne
fashion
shoes, swimming
2
Max
eating
desserts, cars
2
Max
automative
desserts, cars
Now I also want to split the Subhubbies column, which I can do using CTE. The format I want is (Swimming is a subhubby of Sports, shoes-Fashion, desserts-eating and cars-automative) :
ID
Name
Hobbies
SubHobbies
1
Anne
sports
swimming
1
Anne
fashion
shoes
2
Max
eating
desserts
2
Max
automative
cars
There are many clients, hobbies and sub-hobbies in this table so I don't want to do this one by one. Any ideas for splitting and placing them in the right place with queries?
You need to define the Hobbies table:
CREATE TABLE Hobbies (
`Hobby` TEXT,
`SubHobby` TEXT
);
INSERT INTO Hobbies
(`Hobby`, `SubHobby`)
VALUES
('sports', 'swimming'),
('fashion', 'shoes'),
('eating', 'desserts'),
('automative', 'cars');
You should have this table anyway, because otherwise your Clients table is not normalized. Then you run this query against the Clients/Hobbies tables:
SELECT c.ID, c.Name, h.Hobby, h.SubHobby
FROM Clients AS c, Hobbies AS h
WHERE instr(' ' || c.SubHobbies || ',', ' ' || h.SubHobby || ',')
Depending on your data, simple WHERE instr(c.SubHobbies, h.SubHobby) might also do just fine.
Suppose I have these tables:
person
id name
-- ----
1 dude
2 john
3 doe
...etc
favourite_food
personid food
-------- ------
1 apples
5 apples
5 oranges
And I want to get a list of the names of people who like at least the foods that person 5 likes. Something like below:
SELECT p.name FROM person p
LEFT JOIN favourite_food ff ON ff.personid = p.id
WHERE ff.food = (SELECT food FROM favourite_food WHERE personid = 5)
AND ff.personid <> 5;
Except I have no idea how to specify the 'at least' part. Do I have to create a temporary table or so?
SQL essentially works with sets, so it often helps to reformulate your problem strictly in terms of set theory.
"At least" could be reformulated this way:
If we look only at foods that are favourite foods of person 5, which persons have the same number of favourite foods as person 5?
SELECT name
FROM person
WHERE id IN (SELECT personid
FROM favourite_food
WHERE food IN (SELECT food
FROM favourite_food
WHERE personid = 5)
GROUP BY personid
HAVING COUNT(food) = (SELECT COUNT(food)
FROM favourite_food
WHERE personid = 5)
)
Alternatively, use this reformulation:
We do not want persons who do not like a food that person 5 likes.
Therefore, find all persons for which no food exists that is liked by person 5 but not liked by that person:
SELECT name
FROM person
WHERE NOT EXISTS (SELECT 1
FROM favourite_food AS person5_food
WHERE personid = 5
AND NOT EXISTS (SELECT 1
FROM favourite_food
WHERE personid = person.id
AND food = person5_food.food)
)
(Actually, SQL is based on the relational algebra, and the operation you want is called division.)
I have this query that has no problem:
SELECT m.movie_name, cd.times_requested
FROM movie m,
(select *
from(
select movie_id, count(movie_id) as times_requested
from movie_queue
where status_id=0 or status_id=1
group by movie_id
) ab
where times_requested>1) cd
WHERE m.movie_id=cd.movie_id;
It returns the following list.
MOVIE_NAME TIMES_REQUESTED
----------------------------------------------------------------------
E.T. the Extra-Terrestrial 2
Indiana Jones and the Kingdom of the Crystal Skull 2
War of the Worlds 3
Unbreakable 3
Question:
How do I add another column showing the amount of DVDs available for each movie, I can not just join it because I need to group DVDs by movie_id first?.
Table above is OK, but I want to add a third column, the third column will contain information about the number of DVDs available for each movie. the problem is that the number of dvds is stored in another table call DVDS. The structure of the table DVDs is similar to this:
DVD_ID MOVIE_ID DVD_ENTRY_DATE
---------- ---------- --------------
1 1 24-JUL-12
2 1 24-JUL-12
3 1 24-JUL-12
4 2 24-JUL-12
5 2 24-JUL-12
Desired Result:
Final table should look similar to the one below:
MOVIE_NAME TIMES_REQUESTED DVDS_AVAILABLE
-------------------------------------------------------------------
E.T. the Extra-Terrestrial 2 3
Indiana Jones and the Kingdom 2 1
War of the Worlds 3 3
Unbreakable 3 1
I tried the following code, but did not get the result I wanted
I am assuming I need to go to dvd table first and find all dvds that match the movie_id I want and group them by movie_id. I tried the code below, but instead of returning the 4 rows I want it is returning 72.
SELECT m.movie_name, pomid.times_requested, d.dvds_available
FROM movie m,
(select *
from(
select movie_id, count(movie_id) as times_requested
from movie_queue
where status_id=0 or status_id=1
group by movie_id
) mid
where times_requested>1) pomid,
(select movie_id, count(movie_id) as dvds_available
from dvd
group by movie_id) d
WHERE m.movie_id=pomid.movie_id;
Thanks for your suggestions in how to fix this.
A join to d.movie_id is missing, that's why you get too many rows. (Quick check: how many tables do I have? How many joins do I have?)
And I'd also add an outer join to get all movies, even when there are no dvd or movie_queue entries.
SELECT m.movie_name
,NVL(pomid.times_requested,0) times_requested
,NVL(d.dvds_available,0) dvds_available
FROM movie m
,(SELECT *
FROM (SELECT movie_id
,COUNT (movie_id) AS times_requested
FROM movie_queue
WHERE status_id = 0
OR status_id = 1
GROUP BY movie_id) mid
WHERE times_requested > 1) pomid
,(SELECT movie_id
,COUNT (movie_id) AS dvds_available
FROM dvd
GROUP BY movie_id) d
WHERE m.movie_id = pomid.movie_id(+)
AND d.movie_id(+) = pomid.movie_id;
http://www.sqlfiddle.com/#!4/5437a/11
Not sure I really understand your table structure, but this might get you started:
select movie_id,
sum(case when status_id in (0,1) then 1 else 0 end) as times_requested,
count(movie_id) as dvd_available,
from movie_queue
group by movie_id;
I'm doing a test web shop that has sections within sections. There can be an unlimited number of levels so I have just one table Section.
The table has the following columns:
SectionID, SectionTitle, SectionLevel, ParentID, PageOrder
SectionLevel: 1 being topmost (no parent)
PageOrder: Within it's parent group, which order it should go in.
And for test data :
SectionID SectionTitle SectionLevel ParentID PageOrder
--------- ------------ ------------ -------- ---------
2 Ladies 1 0 2
3 Mens 1 0 3
4 Jewellery 2 2 1
5 Clothing 2 2 2
6 Clothing 2 3 1
7 Accessories 2 3 2
I want to return this data in one table so that the first top level section is first, and then all of it's children's sections are next, and then the second top level section etc.
I've had a play around with it but can't get to come out right. I think that it should be possible to do it if I redesigned the table but can't think how.
The data should come back in the following order:
SectionID SectionTitle SectionLevel ParentID PageOrder
--------- ------------ ------------ -------- ---------
2 Ladies 1 0 2
4 Jewellery 2 2 1
5 Clothing 2 2 2
3 Mens 1 0 3
6 Clothing 2 3 1
7 Accessories 2 3 2
WITH tree (SectionID, ParentID, SectionLevel, SectionTitle) AS
(
SELECT SectionID, ofs.ParentID, ofs.SectionLevel, ofs.SectionTitle
FROM Section ofs
WHERE ofs.ParentID = 0
ORDER BY SectionID
UNION ALL
SELECT SectionID, ofs.ParentID, ofs.SectionLevel, ofs.SectionTitle
FROM Section ofs
JOIN tree ON tree.ID = ofs.ParentID
ORDER BY PageOrder
)
This is deliberately slightly niaive to make it easier on my fingers. It can be tuned if necessary to allow more nodes in the tree to be accomodated.
DECLARE
#maxPageOrder INT,
#maxLevel INT,
#multiplier INT
SELECT
#maxPageOrder = MAX(PageOrder) + 1,
#maxLevel = MAX(SectionLevel)
FROM
Section
SELECT
#multiplier = POWER(#maxPageOrder, #maxLevel - 1)
;
WITH
recursed_tree
AS
(
SELECT
SectionID AS SectionID,
PageOrder * #multiplier AS finalOrder,
#multiplier / #maxPageOrder AS multiplier
FROM
Section
WHERE
ParentID = 0
UNION ALL
SELECT
child.SectionID,
parent.finalOrder + child.PageOrder * parent.multiplier,
parent.multiplier / #maxPageOrder
FROM
recursed_tree AS parent
INNER JOIN
Section AS child
ON child.ParentID = parent.SectionID
)
SELECT
Section.*
FROM
Section
INNER JOIN
recursed_tree
ON Section.SectionID = recursed_tree.SectionID
ORDER BY
recursed_tree.finalOrder
Note: This assumes all page orders start from 1, rather than 0.
Here's the messy solution I was talking about..
select
a.SectionID, a.SectionTitle, a.SectionLevel, a.ParentID, a.PageOrder
from
section a
left outer join section b on b.SectionID=a.ParentID
left outer join section c on c.SectionID=b.ParentID
order by
c.PageOrder, b.PageOrder, a.PageOrder
As I mentioned in the comment, better to do it in code.. this is only for 3 levels (you could make it more by adding another outer join and order by clause).
This is rather complicated in SQL. If you have access to Safari Books, there are some relevant sections in Joe Celko's Trees and Hierarchies in SQL for Smarties.
I have a table with 5 columns: ID, Session1, Session2, Session3, Session4. What I want to do is to display the number of same items from each column. Same item can't appear in more than one column.
ID Session1 Session2 Session3 Session4
-- -------- -------- -------- --------
1 Music Web Tech Future
2 Art Articles Search Introduction
3 Music Books Camera Phone
4 Music Glass Cup Future
5 Art Books Tech Future
6 Music Glass Cup Phone
I want to display it like this on an asp.net page.
Music: 4
Art: 2
Web: 1
Articles: 1
Books: 2
Glass: 2
Tech: 2
Search: 1
Camera: 1
Cup: 2
Future: 3
introduction: 1
Phone: 2
how would I construct a sql query and display them on asp.net?
You could simply use:
SELECT
session1,
COUNT(*)
FROM
My_Table
GROUP BY
session1
UNION ALL
SELECT
session2,
COUNT(*)
FROM
My_Table
GROUP BY
session2
...
With SQL Server you could do something like this:
SELECT Session, COUNT(*) FROM (
SELECT Session1 AS Session FROM TableName
UNION ALL
SELECT Session2 AS Session FROM TableName
UNION ALL
SELECT Session3 AS Session FROM TableName
UNION ALL
SELECT Session4 AS Session FROM TableName) T1
GROUP BY Session
It's not pretty, but it should work.
try:
Select z.Val, Count(*) ValueCount
From (Select Distinct session1 val From Table Union
Select Distinct session2 val From Table Union
Select Distinct session3 val From Table Union
Select Distinct session4 val From Table) Z
Join Table t on Z.Val In (t.Session1, t.Session2, t.Session3, t.Session4)
Group By z.Val