Oracle SQL Columns to Rows without UNPIVOT - sql

What I currently have:
Team User Apples Oranges Pears
Red Adam 4 5 6
Red Avril 11 12 13
Blue David 21 22 23
What's needed:
Team User Product Count
Red Adam Apples 4
Red Adam Oranges 5
Red Adam Pears 6
Red Avril Apples 11
Red Avril Oranges 12
Red Avril Pears 13
Blue David Apples 21
....
This is to be implemented using Oracle SQL. I understand this can be done using UNPIVOT, but my Oracle SQL version is too old to support this method. Can someone provide an example of how to achieve this using CROSS APPLY or equivalent methods? Count changes depending on team-user-product combination, and the number of product types may change slightly in the future so a scalable solution might be necessary.
This is time-sensitive, so I appreciate the help.

You can do this using a cross join and some case statements by using a dummy subquery that holds the same number of rows as you have columns that you want to unpivot (since you want each column to go into its own row) like so:
WITH your_table AS (SELECT 'Red' Team, 'Adam' usr, 4 Apples, 5 Oranges, 6 Pears FROM dual UNION ALL
SELECT 'Red' Team, 'Avril' usr, 11 Apples, 12 Oranges, 13 Pears FROM dual UNION ALL
SELECT 'Blue' Team, 'David' usr, 21 Apples, 22 Oranges, 23 Pears FROM dual)
-- end of mimicking your table. See SQL below:
SELECT yt.team,
yt.usr,
CASE WHEN d.id = 1 THEN 'Apples'
WHEN d.id = 2 THEN 'Oranges'
WHEN d.id = 3 THEN 'Pears'
END product,
CASE WHEN d.id = 1 THEN yt.apples
WHEN d.id = 2 THEN yt.oranges
WHEN d.id = 3 THEN yt.pears
END count_of_product
FROM your_table yt
CROSS JOIN (SELECT LEVEL ID
FROM dual
CONNECT BY LEVEL <= 3) d -- number of columns to unpivot
ORDER BY team, usr, product;
TEAM USR PRODUCT COUNT_OF_PRODUCT
---- ----- ------- ----------------
Blue David Apples 21
Blue David Oranges 22
Blue David Pears 23
Red Adam Apples 4
Red Adam Oranges 5
Red Adam Pears 6
Red Avril Apples 11
Red Avril Oranges 12
Red Avril Pears 13
Doing it this way means that you only have to go through the table once, rather than multiple times if you were doing the union all method.
ETA: Here's the method that Aleksej was referring to - I would suggest testing both methods against your set of data (which is hopefully large enough to be representative) to see which one is more performant:
WITH your_table AS (SELECT 'Red' Team, 'Adam' usr, 4 Apples, 5 Oranges, 6 Pears FROM dual UNION ALL
SELECT 'Red' Team, 'Avril' usr, 11 Apples, 12 Oranges, 13 Pears FROM dual UNION ALL
SELECT 'Blue' Team, 'David' usr, 21 Apples, 22 Oranges, 23 Pears FROM dual)
-- end of mimicking your table. See SQL below:
SELECT yt.team,
yt.usr,
CASE WHEN LEVEL = 1 THEN 'Apples'
WHEN LEVEL = 2 THEN 'Oranges'
WHEN LEVEL = 3 THEN 'Pears'
END product,
CASE WHEN LEVEL = 1 THEN yt.apples
WHEN LEVEL = 2 THEN yt.oranges
WHEN LEVEL = 3 THEN yt.pears
END count_of_product
FROM your_table yt
CONNECT BY PRIOR team = team
AND PRIOR usr = usr
AND PRIOR sys_guid() IS NOT NULL
AND LEVEL <= 3
ORDER BY team, usr, product;
TEAM USR PRODUCT COUNT_OF_PRODUCT
---- ----- ------- ----------------
Blue David Apples 21
Blue David Oranges 22
Blue David Pears 23
Red Adam Apples 4
Red Adam Oranges 5
Red Adam Pears 6
Red Avril Apples 11
Red Avril Oranges 12
Red Avril Pears 13

You can use a big union all like this:
select
Team,
"User",
'Apples' Product,
Apples "Count"
from your_table
union all
select
Team,
"User",
'Oranges' Product,
Oranges "Count"
from your_table
union all
select
Team,
"User",
'Pears' Product,
Pears "Count"
from your_table
union all
. . .
Also, try not to use keywords such as User or Count as identifiers or else, wrap them in double quotes like I did.

Related

SQL query show customers who bought apples, but not potatoes

Not sure how to explain this..
I have a similar table, but i have simplified it with the following:
I have a table of goods shipped to different cusotmers. Some have bought apples only, others have bought apples and potates.
I want an SQL query to return only customers where "To be billed" = Yes AND the customer hasnt bought any vegetables.
So for example if the table looks like this:
Item
Name
Group
To_be_billed
CustomerNo.
2000
Apple
Fruit
Yes
1
2000
Apple
Fruit
No
2
2000
Apple
Fruit
No
3
2000
Apple
Fruit
Yes
4
2000
Apple
Fruit
Yes
5
4000
Potato
Vegetable
No
2
4000
Potato
Vegetable
No
4
I want the query to return:
Item
Name
Group
To_be_billed
CustomerNo.
2000
Apple
Fruit
Yes
1
2000
Apple
Fruit
Yes
5
The reason 4 has bought apples, and is to be billed, but the customer also bought Potatoes, so is to be ignored...
You can create a CTE to check for CustomerNo.s that you need to ignore, and then use not exists:
with bought_veg as
(
select "CustomerNo."
from tbl
where tbl."Group" like 'Vegetable'
)
select tbl.*
from tbl
where not exists (select 1 from bought_veg where tbl."CustomerNo." = bought_veg."CustomerNo.")
and tbl.To_be_billed = 'Yes'
Example without CTE:
select tbl.*
from tbl
where not exists (select "CustomerNo." from tbl t2 where tbl.[CustomerNo.] = t2.[CustomerNo.] and "Group" like 'Vegetable')
and tbl.To_be_billed = 'Yes'

Oracle SQL use a column as a query and then display the results over the original column

I have two tables.
The first table, named DATA, looks like:
DATE NAME DESCRIPTION
5/2 5 Orange Juice
5/4 2 Apple Juice
5/5 1 Cranberry Juice
5/6 1 Lemon Juice
The second table is called NAMES and looks like:
NAME_ID NAME
5 Bob
4 Frank
3 Megan
2 Tim
1 Brian
I want to, query all columns from the DATA table and perform a lookup on the NAME column, have it grab that number, look that number up in the NAMES table and replace that data in the DATA table, NAME column, so I would get something like:
DATE NAME DESCRIPTION
5/2 Bob Orange Juice
5/4 Tim Apple Juice
5/5 Brian Cranberry Juice
5/6 Brian Lemon Juice
I am a total SQL noob, so my attempts so far have been google the crap out of this, and I keep getting lead to subqueries, but that doesn't seem to solve the problem.
No, you don't need subqueries - but you do need joins. Sample data from line #1 - 10; query that does the job begins at line #12. I guess that "date" column actually is DATE datatype, but - for simplicity - I left it as a string.
SQL> with
2 -- sample data
3 data (datum, name, description) as
4 (select '5/2', 5, 'Orange Juice' from dual union all
5 select '5/4', 2, 'Apple Juice' from dual
6 ),
7 names (name_id, name) as
8 (select 5, 'Bob' from dual union all
9 select 2, 'Tim' from dual
10 )
11 -- query you probably need
12 select d.datum,
13 n.name,
14 d.description
15 from data d join names n on n.name_id = d.name
16 order by d.datum;
DATUM NAME DESCRIPTION
----- ----- ------------
5/2 Bob Orange Juice
5/4 Tim Apple Juice
SQL>

How to get a row of unique values from prior row followed by nulls till the next value?

I have a query that has multiple joins and fields. I have one row that has alot of duplicates. I need to only get the distict values from this specific row while leaving the size of the query the same due to the other joins.
I have tried group by and districts but they eliminate other critical information in the query. I need to leave the query length the same.
example:(pseudocode)
SELECT
Name
,StateID
,Age
,Toy
,ManufactureName
From
peopleTable as people
LEFT JOIN toyTable on people.id = toytable.id
LEFT JOIN ManufactureTable on toyTable.toyId=ManufactureTable.ManId
WHERE
toytable.id >1000
output
Name StateID Age Toy Manufacture
Carlo 1 10 Woody Disney
Sid 1 10 Buzz Disney
Abby 1 10 Car RaceMan
Bobby 4 10 Doll Barbie
Sally 6 10 Book Barns&
Jim 6 10 Woody Disney
ExpectedOutput
Name StateID Age Toy Manufacture NewField
Carlo 1 10 Woody Disney 1
Sid 1 10 Buzz Disney NULL
Abby 1 10 Car RaceMan NULL
Bobby 4 10 Doll Barbie 4
Sally 6 10 Book Barns& 6
Jim 6 10 Woody Disney Null
Would something like this help?
Using ROW_NUMBER analytic function, find out the first row in a group of those that share the same stateid. Note that I used order by null as I don't know which one is the first (name isn't, nor is age or toy or manufacture). If you don't care, leave it as is. If you know how to sort them, use that column.
SQL> with test (name, stateid, age, toy, manufacture) as
2 (select 'Carlo', 1, 10, 'Woody', 'Disney' from dual union all
3 select 'Sid' , 1, 10, 'Buzz' , 'Disney' from dual union all
4 select 'Abby' , 1, 10, 'Car' , 'RaceMan' from dual union all
5 select 'Bobby', 4, 10, 'Doll' , 'Barbie' from dual union all
6 select 'Sally', 6, 10, 'Book' , 'Barns&' from dual union all
7 select 'Jim' , 6, 10, 'Woody', 'Disney' from dual
8 )
9 select name, stateid, age, toy, manufacture,
10 case when row_number() over (partition by stateid order by null) = 1 then stateid
11 else null
12 end new_field
13 from test;
NAME STATEID AGE TOY MANUFAC NEW_FIELD
----- ---------- ---------- ----- ------- ----------
Carlo 1 10 Woody Disney 1
Sid 1 10 Buzz Disney
Abby 1 10 Car RaceMan
Bobby 4 10 Doll Barbie 4
Sally 6 10 Book Barns& 6
Jim 6 10 Woody Disney
6 rows selected.
SQL>

Oracle find common value in two different columns

If I have a structure like this:
CREATE TABLE things (
id,
personA varchar2,
personB varchar2,
attribute ...,
)
And I want to find, for a given attribute, if I have at least 1 common person for all my things, how would I go about it?
So if my data is (and it could be more than 2 per attribute):
1, John, Steve, Apple
2, Steve, Larry, Apple
3, Paul, Larry, Orange
4, Paul, Larry, Orange
5, Chris, Michael, Tomato
6, Steve, Larry, Tomato
For Apple, Steve is my common person, For Orange both Paul and Larry are, and for Tomato I have no common people. I don't need a query that returns all of these at once, however. I have one of these attributes and want 0, 1, or 2 rows depending on what kind of commonality I have. I've been trying to come up with something but can't quite figure out.
This will give you your common person / attribute list. I ran it against your sample data and got the expected result. Hope it's at least pointing in the right direction :)
WITH NormNames AS (
SELECT PersonA AS Person, Attribute FROM things
UNION ALL SELECT PersonB AS Person, Attribute FROM things
)
SELECT
Person, Attribute, COUNT(*)
FROM NormNames
GROUP BY Person, Attribute
HAVING COUNT(*) >= 2
If you're on 11gR2 you could also use the unpivot operator to avoid the self-join:
select person, attribute
from (
select *
from things
unpivot (person for which_person in (persona as 'A', personb as 'B'))
)
group by person, attribute
having count(*) > 1;
PERSON ATTRIBUTE
---------- ----------
Steve Apple
Paul Orange
Larry Orange
3 rows selected.
Or to just the the people who match the attribute, which I think is what the end of your question is looking for:
select person
from (
select *
from things
unpivot (person for which_person in (persona as 'A', personb as 'B'))
)
where attribute = 'Apple'
group by person, attribute
having count(*) > 1;
PERSON
----------
Steve
1 row selected.
The unpivot translates columns into rows. Run on its own it transforms your original six rows into twelve, replacing the original persona/personb columns with a single person and an additional column indicating which column the new row was formed from, which we don't really care about here:
select *
from things
unpivot (person for which_person in (persona as 'A', personb as 'B'));
ID ATTRIBUTE W PERSON
---------- ---------- - ----------
1 Apple A John
1 Apple B Steve
2 Apple A Steve
2 Apple B Larry
3 Orange A Paul
3 Orange B Larry
4 Orange A Paul
4 Orange B Larry
5 Tomato A Chris
5 Tomato B Michael
6 Tomato A Steve
6 Tomato B Larry
12 rows selected.
The outer query is then doing a simple group.
Here's one method.
It implements an unpivot method by cross-joining to a list of numbers (you could use the unpivot method Alex uses) and then joins the result set, hopefully with a hash join for added goodness.
with
row_generator as (
select 1 counter from dual union all
select 2 counter from dual),
data_generator as (
select
attribute,
id ,
case counter
when 1 then persona
when 2 then personb
end person
from
things,
row_generator)
select
t1.attribute,
t1.person
from
row_generator t1,
row_generator t2
where
t1.attribute = t2.attribute and
t1.person = t2.person and
t1.id != t2.id;

SQL calculate item frequency using multiple / dependent columns?

I'm completely new to SQL, and have read StackOverflow posts on SQL to try and figure this out, and other sources and unable to do this in SQL. Here goes...
I have a table of 3 columns and thousands of rows, with data for first 2 columns. The third column is currently empty and I need to populate the third column based on data already in the first and second columns.
Say I have states in the first column and fruit entries in the second column. I need to write an SQL statement(s) that calculates the number of different states where each fruit comes from, and then inserts this popularity number into the third column for every row. A popularity number of 1 in that row means that fruit only comes from one state, a popularity number of 4 means the fruit comes from 4 states. So my table is currently like:
state fruit popularity
hawaii apple
hawaii apple
hawaii banana
hawaii kiwi
hawaii kiwi
hawaii mango
florida apple
florida apple
florida apple
florida orange
michigan apple
michigan apple
michigan apricot
michigan orange
michigan pear
michigan pear
michigan pear
texas apple
texas banana
texas banana
texas banana
texas grape
And I need to figure out how to calculate and then update the third column, named popularity, which is the number of states that exports that fruit. The goal is to produce (sorry bad pun) the table below, where based on above table, "apple" appears in all 4 states, oranges and banana appear in 2 states, and kiwi, mango, pear, and grape only appear in 1 state, hence their corresponding popularity numbers.
state fruit popularity
hawaii apple 4
hawaii apple 4
hawaii banana 2
hawaii kiwi 1
hawaii kiwi 1
hawaii mango 1
florida apple 4
florida apple 4
florida apple 4
florida orange 2
michigan apple 4
michigan apple 4
michigan apricot 1
michigan orange 2
michigan pear 1
michigan pear 1
michigan pear 1
texas apple 4
texas banana 2
texas banana 2
texas banana 2
texas grape 1
My small programmer brain says to try and figure out a way to loop through the data in some kind of script, but reading up a little on SQL and databases, it seems like you don't write long and slow looping scripts in SQL. I'm not even sure if you can? but instead that there are better/faster ways to do this in SQL.
Anyone know how to, in SQL statement(s), calculate and update the third column for each row, which is here called popularity and corresponds to the number of states that each fruit comes from? Thanks for reading, very grateful for any help.
So far I have tried these SQL statements below, which output but don't quite get me what I need:
--outputs those fruits appearing multiple times in the table
SELECT fruit, COUNT(*)
FROM table
GROUP BY fruit
HAVING COUNT(*) > 1
ORDER BY COUNT(*) DESC
--outputs those fruits appearing only once in the table
SELECT fruit, COUNT(*)
FROM table
GROUP BY fruit
HAVING COUNT(*) = 1
--outputs list of unique fruits in the table
SELECT COUNT (DISTINCT(fruit))
FROM table
If you want to simply update your table with the priority it would look like:
update my_table x
set popularity = ( select count(distinct state)
from my_table
where fruit = x.fruit )
If you want to select the data then you can use an analytic query:
select state, fruit
, count(distinct state) over ( partition by fruit ) as popularity
from my_table
This provides the number of distinct states, per fruit.
I ran this and got (what I think) is what you want:
WITH t
AS (SELECT 'hawaii' as STATE, 'apple' as fruit FROM dual
UNION ALL
SELECT 'hawaii' as STATE, 'apple' as fruit FROM dual
UNION ALL
SELECT 'hawaii' as STATE, 'banana' as fruit FROM dual
UNION ALL
SELECT 'hawaii' as STATE, 'kiwi' as fruit FROM dual
UNION ALL
SELECT 'hawaii' as STATE, 'kiwi' as fruit FROM dual
UNION ALL
SELECT 'hawaii' as STATE, 'mango' as fruit FROM dual
UNION ALL
SELECT 'florida' as STATE, 'apple' as fruit FROM dual
UNION ALL
SELECT 'florida' as STATE, 'apple' as fruit FROM dual
UNION ALL
SELECT 'florida' as STATE, 'apple' as fruit FROM dual
UNION ALL
SELECT 'florida' as STATE, 'orange' as fruit FROM dual
UNION ALL
SELECT 'michigan' as STATE, 'apple' as fruit FROM dual
UNION ALL
SELECT 'michigan' as STATE, 'apple' as fruit FROM dual
UNION ALL
SELECT 'michigan' as STATE, 'apricot' as fruit FROM dual
UNION ALL
SELECT 'michigan' as STATE, 'orange' as fruit FROM dual
UNION ALL
SELECT 'michigan' as STATE, 'pear' as fruit FROM dual
UNION ALL
SELECT 'michigan' as STATE, 'pear' as fruit FROM dual
UNION ALL
SELECT 'michigan' as STATE, 'pear' as fruit FROM dual
UNION ALL
SELECT 'texas' as STATE, 'apple' as fruit FROM dual
UNION ALL
SELECT 'texas' as STATE, 'banana' as fruit FROM dual
UNION ALL
SELECT 'texas' as STATE, 'banana' as fruit FROM dual
UNION ALL
SELECT 'texas' as STATE, 'banana' as fruit FROM dual
UNION ALL
SELECT 'texas' as STATE, 'grape' as fruit FROM dual)
SELECT state,
fruit,
count(DISTINCT state) OVER (PARTITION BY fruit) AS popularity
FROM t;
Returned
florida apple 4
florida apple 4
florida apple 4
hawaii apple 4
hawaii apple 4
michigan apple 4
michigan apple 4
texas apple 4
michigan apricot 1
hawaii banana 2
texas banana 2
texas banana 2
texas banana 2
texas grape 1
hawaii kiwi 1
hawaii kiwi 1
hawaii mango 1
florida orange 2
michigan orange 2
michigan pear 1
michigan pear 1
Obviously, you'd only need to run:
SELECT state,
fruit,
count(DISTINCT state) OVER (PARTITION BY fruit) AS popularity
FROM table_name;
Hope it helps...
If your table is #fruit...
To count the different states for each fruit
select fruit, COUNT(distinct state) statecount from #fruit group by fruit
and so to update the table with these values
update #fruit
set popularity
= statecount
from
#fruit
inner join
(select fruit, COUNT(distinct state) statecount from #fruit group by fruit) sc
on #fruit.fruit = sc.fruit
This should get you most of the way there. Basically you want to get a count of distinct states that the fruit is in and then use that to join back to the original table.
update table
set count = cnt
from
(
select fruit, count(distinct state) as cnt
from table
group by fruit) cnts
inner join table t
on cnts.fruit = t.fruit
Another option:
SELECT fruit
, COUNT(*)
FROM
(
SELECT state
, fruit
, ROW_NUMBER() OVER (PARTITION BY state, fruit ORDER BY NULL) rn
FROM t
)
WHERE rn = 1
GROUP BY fruit
ORDER BY fruit;
Try this:
select a.*,b.total
from [table] as a
left join
(
SELECT fruit,count(distinct [state]) as total
FROM [table]
group by fruit
) as b
on a.fruit = b.fruit
Note this is SQL Server code, do your own tweaks if necessary.
try this
create table states([state] varchar(10),fruit varchar(10),popularity int)
INSERT INTO states([state],fruit)
VALUES('hawaii','apple'),
('hawaii','apple'),
('hawaii','banana'),
('hawaii','kiwi'),
('hawaii','kiwi'),
('hawaii','mango'),
('florida','apple'),
('florida','apple'),
('florida','apple'),
('florida','orange'),
('michigan','apple'),
('michigan','apple'),
('michigan','apricot'),
('michigan','orange'),
('michigan','pear'),
('michigan','pear'),
('michigan','pear'),
('texas','apple'),
('texas','banana'),
('texas','banana'),
('texas','banana'),
('texas','grape')
update t set t.popularity=a.cnt
from states t inner join
(SELECT fruit,count(distinct [state]) as cnt
FROM states
group by fruit) a
on t.fruit =a.fruit