How can I get a distinct list of elements in a hierarchical query? - sql

I have a database table, with people identified by a name, a job and a city. I have a second table that contains a hierarchical representation of every job in the company in every city.
Suppose I have 3 people in the people table:
[name(PK),title,city]
Jim, Salesman, Houston
Jane, Associate Marketer, Chicago
Bill, Cashier, New York
And I have thousands of job type/location combinations in the job table, a sample of which follow. You can see the hierarchical relationship since parent_title is a foreign key to title:
[title,city,pay,parent_title]
Salesman, Houston, $50000, CEO
Cashier, Houston, $25000
CEO, USA, $1000000
Associate Marketer, Chicago, $75000
Senior Marketer, Chicago, $125000
.....
The problem I'm having is that my Person table is a composite key, so I don't know how to structure the start with part of my query so that it starts with each of the three jobs in the cities I specified.
I can execute three separate queries to get what I want, but this doesn't scale well. e.g.:
select * from jobs
start with city = (select city from people where name = 'Bill') and title = (select title from people where name = 'Bill')
connect by prior parent_title = title
UNION
select * from jobs
start with city = (select city from people where name = 'Jim') and title = (select title from people where name = 'Jim')
connect by prior parent_title = title
UNION
select * from jobs
start with city = (select city from people where name = 'Jane') and title = (select title from people where name = 'Jane')
connect by prior parent_title = title
How else can I get a distinct list (or I could wrap it with a distinct if not possible) of all the jobs which are above the three people I specified?

Please try this. I haven't tested this.
SELECT distinct *
FROM jobs
START WITH ( city, title ) IN
( SELECT city, title
FROM people
WHERE name IN ( 'Bill', 'Jim', 'Jane' )
)
CONNECT BY PRIOR parent_title = title;

this should work:
SQL> SELECT *
2 FROM jobs
3 START WITH (title, city) IN (SELECT title, city FROM people)
4 CONNECT BY PRIOR parent_title = title;
TITLE CITY PAY PARENT_TITLE
------------------ ------- ---------- ------------
Associate Marketer Chicago 7500
Salesman Houston 5000 CEO
CEO USA 100000

Related

How do I select all values which satisfy certain aggregate properties?

Say, I have a table (named "Customers") which consists of:
CustomerName
Country
City
I am trying to list the names of all customers from cities where there are at least two customers.
This is my initial attempt:
SELECT CustomerName, City
FROM Customers
GROUP BY City
HAVING COUNT(City) > 1
This is the result that I got:
CustomerName
City
Person A
New York
Person C
Los Angeles
Here, Person A is a person from NY who appears on the top of the table and similar for Person B. However, what I wanted was the listing of all customers from New York and LA.
When I tried:
SELECT COUNT(CustomerName), City
FROM Customers
GROUP BY City
HAVING COUNT(City) > 1
I had
COUNT(CustomerName)
City
3
New York
5
Los Angeles
This means that the code is working properly, except that my original code only displays a person on top of the table from NY and LA. How do I resolve this issue?
Get cities with more than 1 customer in a subquery, and use that list to select the customers:
SELECT cst.CustomerName
FROM Customers cst
WHERE Cst.City in (
-- All cities where there are at least two customers
SELECT CstGE2.City
FROM Customers CstGE2
GROUP BY CstGE2.City
HAVING count(*) >= 2
)
How about this? I've taken the city out of the select part since you said you just wanted customer names.
SELECT a.CustomerName
FROM Customers a
WHERE (
SELECT COUNT(b.CustomerName)
FROM Customers b
WHERE b.City = a.City
) > 1
ORDER BY a.CustomerName

SQLite query to get table based on values of another table

I am not sure what title has to be here to correctly reflect my question, I can only describe what I want.
There is a table with fields:
id, name, city
There are next rows:
1 John London
2 Mary Paris
3 John Paris
4 Samy London
I want to get a such result:
London Paris
Total 2 2
John 1 1
Mary 0 1
Samy 1 0
So, I need to take all unique values of name and find an appropriate quantity for unique values of another field (city)
Also I want to get a total quantity of each city
Simple way to do it is:
1)Get a list of unique names
SELECT DISTINCT name FROM table
2)Get a list of unique cities
SELECT DISTINCT city FROM table
3)Create a query for every name and city
SELECT COUNT(city) FROM table WHERE name = some_name AND city = some_city
4)Get total:
SELECT COUNT(city) FROM table WHERE name = some_name
(I did't test these queries, so maybe there are some errors here but it's only to show the idea)
As there are 3 names and 2 cities -> 3 * 2 = 6 queries to DB
But for a table with 100 cities and 100 names -> 100 * 100 = 10 000 queries to DB
and it may take a lot of time to do.
Also, names and cities may be changed, so, I can't create a query with predefined names or cities as every day it's new ones, so, instead of London and Paris it may be Moscow, Turin and Berlin. The same thing with names.
How to get such table with one-two queries to original table using sqlite?
(sqlite: I do it for android)
You can get the per-name results with conditional aggregation. As for the total, unfortunately SQLite does not support the with rollup clause, that would generate it automatically.
One workaround is union all and an additional column for ordering:
select name, london, paris
from (
select name, sum(city = 'London') london, sum(city = 'Paris') paris, 1 prio
from mytable
group by name
union all
select 'Total', sum(city = 'London'), sum(city = 'Paris'), 0
from mytable
) t
order by prio, name
Actually the subquery might not be necessary:
select name, sum(city = 'London') london, sum(city = 'Paris') paris, 1 prio
from mytable
group by name
union all
select 'Total', sum(city = 'London'), sum(city = 'Paris'), 0
from mytable
order by prio, name
#GMB gave me the idea of using group by, but as I do it for SQLite on Android, so, the answer looks like:
SELECT name,
COUNT(CASE WHEN city = :london THEN 1 END) as countLondon,
COUNT(CASE WHEN city = :paris THEN 1 END) as countParis
FROM table2 GROUP BY name
where :london and :paris are passed params, and countLondon and countParis are fields of the response class

How to find people in a database who live in the same cities?

I'm new to SQL, and I'm asking for help in an apparently easy question, but it gets cumbersome in my mind.
I have the following table:
ID NAME CITY
---------------------
1 John new york
2 Sam new york
3 Tom boston
4 Bob boston
5 Jan chicago
6 Ted san francisco
7 Kat boston
I want a query that returns all the people who live in a city that another person registered in the database also lives in.
The answer, for the table I showed above, would be:
ID NAME CITY
---------------------
1 John new york
2 Sam new york
3 Tom boston
4 Bob boston
7 Kat boston
This is really a two part question:
What cities have more than one user located in them?
What users live in that subset of cities?
Let's answer it in two parts. Let's also make the simplifying assumption (not stated in your question) that the Users table has only one entry per user per city.
To find cities with more than one user:
SELECT City FROM Users GROUP BY City HAVING COUNT(*) > 1
Now, let's find all the users for those cities:
SELECT ID, User, City FROM Users
WHERE City IN (SELECT City FROM Users GROUP BY CITY HAVING COUNT(*) > 1)
I would use EXISTS :
SELECT t.*
FROM table t
WHERE EXISTS (SELECT 1 FROM table t1 WHERE t1.city = t.city AND t1.name <> t.name);
To avoid a correlated subquery which leads to a nested loop, you could perform a self join:
SELECT id, name, city
FROM persons
JOIN (SELECT city
FROM persons
GROUP BY city HAVING count(*) > 1) AS cities
USING (city);
This might be the most performant solution.
This will give you the rows that have the same city more than 1 time:
SELECT persons.*
FROM persons
WHERE (SELECT COUNT(*) FROM persons AS p GROUP BY CITY HAVING p.CITY = persons.CITY) > 1
This is just a different flavor from the others that have posted.
SELECT ID,
name,
city
FROM (SELECT DISTINCT
ID,
name,
city,
COUNT(1) OVER (PARTITION BY city) AS cityCount
FROM table) t
WHERE cityCount > 1
This can be expressed many ways. Here is one possible way:
select * from persons p
where exists (
select 1 from persons p2
where p2.city = p.city and p2.name <> p.name
)

How to select only distinct row when there might be duplicates using SQL?

I have a tricky SQL query I could use some help with.
I have a phone directory table, that was not designed very well. It has name, phone number, job description and primary_job_indicator. However, the primary_job_indicator isn't doing it's job. Not everyone has a primary_job.
Here's some sample data:
fname lname phone email job primary_job_ind
Tim Burton 222-2222 tburton#ok.com manager Y
Jim Classy 222-3333 tclassy#ok.com instructor Y
Jim Classy 222-3333 tclassy#ok.com dept head N
Jane Dill 222-4444 jdill#ok.com janitor N
I would like to select only the following, one row, with one job per person :
Tim Burton 222-2222 tburton#ok.com manager
Jim classy 222-3333 jclassy#ok.com instructor
Jane Dill 222-4444 jdill#ok.com janitor
I want to select from the table and avoid duplicate name+phone number+email.
If the person has only one row in the table, I want to select that row.
If the person has more than one row in the table, I want to select only one row - the one with primary_job_ind = 'Y' if it exists
I can't figure out how to do it :
SELECT fname, lname, phone, email, job
FROM phonedirectory
WHERE (( primary_job_ind = 'Y' ) OR ??????? )
Assuming there is no typo (which there probably is), this should do the job
select fname, lname, phone, email, job
from (
SELECT fname, lname, phone, email, job,
row_number() over (
partition by fname, lname, phone, email
order by primary_job_ind desc
) r
FROM phonedirectory
) where r = 1
It numbers the rows, belonging to the same person, with the primary job first, then takes only the first row for each person.
See the documenation of ROW_NUMBER for details

SQL: Join with complete participation

Suppose that I have a table Person(Name, Hobby) and there are 3 hobbies in total. The table's values are like
Amy | Stamp Collection
Kevin | Mountain Biking
Kevin | Stamp Collection
Ron | Mountain Biking
Here, Kevin has both the hobbies Mountain Biking and Stamp Collection. I need to write a query to retrieve Kevin.
How can I get the person who has all the hobbies?
Thanks
SELECT Name
FROM Person
GROUP BY Name
HAVING COUNT(*) = (SELECT COUNT(DISTINCT Hobby) FROM Person)
Runnable example
note : not tested, and its correct in Oracle sql
you can try this :
SELECT *
FROM
(
SELECT p.name,
count(distinct p.hobby) cnt
FROM Person p
GROUP BY p.name
) p2
WHERE p2.cnt = (SELECT count(distinct Hobby)
FROM Person)
If you need to get the person's name and count use this:
SELECT count(*) as count, Name FROM Person group by Name limit 1
This will get you the person's name and the amount of hobbies they have. To get all hobbies add the field to the query and remove the grouping:
SELECT Name, Hobby FROM Person where Name = {name} limit 1
(without the curly braces)