SQL View with Data from two tables - sql

I can't seem to crack this - I have two tables (Persons and Companies), and I'm trying to create a view that:
shows all persons
also returns companies by themselves once, regardless of how many persons are related to it
orders by name across both tables
To clarify, some sample data:
(Table: Companies)
Id Name
1 Banana
2 ABC Inc.
3 Microsoft
4 Bigwig
(Table: Persons)
Id Name RelatedCompanyId
1 Joe Smith 3
2 Justin
3 Paul Rudd 4
4 Anjolie
5 Dustin 4
The output I'm looking for is something like this:
Name PersonName CompanyName RelatedCompanyId
ABC Inc. NULL ABC Inc. NULL
Anjolie Anjolie NULL NULL
Banana NULL Banana NULL
Bigwig NULL Bigwig NULL
Dustin Dustin Bigwig 4
Joe Smith Joe Smith Microsoft 3
Justin Justin NULL NULL
Microsoft NULL Microsoft NULL
Paul Rudd Paul Rudd Bigwig 4
As you can see, the new "Name" column is ordered across both tables (the company names appear correctly in between the person names), and each company appears exactly once, regardless of how many people are related to it.
Can this even be done in SQL?! P.S. I'm trying to create a view so I can use this later for easy data retrieval, fulltext indexing and make the programming side simpler by just querying the view.

Here's one way:
select * from (
select Name, null as PersonName, Name as CompanyName, null as RelatedCompanyID
from Companies
union
select Persons.Name as Name, Persons.Name as PersonName, Companies.Name as CompanyName, RelatedCompanyID
from Persons
left join Companies on Persons.RelatedCompanyID = Companies.ID
) as AggregatedData
order by AggregatedData.Name
Or slightly more readably with a common table expression, although there's no other real benefit in this case:
with AggregatedData as (
select Name, null as PersonName, Name as CompanyName, null as RelatedCompanyID
from Companies
union
select Persons.Name as Name, Persons.Name as PersonName, Companies.Name as CompanyName, RelatedCompanyID
from Persons
left join Companies on Persons.RelatedCompanyID = Companies.ID
)
select * from AggregatedData
order by AggregatedData.Name

Related

SQL left join on a table with alternative results - implementing selection order for optimal performance

I would like to join two rather large tables in an optimized way and looking for a solution here.
One table has Customers (with ID as primary key).
This to be left joined with:
Address table has different types of address data - it also has ID as pk, Customer ID as a foreign key but with duplications, type of address for the given row and the address itself.
I would like to implement a selection order - if the most preferred address type (in this case Postal) exists for a customer, then take the this address type else take second preference (in this case Delivery) as the next best and then the third type (Locational) as least preferable. There might be more than 3 options in the real problem, maybe 5-6.
To illustrate my problem:
Customer table:
id
name
data
1
Joe
some data Joe
2
Eva
some data Eva
3
David
some data David
4
Adam
some data Adam
Address Table
id
cust_id
address type
address
1
1
Postal
Postal type address for Joe
2
1
Delivery
Delivery type address for Joe
3
1
Locational
Locational type address for Joe
4
2
Delivery
Delivery type address for Eva
5
2
Locational
Locational type address for Eva
6
3
Locational
Locational type address for David
The desired output would look like this:
id
name
data
address type
address
1
Joe
some data Joe
Postal
Posta type address for Joe
2
Eva
some data Eva
Delivery
Delivery type address for Eva
3
David
some data David
Locational
Locational type address for David
4
Adam
some data Adam
null
null
So for customer 1 (Joe) the Postal type address was selected as it did exists. Then for customer 2 (Eva) type Delivery was selected as the second best option, and finally for customer 3 (David) it was the Locational type address. No address was selected for Adam.
I could figure out some options with a number of steps, eg. joining address table multiple times and then selecting the addresses with CASE WHEN type wrapping query but there must be an easier way to do this.
Here is a fiddle (MS SQL Server)
http://sqlfiddle.com/#!18/e0ffce
Thanks in advance
Use a CTE with DENSE_RANK() on your Address table. To pick up Customers that do not have an address, add a COALESCE to the WHERE clause:
WITH CTE AS (
SELECT *, DENSE_RANK() OVER (PARTITION BY cust_id ORDER BY
CASE WHEN address_type = 'Postal' THEN 1
WHEN address_type = 'Delivery' THEN 2
WHEN address_type = 'Locational' THEN 3
ELSE NULL END ASC) AS dr
FROM Address)
SELECT a.*, b.address_type, b.address
FROM Customer a
LEFT JOIN CTE b
ON a.id = b.cust_id
WHERE COALESCE(dr, 1) = 1
Result:
id
name
some_data
address_type
address
1
Joe
some data Joe
Postal
Postal type address for Joe
2
Eva
some data Eva
Delivery
Delivery type address for Eva
3
David
some data David
Locational
Locational type address for David
4
Adam
some data Adam
null
null
Demo here.
Using a CTE with DENSE_RANK() on my Address table with an explicit order using CASE WHEN does the trick.
Slight modification of GRIV's answer above:
WITH CTE AS (
SELECT *, DENSE_RANK() OVER (PARTITION BY Cust_ID ORDER BY addr_order ASC) AS rank
FROM (
SELECT *,
CASE WHEN Address_Type LIKE 'Postal' THEN 1 ELSE
CASE WHEN Address_Type LIKE 'Delivery' THEN 2 ELSE
CASE WHEN Address_Type LIKE 'Locational' THEN 3 ELSE null
END
END
END AS addr_order
FROM Address
) z )
SELECT a.*
, b.address_type
, b.address
FROM Customer a
LEFT JOIN CTE b
ON a.id = b.cust_id
AND rank = 1
You can use row_number()over(partition by Cust_id order by [address type] asc) as Rn. Now use a cte or subquery to get the Rn=1 which will get you the desired result. I hope it works for you .

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
)

Rows as Columns without Join

Please have a look at this. The result shows, indeed, a join of two sets. I want the output as following i.e. No Cartesian Product.
ID_1 TYPE_1 NAME_1 ID_2 TYPE_2 NAME_2
===============================================================
TP001 1 Adam Smith TV001 2 Leon Crowell
TP002 1 Ben Conrad TV002 2 Chris Hobbs
TP003 1 Lively Jonathan
I used one of the solution, join, known to me to select rows as columns but i need results in required format while join is not mandatory.
You need an artificial column as id. Use rownum for that on both types of teachers.
Because you do not know if there are more Teachers of type 1 or of Type 2, you must do a full outer join to combine both sets.
SELECT *
FROM (SELECT ROWNUM AS cnt, teacherid
, teachertype, teachername
FROM teachers
WHERE teachertype = 1) qry1
FULL OUTER JOIN (SELECT ROWNUM AS cnt, teacherid
, teachertype, teachername
FROM teachers
WHERE teachertype = 2) qry2
ON qry1.cnt = qry2.cnt
In general, databases think in rows, not in columns. In your example you are lucky - you only have two types of teachers. For every new type of teacher you would have to alter your statement and append a full outer join only to present the output of your query in a special way - one set per column.
But with a simple select you retrive the same Information and it will work regardless how many teacher types you have.
SQL is somewhat limited in presenting data, i would leave that to the client retriving the data or use PL/SQL for a more generic aproach.
There should be some constraint of keys on which you join table or tables. If there is no constraint it will always result in Cartesian Product i.e number of rows of first table x numbers of rows of second table
SELECT TONE.TEACHERID ID_1, TONE.TEACHERTYPE TYPE_1, TONE.TEACHERNAME NAME_1
,TTWO.TEACHERID ID_2, TTWO.TEACHERTYPE TYPE_2, TTWO.TEACHERNAME NAME_2
FROM
(SELECT TEACHERID, TEACHERTYPE, TEACHERNAME FROM TEACHERS WHERE TEACHERTYPE = 1)
TONE
FULL OUTER JOIN
(SELECT TEACHERID, TEACHERTYPE, TEACHERNAME FROM TEACHERS WHERE TEACHERTYPE = 2)
TTWO
ON TONE.TEACHERID = REPLACE(TTWO.TEACHERID,'TV','TP');
ID_1 TYPE_1 NAME_1 ID_2 TYPE_2 NAME_2
===== ====== ====== ====== ====== ======
TP001 1 Adam Smith TV001 2 Leon Crowell
TP002 1 Ben Conrad TV002 2 Chris Hobbs
TP003 1 Lively Jonathan (null) (null) (null)
http://www.sqlfiddle.com/#!4/c58f3/28

mysql select in two tables

I have two tables and one reference table for the query. Any suggestion or help would greatly appreciated.
table1
user_id username firstname lastname address
1 john867 John Smith caloocan
2 bill96 Bill Jones manila
table2
user_name_id username firstname lastname address designation
1 jakelucas Jake Lucas caloocan employee
2 jadejones Jade Jones Quezon student
3 bong098 Bong Johnson pasig employee
reference table
ref_id username friend_username
1 tirso bill96
2 tirso jadejones
2 tirso bong098
the output should like this
user_id user_name_id username firstname lastname address designation
2 bill96 Bill Jones manila
2 jadejones Jade Jones Quezon student
3 bong098 Bong Johnson pasig employee
Since some decent union queries have already been posted, I'll talk about your db design a little bit.
I would definitely take what IronGoofy said into serious consideration before you take too much time looking into joining these tables together. It seems that you have a lot of duplicate data to manage with your tables, and that could get out of hand rather quickly should this scale up.
I think you should probably try and separate your data out so that the important information can be linked on the user_id.
So, for instance, you could have a few tables here...
User Information Table:
---------
User_id
Username
First Name
Last Name
Address
Designation_id
Friend Link Table:
---------
Friend_link_id
User_id
Friend_user_id
Designation Table:
---------
Designation_id
Designation_name
So, rather than link on your user names all over the place, you would simply join on the various ID's. A bit cleaner and missing the duplicate data issue that you had before IMO. Hope this helps...
Can you try something like this
SELECT [table1].[USER_ID],
NULL user_name_id,
[table1].username,
[table1].firstname,
[table1].lastname,
[table1].address,
NULL designation
FROM reference_table INNER JOIN
table1 ON [reference_table].friend_username = [table1].username
UNION
SELECT NULL USER_ID,
[table2].user_name_id,
[table2].username,
[table2].firstname,
[table2].lastname,
[table2].address,
[table2].designation
FROM reference_table INNER JOIN
table2 ON [reference_table].friend_username = [table2].username
It's not quite clear what you are trying to achieve, but here's a guess:
SELECT user_id, NULL as user_name_id, username, ...
FROM ref_tab r join table1 t1 on r.friend_username = t1.username
WHERE r.ref_id = 1
UNION
SELECT NULL as user_id, user_name_id, username, ...
FROM ref_tab r join table1 t2 on r.friend_username = t2.username
WHERE r.ref_id = 2
But I'd have a hard look at the DB design and think about some improvements ...