MS Access Outer Join Multiple Fields to Single Record - database

I need to do the exact same thing as shown here: INNER or LEFT Joining Multiple Table Records Into A Single Row but have it work in an MS Access Query.
Below is the scenario:
Phone Table
+----------------+-------------+
| Field | Type |
+----------------+-------------+
| f_id | int(15) |
| f_client_id | int(11) |
| f_phone_type | varchar(50) |
| f_phone_number | varchar(13) |
+----------------+-------------+
Clients Table
+-----------------------------+--------------+------+-----+
| Field | Type | Null | Key |
+-----------------------------+--------------+------+-----+
| f_id | int(15) | NO | PRI |
| f_first_name | varchar(13) | YES | MUL |
| f_mi | char(1) | YES | |
| f_last_name | varchar(20) | NO | MUL |
+-----------------------------+--------------+------+-----+
With a standard LEFT or INNER join, I get something like this:
+------------+------------+--------------+
| name | Phone Type | Phone Number |
+------------+------------+--------------+
| John Smith | Home | 712-555-6987 |
| John Smith | Work | 712-555-1236 |
+------------+------------+--------------+
I need a query that will give me the work and home numbers that belong to a given client:
+------------+----------------+--------------+
| Name | Work Number | Home Number |
+------------+----------------+--------------+
| John Smith | 712-555-1236 | 712-555-6987 |
+------------+----------------+--------------+
The solution in SQL was
SELECT CONCAT(c.f_first_name, ' ', c.f_last_name) as Client_Name,
wp.f_phone_number as Work_Number,
hp.f_phone_number as Home_Number
FROM clients c
LEFT OUTER JOIN phone hp
ON hp.f_client_id = c.f_id
AND
hp.phone_type = 'home'
LEFT OUTER JOIN phone wp
ON wp.f_client_id = c.f_id
AND
wp.phone_type = 'work'
This however does not translate to MS Access, the Join fails. What's the best way to accomplish this same thing through Access?

There are two problems you haven't discovered yet.
there is no CONCAT() function in Access or its database engine
your query attempts to use phone_type, but the actual field name is f_phone_type
When joining more than two tables, Access' database engine requires parentheses. It's easiest to get that right by using the query designer to set up the joins. Also the query designer will substitute LEFT JOIN for LEFT OUTER JOIN; either way works for me in Access 2003.
This one is easy to build in the query designer, but only returns rows for clients who have both home and work numbers. I used it as a starting point, then later adjusted the ON clauses similar to your original.
SELECT
c.f_first_name & " " & c.f_last_name AS [Name],
wp.f_phone_number AS [Work Number],
hp.f_phone_number AS [Home Number]
FROM
(Clients AS c
LEFT JOIN Phones AS hp
ON c.f_id = hp.f_client_id)
LEFT JOIN Phones AS wp
ON c.f_id = wp.f_client_id
WHERE
hp.f_phone_type='Home'
AND wp.f_phone_type='Work';
Moving those WHERE conditions into the ON expressions, as in your SQL Server example, will return all clients whether or not you have any phone numbers on file for them. However that approach will require parentheses around the ON expressions. And those JOINS can not be displayed in the query designer.
SELECT
c.f_first_name & " " & c.f_last_name AS [Name],
wp.f_phone_number AS [Work Number],
hp.f_phone_number AS [Home Number]
FROM
(Clients AS c
LEFT JOIN Phones AS hp
ON (c.f_id = hp.f_client_id AND hp.f_phone_type='Home'))
LEFT JOIN Phones AS wp
ON (c.f_id = wp.f_client_id AND wp.f_phone_type='Work');
Update: For myself, I would prefer to do this with subqueries.
SELECT
c.f_first_name & " " & c.f_last_name AS [Name],
wp.f_phone_number AS [Work Number],
hp.f_phone_number AS [Home Number]
FROM
(Clients AS c
LEFT JOIN [
SELECT f_client_id, f_phone_number
FROM Phones
WHERE f_phone_type='Home'
]. AS hp
ON c.f_id = hp.f_client_id)
LEFT JOIN [
SELECT f_client_id, f_phone_number
FROM Phones
WHERE f_phone_type='Work'
]. AS wp
ON c.f_id = wp.f_client_id;

You can also use sub query, such as:
SELECT (firstname & " " & lastname ) AS fullname,
(SELECT f_PhoneNumber FROM tblPhones
WHERE f_clientID = clients.id AND f_PhoneType = "Home")
AS HomeNumber,
(SELECT f_PhoneNumber FROM tblPhones
WHERE f_clientID = clients.id AND f_PhoneType = "Work")
AS WorkNumber
FROM clients

Related

SQLite3 Query COUNT combining two tables

I'm having trouble trying to get my query working as intended, I have two tables (names and classes). Where names has two rows (username and name) & classes has three rows (class, username and time).
I'm trying to extract username, name and the amount of classes each person teaches (see table below), which can be figured out by looking at how many times a given "username" appears in the "classes" table. The problem is that there are some usernames that appear in the "names" table but don't appear in the "classes" table, these users should be assigned "0" in the count. There's also users who appear in the classes table but don't appear in the names table, these people should still be taken into account but with a null value as their name.
names table classes table
__________________ ________________________
|username | name | |class | username |time |
|ab | Ali | |xxx | ch |xxx |
|ch | Chi | |xxx | dd |xxx |
|dd | Dia | |xxx | ee |xxx |
etc...
desired output from query
__________________________________________
|username | name |number of classes taught|
|ab | Ali |0 |
|ch | Chi |1 |
|dd | Dia |3 |
|ee | null |5 |
etc....
I have the following query so far
SELECT
names.username,
name,
COUNT(*)
FROM
classes
INNER JOIN names ON
names.username = classes.username
GROUP BY
classes.username
ORDER BY
COUNT(*)
actual output from query
__________________________________________
|username | name |number of classes taught|
|ch | Chi |1 |
|dd | Dia |3 |
But this gives me an output that doesn't include users who don't teach and also excludes users who don't have a real name. I'm not sure how to resolve this issue, would I need to combine the two username rows without creating duplicates? or something along those lines?
If you want all users, start with the user table. If you want all users that don't have any classes, do a LEFT join and not an INNER join
SELECT
names.username,
name,
COUNT(*)
FROM
names
LEFT JOIN classes ON
names.username = classes.username
GROUP BY
classes.username
ORDER BY
COUNT(*)
What you need is a FULL OUTER join which is not supported by SQLite, but can be emulated with LEFT joins and UNION ALL and then aggregate:
select username, name, count(*) [number of classes taught]
from (
select n.username, n.name, c.class
from names n left join classes c
on c.username = n.username
union all
select c.username, n.name, c.class
from classes c left join names n
on c.username = n.username
where n.username is null
)
group by username, name
See a simplified demo.

Check if records exists at least once in LEFT JOINED Table

I have an Images, Orders and OrderItems table, I want to match for any images, if any has already been bought by the User passed as parameters by displaying true or false in an IsBought column.
Select Images.Id,
Images.Title,
Images.Description,
Images.Location,
Images.PriceIT,
Images.PostedAt,
CASE WHEN OrderItems.ImageId = Images.Id THEN CAST(1 AS BIT)
ELSE CAST(0 AS BIT) END
AS 'IsBought'
FROM Images
INNER JOIN Users as u on Images.UserId = u.Id
LEFT JOIN Orders on Orders.UserId = #userId
LEFT JOIN OrderItems on Orders.Id = OrderItems.OrderId and OrderItems.ImageId = Images.Id
Group By Images.Id,
Images.Title,
Images.Description,
Images.Location,
Images.PriceIT,
Images.PostedAt,
OrderItems.ImageId,
Orders.UserId
When I use this CASE WHEN I have duplicates when the item has been bought where IsBought is True and the duplicate is False.
In the case where the Item has never been bought, there is no duplicates, IsBought is just equal to False
----------------------------------
| User | type |
----------------------------------
| Id | nvarchar(450) |
----------------------------------
| .......|
----------------------------------
----------------------------------
| Orders | type |
----------------------------------
| Id | nvarchar(255) |
----------------------------------
| UserId | nvarchar(450) |
----------------------------------
| ........................... |
----------------------------------
----------------------------------
| OrderItems | type |
----------------------------------
| Id | nvarchar(255) |
----------------------------------
| OrderId | nvarchar(255) |
----------------------------------
| ImageId | int |
----------------------------------
----------------------------------
| Images | type |
----------------------------------
| Id | int |
----------------------------------
| UserId | nvarchar(450) |
----------------------------------
| Title | nvarchar(MAX) |
----------------------------------
| Description| nvarhar(MAX) |
----------------------------------
| ......................... |
----------------------------------
Any ideas on how I could just have one row per Images with IsBought set to true or false but not duplicates?
I would like something like this:
----------------------------------------------------------------------------
| Id | Title | Description | Location | PriceIT | Location | IsBought |
----------------------------------------------------------------------------
| 1 | Eiffel Tower | .... | ...... | 20.0 | Paris | true |
----------------------------------------------------------------------------
| 2 | Tore di Pisa | .... | ...... | 20.0 | Italia | false |
---------------------------------------------------------------------------
| etc ......
---------------------------------------------------------------------------
Your query logic looks suspicious. It is unusual to see a join that consists only of a comparison of a column from the unpreserved table to a parameter. I suspect that you don't need a join to users at all since you seem to be focused on things "bought" by a person and not things "created" (which is implied by the name "author") by that same person. And a group by clause with no aggregate is often a cover-up for a logically flawed query.
So start over. You want to see all images apparently. For each, you simply want to know if that image is associated with any order of a given person.
select img.*, -- you would, or course, only select the columns needed
(select count(*) from Sales.SalesOrderDetail as orddet
where orddet.ProductID = img.ProductID) as [Order Count],
(select count(*) from Sales.SalesOrderDetail as orddet
inner join Sales.SalesOrderHeader as ord
on orddet.SalesOrderID = ord.SalesOrderID
where orddet.ProductID = img.ProductID
and ord.CustomerID = 29620
) as [User Order Count],
case when exists(select * from Sales.SalesOrderDetail as orddet
inner join Sales.SalesOrderHeader as ord
on orddet.SalesOrderID = ord.SalesOrderID
where orddet.ProductID = img.ProductID
and ord.CustomerID = 29620) then 1 else 0 end as [Has Ordered]
from Production.ProductProductPhoto as img
where img.ProductID between 770 and 779
order by <something useful>;
Notice the aliases - it is much easier to read a long query when you use aliases that are shorter but still understandable (i.e., not single letters). I've included 3 different subqueries to help you understand correlation and how you can build your logic to achieve your goal and help debug any issues you find.
This is based on AdventureWorks sample database - which you should install and use as a learning tool (and to help facilitate discussions with others using a common data source). Note that I simply picked a random customer ID value - you would use your parameter. I filtered the query to a range of images to simplify debugging. Those are very simple but effective methods to help write and debug sql.

SQL - Query Multiple Dissimilar Tables (not UNION)

Language: T-SQL
Server: SQL Server 2008 R2 - SQL Server 2014
I have what, based on searching here an elsewhere, appears to either be a unique problem or I can't properly verbalize what I'm trying to accomplish. I'd like to query across multiple dissimilar tables that have dissimilar field structures and JOIN them to a single other table. We have a table of ASSETS tb_assets and a table of LICENSES tb_licenses. I'd like to query across both of these and JOIN them to the table of VENDORS tb_vendors.
Like this:
---------------------- ---------------------------
| TB_ASSETS | | TB_LICENSES |
---------------------- ---------------------------
| f_assetvendor | <~~~ ~~~> | f_licensevendor |
| f_assettag | | | | f_licensename |
| f_assetname | | | | f_licenseexpirationdate |
| | | | | f_licensequantity |
---------------------- | | ---------------------------
| |
~~~~~~~~ ~~~~~~~~~~
| ---------------------- |
| | TB_VENDORS | |
| ---------------------- |
~~> | f_vendorGUID | <~~
| f_vendorname |
----------------------
For a short example, I want to search for a vendor name (f_vendorname) of Amazon, I'd like to query against tb_assets as well as against tb_licenses. The query I tried below errors with Invalid column name 'f_assetvendor', so I'm doing something wrong.
SELECT
f_assetvendor AS 'AssetVendor', f_licensevendor as 'LicenseVendor'
FROM
tb_assets, tb_licenses
LEFT JOIN
tb_vendors assven ON assven.f_vendorGUID = f_assetvendor
LEFT JOIN
tb_vendors licven ON licven.f_vendorGUID = f_licensevendor
WHERE
f_vendorname LIKE '%Amazon%'
Regarding my title stating "not UNION", I can't use a UNION here because with UNION column names for the final result set are taken from the first query, the columns must have the same data types, and both tables must have the same number of columns.
Give this a go;
SELECT
v.f_vendorGUID,
v.f_vendorname,
a.f_assetvendor AssetVendor,
l.f_licensevendor LicenseVendor
FROM
TB_VENDORS v
JOIN
TB_ASSETS a ON v.f_vendorGUID = a.f_assetvendor
JOIN
TB_LICENSES l ON v.vendorGUID = l.f_licensevendor
WHERE
v.vendorname LIKE '%Amazon%'
You can use the TB_VENDORS as the main table and join the other two tables to it, in this instance (inner join) there's no particular order they should be in. You've shown in your diagram that there's a join between these tables. If you have the chance of missing data in either TB_ASSETS or TB_LICENCES, use LEFT JOIN instead of JOIN.
Please get out of the habit of that old style join you've used in your FROM statement, it's a really old way of doing it.
I know you said no to a union statement but I think this gives you what you need unless I am misunderstanding your query.
Select [t].[Vendor]
, [t].[VendorType]
From ( Select [f_assetvendor] As 'Vendor'
, 'Asset' As 'VendorType'
From [tb_assets]
Left Join [tb_vendors] [assven]
On [assven].[f_vendorGUID] = [f_assetvendor]
Union All
Select [f_licensevendor] As 'Vendor'
, 'License' As 'VendorType'
From [tb_licenses]
Left Join [tb_vendors] [licven]
On [licven].[f_vendorGUID] = [f_licensevendor]
) [t]
Where [t].[Vendor] Like '%Amazon%';
However you get there, you will need to make your tables similar enough to report together.

SQL Server making rows into columns

I'm trying to take three tables that I have and show the data in a way the user asked me to do it. The tables look like this. (I should add that I am using MS SQL Server)
First Table: The ID is varchar, since it's an ID they use to identify assets and they use numbers as well as letters.
aID| status | group |
-----------------------
1 | acti | group1 |
2 | inac | group2 |
A3 | acti | group1 |
Second Table: This table is fixed. It has around 20 values and the IDs are all numbers
atID| traitname |
------------------
1 | trait1 |
2 | trait2 |
3 | trait3 |
Third Table: This table is used to identify the traits the assets in the first table have. The fields that have the same name as fields in the above tables are obviously linked.
tID| aID | atID | trait |
----------------------------------
1 | 1 | 1 | NAME |
2 | 1 | 2 | INFO |
3 | 2 | 3 | GOES |
4 | 2 | 1 | HERE |
5 | A3 | 2 | HAHA |
Now, the user wants the program to output the data in the following format:
aID| status | group | trait1 | trait2 | trait 3
-------------------------------------------------
1 | acti | group1 | NAME | INFO | NULL
2 | inac | group2 | HERE | NULL | GOES
A3 | acti | group1 | NULL | HAHA | NULL
I understand that to achieve this, I have to use the Pivot command in SQL. However, I've read and tried to understand it but I just can't seem to get it. Especially the part where it asks for a MAX value. I don't get why I need that MAX.
Also, the examples I've seen are for one table. I'm not sure if I can do it with three tables. I do have a query that joins all three of them with the information I need. However, I don't know how to proceed from there. Please, any help with this will be appreciated. Thank you.
There are several ways that you can get the result, including using the PIVOT function.
You can use an aggregate function with a CASE expression:
select t1.aid, t1.status, t1.[group],
max(case when t2.traitname = 'trait1' then t3.trait end) trait1,
max(case when t2.traitname = 'trait2' then t3.trait end) trait2,
max(case when t2.traitname = 'trait3' then t3.trait end) trait3
from table1 t1
inner join table3 t3
on t1.aid = t3.aid
inner join table2 t2
on t3.atid = t2.atid
group by t1.aid, t1.status, t1.[group];
See SQL Fiddle with Demo
The PIVOT function requires an aggregate function this is why you would need to use either the MIN or MAX function (since you have a string value).
If you have a limited number of traitnames then you could hard-code the query:
select aid, status, [group],
trait1, trait2, trait3
from
(
select t1.aid,
t1.status,
t1.[group],
t2.traitname,
t3.trait
from table1 t1
inner join table3 t3
on t1.aid = t3.aid
inner join table2 t2
on t3.atid = t2.atid
) d
pivot
(
max(trait)
for traitname in (trait1, trait2, trait3)
) piv;
See SQL Fiddle with Demo.
If you have an unknown number of values, then you will want to look at using dynamic SQL to get the final result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(traitname)
from Table2
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT aid, status, [group],' + #cols + '
from
(
select t1.aid,
t1.status,
t1.[group],
t2.traitname,
t3.trait
from table1 t1
inner join table3 t3
on t1.aid = t3.aid
inner join table2 t2
on t3.atid = t2.atid
) x
pivot
(
max(trait)
for traitname in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo

How to show data from joined table if join id does not exist in joined table?

I have the following tables with corresponding data:
RequestsTable
ReqID | ReqBy | ReqDate
1 | User1 | 30/04/2013
2 | User2 | 30/04/2013
3 | MasterUser | 30/04/2013
and
RequestorsTable
ReqUserName | ReqFullName
User1 | Sample User 1
User2 | Sample User 2
and i need the output to be like this:
ReqID | ReqBy | ReqDate
1 | Sample User 1 | 30/04/2013
2 | Sample User 2 | 30/04/2013
3 | MasterUser | 30/04/2013
May I know how can I do this^^?
Just a quick note: MasterUser is a superuser account and is therefore not in the database, but could make requests.
Thanks for the help.
Using ISNULL and a LEFT OUTER JOIN allows you to selectively choose what column to return in the resultset.
If a matching record is found in RequestorsTable, use that value
otherwise, use the ReqBy value from RequestsTable
Sample Select statement
SELECT rt.ReqID, ISNULL(rst.ReqFullName, rt.ReqBy), rt.ReqDate
FROM RequestsTable rt
LEFT OUTER JOIN RequestorsTable rst ON rst.ReqUserName = rt.ReqBy
An alternative to ISNULL would be COALESCE wich more or less performs similar functionality.
Sample Select statement
SELECT rt.ReqID, COALESCE(rst.ReqFullName, rt.ReqBy), rt.ReqDate
FROM RequestsTable rt
LEFT OUTER JOIN RequestorsTable rst ON rst.ReqUserName = rt.ReqBy
Try this one -
SELECT
rt.ReqID
, ReqBy = ISNULL(rt2.ReqFullName, rt.ReqBy)
, rt.ReqDate
FROM dbo.RequestsTable rt
LEFT JOIN dbo.RequestorsTable rt2 ON rt.ReqBy = rt2.ReqUserName

Resources