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

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;

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

How to make update query using data from two another tables (postgresql)

I have 3 tables:
manager with columns:
id serial PK
money int
facilities_work_data with columns:
id serial PK
income integer
manager_facilities with colunns:
id serial PK
manager_id references manager(id)
facilities_work_data_id references facilities_work_data(id)
The aim is to update all manager table rows by adding to manager.money column the value from facilities_work_data.income which in turn has to have id column selected as:
SELECT facilities_work_data_id from manager_facilities WHERE manager.id = manager_facilities.manager_id
At least I found the following request:
UPDATE A AS a SET money = a.money + b.income FROM B AS b WHERE b.a_id = a.id
But its not my case, seems like I need one more join.
Could you help with such a request?
you can get data from two tables and add the conditions in the where clause like below
update manager M
set money = money + FWD.income
FROM manager_facilities MF, facilities_work_data FWD
WHERE M.id = MF.manager_id
AND MF.facilities_work_data_id = FWD.id

SQL query with joins help needed

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)

SQL Query relationship coding

Need some help making this work please. Some help please to ensure I always return just one row per ProductID. My Product Table contains base information on Products which can be properties in this case. It links to the Properties table via a ProductCode.
What I would like is to have the query return just the one row which it really should. Essentially, the row should display the PropertyLabel whether or not there are values in the PropertyProductValues table for that particular propertyLabel. The code below returns a bunch of rows which really it shouldn't, or I don't want it to. And I would appreciate help fine tuning this please. thanks in Advance.
Create Table Product
{
ProductID uniqueidentifier,
ProductName Varchar(50),
ProductCode Varchar(50)
}
Create Table Properties
{
PropertyID uniqueidentifier,
PropertyProductcode Varchar(50)
}
Create Table PropertyProductValues
{
ID uniqueidentifier,
PropertyID uniqueidentifier,
ProductID uniqueidentifier,
PropertyLabel Varchar(50),
UnitID uniqueidentifier
}
Create Table unit
{
UnitID uniqueidentifier,
Unit Varchar(50)
}
Select productid , PropertyLabel
PropertyValue
PropertyUnit
from Product
inner join Properties on ProductCode = PropertyProductCode
left join PropertyProductValues on Properties.PropertyID = ProductProductValues.PropertyID
left join Unit on PropertyValues.UnitID = Unit.UnitID
Where ltrim(rtrim(lower(PropertyLabel))) = lower('Special')
Sample Data
Product Table
b0359c76-8622-4006-82f2-1b4c91a8f6b3 Brown Building YSHJT
aba475c4-5a5c-483a-9faa-67f67ff9672d Sket Building GTJHD54
8f645348-8871-4fad-8c85-9fc6a1169b33 HOUSE 9 DGFS345
Properties Table
27c6485b-f7a5-4243-9304-71939230964d DGFS345
88e911e5-bf40-4f14-89cc-4ed2984c342e GTJHD54
d217cc14-6fcb-4251-a6d7-8fd1b3398a32 YSHJT
689cfd9d-bc87-4e23-afb3-d6cbf1e961f6 FFFSRW
bf7ae151-2e3f-4e0a-bf8b-d44ef30cf276 WYSJD
PropertyProductValues Table
6cf47434-c834-455d-a606-3d10cb9f7ab3 b0359c76-8622-4006-82f2-1b4c91a8f6b3 b0359c76-8622-4006-82f2-1b4c91a8f6b3 Storey
82ee0f80-afe4-45c2-877c-e79ce286170f 27c6485b-f7a5-4243-9304-71939230964d b0359c76-8622-4006-82f2-1b4c91a8f6b3 Bedrooms
5ff7f809-6b1f-4d6c-a125-29ce67f097d7 27c6485b-f7a5-4243-9304-71939230964d 8f645348-8871-4fad-8c85-9fc6a1169b33 Bathrooms
7f634b81-1212-4223-af42-29b4dc2bafcc 27c6485b-f7a5-4243-9304-71939230964d aba475c4-5a5c-483a-9faa-67f67ff9672d Material
cae10884-edf0-4340-bde6-5207e28a07cc 689cfd9d-bc87-4e23-afb3-d6cbf1e961f6 b0359c76-8622-4006-82f2-1b4c91a8f6b3 Basement
83943759-39d7-406e-92ea-4202a9b1d716 bf7ae151-2e3f-4e0a-bf8b-d44ef30cf276 b0359c76-8622-4006-82f2-1b4c91a8f6b3 Storey
c045d0ff-34db-4427-9fb7-fd41fc92f310 bf7ae151-2e3f-4e0a-bf8b-d44ef30cf276 aba475c4-5a5c-483a-9faa-67f67ff9672d balcony
Unit Table
UNITID UNIT
3a4a5216-0704-495e-b0a7-560787e0847a kg
6b4493f6-c2e4-4682-b71d-93cb1893eb73 m
2f2b4fee-9e69-4a71-a098-36e2da71471e l
55c8e5bf-edde-4977-ab7b-8827e337a31e oz
19528647-bd9f-48e0-ba86-b722d5b8ab0e cm
cee5cd46-1ae4-4b49-8b1c-9b3e0bd5270f mm
It would help if you gave the table structure of your 3 tables, but in principle, to get 1 row per PropertyId, you'll be doing a GROUP BY PropertyId.

Select from many to many table query

I have some tables:
Sessions
SessionID int PK
Created datetime
SiteId int FK
Tracking_Parameters
ParamID int PK
ParamName nvarchar
Session_Custom_Tracking
SessionID int FK
ParamID int FK
ParamValue nvarchar
Site_Custom_Parameters
SiteID int FK
ParamID int FK
ParamKey nvarchar
Sessions: Contains the unique session id for a visitor and the time they entered the site.
Tracking_Parameters: Contains a list of things that you may want to track on a site (i.e. Email Open, Email Click, Article Viewed, etc.)
Site_Custom_Parameters: For a particular site (table not shown), declares the key value for a Tracking_Parameter (i.e. the key to look for in a query string or route)
Session_Custom_Tracking: The link between a session and a tracking parameter and also contains the value for the parameter's key when it was found by my application.
Question:
I want to select session id's where for these particular sessions, there is a record in the Session_Custom_Tracking for two different ParamID's. I want to find sessions where a user both opened an email (paramid 1) and clicked (paramid 3) a link in that email.
You can join to the same table twice:
SELECT S.SessionID
FROM Sessions AS S
JOIN Session_Custom_Tracking AS SCT1
ON SCT1.SessionID = S.SessionID
AND SCT1.ParamID = 1
JOIN Session_Custom_Tracking AS SCT2
ON SCT2.SessionID = S.SessionID
AND SCT2.ParamID = 3
An alteranative that might be easier to read (because it more closely matches the way you describe the problem) is to use WHERE EXISTS:
SELECT S.SessionID
FROM Sessions AS S
WHERE EXISTS
(
SELECT *
FROM Session_Custom_Tracking AS SCT1
WHERE SCT1.SessionID = S.SessionID
AND SCT1.ParamID = 1
)
AND EXISTS
(
SELECT *
FROM Session_Custom_Tracking AS SCT2
WHERE SCT2.SessionID = S.SessionID
AND SCT2.ParamID = 3
)