Pivot with unknown row result - sql

to make my problem simple, let's say I have two tables: users and cars, that have a many-to-many relation (one user can have many cars, and one car can be of many users):
tbl_users tbl_user_car tbl_cars
------------- ---------------------------- ------
id_usr | name id_usr_car | fk_usr | fk_car id_car | name
1 | usr1 1 | 1 | 1 1 | car1
2 | usr2 2 | 1 | 2 2 | car2
3 | usr3 3 | 2 | 1 3 | car3
I have a web CRUD that admin could associate cars to users, so I don't know how many cars has an user. So I want to make a query that shows the user and all its associated cars but like a pivot, something like this:
SELECT * FROM pivot
------
name_usr | id_car | name_car | id_car2 | name_car2 | ... n number of cars
usr1 | 1 | car1 | 2 | car2 | ...
usr2 | 1 | car1 | | | ...
usr3 | | | | | ...
I tried to use crosstab() function but I need to know how many columns I want to return, and I need to make 5 inner join to see more data. Any help would be very appreciated.

Related

For each unique entry, include all rows from another list

I have 2 tables as such:
cars: contains price of some parts for each car
| Car | Parts | Price |
| -------- | -------------- | -------|
| A | Windshield | 100 |
| A | Rims | 50 |
| B | Bumper | 200 |
| B | Rims | 60 |
parts: contains all possible parts for a car
| Parts |
|--------------|
| Windshield |
| Rims |
| Bumper |
| Headlights |
I want each car in cars to have every entry in parts. The end result should look like this:
| Car | Parts | Price |
| -------- | -------------- | -------|
| A | Windshield | 100 |
| A | Rims | 50 |
| A | Bumper | 0 |
| A | Headlights | 0 |
| B | Bumper | 200 |
| B | Rims | 60 |
| B | Windshield | 0 |
| B | Headlights | 0 |
Any ideas on how I could do this?
PS: The order matters less
You may use a calendar table approach:
SELECT c.Car, p.Parts, COALESCE(t.Price, 0) AS Price
FROM (SELECT DISTINCT Car FROM cars) c
CROSS JOIN parts p
LEFT JOIN cars t
ON t.Car = c.Car AND t.Parts = p.Parts
ORDER BY c.Car, p.Parts;
But as #Larnu has correctly pointed out in his comment, your schema should have a separate table containing all cars. This would avoid the distinct select I have in my answer above.
If you want to insert the additional rows into cars, then the code would look like:
insert into cars (car, parts, price)
select c.car, p.part, 0
from (select distinct car from cars) c cross join
parts p
where not exists (select 1
from cars c2
where c2.car = c.car and c2.part = c.part
);
This generates all combinations of cars and parts. It then filters out the ones that don't already exist in cars.
Note that left join and not exists are pretty much the same in this context. In an insert query, though, I usually use not exists because I think the intention is clearer.

How can I query pairwise event attendance in MS Access given its subquery restrictions?

My colleague and I have been wracking our heads on this for days and have come to the conclusion that we just don't know SQL well enough to solve this problem. Please help us!
We have a table in MS Access called EventAttendance with 2 fields: MemberID (the person who attended the event) and EventID (the event they attended). Unique Member and Event IDs are stored in their own tables (Member and Event, respectively.) EventAttendance contains entries for hundreds of members and events, but a simple collection of records for 3 events might look like this:
|---------------------|------------------|
| MemberID | EventID |
|---------------------|------------------|
| 1 | 1 |
|---------------------|------------------|
| 2 | 1 |
|---------------------|------------------|
| 4 | 1 |
|---------------------|------------------|
| 1 | 2 |
|---------------------|------------------|
| 2 | 2 |
|---------------------|------------------|
| 3 | 2 |
|---------------------|------------------|
| 1 | 3 |
|---------------------|------------------|
| 3 | 3 |
|---------------------|------------------|
| 4 | 3 |
|---------------------|------------------|
In this example, Member 1 attended Events 1, 2, & 3; Member 2 attended Events 1 & 2; Member 3 attended Events 2 & 3; and Member 4 attended Events 1 & 3.
We are now trying to create a table that documents how often each pair of members attended an event together. Ideally, we would want the table to look like the one below (for the example records above), where CoAttendance is the number of events each pair attended together:
|---------------------|------------------|------------------|
| Member1 | Member2 | CoAttendance |
|---------------------|------------------|------------------|
| 1 | 2 | 2 |
|---------------------|------------------|------------------|
| 1 | 3 | 2 |
|---------------------|------------------|------------------|
| 1 | 4 | 2 |
|---------------------|------------------|------------------|
| 2 | 3 | 1 |
|---------------------|------------------|------------------|
| 2 | 4 | 1 |
|---------------------|------------------|------------------|
| 3 | 4 | 1 |
|---------------------|------------------|------------------|
This has proven a lot more challenging than we assumed. First, we haven't been able to figure out to get pairs in such a way that combinations don't repeat, but we have been able to list all permutations by querying a perfect copy of the Member Table (MemberClone) and using the following query:
SELECT Member.MemberID AS Member1, MemberClone.MemberID AS Member2
FROM Member, MemberClone;
This query resulted in a table like so:
|---------------------|------------------|
| Member1 | Member2 |
|---------------------|------------------|
| 1 | 1 |
|---------------------|------------------|
| 1 | 2 |
|---------------------|------------------|
| 1 | 3 |
|---------------------|------------------|
| 1 | 4 |
|---------------------|------------------|
| 2 | 1 |
|---------------------|------------------|
| 2 | 2 |
|---------------------|------------------|
| 2 | 3 |
|---------------------|------------------|
| 2 | 4 |
|---------------------|------------------|
| 3 | 1 |
|---------------------|------------------|
| 3 | 2 |
|---------------------|------------------|
| 3 | 3 |
|---------------------|------------------|
| 3 | 4 |
|---------------------|------------------|
| 4 | 1 |
|---------------------|------------------|
| 4 | 2 |
|---------------------|------------------|
| 4 | 3 |
|---------------------|------------------|
| 4 | 4 |
|---------------------|------------------|
Not perfect, but good enough (this is a problem we'd like solved eventually, but not the main one.)
The bigger problem is getting the third column (CoAttendance) to work. The closest we've gotten to a solution is through the use of subqueries:
SELECT Member.MemberID AS Member1, MemberClone.MemberID AS Member2,
(SELECT Count(*)
FROM (SELECT EventID FROM EventAttendance WHERE EventAttendance.MemberID = Member1) AS Member1Attendance
INNER JOIN (SELECT EventID FROM EventAttendance WHERE EventAttendance.MemberID = Member2) AS Member2Attendance
ON Member1Attendance.EventID = Member2Attendance.EventID) AS CoAttendance
FROM MemberClone, Member;
This should theoretically generate a list of only events that Member1 and Member2 attended together for each pair, and the count(*) operation would count those events.
The problem is that Access subqueries can only see one level above themselves, so Member1 and Member2 are undefined in the nested subqueries (they are defined 2 levels above.) I've tried finding solutions, but find that I just don't understand SQL enough to process similar solutions posted elsewhere (e.g., Nested subquery in Access alias causing "enter parameter value") while also making the inner join work.
Any help you can offer would be super appreciated!
I think you just want a self-join with aggregation:
select ea1.memberid, ea2.memberid, count(*) as num_events
from EventAttendance as ea1 inner join
EventAttendance as ea2
on ea1.eventid = ea2.eventid and ea1.memberid < ea2.memberid
group by ea1.memberid, ea2.memberid;
MS Access might be finicky about the < in the on clause. This is an inner join, so you can do:
select ea1.memberid, ea2.memberid, count(*) as num_events
from EventAttendance as ea1 inner join
EventAttendance as ea2
on ea1.eventid = ea2.eventid
where ea1.memberid < ea2.memberid
group by ea1.memberid, ea2.memberid;
This requires doing a match finding all people from events (so a self-join on EventAttendance)
SQL for this is below
SELECT Member.ID AS Member1, Member_1.ID AS Member2, Count(EventAttendance_1.Event_ID) AS CountOfEvent_ID
FROM (Member INNER JOIN (EventAttendance INNER JOIN EventAttendance AS EventAttendance_1 ON EventAttendance.Event_ID = EventAttendance_1.Event_ID) ON Member.ID = EventAttendance.Member_ID) INNER JOIN Member AS Member_1 ON EventAttendance_1.Member_ID = Member_1.ID
GROUP BY Member.ID, Member_1.ID
HAVING (((Member_1.ID)>[Member].[ID]));
That is the SQL provided by Access for the following setup:
Note that you do not necessarily need links to Member or Member_1 - just that you may want to get info about them other than their ID.
---- Update
Gah this is basically the same answer as #Gordon's above (so I've upvoted his). I've not deleted this one in case you find the picture useful (when using Access, I much prefer the GUI query designer).

SQL on Self Table Join

I am trying to do a simple self-join SQL and a join to a 2nd table and for the life of me I can't figure it out. I've done some research and can't seem to glean the answer from similar questions. This query is for MS-Access running in VB.NET.
I have 2 tables:
TodaysTeams
-----------
TeamNum PlayerName PlayerID
------- ---------- --------
1 Mark 100
1 Brian 101
2 Mike 102
2 Mike 102
(Note the last 2 rows above are not a typo. In this case a player can be paired with themselves to form a team)
TodaysTeamsPoints
-----------------
TeamNum Points
------- ------
1 90
2 85
The result I want is (2 rows, 1 for each team):
TeamNum PlayerName1 PlayerName2 Points
------- ----------- ----------- ------
1 Mark Brian 90
2 Mike Mike 85
Here is my SQL:
SELECT DISTINCT A.TeamNum, A.PlayerName as PlayerName1, B.PlayerName AS PlayerName2, C.Points
FROM ((TodaysTeams A INNER JOIN
TodaysTeamsPoints C ON A.TeamNum = C.TeamNum) INNER JOIN
TodaysTeams B ON A.TeamNum = B.TeamNum)
ORDER BY C.Points DESC
I know I am missing another join as I'm returning a cartesian produce (i.e. too many rows).
I would appreciate help as to what I am missing here.
Thank you.
Whilst Gordon's suggested method will work well providing that there are at most two players per team, the method breaks down if ever you add another team member and wish to display them in a separate column.
The difficulty in displaying the data in a manner that you can describe logically but cannot easily produce using a query usually implies that the database structure is sub optimal.
For your particular setup, I would personally recommend the following structure:
+---------------+ +----------+------------+
| Players | | PlayerID | PlayerName |
+---------------+ +----------+------------+
| PlayerID (PK) | | 100 | Mark |
| PlayerName | | 101 | Brian |
+---------------+ | 102 | Mike |
+----------+------------+
+-------------+ +--------+----------+
| Teams | | TeamID | TeamName |
+-------------+ +--------+----------+
| TeamID (PK) | | 1 | Team1 |
| TeamName | | 2 | Team2 |
+-------------+ +--------+----------+
+-------------------+ +--------+--------------+----------+
| TeamPlayers | | TeamID | TeamPlayerID | PlayerID |
+-------------------+ +--------+--------------+----------+
| TeamID (PK) | | 1 | 1 | 100 |
| TeamPlayerID (PK) | | 1 | 2 | 101 |
| PlayerID (FK) | | 2 | 1 | 102 |
+-------------------+ | 2 | 2 | 102 |
+--------+--------------+----------+
Using this method, you can use condition aggregation or a crosstab query pivoting on the TeamPlayerID to produce each of your columns, and you would not be limited to two columns.
You can use aggregation:
SELECT ttp.TeamNum, MIN(tt.PlayerName) as PlayerName1,
MAX(tt.PlayerName) as PlayerName2,
ttp.Points
FROM TodaysTeamsPoints as ttp INNER JOIN
TodaysTeams as tt
ON tt.TeamNum = ttp.TeamNum
GROUP BY ttp.TeamNum, ttp.Points
ORDER BY ttp.Points DESC;

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.

How to re-key a hierarchy in a table?

I have a two tables
cars: contains hierarchy data about cars
+-----+-------------+-----------+
| id | description | parent_id |
+-----+-------------+-----------+
| 1 | All cars | 1 |
| 30 | Toyota | 1 |
| 34 | Yaris | 30 |
| 65 | Yaris | 30 |
| 87 | Avensis | 30 |
| 45 | Avensis | 30 |
| 143 | Skoda | 1 |
| 199 | Octavia | 143 |
| 12 | Yeti | 143 |
+-----+-------------+-----------+
car_mapping: contains mapping data where duplicate cars (with different ids) are mapped to one id.
+--------+----------+--------+
| car_id | car_name | map_id |
+--------+----------+--------+
| 34 | Yaris | 1 |
| 65 | Yaris | 1 |
| 87 | Avensis | 2 |
| 45 | Avensis | 2 |
| 199 | Octavia | 3 |
| 12 | Yeti | 4 |
| 30 | Toyota | 5 |
| 143 | Skoda | 6 |
| 1 | All cars | 0 |
+--------+----------+--------+
Now, the idea is to create a third table, cars_new, based on cars and car_mapping which removes duplicates and re-keys the hierarchy in the cars table based on the map_id field in the car_mapping table. Here is the resulting cars_new:
+--------+----------+---------------+
| map_id | car_name | parent_map_id |
+--------+----------+---------------+
| 0 | All | 0 |
| 1 | Yaris | 5 |
| 2 | Avensis | 5 |
| 3 | Octavia | 6 |
| 4 | Yeti | 6 |
| 5 | Toyota | 0 |
| 6 | Skoda | 0 |
+--------+----------+---------------+
Here is the SQL Fiddle for this question. Any ideas how to re-key this hiearchy?
select distinct cm.map_id, cm.car_name, cm2.map_id parent_map_id
from cars c, car_mapping cm, car_mapping cm2
where c.id = cm.car_id
and c.parent_id = cm2.car_id(+)
order by cm.map_id;
PS: in your car_mapping table, you need one extra line (first one below) to get exactly the result you want:
+--------+----------+--------+
| car_id | car_name | map_id |
+--------+----------+--------+
| 1 | All | 0 |
| 34 | Yaris | 1 |
| 65 | Yaris | 1 |
Etc..
Based on #Majid LAISSI's accepted answer, this seems to work both in Oracle and SQL Server:
select distinct cm.map_id, cm.car_name, cm2.map_id as parent_map_id
from cars c
left outer join car_mapping cm on c.id = cm.car_id
left outer join car_mapping cm2 on c.parent_id = cm2.car_id
order by cm.map_id;
You don't have a hierarchy, and you're better off not creating one. Observe that your "cars" table doesn't describe cars; it merely assigns a string to a number (and another number to that number). Right from the get-go, "all cars" isn't a car, and "Toyota" is a car manufacturer, not a car.
The solution -- which would help with your uniqueness issue and simplify your queries -- is to use one table for each distinct thing:
manufactures { mfg_id, name } -- e.g. GM, Ford
makes { make_id, name, mfg_id } -- e.g. Chevrolet, Lincoln; links to manufactures
models { name, make_id } -- e.g. Yaris, etc.; links to makes.
Be sure to make "name" unique in each of the tables to prevent spurious IDs from being created.
This will let you assign new attributes to these things as they arise, such as the years they were made or how many were sold, or how many doors each model comes in. It will also let you prevent "relations" of Ford to GM or, say, making Yaris the parent of "all cars".
(BTW, I suggest you eschew "map" or "mapping" in a table name, because it doesn't say anything. Every table relates the elements in the row to each other. Every table maps the key to its values. The good news is that your car_mapping table disappears in the new design.)
As for how to convert the existing cars table, it will be a nuisance. Assuming cars_mapping is right, you'll be able to insert into each table, joining to it and taking the min(id) while grouping by name. You'll need three such queries, followed by some careful eyeballing to check for, er, misalignment.