SQL server inner join not returning Description - sql

Looking for some help trying to get some information out of my point of sale db. It is a MS Sql 13.0.4001.0 database
I have two tables. A "stock" table and a "stock UDF" table. They look a little like this:
The Stock Table lets call "ST" and the Stock UDF table "SU"
Stock Table has the following columns
SKU, Description, UDF1 ID, UDF2 ID,UDF3 ID,UDF4 ID
The Stock UDF table has the following columns
ID, Description
I want to create a query that returns a record but instead of the ID under the UDF1 ID column I want to get the description from the SU table.
A sample record in the ST currently looks like this
SKU, Description, UDF1 ID, UDF2 ID,UDF3 ID,UDF4 ID
1000 Orange 2 1 3 Null
The SU table looks like this
ID, Description
1 Fruit
2 Salads
3 Desserts
4 Vegetables
5 Raw
6 Cooked
I want to create a query that returns the following
SKU Description UDF1 UDF2 UDF3 UDF4
1000 Oranges Salads Fruit Desserts
Not sure how to do the inner join correctly.
Something like this:
select st.SKU, st.Description, st.[UDF1 Id], st.[UDF2 Id], st.[UDF3 Id], st.[UDF4 Id]
from [Stock] as st inner join [Stock UDF] as su on st.UDF1 ID = su.ID
But doesn't return what I want.
Thanks in advance.

You simply need to join to the table with the description multiple times, as follows. Joins are implicitly INNER JOIN, so it's not necessary to state that unless you want to. Note, however, that in this case you must use a LEFT OUTER join type, else where there is no match in the joined table, you will return no rows.
EDIT
I've added COALESCE() functions so that in the event a su.Decription field returns NULL, the query will actually output an empty string instead of the NULL. COALESCE() is a handy function that returns the first non-null value in its set of arguments.
SELECT st.SKU
,st.Description
,COALESCE(su1.Description, '') [UDF1 Desc]
,COALESCE(su2.Description, '') [UDF2 Desc]
,COALESCE(su3.Description, '') [UDF3 Desc]
,COALESCE(su4.Description, '') [UDF4 Desc]
FROM Stock st
LEFT JOIN [Stock UDF] su1 ON st.UDF1 = su1.ID
LEFT JOIN [Stock UDF] su2 ON st.UDF2 = su2.ID
LEFT JOIN [Stock UDF] su3 ON st.UDF3 = su3.ID
LEFT JOIN [Stock UDF] su4 ON st.UDF4 = su4.ID

You need to use left joins as you want stock descriptions even if the record is missing. Specifying join or inner join means that the record/field can't be null and return.
select
st.SKU, st.Description,
st.[UDF1 Id], ISNULL(su1.Description,''),
st.[UDF2 Id], ISNULL(su2.Description,''),
st.[UDF3 Id], ISNULL(su3.Description,''),
st.[UDF4 Id], ISNULL(su4.Description,'')
from [Stock] as st
LEFT join [Stock UDF] as su1 on st.[UDF1 Id] = su1.ID
LEFT join [Stock UDF] as su2 on st.[UDF2 Id] = su2.ID
LEFT join [Stock UDF] as su3 on st.[UDF3 Id] = su3.ID
LEFT join [Stock UDF] as su4 on st.[UDF4 Id] = su4.ID

As there are 4 foreign keys in the [Stock] table (UDF1 ID, UDF2 ID,UDF3 ID,UDF4 ID), you need to join [Stock UDF] table 4 times.
left join [Stock UDF] as su1 on st.UDF1 ID = su1.ID
left join [Stock UDF] as su2 on st.UDF2 ID = su2.ID
left join [Stock UDF] as su3 on st.UDF3 ID = su3.ID
left join [Stock UDF] as su4 on st.UDF4 ID = su4.ID
As there are null values consider using LEFT JOIN instead of INNER JOIN.

Related

Access Query and attributes

The setup is this: every location has many different accounts. Some are supplier only accounts, some are not. Each account has many bills associated with it.
I need to do one of two things:
Create a dynamic attribute in the location table that will tell me if the location is associated with an account (or many) that are supplier only. Should be a true/false attribute.
OR
Create a query that will return all the bills for all the locations that are associated with a supplier only account. I do not want a query that only returns bills from supplier only accounts.
Thanks for you help!
Here's the answer:
Location3PS Query:
SELECT DISTINCT Locations.Number
FROM Locations INNER JOIN Accounts ON Locations.Number = Accounts.[Location Num]
WHERE (((Accounts.[Supplier Only?])=True));
Final Query to Get Bills:
SELECT Bills.*, Location3PS.*
FROM
Location3PS INNER JOIN
(
Locations INNER JOIN
(
Accounts INNER JOIN Bills
ON Accounts.[Account Number] = Bills.[Account Number]
)
ON Locations.Number = Accounts.[Location Num]
)
ON Location3PS.Number = Locations.Number;
You can accomplish this without involving the Locations table, e.g.:
select b.* from
(bills b inner join accounts a on b.[account number] = a.[account number])
inner join
(select distinct c.[location num] from accounts c where c.[supplier only?] = true) l
on a.[location num] = l.[location num]
Alternatively, you can use a correlated subquery, e.g.:
select b.*
from bills b inner join accounts a on b.[account number] = a.[account number]
where exists
(select 1 from accounts c where c.[location num] = a.[location num] and c.[supplier only?] = true)

Append SQL columns based on different JOINs

My problem is I have a table where I want to return a column from an SQL database as two different columns based on different JOIN conditions.
My two select statements:
My first statement returns the check numbers and amounts for all of our checks that have gone through the process of being deposited -> and then ipaced (just a term).
SELECT COURTESY_CHECK.CHECK_NUMBER,
COURTESY_CHECK.CHECK_AMOUNT,
CHECK_DEPOSIT.ID AS DEPOSIT_ID
FROM COURTESY_CHECK
INNER JOIN CHECK_DEPOSIT ON CHECK_DEPOSIT.COURTESY_CHECK_ID = COURTESY_CHECK.ID
INNER JOIN DEPOSIT ON CHECK_DEPOSIT_ID = DEPOSIT.ID
INNER JOIN IPAC ON DEPOSIT.ID = IPAC.DEPOSIT_ID
My second statement returns the check numbers and amounts of the checks that have just been deposited.
SELECT COURTESY_CHECK.CHECK_NUMBER,
COURTESY_CHECK.CHECK_AMOUNT,
CHECK_DEPOSIT.ID AS DEPOSIT_ID
FROM COURTESY_CHECK
INNER JOIN CHECK_DEPOSIT ON CHECK_DEPOSIT.COURTESY_CHECK_ID = COURTESY_CHECK.ID
I would like to have a table like
IPAC/DEPOSITED AMOUNT DEPOSITED AMOUNT CHECK_NUMBER
--------------------- ---------------- ------------
$4.00 123456
$5.00 654321
I'm using BIRT to compile reports and it really only allows you to chart data based on single data sets (which is a single query) AFAIK. I'd like to chart the total "IPAC/Deposited" amount versus the "Deposited" amount.
You're really close. Wouldn't a UNION query do the trick? Something like this:
SELECT COURTESY_CHECK.CHECK_NUMBER,
COURTESY_CHECK.CHECK_AMOUNT AS [IPAC AMOUNT],
null AS [DEPOSITED AMOUNT],
CHECK_DEPOSIT.ID AS DEPOSIT_ID
FROM COURTESY_CHECK
INNER JOIN CHECK_DEPOSIT ON CHECK_DEPOSIT.COURTESY_CHECK_ID = COURTESY_CHECK.ID
INNER JOIN DEPOSIT ON CHECK_DEPOSIT_ID = DEPOSIT.ID
INNER JOIN IPAC ON DEPOSIT.ID = IPAC.DEPOSIT_ID
UNION
SELECT COURTESY_CHECK.CHECK_NUMBER, null AS [IPAC AMOUNT],
COURTESY_CHECK.CHECK_AMOUNT AS [DEPOSITED AMOUNT],
CHECK_DEPOSIT.ID AS DEPOSIT_ID
FROM COURTESY_CHECK
INNER JOIN CHECK_DEPOSIT ON CHECK_DEPOSIT.COURTESY_CHECK_ID = COURTESY_CHECK.ID
Edit ... this CASE statement with outer joins might be more efficient -- I think this would work, too:
SELECT COURTESY_CHECK.CHECK_NUMBER,
CASE WHEN IPAC.DEPOSIT_ID IS NOT NULL THEN COURTESY_CHECK.CHECK_AMOUNT
ELSE NULL END AS [IPAC AMOUNT],
CASE WHEN IPAC.DEPOSIT_ID IS NOT NULL THEN NULL ELSE COURTESY_CHECK.CHECK_AMOUNT
END AS [DEPOSITED AMOUNT],
CHECK_DEPOSIT.ID AS DEPOSIT_ID
FROM COURTESY_CHECK
INNER JOIN CHECK_DEPOSIT ON CHECK_DEPOSIT.COURTESY_CHECK_ID = COURTESY_CHECK.ID
LEFT JOIN DEPOSIT ON CHECK_DEPOSIT_ID = DEPOSIT.ID
LEFT JOIN IPAC ON DEPOSIT.ID = IPAC.DEPOSIT_ID

SQL - query to report MAX DATE results or NULL results

I have an SQL database with 3 tables:
Customer record table, holding master data (each row is unique)
Customer notes, each note data/time stamped (there could be many notes per customer, or none at all)
Product table, showing which customer has bought which product
Table: tbl_Customer_Records
CustomerID---- Company Name-----Company Segment----- Promo Code
Table: tbl_Customer_Notes
CustomerID---- Note-----Created Date
Table: tbl_Customer_Products
CustomerID---- Product Category
What I want is to pull a list of customer records which includes the latest note only, so there are no duplicate lines if multiple notes exist. But I also want my report to include customer records if no notes exist at all. I've achieved the first part of this with a SELECT MAX function, and that works well, the problem is when I add the OR = NULL clause in the final line of code below. This doesn't work, and I can't figure out a solution.
Any suggestions will be greatly appreciated!
SELECT
[tbl_Customer_Records].[CustomerID],
[tbl_Customer_Records].[Company Name],
[tbl_Customer_Records].[Company Segment],
[tbl_Customer_Records].[Promo Code],
[tbl_Customer_Notes].[Note],
[tbl_Customer_Products].[Product Category]
FROM
tbl_Customer_Records
LEFT OUTER JOIN tbl_Customer_Notes
ON tbl_Customer_Records.CustomerID = tbl_Customer_Notes.CustomerID
LEFT OUTER JOIN tbl_Customer_Products
ON tbl_Customer_Records.CustomerID = tbl_Customer_Products.CustomerID
WHERE
[Product Category] in ('Nuts','Bolts','Screws','Spanners')
AND
[Created Date] in (SELECT MAX ([Created Date]) FROM tbl.Customer_Notes GROUP BY [CustomerID])
OR tbl_Customer_Note.Note is null
There're a few tricks to do this kind of query (row_number or join with grouped data), but I think most cleanest one in your case is to use outer apply:
select
cr.[CustomerID],
cr.[Company Name],
cr.[Company Segment],
cr.[Promo Code],
cn.[Note],
cp.[Product Category]
from tbl_Customer_Records as cr
left outer join tbl_Customer_Products as cp on cp.CustomerID = cr.CustomerID
outer apply (
select top 1
t.[Note]
from tbl_Customer_Notes as t
where t.[CustomerID] = cr.[CustomerID]
order by t.[Created_Date] desc
) as cn
where
cp.[Product Category] in ('Nuts','Bolts','Screws','Spanners')
Changed all clumsy table name.column name to alias.column name, I think it's much more readable this way.
Or:
select
cr.[CustomerID],
cr.[Company Name],
cr.[Company Segment],
cr.[Promo Code],
cn.[Note],
cp.[Product Category]
from tbl_Customer_Records as cr
left outer join tbl_Customer_Products as cp on cp.CustomerID = cr.CustomerID
left outer join tbl_Customer_Notes as cn on
cn.CustomerID = cr.CustomerID and
cn.[Created_Date] = (select max(t.[Created_Date]) from tbl_Customer_Notes as t where t.CustomerID = cr.CustomerID)
where
cp.[Product Category] in ('Nuts','Bolts','Screws','Spanners')
You can add your filter condition in ON predicate to preserve rows from left table and fetch only required matching rows from right table, from first LEFT OUTER JOIN operator. Following query should work:
SELECT
CR.[CustomerID],
CR.[Company_Name],
CR.[Company_Segment],
CR.[Promo_Code],
CN.[Note],
CP.[Product_Category]
FROM
tbl_Customer_Records CR
LEFT OUTER JOIN tbl_Customer_Notes CN
ON CR.CustomerID = CN.CustomerID AND CN.[Created_Date] in (SELECT MAX ([Created_Date])
FROM tbl_Customer_Notes
WHERE CR.CustomerID = tbl_Customer_Notes.CustomerID
GROUP BY [CustomerID])
LEFT OUTER JOIN tbl_Customer_Products CP
ON CR.CustomerID = CP.CustomerID
WHERE
[Product_Category] in ('Nuts','Bolts','Screws','Spanners')
Should work, tried with NULL values:
SELECT a.[CustomerID],
a.[Company Name],
a.[Company Segment],
a.[Promo Code],
a.[Note],
a.[Product Category]
FROM (
SELECT
cr.[CustomerID],
cr.[Company Name],
cr.[Company Segment],
cr.[Promo Code],
cn.[Note],
cp.[Product Category],
ROW_NUMBER() OVER(PARTITION BY cr.[CustomerID] ORDER BY cn.[Created Date] DESC) as rnk
FROM tbl_Customer_Records cr
LEFT JOIN tbl_Customer_Notes cn
ON cr.CustomerID = cn.CustomerID
LEFT JOIN tbl_Customer_Products cp
ON cr.CustomerID = cp.CustomerID
WHERE cp.[Product Category] in ('Nuts','Bolts','Screws','Spanners') )a
WHERE a.rnk = 1
try to use CustomerID not in (select CustomerID from tbl_Customer_Note)

Return results of ID NOT IN Certain Table

I am trying to write a write and the logic I am looking for is basically. If any User IDs from Table 1 do not exist in Table 2 show those in the results. So I did:
SELECT
UserColder.ContactName,
UserColder.Phone,
UserColder.Email,
UserColder.Website,
Country.Name,
UserColderZIP.[ZIP Code],
UserColderZIP.[State Abbreviation]
FROM
dbo.UserColder
LEFT OUTER JOIN dbo.CountryUser
ON UserColder.ID = CountryUser.[User ID]
INNER JOIN dbo.Country
ON CountryUser.[Foreign ID] = Country.ID
LEFT OUTER JOIN dbo.UserColderZIP
ON UserColder.ID = UserColderZIP.UserColder
WHERE
UserColder.ID NOT IN (CountryUser.[User ID])
It returns data without the Where But with the Where I am trying to get it to show all the results where the Id from UserColder don't have any records in CountryUser. Right now it is returning no results and I know ID 2 doesn't exist in there. Any idea what I am doing wrong?
Thanks!
Since you have a LEFT OUTER JOIN, to get records from UserColder that don't exist in CountryUser, check for NULL. The joined rows which have no corresponding value will return NULL.
WHERE
CountryUser.[User ID] IS NULL
Just grab the information from UserColder and make sure that the UserColder.ID is not in CountryUser by checking it (select statement in where).
SELECT
UserColder.ContactName,
UserColder.Phone,
UserColder.Email,
UserColder.Website,
Country.Name,
UserColderZIP.[ZIP Code],
UserColderZIP.[State Abbreviation]
FROM
dbo.UserColder
INNER JOIN dbo.Country
ON CountryUser.[Foreign ID] = Country.ID
LEFT OUTER JOIN dbo.UserColderZIP
ON UserColder.ID = UserColderZIP.UserColder
WHERE
UserColder.ID NOT EXISTS (select [user id] from dbo.CountryUser where CountryUser.[User ID])

Filtering based on COUNT

I have a query that works really well. But I am trying to add a filter so if users_in_this_country is > 1. I know to add users_in_this_country > 1 to the WHERE. But if I add it inside the parenthesis it says invalid column and the same if I add it outside of the parenthesis. This is probably really dumb and easy, but what am I over looking? Thanks!
SELECT u.ContactName
,cu.[User ID]
,c.Name
,c.ID
,cu.[Foreign Table]
,count(*) OVER (PARTITION BY c.ID) AS user_in_this_country
FROM dbo.Country AS c
INNER JOIN dbo.CountryUser AS cu ON c.ID = cu.[Foreign ID]
INNER JOIN dbo.UserColder AS u ON cu.[User ID] = u.ID
WHERE EXISTS (
SELECT *
FROM CountryUser AS cu2
WHERE cu2.[Foreign ID] = cu.[Foreign ID]
AND cu2.[User ID] <> cu.[User ID]
AND cu.[Foreign Table] = 'Country')
The reason you can't refer to it in the WHERE clause is that its very meaning, for a given row, depends on what other rows satisfy the WHERE clause; so it would all be too circular.
The simplest fix is to wrap your entire query in SELECT * FROM ( ... ) t WHERE t.user_in_this_country > 1.
That said, it seems like your query already ensures that user_in_this_country > 1, by virtue of the EXISTS clause, which makes sure that there exists a different CountryUser record that belongs to the same Country and a different User. What am I missing?
With aggregate functions (like COUNT) used in conjuction with OVER clause you have to use a CTE or a subquery, like this
WITH CTE AS (
SELECT u.ContactName
,cu.[User ID]
,c.Name
,c.ID
,cu.[Foreign Table]
,count(*) OVER (PARTITION BY c.ID) AS user_in_this_country
FROM dbo.Country AS c
INNER JOIN dbo.CountryUser AS cu ON c.ID = cu.[Foreign ID]
INNER JOIN dbo.UserColder AS u ON cu.[User ID] = u.ID
WHERE EXISTS (
SELECT *
FROM CountryUser AS cu2
WHERE cu2.[Foreign ID] = cu.[Foreign ID]
AND cu2.[User ID] <> cu.[User ID]
AND cu.[Foreign Table] = 'Country')
)
SELECT *
FROM CTE
WHERE user_in_this_country > 1
Because "users_in_this_country" is not a column, it is an alias which is not valid in the scope of the WHERE clause. I 'm not familiar with "OVER" or PARTITION BY but, my guess is you'd have to do something like this:
WHERE blabla AND (count(*) OVER (PARTITION BY c.ID)) > 1