SQL query with joins help needed - sql

I have four tables.
DocumentList:
DocumentID int
DocumentDescription varchar(100)
DocumentName varchar(100)
DocumentTypeCode int
Archive ud_DefaultBitFalse:bit
DocumentStepLevel:
DocumentStepID int
DocumentID int
StepLevelCode int
DocumentAttachment:
DocumentAttachmentGenID int
DocumentStepID int
AttachmentGenID int
FacilityGenID int
Submitted ud_DefaultBitFalse:bit
Attachment:
AttachmentGenId int
FileName varchar(255)
FileDescription varchar(255)
UploadDate ud_DefaultDate:datetime
DocumentData varbinary(MAX)
MimeType varchar(30)
Archive ud_DefaultBitFalse:bit
UpdateBy int
UpdateDate ud_DefaultDate:datetime
Documentlist table contains a list of documents.
DocumentStepLevel is a table that associate documents in DocumentList with a step level. We have six steps right now and each step have some documents associated with it.
DocumentAttachment table is junction/relationship table that create relationship between DocumentStepLevel and Attachment table.
Attachment table has the actual files data uploaded to the system
Question:
I need to write a query that will fetch the following columns.
DocumentList.[DocumentDescription]
DocumentList.[DocumentName]
DocumentStepLevel.[DocumentStepID]
DocumentStepLevel.[StepLevelCode]
DocumentAttachment.[DocumentAttachmentGenID]
DocumentAttachment.[FacilityGenID]
DocumentAttachment.[Submitted]
Attachment.[FileName]
Attachment.[FileDescription]
Attachment.[UploadDate]
Query should return data from DocumentList table for specific step level. When DocumentAttachment.[Submitted] column is set to true it should also return the data from DocumentAttachment and Attachment tables as well. Otherwise those columns will return nothing.
I tried using left outer join but problem happen when I add Submitted column to query. When I add that column to query it stop returning any data until that flag is set to true.

SELECT *
FROM documentStepLevel dsl
JOIN documentList dl
ON dl.documentId = dsl.documentId
LEFT JOIN
documentAttachment da
ON da.documentStepID = dsl.documentStepId
AND submitted = 1
LEFT JOIN
attachment a
ON a.attachmentGenId = da.attachmentGenId
WHERE dsl.stepLevelCode = #stepLevelCode

Is it DocumentAttachment you're left outer joining?
Difficult to say for sure without seeing your current query, but I'm guessing you've outer joined to DocumentAttachment and then have something like "where documentattachment.submitted = 1"?
In this case I believe it won't return anything as for rows where documentattachment doesn't exist, submitted is effectively null. So you might need to change your where statement to "where (documentattachment.submitted = 1 or documentattachment.submitted is null)"
This also assumes that when DocumentAttachment is populated, submitted by default has a 0 value rather than a null value (otherwise you'll need a different method of ascertaining the absence of a DocumentAttachment)

Related

How do I check if matches on a left join ALL have a match on another table?

I am attempting to write a stored procedure that will return only records who either didn't yield any results on the right side of a LEFT JOIN or for all of the records found on the right side, only return a result set for those who have a match in another table.
To illustrate what I'm attempting to achieve, first consider the following table definitions:
CREATE TYPE [dbo].[TvpDocumentsSent] AS TABLE
(
DocumentId INT
, RecipientId INT
, TransactionId INT
);
CREATE TABLE [dbo].[Recipients]
(
RecipientId INT
, GroupId INT
)
CREATE TABLE [dbo[.[RecipientEmails]
(
RecipientId INT
, TransactionID INT
)
CREATE TABLE [dbo].[DocumentTransactions]
(
TransactionId INT
, DocumentId INT
)
The first table, TvpDocumentsSent is used in the stored procedure as a table-valued parameter. It is indicative of the records that we are checking.
The second table, Recipients houses all of the potential document recipients. It is worth noting that recipients are placed in groups (indicated by the GroupId). All recipients in a group should receive a document before that document is marked as ready-for-archive. That is the part that I'm struggling with, btw.
Next, the RecipientEmails table houses all e-mails (that may or may not have contained a document) that have been sent to a recipient.
The latter table, DocumentTransactions stores a log of all document transactions that have occurred. This tells me what document was sent (indicated by the DocumentId). Although there is not a RecipientId on this table, the TransactionId can be used to trace the DocumentTransaction back to a recipient via the RecipientEmails table.
What I'm struggling with is how to write a query that gives me only a subset of the records passed in via TvpDcoumentsSent; only those who either don't have another recipient waiting for the document in the group or all recipients have received the document (i.e. there is a record in the DocumentTransactions table whose TransactionId maps back to a record in RecipientEmail whose recipient was eligible for this document).
What I've come up with so far is this (Note: I'm aware that I'm using TvpDocumentsSent as a table and not a TVP in the query below. I did this to simplify my explanation.
SELECT
SNT.DocumentId
FROM [dbo].[TvpDocumentsSent] AS SNT
INNER JOIN [dbo].[Recipients] AS RCP ON -- The recipient who recieved the document during this transaction.
RCP.RecipientId = SNT.RecipientId
LEFT JOIN [dbo].[Recipients] AS OTHR_RCP ON -- Other recipients who may have already received the document or could later.
RCP.GroupId = OTHR_RCP.GroupId
AND RCP.RecipientId != OTHR_RCP.RecipientId
WHERE OTHR_RCP.RecipientId IS NULL OR ??????
Keeping in mind that there are n number of recipients who could potentially receive the document, how do I fulfill the OR portion of the WHERE clause to ensure that everyone has received documents?
I tried the following and it does not work correctly:
SELECT
SNT.DocumentId
FROM [dbo].[TvpDocumentsSent] AS SNT
INNER JOIN [dbo].[Recipients] AS RCP ON -- The recipient who recieved the document during this transaction.
RCP.RecipientId = SNT.RecipientId
LEFT JOIN [dbo].[Recipients] AS OTHR_RCP ON -- Other recipients who may have already received the document or could later.
RCP.GroupId = OTHR_RCP.GroupId
AND RCP.RecipientId != OTHR_RCP.RecipientId
LEFT JOIN [dbo].[DocumentTransactions] AS DT ON
SNT.TransactionId = DT.TransactionId
WHERE OTHR_RCP.RecipientId IS NULL OR DT.DocumentId IS NOT NULL
That won't work because as long as one of the recipients have received the document, the OR part of the WHERE clause will pass. Let's say 5 recipients should received the document but only 1 has received it thus far. That OR will see the 1 record's match and pass the WHERE; that's wrong...It should enforce that ALL potential recipients have received the document.
Not sure if the example below is getting close.
Since I had to mock up the sample data & guess the expected results.
But aggregating in a sub-query and then comparing totals could probably help here.
(or via a HAVING clause)
Example snippet:
declare #Recipients table (RecipientId int primary key, GroupId int);
declare #DocumentTransactions table (TransactionId int primary key, DocumentId int);
declare #DocumentsSent table (DocumentId int, RecipientId int, TransactionId int);
declare #RecipientEmails table (RecipientId int, TransactionID int);
insert into #Recipients (RecipientId, GroupId) values
(201,1),(202,1),(203,1),(204,2),(205,2),(206,2);
insert into #DocumentTransactions (TransactionId, DocumentId) values
(301,101),(302,101),(303,101),(304,102),(305,102),(306,102);
insert into #DocumentsSent (DocumentId, RecipientId, TransactionId) values
(101,201,301),(101,202,302),(101,203,303)
,(102,204,304),(102,205,305),(102,206,306);
insert into #RecipientEmails (RecipientId, TransactionId) values
(201,301),(202,302),(203,303)
,(204,304);
SELECT DocumentId
FROM
(
SELECT
tr.DocumentId,
rcpt.GroupId,
count(distinct sent.RecipientId) AS TotalSent,
count(distinct rcptmail.RecipientId) AS TotalRcptEmail
FROM #DocumentsSent AS sent
LEFT JOIN #Recipients AS rcpt ON rcpt.RecipientId = sent.RecipientId
LEFT JOIN #DocumentTransactions AS tr
ON (tr.TransactionId = sent.TransactionId AND tr.DocumentId = sent.DocumentId)
LEFT JOIN #RecipientEmails AS rcptmail
ON (rcptmail.TransactionId = sent.TransactionId AND rcptmail.RecipientId = sent.RecipientId)
GROUP BY tr.DocumentId, rcpt.GroupId
) AS q
WHERE (TotalSent = TotalRcptEmail OR (TotalSent > 0 AND TotalRcptEmail = 0))
GROUP BY DocumentId;
/*
SELECT
tr.TransactionId,
sent.DocumentId,
sent.RecipientId AS RecipientIdSent,
rcpt.GroupId AS GroupIdRcpt,
rcpt.RecipientId AS RecipientIdRcpt,
rcptmail.RecipientId AS RecipientIdEmail
FROM #DocumentsSent AS sent
LEFT JOIN #Recipients AS rcpt ON rcpt.RecipientId = sent.RecipientId
LEFT JOIN #DocumentTransactions AS tr
ON (tr.TransactionId = sent.TransactionId AND tr.DocumentId = sent.DocumentId)
LEFT JOIN #RecipientEmails AS rcptmail
ON (rcptmail.TransactionId = sent.TransactionId AND rcptmail.RecipientId = sent.RecipientId);
*/
Returns:
DocumentId
----------
101

Compare set of data in one table to another table

I've gone round in circles using JOIN and EXISTS and ISNULL. I cannot figure this out and I've gone through hundreds of threads.
Records in table Pages drives the output of individual retail web pages. Fields in this table determine what items are displayed:
PageID INT
Category VARCHAR(30)
Colour VARCHAR(10)
Size VARCHAR(10)
OnSale BIT
e.g.
PageID = 201
Category = Shoes
Colour = Red
Size = Large
OnSale = 1
I need to compare these fields against identically-named fields in Accounts_Emails.
fk_AccountID INT
Category VARCHAR(30)
Colour VARCHAR(10)
Size VARCHAR(10)
OnSale BIT
This table allows users to save options for email newsletters. If they want to be sent more items similar to those on the current page, they click a button on that page.
What I need is a stored procedure that checks if that user already has the exact match of options in a Accounts_Emails record, and if not INSERT a new record with those options.
In the fields, a NULL value means ALL so I need to compare nulls. I pass in the PageID and AccountID to the procedure so I can pick up the current Pages record and limit the Accounts_Emails to the current user.
This is what I have:
IF NOT EXISTS
(
SELECT 1 FROM Accounts_Emails a
JOIN Pages l on l.PageID = #PageID
WHERE
a.fk_AccountID = #AccountID AND
(ISNULL(a.Category,'NULL') = ISNULL(l.Category,'NULL')) AND
(ISNULL(a.Colour,'NULL') = ISNULL(l.Colour,'NULL')) AND
(ISNULL(a.Size,'NULL') = ISNULL(l.Size,'NULL')) AND
(ISNULL(a.OnSale,'NULL') = ISNULL(l.OnSale,'NULL'))
)
it looks like you need to use a MERGE statement. Using Accounts_Emails as TARGET.
Plenty of great examples are online.

How to update table field value in one table from field value in another table

I am trying to update field value from one table to another.
Item with bomRev='A' in Destination table look like show below
Same Item bomRev='A' in source table looks like
I want to update partid field in destination table for bomRev=A by the value in Source filed i want to destination looks exactly like the source.
I tried this but no luck
UPDATE [MIBOMD]
SET [MIBOMD].[partId] = [assy].[partId]
FROM [MIBOMD] INNER JOIN [assy] ON [MIBOMD].[partId] = [assy].[partId]
WHERE bomRev='A' and [bomItem]='600797' AND [MIBOMD].[partId]!=[assy].[partId];
UPDATE m
SET [partId] = a.[partId]
FROM
[MIBOMD] m
INNER JOIN
[assy] a
ON m.[bomItem] = a.[ItemId]
AND m.bomEntry = a.bomEntry
WHERE
m.bomRev='A'
AND m.[bomItem]='600797'
AND m.[partId]!=a.[partId];
You actually were pretty close! Just a couple of key differences. Before I explain I have used Table Aliases in the code I provided it is a shorthand way of referring to the table throughout the query that will make it a little easier to follow and read. To Alias a table after the table name in the from statement simply add a space and an alias or a space " AS " alias.
Now your Join as on partid in your version and that was your main issue. Because you want the records where partid are not the same so you can change the partid of the assy table. Looking at your dataset I was able to determine that the shared key was mibomd.bomItem and assy.ItemId. After clearing that up everything should be good.
Per your comment the only other thing that needed to be added was a second condition on the join to make it unique. [MIBOMD].bomEntry = assy.bomEntry
A little about join conditions. Typically you always want to figure out what the unique relationship between the 2 tables are (bomItem = ItemId and bomEntry = bomEntry) and that is what will go in the ON area of the join. Rarely that will be different and will be for very specific purposes.
Per your comment on how to insert the missing records
INSERT INTO MIBOMD (bomItem, bomRev, bomEntry, lineNbr, dType, partId)
SELECT
bomItem = a.ItemId
,bomRev = 'A' --or change the value to what you want
,a.bomEntry
,lineNbr = ???? --not sure how you are figure this out do if you wan it to be the next line number you can figure that out automatically if you need
,a.partId
FROM
assy a
LEFT JOIN MIBOMD m
ON a.ItemId = m.bomItem
AND a.bomEntry = m.bomEntry
WHERE
m.bomItem IS NULL
This time you would use a left join from assy to mibomd and figure out when they don't match mibomd.bomItem IS NULL

Find which column differs between 2 rows?

We are using audit tables for each operational table, which stores the previous value of its operational equivalent plus change date, change type (UPDATE or DELETE) and its own auto incremental Primary Key.
So, for a table Users with columns UserID, Name, Email there would be a table xUsers with columns ID, OpererationType, OperationDate, UserID, Name, Email.
See that the xTable contains every column that its 'parent' does with 3 extra fields. This pattern is repeated for all tables used by our system.
table Users:
UserID int
Name nvarchar
Email nvarchar
table xUsers:
xUserID int
OpererationType int
OperationDate datetime
UserID int
Name nvarchar
Email nvarchar
Now, my question:
If I have a certain UserID, for which there is 2 entries in the xUsers table when the email was changed twice,
how would I construct a query that identifies which columns (can be more than 1) differ between the two rows in the audit table?
If I'm understanding this correctly, you'd like to create a query passing in the UserID as a parameter, which I'll call #UserID for the following example.
This query will select all rows from xUsers joined onto itself where there is a difference in a non-UserID column, using a series of case statements (one per column) to pull out specifically which columns differ.
SELECT *
, CASE
WHEN a.OperationType <> b.OperationType
THEN 1
ELSE 0
END AS OperationTypeDiffers
, CASE
WHEN a.OperationDate <> b.OperationDate
THEN 1
ELSE 0
END AS OperationDateDiffers
FROM xUsers a
JOIN xUsers b
ON a.xUserID < b.xUserID
AND a.UserID = b.UserID
AND (a.OperationType <> b.OperationType
OR a.OperationDate <> b.OperationDate) -- etc.
WHERE a.UserID = #UserID
You can put the rows of xUsers in a temporary table and then make a while cycle to go for each one and compare the results.
OR
You can do some dynamic SQL and use sysobjects and syscolumns tables to compare each result. It would be more dynamic and then it would be easy to implement for other tables.

Transact-SQL Query / How to combine multiple JOIN statements?

I'm banging my head on this SQL puzzle since a couple of hours already, so i thought to myself : "Hey, why don't you ask the Stack and allow the web to benefit from the solution?"
So here it is. First thing, these are my SQL tables:
Fields
FieldID INT (PK)
FieldName NVARCHAR(50) (IX)
FormFields
FieldID INT (FK)
FormID INT (FK)
Values
FieldID INT (FK)
RecordID INT (FK)
Value NVARCHAR(1000)
Forms
FormID INT (PK)
FormName NVARCHAR(50) (IX)
Records
RecordID INT (PK)
FormID INT (FK)
PoolID INT (FK)
DataPools
PoolID INT (PK)
FormID INT (FK)
PoolName NVARCHAR(50) (IX)
Consider the following constraints.
Each Form has 0 or more DataPool. Each DataPool can only be assigned to one Form.
Each Form has 0 or more Field. Each Field might be assigned to several Form.
Each Record has 0 or more Value. Each Value is linked to a single Record.
Each DataPool has 0 or more Record. Each Record is linked to a single DataPool.
Each Value is linked to one Field.
Also, all the Name columns have unique values.
Now, here's the problem:
I need to query evey value form the Values table based on the following columns:
The Name of the Field linked to the Value
The Name of the DataPool linked the Record linked to the Value
The Name of the Form linked to that DataPool
The 3 columns above must be equal to the 3 received parameters in the stored procedure.
Here's what I got so far:
CREATE PROCEDURE [GetValues]
#FieldName NVARCHAR(50),
#FormName NVARCHAR(50),
#PoolName NVARCHAR(50)
AS SELECT Value FROM [Values]
JOIN [Fields]
ON [Fields].FieldID = [Values].FieldID
WHERE [Fields].FieldName = #FieldName
How can I filter the rows of the Values table by the PoolName column? The DataPools table isn't directly related to the Values table, but it's still related to the Records table which is directly related to the Values table. Any ideas on how to do that?
I feel like I am missing something in your question. If this solution is not addressing the problem, please let me know where it is missing the issue.
SELECT
Values.Value
FROM
Values INNER JOIN Fields ON
Values.FieldId = Fields.FieldId
INNER JOIN FormFields ON
Fields.FieldId = FormFields.FieldId
INNER JOIN Forms ON
FormFields.FormId = Forms.FormId
INNER JOIN DataPools ON
Forms.FormId = DataPools.FormId
WHERE
Fields.FieldName = #FieldName
AND
Forms.FormName = #FormName
AND
DataPools.PoolName = #PoolName;
if i understand what your needing this should work just fine.
select * from
values v
join records r
on v.recordid = r.recordid
join datapool dp
on r.poolid = dp.poolid
join forms f
on r.formid = f.formid
join fields fi
on v.fieldid = fi.fieldid
where
fi.FieldName = #FieldName
AND
f.FormName = #FormName
AND
dp.PoolName = #PoolName;