Split column into two columns based on type code in third column - sql

My SQL is very rusty. I'm trying to transform this table:
+----+-----+--------------+-------+
| ID | SIN | CONTACT | TYPE |
+----+-----+--------------+-------+
| 1 | 737 | b#bacon.com | email |
| 2 | 760 | 250-555-0100 | phone |
| 3 | 737 | 250-555-0101 | phone |
| 4 | 800 | 250-555-0102 | phone |
| 5 | 850 | l#lemon.com | email |
+----+-----+--------------+-------+
Into this table:
+----+-----+--------------+-------------+
| ID | SIN | PHONE | EMAIL |
+----+-----+--------------+-------------+
| 1 | 737 | 250-555-0101 | b#bacon.com |
| 2 | 760 | 250-555-0100 | |
| 4 | 800 | 250-555-0102 | |
| 5 | 850 | | l#lemon.com |
+----+-----+--------------+-------------+
I wrote this query:
SELECT *
FROM (SELECT *
FROM people
WHERE TYPE = 'phone') phoneNumbers
FULL JOIN (SELECT *
FROM people
WHERE TYPE = 'email') emailAddresses
ON phoneNumbers.SIN = emailAddresses.SIN;
Which produces:
+----+-----+--------------+-------+------+-------+-------------+--------+
| ID | SIN | CONTACT | TYPE | ID_1 | SIN_1 | CONTACT_1 | TYPE_1 |
+----+-----+--------------+-------+------+-------+-------------+--------+
| 2 | 760 | 250-555-0100 | phone | | | | |
| 3 | 737 | 250-555-0101 | phone | 1 | 737 | b#bacon.com | email |
| 4 | 800 | 250-555-0102 | phone | | | | |
| | | | | 5 | 850 | l#lemon.com | email |
+----+-----+--------------+-------+------+-------+-------------+--------+
I know that I can select the columns I want, but the SIN column is incomplete. I seem to recall that I should join in the table a third time to get a complete SIN column, but I cannot remember how.
How can I produce my target table (ID, SIN, PHONE, EMAIL)?
Edit and clarification: I am grateful for the answers I have received so far, but as a SQL greenhorn I am unfamiliar with the techniques you are using (case statements, conditional aggregation, and pivoting). Can this not be done using JOIN and SELECT? Please excuse my ignorance in this matter. (It's not that I am not interested in superior techniques, but I do not want to move too fast too soon.)

One way to approach this is conditional aggregation:
select min(ID), SIN,
max(case when type = 'phone' then contact end) as phone,
max(case when type = 'email' then contact end) as email
from people t
group by sin;

Seems a pivot (oracle.com) would work easily here.
SELECT ID, SIN, PHONE, EMAIL
FROM PEOPLE
PIVOT (
MAX(CONTACT)
FOR TYPE IN ('EMAIL', 'PHONE')
)

I realize this is less elegant than all the solutions posted, but here it is anyhow, a solution using only JOIN and SELECT:
SELECT sins.SIN, phone, email
FROM ((SELECT SIN email_sin, contact email
FROM people
WHERE TYPE = 'email') email
FULL JOIN (SELECT SIN phone_sin, contact phone
FROM people
WHERE TYPE = 'phone') phone
ON email.email_sin = phone.phone_sin)
RIGHT JOIN (SELECT DISTINCT SIN FROM people) sins
ON sins.SIN = phone_sin OR sins.SIN = email_sin;
This lacks the ID column.

Related

SQL - joining 3 tables and choosing newest logged entry per id

I got rather complicated riddle to solve. So far I'm unlocky.
I got 3 tables which I need to join to get the result.
Most important is that I need highest h_id per p_id. h_id is uniqe entry in log history. And I need newest one for given point (p_id -> num).
Apart from that I need ext and name as well.
history
+----------------+---------+--------+
| h_id | p_id | str_id |
+----------------+---------+--------+
| 1 | 1 | 11 |
| 2 | 5 | 15 |
| 3 | 5 | 23 |
| 4 | 1 | 62 |
+----------------+---------+--------+
point
+----------------+---------+
| p_id | num |
+----------------+---------+
| 1 | 4564 |
| 5 | 3453 |
+----------------+---------+
street
+----------------+---------+-------------+
| str_id | ext | name |
+----------------+---------+-------------+
| 15 | | Mein st. 33 | - bad name
| 11 | | eck st. 42 | - bad name
| 62 | abc | Main st. 33 |
| 23 | efg | Back st. 42 |
+----------------+---------+-------------+
EXPECTED RESULT
+----------------+---------+-------------+-----+
| num | ext | name |h_id |
+----------------+---------+-------------+-----+
| 3453 | efg | Back st. 42 | 3 |
| 4564 | abc | Main st. 33 | 4 |
+----------------+---------+-------------+-----+
I'm using Oracle SQL. Tried using query below but result is not true.
SELECT num, max(name), max(ext), MAX(h_id) maxm FROM history
INNER JOIN street on street.str_id = history._str_id
INNER JOIN point on point.p_id = history.p_id
GROUP BY point.num
In Oracle, you can use keep:
SELECT p.num,
MAX(h.h_id) as maxm,
MAX(s.name) KEEP (DENSE_RANK FIRST ORDER BY h.h_id DESC) as name,
MAX(s.ext) KEEP (DENSE_RANK FIRST ORDER BY h.h_id DESC) as ext
FROM history h INNER JOIN
street s
ON s.str_id = h._str_id INNER JOIN
point p
ON p.p_id = h.p_id
GROUP BY p.num;
The keep syntax allows you to do "first()" and "last()" for aggregations.

Select from a concatenation of two columns after a left join

Problem description
Let the tables C and V have those values
>> Table V <<
| UnID | BillID | ProductDesc | Value | ... |
| 1 | 1 | 'Orange Juice' | 3.05 | ... |
| 1 | 1 | 'Apple Juice' | 3.05 | ... |
| 1 | 2 | 'Pizza' | 12.05 | ... |
| 1 | 2 | 'Chocolates' | 9.98 | ... |
| 1 | 2 | 'Honey' | 15.98 | ... |
| 1 | 3 | 'Bread' | 3.98 | ... |
| 2 | 1 | 'Yogurt' | 8.55 | ... |
| 2 | 1 | 'Ice Cream' | 7.05 | ... |
| 2 | 1 | 'Beer' | 9.98 | ... |
| 2 | 2 | 'League of Legends RP' | 40.00 | ... |
>> Table C <<
| UnID | BillID | ClientName | ... |
| 1 | 1 | 'Alexander' | ... |
| 1 | 2 | 'Tom' | ... |
| 1 | 3 | 'Julia' | ... |
| 2 | 1 | 'Tom' | ... |
| 2 | 2 | 'Alexander' | ... |
Table C have the values of each product, which is associated with a bill number. Table V has the relationship between the client name and the bill number. However, the bill number has a counter that is dependent on the UnId, which is the store unity ID. That being said, each store has it`s own Bill number 1, number 2, etc. Also, the number of bills from each store are not equal.
Solution description
I'm trying to make select between the C left join V without sucess. Because each BillID is dependent on the UnID, I have to make the join considering the concatenation between those two columns.
I've used this script, but it gives me an error.
SELECT
SUM(C.Value),
V.ClientName
FROM
C
LEFT JOIN
V
ON
CONCAT(C.UnID, C.BillID) = CONCAT(V.UnID, V.BillID)
GROUP BY
V.ClientName
and SQL server returns me this 'CONCAT' is not a recognized built-in function name.
I'm using Microsoft SQL Server 2008 R2
Is the use of CONCAT wrong? Or is it the way I tried to SELECT? Could you give me a hand?
[OBS: The tables I've present you are just for the purpose of explaining my difficulties. That being said, if you find any errors in the explanation, please let me know to correct them.]
You should be joining on the equality of the UnID and BillID columns in the two tables:
SELECT
c.ClientName,
COALESCE(SUM(v.Value), 0) AS total
FROM C c
LEFT JOIN V v
ON c.UnID = v.UnID AND
c.BillID = v.BillID
GROUP BY
c.ClientName;
In theory you could try joining on CONCAT(UnID, BillID). However, you could run into problems. For example, UnID = 1 with BillID = 23 would, concatenated together, be the same as UnID = 12 and BillID = 3.
Note: We wrap the sum with COALESCE, because should a given client have no entries in the V table, the sum would return NULL, which we then replace with zero.
concat is only available in sql server 2012.
Here's one option.
SELECT
SUM(C.Value),
V.ClientName
FROM
C
LEFT JOIN
V
ON
cast(C.UnID as varchar(100)) + cast(C.BillID as varchar(100)) = cast(V.UnID as varchar(100)) + cast(V.BillID as varchar(100))
GROUP BY
V.ClientName

MS Access SQL - Aggregate Count with 2 Example Table

I have a situation where I need to query from a 2 table in MS Access.
I'm new to this SQL access.
Table: UserInfo
===================
| UserID | Gender |
===================
| K01 | M |
| K02 | M |
| K03 | F |
| K04 | M |
===================
Table: OrderInfo
===================================
| OrderID | Type | UserID_FK |
===================================
| 1 | Food | K01 |
| 2 | Food | K01 |
| 3 | Toolkit | K02 |
| 4 | Food | K04 |
| 5 | Toolkit | K03 |
===================================
The question is:
I want to query so the result produce this table, how can I do it in MS Access?
I'm thinking that I should do a subquery but I don't know how to do it.
Table: Summary
================================================================
| UserID | Gender | CountOfToolkit | CountOfFood | TotalCount |
================================================================
| K01 | M | 0 | 2 | 2 |
| K02 | M | 1 | 0 | 1 |
| K03 | F | 1 | 0 | 1 |
| K04 | M | 0 | 1 | 1 |
================================================================
First, Type is a reserved word in MS Access.
You should avoid using reserved words.
Having said that, try this:
SELECT a.UserID, a.Gender, SUM(IIF(b.[Type] ='Food',1,0)) AS CountOfFood, SUM(IIF(b.[Type] ='Toolkit',1,0)) AS CountOfToolkit, COUNT(*) AS TotalCount
FROM UserInfo a INNER JOIN OrderInfo b ON a.UserId = b.UserID_FK
GROUP BY a.UserID, a.Gender
This crosstab query doesn't rely on having just Food & Toolkit as your types - it will count any new types you add.
The NZ wrapped around the Count(sType) ensures the 0 values are shown, while the CLNG ensures it's still treated as a number rather than text.
You could just use Count(sType) AS CountOfType.
TRANSFORM CLNG(NZ(Count(sType),0)) AS CountOfType
SELECT UserID
,Gender
,COUNT(sType) AS TotalCount
FROM UserInfo LEFT JOIN OrderInfo ON UserInfo.UserID = OrderInfo.UserID_FK
GROUP BY UserID, Gender
PIVOT sType

SQL Query to Work out Every Product Combination

I require a SQL query to work out every product combination.
I have three product categories (game, accessory, upgrade) and products assigned to each of these three categories:
+----+------------+-----------+------------+
| id | category | product | prod_code |
+----+------------+-----------+------------+
| 1 | game | GTA | 100 |
| 2 | game | GTA1 | 200 |
| 3 | game | GTA2 | 300 |
| 4 | accessory | Play Pad | 400 |
| 5 | accessory | Xbox Pad | 500 |
| 6 | upgrade | Memory | 600 |
| 6 | upgrade | drive | 700 |
+----+------------+-----------+------------+
I want to take one product from each of the categories and work out every single combination:
+----+--------------+
| id | combinations |
+----+--------------+
| 1 | 100,400,600 |
| 2 | 100,500,600 |
| 3 | 100,400,700 |
| 4 | 100,500,700 |
| ? | etc |
+----+--------------+
How would I go about doing this?
Thanks in advance, Stuart
Use a CROSS JOIN:
SELECT CONCAT(t1.[prod_code], ',',
t2.[prod_code], ',',
t3.[prod_code])
FROM (
SELECT [prod_code]
FROM mytable
WHERE category = 'game') AS t1
CROSS JOIN (
SELECT [prod_code]
FROM mytable
WHERE category = 'accessory') AS t2
CROSS JOIN (
SELECT [prod_code]
FROM mytable
WHERE category = 'upgrade') AS t3
ORDER BY t1.[prod_code], t2.[prod_code], t3.[prod_code]
CROSS JOIN of derived tables, one for each category, produces the following cartesian product: 'game' products x 'accessory' products x 'upgrade' products
Demo here

Create a pivot table from two tables based on dates

I have two MS Access tables sharing a one to many relationship. Their structures are like the following:
tbl_Persons
+----------+------------+-----------+
| PersonID | PersonName | OtherData |
+----------+------------+-----------+
| 1 | PersonA | etc. |
| 2 | PersonB | |
| 3 | PersonC | |
tbl_Visits
+----------+------------+------------+-----------------------
| VisitID | PersonID | VisitDate | dozens of other fields
+----------+------------+------------+-----------
| 1 | 1 | 09/01/13 |
| 2 | 1 | 09/02/13 |
| 3 | 2 | 09/03/13 |
| 4 | 2 | 09/04/13 | etc...
I wish to create a new table based on the VisitDate field, the column headings of which are Visit-n where n is 1 to the number of visits, Visit-n-Data1, Visit-n-Data2, Visit-n-Data3 etc.
MergedTable
+----------+----------+---------------+-----------------+----------+----------------+
| PersonID | Visit1 | Visit1Data1 | Visit1Data2... | Visit2 | Visit2Data1... |
+----------+----------+---------------+-----------
| 1 | 09/01/13 | | | 09/02/13 |
| 2 | 09/03/13 | | | 09/04/13 |
| 3 | etc. | |
I am really not sure how to do this. Whether SQL query or using DAO then looping through records and columns. It is essential that there is only 1 PersonID per row and all his data appears chronologically into columns.
Start of by ranking the visits with something like
SELECT PersonID, VisitID,
(SELECT COUNT(VisitID) FROM tbl_Visits AS C
WHERE C.PersonID = tbl_Visits.PersonID
AND C.VisitDate < tbl_Visits.VisitDate) AS RankNumber
FROM tbl_Visits
Use this query as a base for the 'pivot'
Since you seem to have some visits of persons on the same day (visit 1 and 2) the WHERE clause needs to be a bit more sophisticated. But I hope you get the basic concept.
Pivoting can be done with multiple LEFT JOINs.
I question if my solution will have a high performance, since I did not test it. It is easier in SQL Server than in MS Access to accomplish.