SQL Exclude results from join or subquery where column names don't match up with multiple tables - sql

Ok, I'm working with an existing database (cannot edit the tables, columns, etc.) I am asked to create a report with the data for clients. This worked fine until we needed to create an exclusions group for certain clients.
There are 8 tables that need to be parsed for information in the database to execute this query properly. I've "simplified" it to 6 tables as best as I can.
The tables below are the existing tables, in the best order that I could come up with.
Table: Clients
ClientKey ClientNo ClientName
1 12345 ABC
2 12346 DEF
3 12347 GHI
4 12348 JKL
5 12349 MNO
6 12350 PQR
Table: ClientGroup
ClientKey GroupCode GroupValue
12345 EXCLUSIONSGROUP EXCLUDE
12346 EXCLUSIONSGROUP EXCLUDE
12347 OTHERSTUFF SOMETHING
Table: Groups
GroupCode GroupCodeKey
EXCLUSIONSGROUP 25
OTHERSTUFF 14
Table: GroupValues
GroupCode GroupValue
EXCLUSIONSGROUP EXCLUDE
OTHERSTUFF SOMETHING
EXCLUSIONSGROUP SOMETHING
Table: Images
FileKey Filename
987654 NULL
987653 Filename.jpg
987652 Filename.jpg
987651 NULL
987650 NULL
Table: Files
FileKey ClientKey
987654 12345
987653 12345
987652 12346
987651 12347
987650 12347
To better explain these tables:
Clients holds our clients
ClientGroup holds a list of which clients belong to which groups and the value that this client was assigned in that group (clients can be assigned multiple groups, and/or multiple values for a group)
Groups holds a list of the groups that exist as well as the GroupCodeKey. This table is important to refer to because the GroupCode values can change, so referring to '25' for example is the best way to access the proper GroupCode
GroupValues holds a list of all the possible GroupValues that can be assigned to a GroupCode (Group). They may be added, removed, changed.
Images points to the Files table through the FileKey column which points to the Clients table through the ClientKey column. The Images table tells us if a client's file has an image or not (defined by NULL if it does not exist)
Files contains a list of all documents that belong to a client. A client can (and most likely will) have multiple documents/files.
What I need to do:
I need to find all instances where the Filename in the Images table is NULL and where the Client is NOT in the ClientGroup table with a GroupValue of GroupValues(table)GroupValue(column) equal to 'EXCLUDE' and in the GroupCode of Groups(table)GroupCode(column) equal to Groups(table)GroupCodeKey(column) of '25'
In the following code, ignore columns that are unseen in the tables above, they exist, however to simplify the code and tables above, I've removed them from the code. They are still relevant to mention in the code below under select as both queries pull different columns' information from the database which prevents me from doing an EXCEPT between both queries
The current code (simplified) I have to get all the Clients with NULL Images is:
SELECT f.FileKey AS fkey, f.fNo AS fno, f.fDate AS fdate, cli.ClientNo AS clientno, cli.ClientName AS clientname, /*OTHER TABLE STUFF*/
FROM Files as f
LEFT JOIN Images as img
ON f.FileKey=img.FileKey
/*OTHER LEFT JOINS AND TABLES HERE RETURNING OTHER DATA*/
WHERE
img.[Filename] IS NULL
ORDER BY f.FileKey DESC
The current code I have to get all the Clients that are in the group with a GroupCodeKey of '25' and with a GroupValue of 'EXCEPT' is:
SELECT cli.ClientNo AS clientno, cli.ClientName AS clientname
FROM Clients AS cli
LEFT JOIN ClientGroup AS cg
ON cg.ClientKey = cli.ClientKey
LEFT JOIN Groups AS gc
ON gc.GroupCode = cg.GroupCode
LEFT JOIN GroupValue AS gv
ON gv.GroupCode = gc.GroupCode
WHERE
gc.GroupCodeKey='25' AND
gv.GroupValue='EXCLUDE'
Both above queries work exactly as anticipated on their own.
How would I combine these queries to give me the desired output?
The desired output (according to the tables above and their contents) would be to have the information that matches the first query minus the second one:
ClientNo:123457
ClientName:GHI
FileKey:987651
AND
ClientNo:123457
ClientName:GHI
FileKey:987650
Both these results match a client not belonging to the exceptions group '25' with value of 'EXCLUDE' and both FileKeys (987651 and 987650) have Filename set to NULL
I have tried to join all the tables, but cannot seem to properly create the query (I get either no results or I get results for the clients in the exception group only - whereas I need the ones not in the exceptions group). I have also tried creating a subquery, but I couldn't seem to get that to work either...
Any help regarding this is much appreciated.
Thanks!

Not sure if I have all the criteria right, but the general form of what you want can be gotten using NOT EXISTS
SELECT clientno, clientname, fkey, /*OTHER TABLE STUFF*/
FROM (
SELECT f.FileKey AS fkey, f.fNo AS fno, f.fDate AS fdate, cli.ClientNo AS clientno, cli.ClientName AS clientname, /*OTHER TABLE STUFF*/
FROM Files AS f
LEFT JOIN Images AS img
ON f.FileKey=img.FileKey
/*OTHER LEFT JOINS AND TABLES HERE RETURNING OTHER DATA*/
WHERE
img.[Filename] IS NULL
) incl
WHERE NOT EXISTS (
SELECT * FROM(
SELECT cli.ClientNo AS clientno, cli.ClientName AS clientname
FROM Clients AS cli
LEFT JOIN ClientGroup AS cg
ON cg.ClientKey = cli.ClientKey
LEFT JOIN Groups AS gc
ON gc.GroupCode = cg.GroupCode
LEFT JOIN GroupValue AS gv
ON gv.GroupCode = gc.GroupCode
WHERE
gc.GroupCodeKey='25' AND
gv.GroupValue='EXCLUDE'
) excl
WHERE excl.clientno = incl.ClientNo
AND excl.clientname = incl.ClientName
)
ORDER BY fkey DESC

SELECT im.FileKey
FROM Images AS im
INNER JOIN Files as Fi ON im.filekey = fi.filekey
--NOW THAT WE KNOW ALL CLIENT KEYS THAT MEET THE REQUIREMENTS WE NEED TO GO GET THE FILEKEY
INNER JOIN (
-- FIND ALL IMAGES WHERE FILE IS NOT NULL
SELECT F.clientKey
FROM images AS i
INNER JOIN files AS F on f.fileKey = i.fileKey
WHERE i.filename IS NULL
INTERSECT--GRAB THE INTERSECT BECAUSE WE WANT TO KNOW ALL CLIENTKEYS THAT MEET THE BELOW REQUIREMENTS
--FIND ALL CLIENTS THAT ARE NOT IN EXCLUDED AND IN THE GROUPS TABLE WITH GROUPCODE =25
SELECT C.ClientKey
FROM CLIENTS AS C
INNER JOIN CLientGroup AS CG ON C.clientKey = cg.clientkey AND cg.groupvalue != 'EXCLUDE'
INNER JOIN Groups AS g on CG.groupCode = g.groupCode AND g.groupCodeKey = '25') AS x on x.clientkey = fi.clientkey
I believe this will get you what you want(un tested)

Related

Efficient/optimized query for my query using multiple UNIONS with JOIN

Can someone please have a look into query and suggest any improvement or optimized query for the same so that query runs faster .
So basically, I have 2 table Survey and SurveyInvite.
Sample data for Table Survey
CREATE TABLE dbo.Survey
(
createdate date,
emailinvite char(4),
phoneinvite char(4),
smsinvite char(4),
surveyid int
);
INSERT dbo.Survey VALUES
('20220201','12ab','12bc', null ,1),
('20220210','23be','45hg','45tr',2),
('20220220','65hg', null ,'89kj',3);
Sample data for Table SurveyInvite
CREATE TABLE dbo.SurveyInvite
(
sentdate date,
id char(4)
);
INSERT dbo.SurveyInvite VALUES
('20220201','12ab'),
('20220205','12bc'),
('20220210','23be'),
('20220214','45hg'),
('20220218','45tr'),
('20220220','65hg'),
('20220224','89kj');
The output should be
Type
sentdate
inviteid
surveyid
Email
2022-02-01
12ab
1
Email
2022-02-10
23be
2
Email
2022-02-20
65hg
3
Phone
2022-02-05
12bc
1
Phone
2022-02-14
45hg
2
SMS
2022-02-18
45tr
2
SMS
2022-02-24
89kj
3
So basically, I have to get sentdate from SurveyInvite table against each type(email,phone,sms).
Survey table should be unpivoted on email,phone and sms to transform column into rows.
Here's my query
SELECT 'Email' as Type,esi.sentdate,emailinvite as inviteid,s.surveyid
FROM Survey s
INNER JOIN SurveyInvite esi on s.emailinvite=esi.id
UNION
SELECT 'SMS' as Type,ssi.sentdate,smsinvite as inviteid,s.surveyid
FROM Survey s
INNER JOIN SurveyInvite ssi on s.smsinvite=ssi.id
UNION
SELECT 'Phone' as Type,psi.sentdate,phoneinvite as inviteid,s.surveyid
FROM Survey s
INNER JOIN SurveyInvite psi on s.phoneinvite=psi.id
Please suggest other way to write query if that makes query faster. I am still trying using UNPIVOT,left join,CTE to avoid using UNION.
Sample setup here
You don't need to query the tables three times, you can just unpivot. The easiest way to do this is with a CROSS APPLY (VALUES
SELECT
v.Type,
ssi.sentdate,
v.inviteid,
s.surveyid
FROM Survey s
CROSS APPLY (VALUES
('Email', s.emailinvite),
('Phone', s.phoneinvite),
('SMS', s.smsinvite)
) v (Type, inviteid)
INNER JOIN SurveyInvite ssi on v.inviteid = ssi.id;
I suggest you consider normalizing your database in the first place by storing the data unpivoted in a separate table.
Another way (again the key is to only read either table once instead of three times):
SELECT i.sentdate,
[Type] = REPLACE(u.Types, 'invite', ''),
inviteid = u.id,
u.surveyid
FROM dbo.Survey AS s
UNPIVOT (Id FOR Types IN
(emailinvite, phoneinvite, smsinvite)) AS u
INNER JOIN dbo.SurveyInvite AS i ON u.Id = i.id;
As you can see from the db<>fiddle, this eliminates 4 of the 6 table scans and also an expensive distinct sort.
I assume that you have set the primary and foreign keys correctly. It might also beneficial to have indexes on the foreign keys. See: Should every SQL Server foreign key have a matching index?.
As always with these performance questions. Only benchmarking different variants can tell you which one is the fastest. The same query can perform very differently with a different set of data.
One possibility is to use joins and base the query on SurveyInvite:
SELECT
I.sentdate,
CASE WHEN SE.id IS NOT NULL THEN 'Email'
WHEN SP.id IS NOT NULL THEN 'Phone'
ELSE 'SMS'
END AS Type,
I.id AS inviteid,
CASE WHEN SE.id IS NOT NULL THEN SE.surveyid
WHEN SP.id IS NOT NULL THEN SP.surveyid
ELSE SS.surveyid
END AS surveyid
FROM
SurveyInvite I
LEFT JOIN Survey SE
ON I.emailinvite = SE.id
LEFT JOIN Survey SP
ON I.phoneinvite = SP.id
LEFT JOIN Survey SS
ON I.smsinvite = SS.id

Two group by tables stich another table

I have 3 tables I need to put together.
The first table is my main transaction table where I need to get distinct transaction id numbers and company id. It has all the important keys. The transaction ids are not unique.
The second table has item info which is linked to transaction id numbers which are not unique and I need to pull items.
The third table has company info which has company id.
Now I've sold some of these with the first one through a group by id. The second through a subquery which creates unique ids and joins onto the first one.
The issue I'm having is the third one by company. I cannot seem to create a query that works in the above combinations. Any ideas?
As suggested here is my code. It works but that's because for the company I used count which doesn't give the correct number. How else can I get the company number to come out correct?
SELECT
dep.ItemIDAPK,
dep.TotalOne,
dep.company,
company.vendname,
appd.ItemIDAPK,
appd.ItemName
FROM (
SELECT
csi.ItemIDAPK,
sum(f.TotalOne) as TotalOne,
count(f.DimCurrentcompanyID) company
FROM dbo.ReportOne F with (nolock)
INNER JOIN dbo.DSaleItem csi with (nolock)
on f.DSaleItemID = csi.DSaleItemID
INNER JOIN dbo.DimCurrentcompany cv
ON f.DimCurrentcompanyID = cv.DimCurrentcompanyID
INNER JOIN dbo.DimDate dat
on f.DimDateID = dat.DimDateID
where (
dat.date >='2013-01-29 00:00:00.000'
and dat.date <= '2013-01-30 00:00:00.000'
)
GROUP BY csi.ItemIDAPK
) as dep
INNER JOIN (
SELECT
vend.DimCurrentcompanyID,
vend.Name vendname
FROM dbo.DimCurrentcompany vend
) As company
on dep.company = company.DimCurrentcompanyID
INNER JOIN (
SELECT
c2.ItemIDAPK,
ItemName
FROM (
SELECT DISTINCT ItemIDAPK
FROM dbo.dimitem AS C
) AS c1
JOIN dbo.dimitem AS c2 ON c1.ItemIDAPK = c2.ItemIDAPK
) as appd
ON dep.ItemIDAPK = appd.ItemIDAPK
For further information my output is the following example, I know the code executes and the companyid is incorrect as I just put it with a (count) in their to make the above code execute:
Current Results:
Item Number TLS CompanyID Company Name Item Number Item Name
111111 300 303 Johnson Corp 29323 Soap
Proposed Results:
Item Number TLS CompanyID Company Name Item Number Item Name
111111 300 29 Johnson Corp 29323 Soap

SQL Inner join in a nested select statement

I'm trying to do an inner join in a nested select statement. Basically, There are first and last reason IDs that produce a certain number (EX: 200). In another table, there are definitions for the IDs. I'm trying to pull the Last ID, along with the corresponding comment for whatever is pulled (EX: 200 - Patient Cancelled), then the first ID and the comment for whatever ID it is.
This is what I have so far:
Select BUSN_ID
AREA_NAME
DATE
AREA_STATUS
(Select B.REASON_ID
A.LAST_REASON_ID
FROM BUSN_INFO A, BUSN_REASONS B
WHERE A.LAST_REASON _ID=B.REASON_ID,
(Select B.REASON_ID
A. FIRST_REASON_ID
FROM BUSN_INFO A, BUSN_REASONS B
WHERE A_FIRST_REASON_ID = B.REASON_ID)
FROM BUSN_INFO
I believe an inner join is best, but I'm stuck on how it would actually work.
Required result would look like (this is example dummy data):
First ID -- Busn Reason -- Last ID -- Busn Reason
1 Patient Sick 2 Patient Cancelled
2 Patient Cancelled 2 Patient Cancelled
3 Patient No Show 1 Patient Sick
Justin_Cave's SECOND example is the way I used to solve this problem.
If you want to use inline select statements, your inline select has to select a single column and should just join back to the table that is the basis of your query. In the query you posted, you're selecting the same numeric identifier multiple times. My guess is that you really want to query a string column from the lookup table-- I'll assume that the column is called reason_description
Select BUSN_ID,
AREA_NAME,
DATE,
AREA_STATUS,
a.last_reason_id,
(Select B.REASON_description
FROM BUSN_REASONS B
WHERE A.LAST_REASON_ID=B.REASON_ID),
a.first_reason_id,
(Select B.REASON_description
FROM BUSN_REASONS B
WHERE A.FIRST_REASON_ID = B.REASON_ID)
FROM BUSN_INFO A
More conventionally, though, you'd just join to the busn_reasons table twice
SELECT i.busn_id,
i.area_name,
i.date,
i.area_status,
i.last_reason_id,
last_reason.reason_description,
i.first_reason_id,
first_reason.reason_description
FROM busn_info i
JOIN busn_reason first_reason
ON( i.first_reason_id = first_reason.reason_id )
JOIN busn_reason last_reason
ON( i.last_reason_id = last_reason.reason_id )

Update multiple row values to same row and different columns

I was trying to update table columns from another table.
In person table, there can be multiple contact persons with same inst_id.
I have a firm table, which will have latest 2 contact details from person table.
I am expecting the firm tables as below:
If there is only one contact person, update person1 and email1. If there are 2, update both. If there is 3, discard the 3rd one.
Can someone help me on this?
This should work:
;with cte (rn, id, inst_id, person_name, email) as (
select row_number() over (partition by inst_id order by id) rn, *
from person
)
update f
set
person1 = cte1.person_name,
email1 = cte1.email,
person2 = cte2.person_name,
email2 = cte2.email
from firm f
left join cte cte1 on f.inst_id = cte1.inst_id and cte1.rn = 1
left join cte cte2 on f.inst_id = cte2.inst_id and cte2.rn = 2
The common table expression (cte) used as a source for the update numbers rows in the person table, partitioned by inst_id, and then the update joins the cte twice (for top 1 and top 2).
Sample SQL Fiddle
I think you don't have to bother yourself with this update, if you rethink your database structure. One great advantage of relational databases is, that you don't need to store the same data several times in several tables, but have one single table for one kind of data (like the person's table in your case) and then reference it (by relationships or foreign keys for example).
So what does this mean for your example? I suggest, to create a institution's table where you insert two attributes like contactperson1 and contactperson2: but dont't insert all the contact details (like email and name), just the primary key of the person and make it a foreign key.
So you got a table 'Person', that should look something like this:
ID INSTITUTION_ID NAME EMAIL
1 100 abc abc#inst.com
2 101 efg efg#xym.com
3 101 ijk ijk#fg.com
4 101 rtw rtw#rtw.com
...
And a table "Institution" like:
ID CONTACTPERSON1 CONTACTPERSON2
100 1 NULL
101 2 3
...
If you now want to change the email adress, just update the person's table. You don't need to update the firm's table.
And how do you get your desired "table" with the two contact persons' details? Just make a query:
SELECT i.id, p1.name, p1.email, p2.name, p2.email
FROM institution i LEFT OUTER JOIN person p1 ON (i.contactperson1 = p1.id)
LEFT OUTER JOIN person p2 ON (i.contactperson2 = p2.id)
If you need this query often and access it like a "table" just store it as a view.

Remove Duplicates from LEFT OUTER JOIN

My question is quite similar to Restricting a LEFT JOIN, with a variation.
Assuming I have a table SHOP and another table LOCATION. Location is a sort of child table of table SHOP, that has two columns of interest, one is a Division Key (calling it just KEY) and a "SHOP" number. This matches to the Number "NO" in table SHOP.
I tried this left outer join:
SELECT S.NO, L.KEY
FROM SHOP S
LEFT OUTER JOIN LOCATN L ON S.NO = L.SHOP
but I'm getting a lot of duplicates since there are many locations that belong to a single shop. I want to eliminate them and just get a list of "shop, key" entries without duplicates.
The data is correct but duplicates appear as follows:
SHOP KEY
1 XXX
1 XXX
2 YYY
3 ZZZ
3 ZZZ etc.
I would like the data to appear like this instead:
SHOP KEY
1 XXX
2 YYY
3 ZZZ etc.
SHOP table:
NO
1
2
3
LOCATION table:
LOCATION SHOP KEY
L-1 1 XXX
L-2 1 XXX
L-3 2 YYY
L-4 3 YYY
L-5 3 YYY
(ORACLE 10g Database)
You need to GROUP BY 'S.No' & 'L.KEY'
SELECT S.NO, L.KEY
FROM SHOP S
LEFT OUTER JOIN LOCATN L
ON S.NO = L.SHOP
GROUP BY S.NO, L.KEY
EDIT Following the update in your scenario
I think you should be able to do this with a simple sub query (though I haven't tested this against an Oracle database). Something like the following
UPDATE shop s
SET divnkey = (SELECT DISTINCT L.KEY FROM LOCATN L WHERE S.NO = L.SHOP)
The above will raise an error in the event of a shop being associated with locations that are in multiple divisions.
If you just want to ignore this possibility and select an arbitrary one in that event you could use
UPDATE shop s
SET divnkey = (SELECT MAX(L.KEY) FROM LOCATN L WHERE S.NO = L.SHOP)
I had this problem too but I couldn't use GROUP BY to fix it because I was also returning TEXT type fields. (Same goes for using DISTINCT).
This code gave me duplicates:
select mx.*, case isnull(ty.ty_id,0) when 0 then 'N' else 'Y' end as inuse
from master_x mx
left outer join thing_y ty on mx.rpt_id = ty.rpt_id
I fixed it by rewriting it thusly:
select mx.*,
case when exists (select 1 from thing_y ty where mx.rpt_id = ty.rpt_id) then 'Y' else 'N' end as inuse
from master_x mx
As you can see I didn't care about the data within the 2nd table (thing_y), just whether there was greater than zero matches on the rpt_id within it. (FYI: rpt_id was also not the primary key on the 1st table, master_x).