I am trying to get the SUM from two different but related tables that have a one to many relationship, but when I add a where condition to the second table the first does not properly sum up the total. Can this be done in a single query? I should also note that it is critical that both consider the same set of LocationId's as they come from an outside filter. I also need the Activityname condition to happen after the join if at all possible. If that isn't possible then that is fine.
IF OBJECT_ID('tempdb..#tmpVisits') is not null
begin
drop TABLE #tmpVisits
end
IF OBJECT_ID('tempdb..#tmpVisitsByActivity') is not null
begin
drop TABLE #tmpVisitsByActivity
end
CREATE TABLE #tmpVisits
(
AccountId int,
LocationId int,
Dt DATE,
TotalVisits int
)
CREATE TABLE #tmpVisitsByActivity
(
AccountId int,
LocationId int,
EventDate DATE,
TotalCompleted INT,
ActivityName varchar(20)
)
insert INTO #tmpVisits
SELECT 1,10,'2018-09-12',12
union ALL
SELECT 1,11,'2018-09-12',20
union ALL
SELECT 1,22,'2018-09-12',10
insert INTO #tmpVisitsByActivity
SELECT 1,10,'2018-09-12',55,'ActivityA'
union ALL
SELECT 1,10,'2018-09-12',1,'ActivityA'
union ALL
SELECT 1,10,'2018-09-12',2,'ActivityB'
union ALL
SELECT 1,22,'2018-09-12',3,'ActivityC'
SELECT SUM(v.TotalVisits) --expecting 42 actual 10
, SUM(a.TotalCompleted) --expecting 3 actual 3
FROM #tmpVisits v
left JOIN #tmpVisitsByActivity a
ON v.AccountId = a.AccountId
AND v.dt = a.EventDate
AND v.LocationId = a.locationid
WHERE v.dt='2018-09-12' AND v.AccountId=1
AND a.ActivityName='ActivityC'
You can move the where condition clauses in the join condition like below, if you want to use a single query.
SELECT SUM(v.TotalVisits) --expecting 42 actual 10
, SUM(a.TotalCompleted) --expecting 3 actual 3
FROM #tmpVisits v
left JOIN #tmpVisitsByActivity a
ON v.AccountId = a.AccountId
AND v.dt = a.EventDate
AND v.LocationId = a.locationid
AND v.dt='2018-09-12' AND v.AccountId=1
AND a.ActivityName='ActivityC'
The last criteria makes it so that some in #tmpVisits are excluded when there's no match.
But that's easy to get around.
Move the criteria for a.ActivityName to the ON clause, and remove it from the WHERE clause.
...
LEFT JOIN #tmpVisitsByActivity a
ON a.AccountId = v.AccountId AND
a.EventDate = v.dt AND
a.LocationId = v.locationId AND
a.ActivityName = 'ActivityA'
WHERE v.dt = '2018-09-12'
AND v.AccountId = 1
But it would be better to put the second table in a sub-query. Otherwise the first SUM could be wrong.
Example snippet:
DECLARE #Visits TABLE
(
AccountId INT,
LocationId INT,
Dt DATE,
TotalVisits INT
);
DECLARE #VisitsByActivity TABLE
(
AccountId INT,
LocationId INT,
EventDate DATE,
TotalCompleted INT,
ActivityName VARCHAR(20)
);
INSERT INTO #Visits (AccountId, LocationId, Dt, TotalVisits) VALUES
(1,10,'2018-09-12',12),
(1,11,'2018-09-12',20),
(1,22,'2018-09-12',10);
INSERT INTO #VisitsByActivity (AccountId, LocationId, EventDate, TotalCompleted, ActivityName) VALUES
(1,10,'2018-09-12',55,'ActivityA'),
(1,10,'2018-09-12',1,'ActivityA'),
(1,10,'2018-09-12',2,'ActivityB'),
(1,22,'2018-09-12',1,'ActivityC'),
(1,22,'2018-09-12',2,'ActivityC');
SELECT
SUM(v.TotalVisits) AS TotalVisits,
SUM(ac.TotalCompleted) AS TotalCompleted
FROM #Visits v
LEFT JOIN
(
SELECT AccountId, EventDate, locationid,
SUM(TotalCompleted) AS TotalCompleted
FROM #VisitsByActivity
WHERE ActivityName = 'ActivityC'
GROUP BY AccountId, EventDate, locationid
) AS ac
ON (ac.AccountId = v.AccountId AND ac.EventDate = v.dt AND ac.LocationId = v.locationid)
WHERE v.dt = '2018-09-12'
AND v.AccountId=1
Related
I have 2 tables that contains typed events over time.
The first table #T1 contains events that always comes before events in the second table #T2.
A third table #E contains records that defines for an event the values that comes in #T1 and #T2 respectively.
Sample data:
CREATE TABLE #T1
(
EventTimestamp DateTime,
VehicleId int,
EventId varchar(50),
EventValue varchar(50)
);
CREATE TABLE #T2
(
EventTimestamp DateTime,
VehicleId int,
EventId varchar(50),
EventValue varchar(50)
);
CREATE TABLE #E
(
EventId varchar(50),
FirstValue int,
LastValue varchar(50)
);
INSERT INTO #T1(EventTimestamp, VehicleId , EventId, EventValue)
VALUES (GETDATE(), 1, 'TwigStatus', '12'),
(GETDATE(), 2, 'SafeProtectEvent', '5')
INSERT INTO #T2(EventTimestamp, VehicleId , EventId, EventValue)
VALUES (DATEADD(second, 30, GETDATE()), 1, 'TwigStatus', '7'),
(DATEADD(second, 30, GETDATE()), 2, 'SafeProtectEvent', '6')
INSERT INTO #E(EventId, FirstValue, LastValue)
VALUES ('TwigStatus', '12', '7'),
('SafeProtectEvent', '5', '6')
DECLARE #EventId varchar(50) = 'TwigStatus';
DECLARE #FirstValue varchar(50) = '12';
DECLARE #LastValue varchar(50) = '7';
WITH ord AS
(
SELECT
first, last,
EventNr = ROW_NUMBER() OVER (ORDER BY first)
FROM
(SELECT
first = t1.EventTimestamp, last = t2.EventTimestamp,
rn = ROW_NUMBER() OVER (PARTITION BY t1.VehicleId ORDER BY t2.EventTimestamp)
FROM
#T1 t1
INNER JOIN
#T2 t2 ON t2.EventTimestamp > t1.EventTimestamp
AND t2.EventValue = #LastValue
WHERE
t1.EventId = #EventId AND t1.EventValue = #FirstValue) ids
WHERE
rn = 1
)
SELECT
t.VehicleId, o.first, o.last, t.EventId, t.EventValue
FROM
#T2 t
INNER JOIN
ord o ON t.EventTimestamp >= o.first
AND t.EventTimestamp <= o.last;
WHERE t.EventId = #EventId;
DROP TABLE #E;
DROP TABLE #T1;
DROP TABLE #T2;
Basically, for a record in table E you see that for eventID 'TwigStatus' the value '12' should come first in table T1 and then '7' should be next in table T2. There is a second event sequence that is defined.
The VehicleId column is the link between the tables T1 and T2.
I need to compute the delay between two matching events in table T1 and T2.
To start simple, I do not use the table E yet, I'm using variables that contains predefined values and I'm returning timestamps.
But the result of the query above;
VehicleId first last EventId EventValue
1 2020-09-15 16:00:37.670 2020-09-15 16:01:07.670 TwigStatus 7
2 2020-09-15 16:00:37.670 2020-09-15 16:01:07.670 SafeProtectEvent 6
Is not what I'm expecting because the EventId 'SafeProtectEvent' Should be filtered out for now.
So I have 2 questions:
How to avoid the second event to show with the actual query.
How to work with the content of the table E and get rid of variables to process event sequences.
EDIT 1: Problem 1 Solved by adding a restriction on the query (see above)
Update/new version below - now allows rows in T1 without matching rows in T2.
Based on discussion on comments below, I have updated this suggestion.
This code replaces everything from the DECLARE #EventId to the end of that SELECT statement.
Logic is as follows - for each row in T1 ...
Determine the time boundaries for that row in T1 (between its EventTimestamp, and the next EventTimestamp in T1 for that vehicle; or 1 day in the future if there is no next event)
Find the matching rows in T2, where 'matching' means a) same VehicleId, b) same EventId, c) EventValue is limited by possibilities in #E, and d) occurs within the time boundaries of T1
Find the first of these rows, if available
Calculate EventDelay as the times between the two timestamps
; WITH t1 AS
(SELECT VehicleId,
EventTimestamp,
EventId,
EventValue,
COALESCE(LEAD(EventTimestamp, 1) OVER (PARTITION BY VehicleID ORDER BY EventTimestamp), DATEADD(day, 1, getdate())) AS NextT1_EventTimeStamp
FROM #T1
),
ord AS
(SELECT t1.VehicleId,
t1.EventTimestamp AS first,
t2.EventTimestamp AS last,
t1.EventId,
t2.EventValue,
ROW_NUMBER() OVER (PARTITION BY t1.VehicleId, t1.EventTimestamp, t1.EventId ORDER BY t2.EventTimestamp) AS rn
FROM t1
LEFT OUTER JOIN #E AS e ON t1.EventId = e.EventId
AND t1.EventValue = e.FirstValue
LEFT OUTER JOIN #T2 AS t2 ON t1.VehicleID = t2.VehicleID
AND t1.EventID = t2.EventID
AND t2.eventId = e.EventId
AND t2.EventValue = e.LastValue
AND t2.EventTimestamp > t1.EventTimestamp
AND t2.EventTimestamp < NextT1_EventTimeStamp
)
SELECT VehicleId, first, last, EventId, EventValue,
DATEDIFF(second, first, last) AS EventDelay
FROM ord
WHERE rn = 1
The ever-growing DB<>fiddle has the latest updates, as well as original posts and previous suggestions.
Hello all I have a requirement where I need to filter the rows with multiple conditions and exclude the result if a single entry exists in matching. Here are my sample tables
DECLARE #CUSTOMER TABLE
(
CUSTOMERID INT,
CUSTOMERNAME NVARCHAR(100)
)
DECLARE #ORDER TABLE
(
ORDERID INT,
CUSTOMERID INT,
ISSPECIALORDER INT,
SPECIALORDERID INT
)
DECLARE #SPECIALORDERDTL TABLE
(
SPECIALORDERID INT,
SPECIALORDERDATAID INT
)
DECLARE #SPECIALORDERDATA TABLE
(
SPECIALORDERDATAID INT,
SPECIALORDERMASTERID INT
)
INSERT INTO #CUSTOMER VALUES (100,'CUSTOMER1'),(200,'CUSTOMER2'),(300,'CUSTOMER3'),(400,'CUSTOMER4`enter code here`')
INSERT INTO #ORDER VALUES (1,100,0,1),(2,100,1,1),(3,100,1,2),(4,200,0,1),(5,200,1,1),(6,200,1,4),(7,300,1,5),(8,400,1,6)
INSERT INTO #SPECIALORDERDTL VALUES(1,1),(2,1),(3,2),(4,4)
INSERT INTO #SPECIALORDERDATA VALUES(1,1),(2,1),(3,1),(4,2),(5,2) -- 2 a special order
SELECT C.CUSTOMERID,C.CUSTOMERNAME
FROM #ORDER O
INNER JOIN #CUSTOMER C ON C.CUSTOMERID=O.CUSTOMERID
INNER JOIN #SPECIALORDERDTL SO ON SO.SPECIALORDERID = O.SPECIALORDERID
INNER JOIN #SPECIALORDERDATA SOD ON SO.SPECIALORDERDATAID = SOD.SPECIALORDERDATAID
WHERE SOD.SPECIALORDERID <> 2 AND O.ISSPECIALORDER =0
GROUP BY C.CUSTOMERID,C.CUSTOMERNAME
ORDER BY C.CUSTOMERNAME
When I have an entry in #SPECIALORDERDTL with SPECIALORDERMASTERID as 2 I need to consider them as special entries and exclude those. So my query should return only the customer with 100.
It is not clear from your description or SQL what exactly want. From my understanding:
DECLARE #CUSTOMER TABLE
(
CUSTOMERID INT,
CUSTOMERNAME NVARCHAR(100)
)
DECLARE #ORDER TABLE
(
ORDERID INT,
CUSTOMERID INT,
ISSPECIALORDER INT,
SPECIALORDERID INT
)
DECLARE #SPECIALORDERDTL TABLE
(
SPECIALORDERID INT,
SPECIALORDERDATAID INT
)
DECLARE #SPECIALORDERDATA TABLE
(
SPECIALORDERDATAID INT,
SPECIALORDERMASTERID INT
)
INSERT INTO #CUSTOMER VALUES
(100,'CUSTOMER1'),
(200,'CUSTOMER2'),
(300,'CUSTOMER3'),
(400,'CUSTOMER4')
INSERT INTO #ORDER VALUES
(1,100,0,1),
(2,100,1,1),
(3,100,1,2),
(4,200,0,1),
(5,200,1,1),
(6,200,1,4),
(7,300,1,5),
(8,400,1,6)
INSERT INTO #SPECIALORDERDTL VALUES(1,1),(2,1),(3,2),(4,4)
INSERT INTO #SPECIALORDERDATA VALUES(1,1),(2,1),(3,1),(4,2),(5,2) -- 2 a special order
SELECT C.CUSTOMERID,C.CUSTOMERNAME
from #Customer c
where exists (select * from #ORDER o where o.CustomerId = c.CustomerId)
and not exists (
select *
from #ORDER O
LEFT JOIN #SPECIALORDERDTL SO ON SO.SPECIALORDERID = O.SPECIALORDERID
LEFT JOIN #SPECIALORDERDATA SOD ON SO.SPECIALORDERDATAID = SOD.SPECIALORDERDATAID
WHERE (SO.SPECIALORDERID IS NULL
or SOD.SPECIALORDERMASTERID = 2 --AND O.ISSPECIALORDER =0
) AND O.CustomerId = c.CustomerId
);
GO
CUSTOMERID | CUSTOMERNAME
---------: | :-----------
100 | CUSTOMER1
db<>fiddle here
Assuming I understand the question, I think a conditional aggregation in the having clause is probably the simplest way to get the result you want:
SELECT C.CUSTOMERID, C.CUSTOMERNAME
FROM #CUSTOMER As C
JOIN #ORDER O
ON C.CUSTOMERID = O.CUSTOMERID
JOIN #SPECIALORDERDTL SO
ON O.SPECIALORDERID = SO.SPECIALORDERID
JOIN #SPECIALORDERDATA SOD
ON SO.SPECIALORDERDATAID = SOD.SPECIALORDERDATAID
GROUP BY C.CUSTOMERID, C.CUSTOMERNAME
HAVING COUNT(CASE WHEN SOD.SPECIALORDERMASTERID = 2 THEN 1 END) = 0
The having clause will filter out every customer where at least one of the orders associated with them have a specialordermasterid of 2.
From your description it sounds like not every customer will have an entry in SPECIALORDERDTL or SPECIALORDERDTA so you don't want to inner join to those tables.
What you need is a "not exists" correlated subquery to check that the customers do not have a matching row in those tables.
So you can remove the inner joins to SPECIAL* tables and add something like:-
where not exists (select null from SPECIALORDERDTL SO where
SO.SPECIALORDERID = O.SPECIALORDERID and SO.SPECIALORDERMASTERID = 2)
From your description I'm not quite sure where "SOD.SPECIALORDERID <> 2 AND O.ISSPECIALORDER =0" fit into it, so please give further details of outputs if you can't resolve using subquery.
Following your clarification, please try something like this:-
SELECT distinct C.CUSTOMERID,C.CUSTOMERNAME
FROM #ORDER O
INNER JOIN #CUSTOMER C ON C.CUSTOMERID=O.CUSTOMERID
where not exists
(select null from #SPECIALORDERDTL SO
INNER JOIN #SPECIALORDERDATA SOD ON SO.SPECIALORDERDATAID = SOD.SPECIALORDERDATAID
where SO.SPECIALORDERID = O.SPECIALORDERID and
SOD.SPECIALORDERMASTERID = 2
)
order by C.CUSTOMERNAME
Query below works as planned, it shows exactly the way i joined it, and that is fine, but problem with it, is that if you have more "specialization" tables for users, something like "Mail type" or anything that user can have more then one data ... you would have to go two left joins for each and "give priority" via ISNULL (in this case)
I am wondering, how could I avoid using two joins and "give" priority to TypeId 2 over TypeId 1 in a single join, is that even possible?
if object_id('tempdb..#Tab1') is not null drop table #Tab1
create table #Tab1 (UserId int, TypeId int)
if object_id('tempdb..#Tab2') is not null drop table #Tab2
create table #Tab2 (TypeId int, TypeDescription nvarchar(50))
insert into #Tab1 (UserId, TypeId)
values
(1, 1),
(1, 2)
insert into #Tab2 (TypeId, TypeDescription)
values
(1, 'User'),
(2, 'Admin')
select *, ISNULL(t2.TypeDescription, t3.TypeDescription) [Role]
from #Tab1 t1
LEFT JOIN #Tab2 t2 on t1.TypeId = t2.TypeId and
t2.TypeId = 2
LEFT JOIN #Tab2 t3 on t1.TypeId = t3.TypeId and
t3.TypeId = 1
The first problem is determining priority. In this case, you could use the largest TypeId, but that does not seem like a great idea. You could add another column to serve as a priority ordinal instead.
From there, it is a top 1 per group query:
using top with ties and row_number():
select top 1 with ties
t1.UserId, t1.TypeId, t2.TypeDescription
from #Tab1 t1
left join #Tab2 t2
on t1.TypeId = t2.TypeId
order by row_number() over (
partition by t1.UserId
order by t2.Ordinal
--order by t1.TypeId desc
)
using common table expression and row_number():
;with cte as (
select t1.UserId, t1.TypeId, t2.TypeDescription
, rn = row_number() over (
partition by t1.UserId
order by t2.Ordinal
--order by t1.TypeId desc
)
from #Tab1 t1
left join #Tab2 t2
on t1.TypeId = t2.TypeId
)
select UserId, TypeId, TypeDescription
from cte
where rn = 1
rextester demo for both: http://rextester.com/KQAV36173
both return:
+--------+--------+-----------------+
| UserId | TypeId | TypeDescription |
+--------+--------+-----------------+
| 1 | 2 | Admin |
+--------+--------+-----------------+
Actually I don't think you don't need a join at all. But you have to take the max TypeID without respect to the TypeDescription, since these differences can defeat a Group By. So a workaround is to take the Max without TypeDescription initially, then subquery the result to get the TypeDescription.
SELECT dT.*
,(SELECT TypeDescription FROM #Tab2 T2 WHERE T2.TypeId = dT.TypeId) [Role] --2. Subqueries TypeDescription using the Max TypeID
FROM (
select t1.UserId
,MAX(T1.TypeId) [TypeId]
--, T1.TypeDescription AS [Role] --1. differences will defeat group by. Subquery for value later in receiving query.
from #Tab1 t1
GROUP BY t1.UserId
) AS dT
Produces Output:
UserId TypeId Role
1 2 Admin
I have rows in a table that looks like this:
[date],[name],[duty],[holiday],[hdaypart],[sick],[sdaypart]
2015-04-27, person1, 1,0,NULL,0,NULL
2015-04-27, person1, 0 1,'fd',0,NULL
I would like to combine these rows to:
[date],[name],[duty],[holiday],[hdaypart],[sick],[sdaypart]
2015-04-27, person1, 1,1,'fd',0,NULL
The duty, holiday and sick columns as BIT columns.
Is there way to do this?
The one solution I can come up with is using subqueries, but it consumes a lot of time. A faster solution would be nice.
This is what I have now:
SELECT DISTINCT [name],[date],[region],[cluster]
,CASE WHEN (SELECT SUM(CONVERT(INT,callduty)) FROM planning AS t2
WHERE t1.[Date] = #datum AND t2.[Name] = t1.[name] AND t2.[Date] = t1.[date] ) > 0
THEN 1 ELSE 0 END AS [CallDuty]
,CASE WHEN (SELECT SUM(CONVERT(INT,holiday)) FROM planning AS t2
WHERE t1.[Date] = #datum AND t2.[Name] = t1.[name] AND t2.[Date] = t1.[date] ) > 0
THEN 1 ELSE 0 END AS [Holiday]
FROM planning AS t1
where t1.[Date] = #datum AND t1.[Name] like #naam
group by t1.[date],t1.[name], t1.Region, t1.cluster
order by t1.[name]
You seem to want to group by date and name and select either the maximum or the not null values within each group. MAX aggregate function is suitable for both of these selections:
SELECT [date],[name], MAX([duty]), MAX([holiday]),
MAX([hdaypart]), MAX([sick]), MAX([sdaypart])
FROM mytable
GROUP BY [date],[name]
By looking at your example, I assume that you want to get the maximum values for a specific user.
You could do this using a group by and max
select max([date]),[name],max([duty]),max([holiday]),max([hdaypart]),max([sick]),max([sdaypart])
from yourtable
group by name
This is not really pretty but should perform better than using subqueries.
EDIT:
If you have columns with bit sql types, use
max(cast([bitColumn] as int))
Adding the date column in the group by, as suggested by Giorgos Betsos, the result is
select [date],
[name],
max([duty]),
max([holiday]),
max(cast([hdaypart] as int)),
max(cast([sick] as int)),
max(cast([sdaypart] as int))
from yourtable
group by [date],[name]
declare #t table ([date] date,[name] varchar(10),[duty] varchar(10),[holiday] int,[hdaypart] varchar(10),[sick] int,[sdaypart]
int
)
insert into #t([date],[name],[duty],[holiday],[hdaypart],[sick],[sdaypart])values ('2015-04-27','person1',1,0,NULL,0,NULL),
('2015-04-27','person1',1,0,'fd',0,NULL)
select MAX([date]),MAX([name]),MAX([duty]),MAX([holiday]),MAX([hdaypart]), [sick],[sdaypart] from #t
group by sick,[sdaypart]
OR
select [date],[name],[duty],[holiday],MAX([hdaypart])AS H,[sick],[sdaypart] from #t
group by [date],[name],[duty],[holiday],[sick],[sdaypart]
UNION
select [date],[name],[duty],[holiday],MAX([hdaypart])AS H,[sick],[sdaypart] from #t
group by [date],[name],[duty],[holiday],[sick],[sdaypart]
CREATE TABLE #Combine
(
[date] date,
[name] VARCHAR(10),
[duty] CHAR(1),
[holiday] CHAR(1),
[hdaypart] CHAR(5),
[sick] CHAR(1),
[sdaypart] VARCHAR(10)
)
INSERT INTO #Combine VALUES('2015-04-27', 'person1', '1','0',NULL,'0',NULL),
('2015-04-27', 'person1', '0','1','fd','0',NULL)
SELECT MAX(Date) [date],MAX(name) [name],MAX(Duty) [duty],MAX(holiday) holiday,
MAX(hdaypart) hdaypart,max(sick) sick,max(sdaypart)sdaypart FROM #Combine
I'm attempting to enable purchase price comparisons across 2 or more members based on the members most recent price paid based on the purchase date.
I have four tables: Member, Items, UOM and Fact
Member (membername varchar(50), memberkey int)
Items (itemname varchar(50), itemkey int)
UOM (uomname varchar(50), uomkey int)
Fact (memberkey int, itemkey int, uomkey int, purchaseamount decimal(18,2), quantity int, purchasedate date)
My UI allows selection of two or more members to allow comparison of prices per uom. My result set has to include items where at least two of the selected members have purchased that item and exclude all others.
I set my member list in a temp table by the following:
declare #MemberKeys as varchar(max)
set #MemberKeys = '702,1382,1389,1390,1391,1392,1393,1394,1395,1396,1397,1401,1402,1404,1405,1406,1516,1844';
create table #mk (memberName varchar(253), memberkey smallint)
insert into #mk (memberName, memberkey)
Select Rownbr + '.) ' + membername, memberkey from (
SELECT
cast(ROW_NUMBER() OVER(ORDER BY [MemberFacilityName] ASC) as varchar (10)) AS RowNbr
,k.value as memberkey
,m.memberName
FROM
Member m
INNER JOIN dbo.String_To_SmallInt_Table(#MemberKeys, ',') AS k
ON m.Memberkey = k.value
) X
Then I use the temp table to filter when querying the fact, uom and item tables.
select m.membername
,i.itemname
,u.uomname
,purchaseamount
,quantity
,purchaseamount/quantity as price
from Fact f
join #mk m
on m.memberkey = f.memberkey
join Item i
on i.itemkey = f.itemkey
join UOM u
on u.uomkey= f.uomkey
Now I need to do the following but need some guidance to accomplish it.
1.) filter out items that are not used by at least two of the select members.
2.) show only the most recent purchase price per member\item\uom based on the purchase date.
3.) order the result set to show member then item for easy comparison (similar to the simplified list below).
Member Item Price
mbr1 A 1.11
mbr2 A 1.12
mbr3 A 1.52
mbr4 A 2.01
mbr1 B 3.01
mbr2 B 3.03
mbr3 B 3.12
mbr4 B 3.41
mbr1 C 6.01
mbr2 C 6.63
mbr3 C 6.92
mbr4 C 6.99
Here's how I implemented this...tell me if my logic is sound:
/****Create Sample Data*****/
-->Member table
IF exists (SELECT 1 from dbo.sysobjects WHERE name = 'Member')
DROP TABLE Member
GO
CREATE TABLE Member (membername VARCHAR(50), memberkey INT)
GO
INSERT INTO Member VALUES
('mbr1',702),
('mbr2',1382),
('mbr3',1389),
('mbr4',1390),
('mbr5',1391),
('mbr6',1392),
('mbr7',1393),
('mbr8',1394),
('mbr9',1395),
('mbr10',1396),
('mbr11',1397),
('mbr12',1401),
('mbr13',1402),
('mbr14',1404),
('mbr15',1405),
('mbr16',1406),
('mbr17',1516),
('mbr18',1111)-->Should NOT show up in query
GO
-->Items table
IF exists (SELECT 1 from dbo.sysobjects WHERE name = 'Items')
DROP TABLE Items
GO
CREATE TABLE Items (itemname VARCHAR(50), itemkey INT)
GO
INSERT INTO Items VALUES
('A',1),
('B',2),
('C',3),
('D',4)
GO
-->UOM table
IF exists (SELECT 1 from dbo.sysobjects WHERE name = 'UOM')
DROP TABLE UOM
GO
CREATE TABLE UOM (uomname VARCHAR(50), uomkey INT)
GO
INSERT INTO UOM VALUES ('QTY', 1)
GO
-->Fact table
IF exists (SELECT 1 from dbo.sysobjects WHERE name = 'Fact')
DROP TABLE Fact
GO
CREATE TABLE Fact (memberkey INT, itemkey INT, uomkey INT, purchaseamount decimal(18,2), quantity INT, purchasedate date)
GO
INSERT INTO Fact VALUES
(702, 1, 1, 1.11, 2, '1/3/2012'),-->Should show up in query
(1382, 1, 1, 1.12, 3, '1/4/2013'),-->Should NOT show up in query
(1382, 1, 1, 1.14, 2, '7/5/2013'),-->Should show up in query
(1404, 1, 1, 1.21, 2, '1/7/2012'),-->Should show up in query
(1401, 2, 1, 3.01, 1, '4/2/2013'),-->Should NOT show up in query
(1111, 3, 1, 6.92, 1, '12/12/2012'),-->Should NOT show up in query
(702, 3, 1, 5.01, 2, '4/1/2011'),-->Should show up in query
(1401, 3, 1, 4.01, 1, '6/5/2012'),-->Should show up in query
(1397, 4, 1, 5.45, 1, '7/4/2013'),-->Should NOT show up in query
(1397, 4, 1, 5.22, 3, '3/15/2011')-->Should NOT show up in query
GO
/*****Code to get results*****/
BEGIN
-->Members to Filter On
DECLARE #MemberKeys AS VARCHAR(max)
SET #MemberKeys = '702,1382,1389,1390,1391,1392,1393,1394,1395,1396,1397,1401,1402,1404,1405,1406,1516,1844';
-->Parse out comma delimited VALUES into a table variable
DECLARE #Member TABLE
(
memberkey INT
)
DECLARE #spot SMALLINT, #str VARCHAR(max), #sql VARCHAR(max)
WHILE #MemberKeys <> ''
BEGIN
SET #spot = CHARINDEX(',', #MemberKeys)
IF #spot>0
BEGIN
SET #str = LEFT(#MemberKeys, #spot-1)
SET #MemberKeys = RIGHT(#MemberKeys, LEN(#MemberKeys)-#spot)
END
ELSE
BEGIN
SET #str = #MemberKeys
SET #MemberKeys = ''
END
INSERT INTO #Member VALUES(CONVERT(VARCHAR(100),#str))
END
END;
-->Display Results
WITH staged(memberkey, membername, itemname ,itemkey, uomname, uomkey, purchaseamount, quantity, price, purchasedate, noitems )
AS
(
SELECT
m.memberkey
,m.membername
,i.itemname
,i.itemkey
,u.uomname
,u.uomkey
,f.purchaseamount
,f.quantity
,f.purchaseamount/f.quantity as price
,f.purchasedate
,COUNT(m.memberkey) OVER(PARTITION BY i.itemkey )-COUNT(m.memberkey) OVER(PARTITION BY convert(VARCHAR,m.memberkey)+convert(VARCHAR,i.itemkey) ) as noitems
FROM
Fact f
JOIN Member m ON m.memberkey = f.memberkey
JOIN Items i ON i.itemkey = f.itemkey
JOIN UOM u ON u.uomkey= f.uomkey
WHERE
EXISTS(SELECT 1 FROM #Member m2 WHERE m.memberkey=m2.memberkey)
)
SELECT
memberkey,
membername,
itemname ,
itemkey,
uomname,
uomkey,
sum(purchaseamount) as purchaseamount ,
sum(quantity) as quantity ,
sum(price) as price,
max(purchasedate) as purchasedate
FROM
staged st
WHERE
noitems>0
and exists(
select memberkey,
itemkey ,
uomkey,
max(purchasedate) as maxdate
from staged st2
where st.memberkey=st2.memberkey
and st.itemkey=st2.itemkey
and st.uomkey=st2.uomkey
group by
memberkey,
itemkey ,
uomkey
having st.purchasedate=max(st2.purchasedate)
)
GROUP BY
memberkey
,membername
,itemname
,uomname
, itemkey
, uomkey
ORDER BY
itemname
,memberkey;
I was able to figure this out on my own but will post my own answer; maybe it could help others with similar tasks.
I was able to complete the three tasks by introducing a second temp table to identify the most recent purchase price per item and member. Then joining the #mostrecentpurchase temp table to the base table enable effective member price comparisons.
To limit the result set to only items where two or more of the selected members had documented prices, I used the OVER clause and partitioned by item and unit of measure to get a count of members per item/uom. I then used this count in the where clause to filter out rows where the count was less than one.
Finally, the sorting was accomplished by simple order by clause. The completed tsql script is below.
declare #MemberKeys as varchar(max)
set #MemberKeys = '702,1382,1389,1390,1391,1392,1393,1394,1395,1396,1397,1401,1402,1404,1405,1406,1516,1844';
create table #mk (memberName varchar(253), memberkey smallint)
insert into #mk (memberName, memberkey)
Select Rownbr + '.) ' + membername, memberkey from (
SELECT
cast(ROW_NUMBER() OVER(ORDER BY [MemberFacilityName] ASC) as varchar (10)) AS RowNbr
,k.value as memberkey
,m.memberName
FROM
Member m
INNER JOIN dbo.String_To_SmallInt_Table(#MemberKeys, ',') AS k
ON m.Memberkey = k.value
) X
create table #mostrecentpurchase(purchasedate date, itemkey int, uomkey int, memberkey int)
Insert into #mostrecentpurchase(purchasedate, itemkey, uomkey, memberkey)
select max(f.PurchaseDate) purchasedate
, f.itemKey
, f.uomkey
, f.memberkey
from Fact f
join #mk m
on m.memberkey = f.memberkey
group by f.itemkey
, f.uomkey
, f.memberkey
select x.* FROM (
select m.memberName
, i.itemname
, i.itemkey
, f.purchasedate
, sum(f.purchaseamount) as purchaseamount
, sum(f.quantity) as quantity
, u.uomname
, sum(f.purchaseamount)/sum(f.quantity) as price
, count(m.memberName) OVER(PARTITION BY i.vendorItem_PK,u.UnitOfmeasure) AS mbrCount
from
fact f
join #mk m
on m.memberkey = f.memberkey
join #mostrecentpurchase mrp
on mrp.purchasedate = f.PurchaseDate
and mrp.memberkey = f.memberkey
and mrp.uomkey = f.uomkey
and mrp.vendoritemkey = f.itemkey
join item i
on i.itemkey = f.itemkey
join uom u
on u.uomkey = f.uomkey
group by m.membername,i.itemname,i.itemkey,f.purchasedate,u.uomname
) X
where mbrCount >= #MemberCompCount
order by X.itemname, X.memberName
drop table #mk;
drop table #mostrecentpurchase;