PIVOT with dynamic columns and JOINS 5+ tables - sql

This is an extension of a question posted by me earlier which can be found here : Previous Question
Now i have 2 more tables that are also connected to ProfileFan tracking the Activities of the Fan(ProfileFan).
Table Fan
-----------------------
| FanId | Name | Info |
-----------------------
| 17111 | Fan1 | Info1|
-----------------------
| 17112 | Fan2 | Info2|
-----------------------
Table ProfileFan m:1 Fan FanId(FK)
-----------------------------------
| Id | LinkedInProfileId | FanId |
-----------------------------------
| 1111 | 1 | 17111 |
---------------------------------
| 1112 | 2 | 17111 |
----------------------------------
| 1113 | 1 | 17112 |
----------------------------------
| 1114 | 2 | 17112 |
----------------------------------
Table LinkedInProfile
--------------------------
| Id | Name | Client |
--------------------------
| 1 | Linked1 | Client1|
--------------------------
| 2 | Linked2 | Client1|
--------------------------
Table FanActivity m:1 ProfileFan via ProfileFanId (FK)
------------------------------------------------
| Id | Created | ProfileFanId | ActivityId |
-------------------------------------------------
| 1 | 2019-01-05 | 17111 | 1 |
-------------------------------------------------
| 2 | 2019-01-05 | 17111 | 2 |
-------------------------------------------------
| 3 | 2019-01-05 | 17112 | 3 |
-------------------------------------------------
| 4 | 2019-01-05 | 17112 | 4 |
-------------------------------------------------
Table Activity Id(PK)
--------------------
| Id | Name |
---------------------
| 1 | ConAccepted |
---------------------
| 2 | Message |
---------------------
| 3 | LikesContent |
----------------------
| 4 | JoinsGroup |
----------------------
Table DeliveryActions m:1 ProfileFan via ProfileFanId (FK)
------------------------------------------------------
| Id | LoggedAt | ProfileFanId | DeliveryActionId |
-------------------------------------------------------
| 1 | 2019-01-05 | 17111 | 1 |
------------------------------------------------------
| 2 | 2019-01-05 | 17111 | 2 |
------------------------------------------------------
| 3 | 2019-01-05 | 17112 | 3 |
------------------------------------------------------
| 4 | 2019-01-05 | 17112 | 4 |
------------------------------------------------------
Table DeliveryAction Id(PK)
------------------------
| Id | Name |
------------------------
| 1 | M1 |
------------------------
| 2 | M2 |
------------------------
| 3 | M3 |
------------------------
| 4 | M4 |
------------------------
The output of the query should look like this.
-------------------------------------------------------------------------------------------------------------------------------------------------------
| Name | Info | LinkedInProfile1| LinkedInProfile2 | Client |ConAccepted | Message | LikesContent | JoinsGroup | M1 | M2 | M3 | M4 |
---------------------------------------------------------------------------------------------------------------------------------------------------
| Name1 | Info1| Linked1(1111) | Linked2(1112) | Client1 | 2019-01-05 | 2019-01-06 | 2019-01-07 | 2019-01-08 | 2019-01-09 | 2019-01-05 |2019-01-06 | 2019-01-07 |
---------------------------------------------------------------------------------------------------------------------------------------------------
| Name2 | Info2| Linked1(1113) | Linked2(1114) | Client1 |2019-01-05 | 2019-01-06 | 2019-01-07 | 2019-01-08 | 2019-01-09 | 2019-01-05 |2019-01-05 | 2019-01-0 |
---------------------------------------------------------------------------------------------------------------------------------------------------
I need to insert the data from a query for one fan in a single row instead multiple for every row that gives me the joins.
So far as suggested by stackoverflow user i have come to solution to do it for only the LinkedInProfile by using PIVOT and dynamic query.
I Have also created a simulation here https://rextester.com/live/NYYK67222
I need to make the appropriate joins with the FanActivities and DeliveryActions Table On top of the joins so far and put the data in a single row per FAN!
So 1 FAN is under 2 LinkedInProfiles -> This creates 2 ProfileFansId. All the profileFans have many FanActivities and Many DeliveryActions.
I know that i need to use PIVOT with dynamic columns here. But its all new to me.

Since your desired result is different from your sample data on rexter, I just modified few data in FanActivities & DeliveryActions tables.
CREATE TABLE #Fan (FanId INT, Name VARCHAR(100), Info VARCHAR(100));
INSERT INTO #Fan
VALUES (17111, 'Fan1', 'Info1'), (17112, 'Fan2', 'Info2');
CREATE TABLE #LinkedInProfile (Id INT, Name VARCHAR(100), Client VARCHAR(100));
INSERT INTO #LinkedInProfile
VALUES (1, 'Linked1', 'Client1'), (2, 'Linked2', 'Client1');
CREATE TABLE #ProfileFan (Id INT, ProfileId INT, FanId INT);
INSERT INTO #ProfileFan
VALUES (1111, 1, 17111), (1112, 2, 17111), (1113, 1, 17112), (1114, 2, 17112);
CREATE TABLE #Activities (Id INT, Name VARCHAR(100));
INSERT INTO #Activities
VALUES (1, 'ConAccepted'), (2, 'Message'), (3, 'LikesContent'), (4, 'JoinsGroup');
CREATE TABLE #FanActivities (Id INT, Created VARCHAR(100), ProfileFanId INT
, ActivityId INT);
INSERT INTO #FanActivities
VALUES
(1, '2019-01-05', 1111, 1), (1, '2019-01-05', 1111, 2)
, (1, '2019-01-05', 1112, 3), (1, '2019-01-05', 1111, 4)
, (1, '2019-01-05', 1113, 1), (1, '2019-01-05', 1113, 2)
, (1, '2019-01-05', 1114, 3), (1, '2019-01-05', 1113, 4);
CREATE TABLE #DeliveryAction (Id INT, Name VARCHAR(100));
INSERT INTO #DeliveryAction
VALUES (1, 'M1'), (2, 'M2'), (3, 'M3'), (4, 'M4');
CREATE TABLE #DeliveryActions (Id INT, Created VARCHAR(100), ProfileFanId INT
, ActivityId INT);
INSERT INTO #DeliveryActions
VALUES
(1, '2019-01-05', 1111, 1), (1, '2019-01-05', 1111, 2)
, (1, '2019-01-05', 1112, 3), (1, '2019-01-05', 1111, 4)
, (1, '2019-01-05', 1113, 1), (1, '2019-01-05', 1113, 2)
, (1, '2019-01-05', 1114, 3), (1, '2019-01-05', 1113, 4);
And your query would be
;WITH CTE AS(
SELECT F.FanId,F.Name AS FANNAME,F.Info
,CONCAT(lip.Name,'(',pf.Id,')') AS LINKEDPROFILE
,CONCAT('#LinkedInProfile',pf.ProfileId) as LipId
, LIP.Client
,FACT.Name AS FANACTIVITY, FA.Created FANACTIVITYDATE
,DA.Name AS DELVACTIVITY, DAS.Created DELVACTIVITYDATE
FROM #Fan F
LEFT JOIN #ProfileFan pf ON f.FanId = pf.FanId
LEFT JOIN #LinkedInProfile lip ON pf.ProfileId = lip.Id
LEFT JOIN #FanActivities FA ON PF.Id = FA.ProfileFanId
LEFT JOIN #ActivitieS FACT ON FACT.Id = FA.ActivityId
LEFT JOIN #DeliveryActions DAS ON PF.Id = DAS.ProfileFanId
LEFT JOIN #DeliveryAction DA ON DAS.ActivityId = DA.ID
)
SELECT FANNAME, Info, Client
,[#LinkedInProfile1],[#LinkedInProfile2]
,[ConAccepted],[Message],[LikesContent],[JoinsGroup]
,[M1],[M2],[M3],[M4] FROM (
SELECT FANNAME, Info, Client,KEYS,VALUE
FROM CTE
CROSS APPLY (
VALUES (LipId, LINKEDPROFILE)
, (FANACTIVITY,FANACTIVITYDATE)
,(DELVACTIVITY,DELVACTIVITYDATE)
)AS TAB(KEYS,VALUE)
)AS A
PIVOT
(
MAX(VALUE) FOR KEYS IN ([#LinkedInProfile1],[#LinkedInProfile2]
,[ConAccepted],[Message],[LikesContent],[JoinsGroup]
,[M1],[M2],[M3],[M4])
)PV
And the result I got
+---------+-------+---------+-------------------+-------------------+-------------+------------+--------------+------------+------------+------------+------------+------------+
| FANNAME | Info | Client | #LinkedInProfile1 | #LinkedInProfile2 | ConAccepted | Message | LikesContent | JoinsGroup | M1 | M2 | M3 | M4 |
+---------+-------+---------+-------------------+-------------------+-------------+------------+--------------+------------+------------+------------+------------+------------+
| Fan1 | Info1 | Client1 | Linked1(1111) | Linked2(1112) | 2019-01-05 | 2019-01-05 | 2019-01-05 | 2019-01-05 | 2019-01-05 | 2019-01-05 | 2019-01-05 | 2019-01-05 |
| Fan2 | Info2 | Client1 | Linked1(1113) | Linked2(1114) | 2019-01-05 | 2019-01-05 | 2019-01-05 | 2019-01-05 | 2019-01-05 | 2019-01-05 | 2019-01-05 | 2019-01-05 |
+---------+-------+---------+-------------------+-------------------+-------------+------------+--------------+------------+------------+------------+------------+------------+

There's some discrepancies between the sample code in the post and your rextester sample (for example, DeliveryActions.ProfileFanId in your post appears to reference Fan.FanId , but in the rextester sample it references ProfileFan.Id). My answer is built off of your rextester sample.
My personal preference is to not do multiple pivots if I don't have to and, instead, use a cross tab query... it's just easier for me to look at and comprehend rather than having multiple layers of pivots.
You can see this in rextester here.
SELECT
f.Name
,f.Info
,LinkedInProfile1 = MAX(CASE WHEN pf.ProfileId = 1 THEN CONCAT(lip.Name,'(',pf.Id,')') END)
,LinkedInProfile2 = MAX(CASE WHEN pf.ProfileId = 2 THEN CONCAT(lip.Name,'(',pf.Id,')') END)
,lip.Client
,ConAccepted = MAX(CASE WHEN a.Name = 'ConAccepted' THEN fa.Created END)
,Message = MAX(CASE WHEN a.Name = 'Message ' THEN fa.Created END)
,LikesContent = MAX(CASE WHEN a.Name = 'LikesContent ' THEN fa.Created END)
,JoinsGroup = MAX(CASE WHEN a.Name = 'JoinsGroup ' THEN fa.Created END)
,M1 = MAX(CASE WHEN da.Name = 'M1' THEN das.Created END)
,M2 = MAX(CASE WHEN da.Name = 'M2 ' THEN das.Created END)
,M3 = MAX(CASE WHEN da.Name = 'M3 ' THEN das.Created END)
,M4 = MAX(CASE WHEN da.Name = 'M4 ' THEN das.Created END)
FROM fan f
JOIN dbo.ProfileFan pf
ON pf.FanId = f.FanId
JOIN dbo.LinkedInProfile lip
ON lip.Id = pf.ProfileId
JOIN dbo.FanActivities fa
ON fa.ProfileFanId = pf.Id
JOIN Activities a
ON a.Id = fa.ActivityId
JOIN dbo.DeliveryActions das
ON das.ProfileFanId = pf.Id
JOIN dbo.DeliveryAction da
ON da.Id = das.ActivityId
GROUP BY f.Name, f.info, lip.Client

Related

How to build a decision table with "NOT IN (a,b,c)" logic?

Suppose there is a business rule used to derive a value and due to how the business users need to update this, the logic must be held in a table and not a function.
Example of current function
WHEN inputA IN (1,2,3) AND inputB NOT IN (55,66) THEN OUTPUT = 'HQ'
WHEN inputA IN (3,6) AND inputB IN (27,44) THEN OUTPUT = 'Northern'
WHEN inputC IN (6,4,1) AND inputB NOT IN (55,66) THEN OUTPUT = 'Eastern'
etc.
I can build a table with a row for each combination
e.g.
OUTPUT
A val
B val
C val
D val
...
Northern
3
27
Northern
3
44
Northern
6
27
Northern
6
44
How then to model the "NOT IN" part of this?
Surely there is a way where I do not have to create all the 'exception' rows that would potentially need to be updated based on changing reference data?
You could represent these expressions w/ a table where each row contained:
Output
Equals? (true or false)
Input
Value
which would represent "If (INPUT = VALUE) = EQUALS? then OUTPUT", except that you only need 1 satisfying row for a given OUTPUT/INPUT where EQUALS? is true, but all rows must be satisfied where EQUALS? is false.
This assumes your have only one "rule" for each distinct output.
Here's an attempt at using a rule table with positive and negative rules.
The rule table stores the valids as integers, but the invalids in a string with delimiters. (not sure if json or xml would be better)
But having negative rules makes it really tricky.
Because it's easy to ignore a rule by adding another for the same output name.
Personally I think it's safer to use a UDF for this type of rules.
create table test (
id int identity(1,1) primary key,
inputA int,
inputB int,
inputC int
)
create table output_areas (
code varchar(2) primary key,
name varchar(30) not null
)
insert into output_areas values
('HQ', 'HQ')
, ('N', 'Northern'), ('E', 'Eastern')
, ('W', 'Westhern'), ('S', 'Southern')
, ('X', 'Extern'), ('Z', 'The Zone')
create table input_rules (
id int identity(1,1) primary key,
output_area_code varchar(2) not null,
relevance int not null default 0,
inputA_valid int,
inputB_valid int,
inputC_valid int,
inputA_invalid varchar(100),
inputB_invalid varchar(100),
inputC_invalid varchar(100),
foreign key (output_area_code) references output_areas(code)
)
insert into input_rules (output_area_code, relevance)
values ('X', 0);
insert into input_rules (output_area_code, relevance, inputA_valid)
values ('Z',10, 1);
insert into input_rules (output_area_code, relevance, inputA_valid, inputB_invalid)
values
('HQ', 20, 1, '|55|56|')
, ('HQ', 20, 2, '|55|56|')
, ('HQ', 20, 3, '|55|56|')
;
insert into input_rules (output_area_code, relevance, inputA_valid, inputB_valid)
values
('N', 30, 3, 27), ('N', 30, 3, 44)
, ('N', 30, 6, 27), ('N', 30, 6, 44)
;
insert into input_rules (output_area_code, relevance, inputC_valid, inputB_invalid)
values
('E', 20, 6, '|55|66|' )
, ('E', 20, 4, '|55|66|' )
;
insert into test
(inputA, inputB, inputC) values
(1, 56, null)
, (3, 44, null), (3, 66, null)
, (1, 66, null), (1, 88, null)
, (null, 66, 6), (null, 88, 6)
select * from input_rules
id | output_area_code | relevance | inputA_valid | inputB_valid | inputC_valid | inputA_invalid | inputB_invalid | inputC_invalid
-: | :--------------- | --------: | -----------: | -----------: | -----------: | :------------- | :------------- | :-------------
1 | X | 0 | null | null | null | null | null | null
2 | Z | 10 | 1 | null | null | null | null | null
3 | HQ | 20 | 1 | null | null | null | |55|56| | null
4 | HQ | 20 | 2 | null | null | null | |55|56| | null
5 | HQ | 20 | 3 | null | null | null | |55|56| | null
6 | N | 30 | 3 | 27 | null | null | null | null
7 | N | 30 | 3 | 44 | null | null | null | null
8 | N | 30 | 6 | 27 | null | null | null | null
9 | N | 30 | 6 | 44 | null | null | null | null
10 | E | 20 | null | null | 6 | null | |55|66| | null
11 | E | 20 | null | null | 4 | null | |55|66| | null
select *
from test t
outer apply (
select top 1 ref.name as output
from input_rules r
join output_areas ref on ref.code = r.output_area_code
where (r.inputA_valid is null or r.inputA_valid = t.inputA)
and (r.inputB_valid is null or r.inputB_valid = t.inputB)
and (r.inputC_valid is null or r.inputC_valid = t.inputC)
and (r.inputA_invalid is null or r.inputA_invalid not like concat('%|', t.inputA, '|%'))
and (r.inputB_invalid is null or r.inputB_invalid not like concat('%|', t.inputB, '|%'))
and (r.inputC_invalid is null or r.inputC_invalid not like concat('%|', t.inputC, '|%'))
order by r.relevance desc, r.output_area_code asc
) ca
id | inputA | inputB | inputC | output
-: | -----: | -----: | -----: | :-------
1 | 1 | 56 | null | The Zone
2 | 3 | 44 | null | Northern
3 | 3 | 66 | null | HQ
4 | 1 | 66 | null | HQ
5 | 1 | 88 | null | HQ
6 | null | 66 | 6 | Extern
7 | null | 88 | 6 | Eastern
db<>fiddle here

Select rows of two different values in same column based on values updated in rows of different column

I want to update the 'Role1' from the below table for ID in (1,2,3,5,6) with role1= 133 (Passed as Parameter).
Logic should update Role1 only for ID (1,2) as the Role2 value for ID 4 is equal for Role2 value of ID 1,2 --('AB')
So ID (3,5,6) should not be updated and printed as exception , while ID(1,2) should be updated.
Any help and advice is appreciated
Thanks
+----------+-------+--------+--------+
| ID | role1 |role2 |Not |
+----------+-------+--------+--------+
| 1 | 123 | AB | 0 |
| 2 | 456 | AB | 1 |
| 3 | 789 | EF | 0 |
| 4 | 133 | AB | 0 |
| 5 | 453 | EF | 1 |
| 6 | 764 | DF | 0 |
+----------+-------+--------+--------+
output
+----------+------+------+------+
| ID | role1|role2 |not |
+----------+------+------+------+
| 1 | 133 | AB |0 |
| 2 | 133 | AB |1 |
+----------+------+------+------+
SELECT '789', '453', '764' + 'As they could not be updated due to seperate role2'
Making a pitch based on my understanding of the scenario. It seems that what is wanted is to first find the Role2 of the row that has a Role1 of the passed parameter, then update all rows that share that Role2 and a Role1 that doesn't match the parameter.
/* Set up the test scenario */
DECLARE #role1 INT = 133;
DROP TABLE IF EXISTS #roles
CREATE TABLE #roles
(
ID INT,
Role1 INT,
Role2 VARCHAR(2),
[NOT] INT
);
INSERT INTO #roles (ID, Role1, Role2, [Not])
VALUES
(1, 123, 'AB', 0),
(2, 456, 'AB', 1),
(3, 789, 'EF', 0),
(4, 133, 'AB', 0),
(5, 453, 'EF', 1),
(6, 764, 'DF', 0);
/* Selecting in order to validate the output set */
SELECT r2.*
FROM #roles AS r1
JOIN #roles AS r2
ON r1.Role2 = r2.Role2
WHERE r1.Role1 = #role1
AND r2.Role1 <> #role1;
With the set validated, here's what the UPDATE might look like
UPDATE r2
SET r2.Role1 = #role1
FROM #roles AS r1
JOIN #roles AS r2
ON r1.Role2 = r2.Role2
WHERE r1.Role1 = #role1
AND r2.Role1 <> #role1;
SELECT *
FROM #roles;
+----+-------+-------+-----+
| ID | Role1 | Role2 | NOT |
+----+-------+-------+-----+
| 1 | 133 | AB | 0 |
| 2 | 133 | AB | 1 |
| 3 | 789 | EF | 0 |
| 4 | 133 | AB | 0 |
| 5 | 453 | EF | 1 |
| 6 | 764 | DF | 0 |
+----+-------+-------+-----+

SQL Query to count number of records must match total number of records

I have 2 tables as
Result Master
+------+-------------+
| QnID | Description |
+------+-------------+
| 1 | Qn1 |
| 2 | Qn2 |
| 3 | Qn3 |
| 4 | Qn4 |
| 5 | Qn5 |
+------+-------------+
Result Details
+----+------+--------+--------+
| ID | QnID | TCDesc | Result |
+----+------+--------+--------+
| 1 | 1 | TC1 | PASS |
| 2 | 1 | TC2 | FAIL |
| 3 | 1 | TC3 | PASS |
| 4 | 2 | TC1 | PASS |
| 5 | 3 | TC1 | PASS |
| 6 | 3 | TC1 | PASS |
| 7 | 3 | TC3 | PASS |
+----+------+--------+--------+
I need a query which will return following result:
+----+------+--------+
| ID | QnID | Result |
+----+------+--------+
| 1 | 2 | PASS |
| 2 | 3 | PASS |
| 3 | 4 | ERROR |
| 4 | 5 | ERROR |
+----+------+--------+
Conditions:
each question will have different number of testcase "ResultDetails", I need to select questions for which all the test case get passsed (number of entries for a particular question must be same as number of test cases passed for the same) or Error (ResultDetail doesn't have an entry for a question).
Can anyone please help me with a query, thank you.
You can get the desired results using a common table expression and conditional aggregation.
First, create and populate sample tables (Please save us this step in your future questions):
DECLARE #ResultMaster AS TABLE
(
QnID int,
Description char(3)
);
INSERT INTO #ResultMaster (QnID, Description) VALUES
(1, 'Qn1'),
(2, 'Qn2'),
(3, 'Qn3'),
(4, 'Qn4'),
(5, 'Qn5');
DECLARE #ResultDetails AS TABLE
(
ID int,
QnID int,
TCDesc char(3),
Result char(4)
);
INSERT INTO #ResultDetails VALUES
(1, 1, 'TC1', 'PASS'),
(2, 1, 'TC2', 'FAIL'),
(3, 1, 'TC3', 'PASS'),
(4, 2, 'TC1', 'PASS'),
(5, 3, 'TC1', 'PASS'),
(6, 3, 'TC1', 'PASS'),
(7, 3, 'TC3', 'PASS');
Then, use a common table expression to calculate the number of pass details and a simple count to get the number of total details:
WITH CTE AS
(
SELECT M.QnId,
COUNT(CASE WHEN Result = 'PASS' THEN 1 END) As CountPass,
COUNT(Result) As CountDetails
FROM #ResultMaster As M
LEFT JOIN #ResultDetails As D ON M.QnId = D.QnId
GROUP BY M.QnId
)
Then, select from that cte:
SELECT ROW_NUMBER() OVER(ORDER BY QnId) AS Id,
QnId,
CASE WHEN CountDetails = 0 THEN
'ERROR'
ELSE
'PASS'
END
FROM CTE
WHERE CountPass = CountDetails
Results:
+----+------+--------+
| ID | QnID | Result |
+----+------+--------+
| 1 | 2 | PASS |
| 2 | 3 | PASS |
| 3 | 4 | ERROR |
| 4 | 5 | ERROR |
+----+------+--------+
You can see a live demo on rextester.

SQL - Selecting employees that have ended most recent contract but have other contracts open

I've been going around in circles trying to figure this one out.
I'm trying to select employees who have ended their most recent contract but have an active contract still open from previous.
For example, an employee has several contracts (some may be temporary or part time - this is irrelevant) but ends their most recent contract, however, they still continue to be in their older contracts.
Please see the table below as to what I'm trying to achieve - with relevant fields:
+------+-------------+-------------+------------+------------+
| ID | CONTRACT_ID | EMPLOYEE_ID | START_DATE | END_DATE |
+------+-------------+-------------+------------+------------+
| 4321 | 974 | 321 | 21/01/2004 | 31/12/2016 |
+------+-------------+-------------+------------+------------+
| 4322 | 1485 | 321 | 09/01/2009 | 31/08/2014 |
+------+-------------+-------------+------------+------------+
| 4323 | NULL | 321 | 25/07/2009 | 31/01/2010 |
+------+-------------+-------------+------------+------------+
| 4324 | 2440 | 321 | 01/06/2012 | NULL |
+------+-------------+-------------+------------+------------+
| 4325 | 7368 | 321 | 01/01/2017 | NULL |
+------+-------------+-------------+------------+------------+
| 4326 | 7612 | 321 | 14/02/2017 | 06/06/2017 |
+------+-------------+-------------+------------+------------+
Here is the code I currently have, which is not bringing back the correct data:
select
cond.EMPLOYEE_ID
,cond.END_DATE
from
contracts as cond
join
(select
EMPLOYEE_ID
,START_DATE
,END_DATE
from
contracts
where
END_DATE is null) a on a.EMPLOYEE_ID = cond.employee_id and a.START_DATE <
cond.END_DATE
group by cond.end_date, cond.EMPLOYEE_ID
having
max(cond.START_DATE) is not null AND cond.END_DATE is not null
This is what the code results in (example):
+------+-------------+-------------+------------+------------+
| ID | CONTRACT_ID | EMPLOYEE_ID | START_DATE | END_DATE |
+------+-------------+-------------+------------+------------+
| 1234 | NULL | 123 | 03/12/2014 | 26/10/2015 |
+------+-------------+-------------+------------+------------+
| 1235 | NULL | 123 | 30/10/2015 | 28/01/2016 |
+------+-------------+-------------+------------+------------+
| 1236 | NULL | 123 | 06/11/2015 | 28/01/2016 |
+------+-------------+-------------+------------+------------+
| 1237 | 1234 | 123 | 07/03/2016 | NULL |
+------+-------------+-------------+------------+------------+
| 1238 | NULL | 123 | 04/04/2017 | 13/04/2017 |
+------+-------------+-------------+------------+------------+
| 1239 | NULL | 123 | 18/04/2017 | NULL |
+------+-------------+-------------+------------+------------+
As you can see, the most recent contract does not have an end date but there is an open contract.
Any help much appreciated.
using cross apply() to get the most recent start_date, end_date, and the count of open_contracts using a windowed aggregate function count() over() :
select
c.id
, c.contract_id
, c.employee_id
, start_date
, end_date
, max_start_date = x.start_date
, max_end_date = x.end_date
, x.open_contracts
from contracts c
cross apply (
select top 1
i.start_date
, i.end_date
, open_contracts = count(case when i.end_date is null then 1 end) over(partition by i.employee_id)
from contracts i
where i.employee_id = c.employee_id
order by i.start_date desc
) x
where x.end_date is not null
and x.open_contracts > 0
order by c.employee_id, c.start_date asc
test setup with some additional cases:
create table contracts (id int, contract_id int, employee_id int, start_date date, end_date date);
insert into contracts values
(4321, 974, 321, '20040121', '20161231')
,(4322, 1485, 321, '20090109', '20140831')
,(4323, null, 321, '20090725', '20100131')
,(4324, 2440, 321, '20120601', null)
,(4325, 7368, 321, '20170101', null)
,(4326, 7612, 321, '20170214', '20170606')
,(1, 1, 1, '20160101', null)
,(2, 2, 1, '20160701', '20161231')
,(3, 3, 1, '20170101', null) /* most recent is open, do not return */
,(4, 4, 2, '20160101', '20170630')
,(5, 5, 2, '20160701', '20161231')
,(6, 6, 2, '20170101', '20170630') /* most recent is closed, no others open, do not return */
,(7, 7, 3, '20160101', '20170630')
,(8, 8, 3, '20160701', null)
,(9, 9, 3, '20170101', '20170630') /* most recent is closed, one other open, return */
;
rextester demo: http://rextester.com/BUYKJ77928
returns:
+------+-------------+-------------+------------+------------+----------------+--------------+----------------+
| id | contract_id | employee_id | start_date | end_date | max_start_date | max_end_date | open_contracts |
+------+-------------+-------------+------------+------------+----------------+--------------+----------------+
| 7 | 7 | 3 | 2016-01-01 | 2017-06-30 | 2017-01-01 | 2017-06-30 | 1 |
| 8 | 8 | 3 | 2016-07-01 | NULL | 2017-01-01 | 2017-06-30 | 1 |
| 9 | 9 | 3 | 2017-01-01 | 2017-06-30 | 2017-01-01 | 2017-06-30 | 1 |
| 4321 | 974 | 321 | 2004-01-21 | 2016-12-31 | 2017-02-14 | 2017-06-06 | 2 |
| 4322 | 1485 | 321 | 2009-01-09 | 2014-08-31 | 2017-02-14 | 2017-06-06 | 2 |
| 4323 | NULL | 321 | 2009-07-25 | 2010-01-31 | 2017-02-14 | 2017-06-06 | 2 |
| 4324 | 2440 | 321 | 2012-06-01 | NULL | 2017-02-14 | 2017-06-06 | 2 |
| 4325 | 7368 | 321 | 2017-01-01 | NULL | 2017-02-14 | 2017-06-06 | 2 |
| 4326 | 7612 | 321 | 2017-02-14 | 2017-06-06 | 2017-02-14 | 2017-06-06 | 2 |
+------+-------------+-------------+------------+------------+----------------+--------------+----------------+
I'm not a SQL-server expert, but you might try something similar to this:
SELECT *
FROM contracts cont
WHERE cont.end_date IS NOT NULL
AND cont.end_date <= SYSDATE
AND NOT EXISTS (SELECT *
FROM contracts recent
WHERE recent.employee_id = cont.employee_id
AND recent.start_date > cont.start_date)
AND EXISTS (SELECT *
FROM contracts openc
WHERE openc.employee_id = cont.employee_id
AND (openc.end_date IS NULL OR openc.end_date > SYSDATE))
The first 2 conditions search for closed contracts.
The next one ("NOT EXISTS") makes sure the selected contract is the most recent one.
The last part assures there are other open contracts.
Try this dude.
SELECT [EMPLOYEE_ID]
FROM [contracts]
WHERE [END_DATE] IS NULL
AND [EMPLOYEE_ID] IN (SELECT B.[EMPLOYEE_ID] FROM (
SELECT * FROM (
SELECT RowN = Row_Number() over (partition by [EMPLOYEE_ID] ORDER BY[START_DATE] DESC)
, [EMPLOYEE_ID]
, [CONTRACT_ID]
, [END_DATE]
FROM [contracts]
) A
WHERE A.[END_DATE] IS NOT NULL
AND A.[RowN] = 1) B)
You can do this with ROW_NUMBER() and a CTE
See it in action: http://rextester.com/HQVXF56741
In the code below, I changed the dateformat which you may not have to do.
set dateformat dmy
declare #table table (ID int,CONTRACT_ID int, EMPLOYEE_ID int, [START_DATE] datetime, END_DATE datetime)
insert into #table
values
(4321,974,321,'21/01/2004','31/12/2016'),
(4322,1485,321,'09/01/2009','31/08/2014'),
(4323,NULL,321,'25/07/2009','31/01/2010'),
(4324,2440,321,'01/06/2012',NULL),
(4325,7368,321,'01/01/2017',NULL),
(4326,7612,321,'14/02/2017','06/06/2017')
--this applies a row_number to each contract per employee
--the most recent contract (by start date) gets a 1
;with cte as(
select
EMPLOYEE_ID
,ID
,row_number() over (partition by EMPLOYEE_ID order by [START_DATE] desc) as ContractRecentcy
from #table)
--this will return all contacts that are open, which aren't the most recent for the employee.
select
t.*
from
#table t
where
t.END_DATE is null
and t.ID not in (select ID from cte where ContractRecentcy = 1)
set dateformat mdy

How to use joins and sum() in SQL Server query

I have two tables Items and Transactions. In the items table, all the items are listed. In the transactions table it is where a particular employee can request for an item depending on the quantity that he/she requested.
How to use joins to merge the data from two tables that will compute for the balance quantity of each item?
Note: (Quantity Balance = Quantity - TR_Qty)
ITEMS table:
| ID | ITEM | UNIT | QUANTITY | PRICE |
| 1 | Perfume | btl. | 50 | 200.00 |
| 2 | Battery | pc. | 100 | 25.00 |
| 3 | Milk | can | 250 | 70.00 |
| 4 | Soap | pack | 400 | 150.00 |
TRANSACTIONS table:
| ID | ITEM_ID | TR_QTY | REQUSETOR | PROCESSOR | Date |Time |
| 1 | 1 | 20 | A. Jordan | K. Koslav | 12-22-2014 |09:00|
| 2 | 2 | 8 | B. Wilkins | Z. Flores | 12-22-2014 |10:03|
| 3 | 3 | 80 | C. Potran | A. Mabag | 12-26-2014 |14:23|
| 4 | 3 | 45 | D. Korvak | D. Sanchez | 12-28-2014 |15:33|
| 5 | 4 | 22 | C. Carvicci | A. Flux | 12-31-2014 |16:02|
| 6 | 1 | 18 | F. Sansi | N. Mahone | 01-22-2015 |08:45|
| 7 | 4 | 14 | Z. Gorai | M. Sucre | 01-30-2015 |16:33|
| 8 | 2 | 7 | L. ZOnsey | P. Panchito | 02-11-2015 |17:22|
Desired output:
| ID | ITEM | QUANITY BALANCE|
| 1 | Perfume | 462 |
| 2 | Battery | 85 |
| 3 | Milk | 125 |
| 4 |Soap | 364 |
Try this:
DECLARE #Items TABLE(ID INT, Item NVARCHAR(10), Q INT)
DECLARE #Transactions TABLE(ID INT, ItemID INT, TQ INT)
INSERT INTO #Items VALUES
(1, 'Perfume', 500),
(2, 'Battery', 100),
(3, 'Milk', 250),
(4, 'Soap', 400)
INSERT INTO #Transactions VALUES
(1, 1, 20),
(2, 2, 8),
(3, 3, 80),
(4, 3, 45),
(5, 4, 22),
(6, 1, 18),
(7, 4, 14),
(8, 2, 7)
SELECT i.ID, i.Item, MAX(i.Q) - ISNULL(SUM(t.TQ), 0) AS Balance
FROM #Items i
LEFT JOIN #Transactions t ON t.ItemID = i.ID
GROUP BY i.ID, i.Item
ORDER BY i.ID
Output:
ID Item Balance
1 Perfume 462
2 Battery 85
3 Milk 125
4 Soap 364
You can do it for example by using outer apply and creating the sum of quantities in there:
select
I.ID,
I.ITEM,
I.QUANTITY - isnull(T.QUANTITY, 0) as BALANCE
from
ITEMS I
outer apply (
select sum(TR_QTY) as QUANTITY
from TRANSACTIONS T
where T.ITEM_ID = I.ID
) T
SELECT ITEM , ( SELECT (SUM(TRANSACTIONS.TR_QTY)-ITEMS.TR_QTY) FROM TRANSACTIONS WHERE TRANSACTIONS.ITEM_ID = ITEMS.ID ) AS QUANITY BALANCE FROM ITEMS
Field name and table name is as you mentioned in query ( you should change that as space is not valid for field or table name )