Ali, here is an example that will at least get you started. You can run the following in SSMS.
Create some table variables and insert your sample data.
DECLARE @ContactGroup TABLE ( id INT, title VARCHAR(50) );
INSERT INTO @ContactGroup ( id, title ) VALUES ( 1, 'Group A' );
DECLARE @ContactsInGroup TABLE ( ContactID INT, GroupID INT );
INSERT INTO @ContactsInGroup ( ContactID, GroupID ) VALUES ( 1, 1 ), ( 2, 1 ), ( 3, 1 );
DECLARE @ContactVariables TABLE ( id INT, [name] VARCHAR(50), GroupID INT, [Order] INT );
INSERT INTO @ContactVariables ( id, [name], GroupID, [Order] ) VALUES ( 1, 'Invoice', 1, 1 ), ( 2, 'Due Date', 1, 1 );
DECLARE @ContactsVariablesValues TABLE ( ContactVariableID INT, ContactID INT, [value] INT );
INSERT INTO @ContactsVariablesValues ( ContactVariableID, ContactID, [value] ) VALUES ( 1, 1, 600 );
Then query the data as follows:
SELECT
ContactGroup.id AS GroupID
, ContactsInGroup.ContactID
, ContactVars.Invoice
, ContactVars.[Due Date]
FROM @ContactGroup AS ContactGroup
INNER JOIN @ContactsInGroup AS ContactsInGroup
ON ContactGroup.id = ContactsInGroup.GroupID
OUTER APPLY (
SELECT
[Invoice], [Due Date]
FROM (
SELECT
Vars.[name]
, Vals.[value]
FROM @ContactVariables AS Vars
LEFT OUTER JOIN @ContactsVariablesValues Vals
ON Vars.id = Vals.ContactVariableID
WHERE
Vars.GroupID = 1
AND Vals.ContactID = ContactsInGroup.ContactID
) AS ContactData
PIVOT (
MIN( [value] )
FOR [name] IN (
[Invoice], [Due Date]
)
) AS pvt
) AS ContactVars
ORDER BY
ContactGroup.id, ContactsInGroup.ContactID;
Which returns:
+---------+-----------+---------+----------+
| GroupID | ContactID | Invoice | Due Date |
+---------+-----------+---------+----------+
| 1 | 1 | 600 | NULL |
| 1 | 2 | NULL | NULL |
| 1 | 3 | NULL | NULL |
+---------+-----------+---------+----------+
Things to note
The "magic" here is in the OUTER APPLY. This allows us to query a subset of data based on the primary data returned, in this case the GroupID and ContactID. OUTER APPLY will also return rows with NULL values like you desire.
You're going to have some challenges here, namely that to use a PIVOT as shown in my example, you will need to know all the values ( Invoice, Due Date, etc... ) that will become column headers. Based on your setup, I'm thinking this may not be the case, so you will be forced to resort to an technique that creates and executes a dynamic PIVOT statement for you within the OUTER APPLY.
You also might consider using a TABLE VALUED FUNCTION that does the PIVOT work that can then be JOINed on vs. an OUTER APPLY.
You have several options, but hopefully this helps jumpstart your thinking.