SQL Server query - don't want multiple rows with identical data - sql

I have a SQL Server database that has the following three tables - this is simplified for this post.
Stakeholder table (a table that stores a persons personal data... name, address city, state, zip, etc)
Stakeholder_id full_name
---------------------------------------
1 Joe Stakeholder
2 Eric Stakeholder
SH Inquiry table (a table that stores information about when a stakeholder contacts us)
sh_inquiry_id inquiry_link_ID
-----------------------------------------------
1 1
2 1
3 2
Sh Contacts (a table that stores information about when we contact a stakeholder)
sh_contact_id contact_link_id
-----------------------------------------
1 1
2 1
3 2
I want to write a SQL query that shows the stakeholder information once then show all inquiries and all contacts underneath the stakeholder row? is that possible with SQL? So in this case joe stakeholder would be shown once and then there would be 4 rows next (2 inquiries and 2 contacts). Eric stakeholder would be shown once with two rows, 1 inquiry and one contact.
Thanks for any assistance in advance.

As has already been mentioned, you probably want to handle this in your application code. However, you can use a UNION query to sort of do what you want.
With the query below, I changed your latter 2 tables to SH_Inquiry and SH_Contacts (replaced spaces with underscores), which is generally a good habit (it's a bad idea to have spaces in your object names). Also, depending on how your tables are laid out, you might want to merge your Contacts and Inquiry tables (e.g. have one table, with a contact_type field that identifies it as "inbound" or "outbound").
Anyways, using a CTE and union:
WITH Unionized AS
(
SELECT
stakeholder_id,
full_name,
NULL AS contact_or_inquiry,
NULL AS contact_or_inquiry_id
FROM Stakeholder
UNION ALL
SELECT
inquiry_link_id AS stakeholder_id,
NULL AS full_name,
'inquiry' AS contact_or_inquiry,
sh_inquiry_id AS contact_or_inquiry_id
FROM SH_Inquiry
UNION ALL
SELECT
contact_link_id AS stakeholder_id,
NULL AS full_name,
'contact' AS contact,
sh_contact_id AS contact_or_inquiry_id
FROM SH_Contacts
)
SELECT
full_name,
contact_or_inquiry,
contact_or_inquiry_id
FROM Unionized
ORDER BY
stakeholder_id,
contact_or_inquiry,
contact_or_inquiry_id
giving you these results:
+------------------+--------------------+-----------------------+
| full_name | contact_or_inquiry | contact_or_inquiry_id |
+------------------+--------------------+-----------------------+
| Joe Stakeholder | NULL | NULL |
| NULL | contact | 1 |
| NULL | contact | 2 |
| NULL | inquiry | 2 |
| Eric Stakeholder | NULL | NULL |
| NULL | contact | 3 |
| NULL | inquiry | 1 |
| NULL | inquiry | 3 |
+------------------+--------------------+-----------------------+

Related

In a query (no editing of tables) how do I join data without any similarities?

I Have a query that finds a table, here's an example one.
Name |Age |Hair |Happy | Sad |
Jon | 15 | Black |NULL | NULL|
Kyle | 18 |Blonde |YES |NULL |
Brad | 17 | Blue |NULL |YES |
Name and age come from one table in a database, hair color comes from a second which is joined, and happy and sad come from a third table.My goal would be to make the first line of the chart like this:
Name |Age |Hair |Happy |Sad |
Jon | 15 |Black |Yes |Yes |
Basically I want to get rid of the rows under the first and get the non NULL data joined to the right. The problem is that there is no column where the Yes values are on the Jon row, so I have no idea how to get them there. Any suggestions?
PS. With the data I am using I can't just put a 'YES' in the 'Jon' row and call it a day, I would need to find the specific value from the lower rows and somehow get that value in the boxes that are NULL.
Do you just want COALESCE()?
COALESCE(Happy, 'Yes') as happy
COALESCE() replaces a NULL value with another value.
If you want to join on a NULL value work with nested selects. The inner select gets an Id for NULLs, the outer select joins
select COALESCE(x.Happy, yn_table.description) as happy, ...
from
(select
t1.Happy,
CASE WHEN t1.Happy is null THEN 1 END as happy_id
from t1 ...) x
left join yn_table
on x.xhappy_id = yn_table.id
If you apply an ORDER BY to the query, you can then select the first row relative to this order with WHERE rownum = 1. If you don't apply an ORDER BY, then the order is random.
After reading your new comment...
the sense is that in my real data the yes under the other names will be a number of a piece of equipment. I want the numbers of the equipment in one row instead of having like 8 rows with only 4 ' yes' values and the rest null.
... I come to the conclusion that this a XY problem.
You are asking about a detail you think will solve your problem, instead of explaining the problem and asking how to solve it.
If you want to store several pieces of equipment per person, you need three tables.
You need a Person table, an Article table and a junction table relating articles to persons to equip them. Let's call this table Equipment.
Person
------
PersonId (Primary Key)
Name
optional attributes like age, hair color
Article
-------
ArticleId (Primary Key)
Description
optional attributes like weight, color etc.
Equipment
---------
PersonId (Primary Key, Foreign Key to table Person)
ArticleId (Primary Key, Foreign Key to table Article)
Quantity (optional, if each person can have only one of each article, we don't need this)
Let's say we have
Person: PersonId | Name
1 | Jon
2 | Kyle
3 | Brad
Article: ArticleId | Description
1 | Hat
2 | Bottle
3 | Bag
4 | Camera
5 | Shoes
Equipment: PersonId | ArticleId | Quantity
1 | 1 | 1
1 | 4 | 1
1 | 5 | 1
2 | 3 | 2
2 | 4 | 1
Now Jon has a hat, a camera and shoes. Kyle has 2 bags and one camera. Brad has nothing.
You can query the persons and their equipment like this
SELECT
p.PersonId, p.Name, a.ArticleId, a.Description AS Equipment, e.Quantity
FROM
Person p
LEFT JOIN Equipment e
ON p.PersonId = e.PersonId
LEFT JOIN Article a
ON e.ArticleId = a.ArticleId
ORDER BY p.Name, a.Description
The result will be
PersonId | Name | ArticleId | Equipment | Quantity
---------+------+-----------+-----------+---------
3 | Brad | NULL | NULL | NULL
1 | Jon | 4 | Camera | 1
1 | Jon | 1 | Hat | 1
1 | Jon | 5 | Shoes | 1
2 | Kyle | 3 | Bag | 2
2 | Kyle | 4 | Camera | 1
See example: http://sqlfiddle.com/#!4/7e05d/2/0
Since you tagged the question with the oracle tag, you could just use NVL(), which allows you to specify a value that would replace a NULL value in the column you select from.
Assuming that you want the 1st row because it contains the smallest age:
- wrap your query inside a CTE
- in another CTE get the 1st row of the query
- in another CTE get the max values of Happy and Sad of your query (for your sample data they both are 'YES')
- cross join the last 2 CTEs.
with
cte as (
<your query here>
),
firstrow as (
select name, age, hair from cte
order by age
fetch first row only
),
maxs as (
select max(happy) happy, max(sad) sad
from cte
)
select f.*, m.*
from firstrow f cross join maxs m
You can try this:
SELECT A.Name,
A.Age,
B.Hair,
C.Happy,
C.Sad
FROM A
INNER JOIN B
ON A.Name = B.Name
INNER JOIN C
ON A.Name = B.Name
(Assuming that Name is the key columns in the 3 tables)

Reorganize multiple rows in a new table with more columns

I have a table that looks like that:
+----------+----------+----------+----------+--------------------------+
| Club | Role | Name | Lastname | Email |
+----------+----------+----------+----------+--------------------------+
| Porto | 1 | Peter | Pan | peter.pan#mail.com |
| Porto | 2 | Michelle | Obama | michelle.obama#mail.com |
| Monaco | 1 | Serena | Williams | serena.williams#mail.com |
| Monaco | 2 | David | Beckham | david.beckham#mail.com |
+----------+----------+----------+----------+--------------------------+
and i want to get a table like that:
+----------+-----------------+-----------------+---------------------------------+-----------------+-----------------+---------------------------------+
| Club | Role 1 Name | Role 1 Lastname | Role 1 Email | Role 2 Name | Role 2 Lastname | Role 2 Email |
+----------+-----------------+-----------------+---------------------------------+-----------------+-----------------+---------------------------------+
| Porto | Peter | Pan | peter.pan#mail.com | Michelle | Obama | michelle.obama#mail.com |
| Monaco | Serena | Williams | serena.williams#mail.com | David | Beckham | david.beckham#mail.com |
+----------+-----------------+-----------------+---------------------------------+-----------------+-----------------+---------------------------------+
where the persons with different roles in each club puts in the same row.
I would ideally like to find a way to do that in Excel, but i am not sure if its possible. If not, SQL code would also help a lot.
Here is what I could come up with for an excel formula. Hopefully it can push you in the right direction.
This formula is assuming that your first table exists at the range A1:E5 and the second table exists at the range G1:M3. It is also assuming that the second table's column names are just repeating without the Role number attached to the front of it (same as the first table). This formula is an array formula, so you have to make sure to do CTRL+SHIFT+ENTER when inputting it.
{=INDEX($A$2:$E$5,
MATCH(1,($G2=$A$2:$A$5)*((FLOOR((COLUMN() - COLUMN($H$1) ) / 3,1) + 1)=$B$2:$B$5),0),
MATCH(H$1,$A$1:$E$1,0))}
The first part is using the INDEX forumla which pulls data from the range suppled ($A$2:$E$5) based on the row and column numbers supplied by the following MATCH formulas.
The first MATCH is supplying the row number for when the result of the lookup array section is equal to 1. I am checking two conditions, the first is to check for the "Club" name ($G2=$A$2:$A$5) and the second is to check for which "Role" we are currently on ((FLOOR((COLUMN() - COLUMN($H$1) ) / 3,1) + 1). This is using the FLOOR function to round the result down to the whole number and dividing by the number of columns (3 in this case: Name, Lastname, and Email).
The final MATCH is pulling the column number based on the header names from both tables. If you wanted to incorporate the changing names of the roles in the column headers, you could change this part to something like this:
{=INDEX($A$2:$E$5,
MATCH(1,($G2=$A$2:$A$5)*((FLOOR((COLUMN() - COLUMN($H$1) ) / 3,1) + 1)=$B$2:$B$5),0),
MOD(COLUMN() - COLUMN($H$1),3) + 3)}
I am adding 3 to the end of the mod because of the original range that was selected for table 1. The columns that we want to pull start at location 3 in the range.
If you want to do this in Oracle Sql, there's a nice approach in analytical sql.
To convert/swap rows to columns or columns to rows, we can use Pivot or Unpivot operators.
In you example use below query to covert data as you like,
select * from
(
with all_roles as
(select 1 role from dual union all
select 2 role from dual),
ddata as
(select 1 c_role, 'porto' club, 'peter' fname,'pan' lname,'peter.pan#mail.com' email from dual union all
select 2 c_role, 'porto' club, 'Michelle' fname, 'Obama' lname,'michelle.obama#mail.com' email from dual union all
select 1 c_role, 'monaco' club, 'Serena' fname, 'Williams' lname,'serena.williams#mail.com' email from dual union all
select 2 c_role, 'monaco' club, 'David' fname, 'Beckham' lname,'david.beckham#mail.com' email from dual )
(select role, club, fname,lname, email from ddata,all_roles
where all_roles.role=ddata.c_role)) all_data
pivot (
max(fname) fname,
max(lname) lname,
max(email) email
for role in ( 1 role1, 2 role2 )
)
order by club;

How to make multiple instances of foreign key work in one row

Hello people and fellow SQL programmers.
I have been trying to work out a reality model that is situated in Industry.
The client, that the database is ordered by states that:
There are multiple different job locations/offices where his employees work. Each workplace/office has a set number of people that can work here - minimum and maximum. For each workplace there is a group of people that consists of at least 2 people and max at 4 people. There can be only one group stationed in one work place at a time. There are also a few specifications for the group such as - there are no leaders among them - everybody is equal. A certain worker can only be assigned to only one group at a time. And there is to be an evidence in history who and where worked and for how long.
I have been trying to work the table design with its attributes for quite some time but it seems to me that everything i have done so far has some serious holes and is quite messy. I would very much appreciate any feedback and advice from you guys. Thanks in advance.
If I understand correctly you have two entities: employee and office. These will require two tables:
employee: id, name, whatever_else
office: id, desc, min_employees, max_employees, whatever_else
Theoretically the relationship between these two entities is one-to-many, because you say each employee can only be assigned to a single office at a time, so you could add a office_id foreign key to the employee table.
Having to keep track of the history however means that each employee may have multiple associations with the offices, thus making the relationship many-to-many. You will then need another table to model it:
employeeOffice: employee_id, office_id, start_date, end_date
With this model the queries I imagine you'll need to perform would be quite easy; as an example, finding how many employees are currently assigned to each office would be
select t1.id, t1.desc, count(distinct t2.employee_id)
from office t1
join employeeOffice t2
on t1.id = t2.office_id
where t2.end_date is null
group by t1.id, t1.desc
Edit
Take this sample data as an example
employee
id | name
1 | name1
2 | name2
3 | name3
4 | name4
5 | name5
6 | name6
7 | name7
office
id | desc
1 | office1
2 | office2
employeeOffice
employee_id | office_id | start_date | end_date
1 | 2 | '01-01-2107' | '31-01-29107'
1 | 1 | '01-02-2107' |
2 | 1 | '01-01-2107' |
3 | 1 | '01-01-2107' |
4 | 1 | '01-01-2107' |
5 | 2 | '01-01-2107' | '01-03-2107'
6 | 2 | '01-01-2107' |
7 | 2 | '01-01-2107' |
This would mean that employee 1 spent one month in office 2 and then was assigned to office 1. Employee 5 after two months resigned (or was fired), because there's no record for him with empty end_date.
The example query above would give you
id | desc | count
1 | office1 | 4
2 | office2 | 2

Postgres Many to many mapping sql query

Postgresql Database
Table User
-----------
ID | Name
1 | John
2 | Bob
3 | Sarah
Table Photo
-------------
ID | Caption
1 | Vacation
2 | Birthday
3 | Christmas
Table Comment
--------------
ID | User ID | Photo ID| Text
1 | 1 | 1 | Mexico Looks Great
2 | 2 | 1 | Sure Does
3 | 3 | 1 | Too Hot
4 | 1 | 2 | Look at that cake
5 | 3 | 2 | No ice cream?
6 | 1 | 3 | So Happy
Desire: I want to get all the photos that ONLY John(1) and Sara(3) commented on.
How do I build a SQL query that looks for photos that only have comments from user #1 and user #3, I want to EXCLUDE results where more(or less) than those two commented on.
The clearest and most readable way, is the Photos containing comments by:
User1 Intersect User2 Except Any other user
This SQL Fiddle and query will return that:
SELECT *
FROM Photo
WHERE ID IN (
SELECT "Photo ID" FROM Comment WHERE "User ID" = 1
INTERSECT
SELECT "Photo ID" FROM Comment WHERE "User ID" = 3
EXCEPT
SELECT "Photo ID" FROM Comment WHERE "User ID" NOT IN (1, 3)
)
lets do three joins, one for john, one for sara, one for everyone else. Then we'll limit what we get back with the where clause.
select p.*
from photo p
left join comment john on john.photo_id=p.photo_id and john.user_id=1
left join comment sara on sara.photo_id=p.photo_id and sara.user_id=3
left join comment everyone_else on everyone_else.photo_id=p.photo_id and everyone_else.user_id<>3 and everyone_else.user_id<>1
where
everyone_else.id is null
and john.id is not null
and sara.id is not null
There are a couple of ways to do this. One is to use count with case:
select photoid
from comment
group by photoid
having count(distinct userid) = 2
and count(case when userid not in (1,3) then 1 end) = 0
SQL Fiddle Demo
Basically, make sure 2 users have commented and then make sure only user 1 or 3 commented.
You could use an intersection to find only the common photos, which would exclude photos commented by John but not Sarah, or vice versa
select photo_id from comment where user_id = 1
intersect
select photo_id from comment where user_id = 3

SELECT certain fields based on certain WHERE conditions

I am writing an advanced MySQL query that searches a database and retrieves user information. What I am wondering is can I select certain fields if WHERE condition 1 is met and select other fields if WHERE condition 2 is met?
Database: users
------------------------
| user_id | first_name |
------------------------
| 1 | John |
------------------------
| 2 | Chris |
------------------------
| 3 | Sam |
------------------------
| 4 | Megan |
------------------------
Database: friendship
--------------------------------------
| user_id_one | user_id_two | status |
--------------------------------------
| 2 | 4 | 0 |
--------------------------------------
| 4 | 1 | 1 |
--------------------------------------
Status 0 = Unconfirmed
Status 1 = Confirmed
OK, as you can see John & Megan are confirmed friends while Chris & Megan are friends but the relationship is unconfirmed.
The query I am trying to write is as follow: Megan(4) searches for new friends I want all of the users except for the ones she is a confirmed friend with to be returned. So, the results should return 2,3. But since a relationship with user_id 2 exists but is not confirmed, I want to also return the status since an entry in the friendship table does exist between the two. If a user exist but there is no connection in the relationship table it still returns that users information but returns status as a NULL or doesn't return status at all since it doesn't exist in that table.
I hope this makes since. Ask questions if you need to.
Why not use a left join or an if-not-exists?
SELECT users.*
FROM (users LEFT JOIN friendships
ON status=1 AND (user_id_one=user_id OR user_id_two=user_id) )
WHERE
status IS NULL
or
SELECT users.*
FROM users
WHERE
NOT EXISTS (SELECT *
FROM friendships
WHERE status=1
AND (user_id_one=user_id
OR user_id_two=user_id))
You can create to separate queries and then UNION the result tables. In each query, add a field that always has the same value.
So something like this should work:
(SELECT id, 'Not Friends' As Status FROM t1 WHERE condition1)
UNION
(SELECT id, 'Unconfirmed' As Status FROM t1 WHERE condition2)
Just make sure the same number and name of fields exists in both queries.