Count entries across three tables based on month in SQL or LINQ
I would like to extract some data from three tables in a SQL Server 2005 database. While this can surely be done in code, it seems like this could be done reasonably well in SQL (bonus points for LINQ!).
Basically, I would like to know for each month how many calls and meetings each employee has held with each of our clients. Something like this:
Employee GUID Customer GUID Jan calls Jan mtgs Feb calls Feb mtgs...
[a guid] [another guid] 5 0 7 3
The data is spread across three tables. For simplicity's sake, let's just show the releva开发者_运维问答nt columns:
Communications Table
[CommunicationId] (PK, uniqueidentifier)
[Type] (nvarchar(1)) ('C' for call, 'M' for meeting, etc.)
[Date] (datetime)
Person-Communication Table
[PersonId] (PK, FK, uniqueidentifier) (Can contain GUIDs for employees or clients, see Person Table below)
[CommunicationId] (PK, FK, uniqueidentifier)
Person Table
[PersonId] (PK, uniqueidentifier)
[Type] (nvarchar(1)) ('E' for employee, 'C' for customer)
So, the questions:
- Can this be done in SQL without horrendous code or big performance problems?
- If so, how? I'd even settle for a good high-level strategy. I'm guessing pivots will play a big role here (particularly the "Complex PIVOT Example"). DATEPART(MONTH, Date) seems like a good method for partitioning the communications by month along the lines of:
SELECT DATEPART(MONTH, Date), COUNT(*) FROM [CommunicationTable] WHERE DATEPART(YEAR, Date) = '2009' GROUP BY DATEPART(MONTH, Date) ORDER BY DATEPART(MONTH, Date)
... which gets me the number of communications in each month in 2009:
1 2871
2 2639
3 3654
4 2751
5 1773
6 2575
7 2906
8 2398
9 2621
10 2638
11 1705
12 2290
Non PIVOT, CASE using syntax:
WITH summary AS (
SELECT emp.personid AS emp_guid,
cust.personid AS cust_guid,
DATEPART(MONTH, ct.date) AS mon,
ct.type,
COUNT(*) AS num_count
FROM COMMUNICATIONTABLE ct
LEFT JOIN PERSON_COMMUNICATION pc ON pc.communicationid = ct.communicationid
JOIN PERSON emp ON emp.personid = pc.personid
AND emp.type = 'E'
JOIN PERSON cust ON cust.personid = p.personid
AND cust.type = 'C'
WHERE ct.date BETWEEN '2009-01-01' AND '2009-12-31'
GROUP BY emp.personid, cust.personid, DATEPART(MONTH, ct.ate), ct.type)
SELECT s.emp_guid,
s.cust_guid,
MAX(CASE WHEN s.mon = 1 AND s.type = 'C' THEN s.num_count ELSE 0 END) AS "Jan calls",
MAX(CASE WHEN s.mon = 1 AND s.type = 'M' THEN s.num_count ELSE 0 END) AS "Jan mtgs",
... --Copy/Paste two lines, update the month check... and the col alias
FROM summary s
GROUP BY s.emp_guid, s.cust_guid
Use WHERE ct.date BETWEEN '2009-01-01' AND '2009-12-31'
because WHERE DATEPART(YEAR, Date) = '2009'
can't use an index if one exists on the date
column.
This should get you started I did one month for one year for you, you can also add in the date range restrictions:
SELECT PE.PersonID as EmployeeID,PC2.PersonID as CustomerID,
SUM(CASE WHEN DATEPART(MONTH, C.[Date]) = 1
AND DATEPART(YEAR,C.[Date]) = 2009
AND C.[type] = 'C' THEN 1 ELSE 0 END) AS [Jan 2009 Calls]
FROM PersonTable PE
JOIN PersonCommunicationTable PC ON PE.PersonID = PC.PersonID
JOIN CommunicationsTable C ON PC.CommunicationID = C.CommunicationID
JOIN PersonCommunicationTable PC2 ON PC.CommunicationID = PC2.CommunicationID AND NOT PC2.PersonID = PC.PersonID
WHERE PE.Type = 'E'
Here is a reasonably equivalent solution using Pivot.
Declare @Comm TABLE
(
[CommunicationId] uniqueidentifier PRIMARY KEY DEFAULT NEWID(),
[Type] nvarchar(1), -- ('C' for call, 'M' for meeting, etc.)
[Date] datetime
)
Declare @Person TABLE
(
[PersonId] uniqueidentifier PRIMARY KEY DEFAULT NEWID(),
[Type] Nvarchar(1) -- ('E' for employee, 'C' for customer)
)
Declare @PersonComm TABLE
(
[PersonId] uniqueidentifier, -- (Can contain GUIDs for employees or clients, see Person Table below)
[CommunicationId] uniqueidentifier
)
INSERT INTO @Person(Type)
Select 'C' UNION ALL Select 'E' UNION ALL Select 'C' UNION ALL Select 'E'
INSERT INTO @Comm([Type],[Date])
Select 'C', '01/04/2010' UNION ALL Select 'C', '01/04/2010'
UNION ALL Select 'C', '04/04/2010' UNION ALL Select 'C', '05/01/2010'
UNION ALL Select 'C', '08/04/2009' UNION ALL Select 'C', '09/01/2009'
UNION ALL Select 'M', '01/04/2010' UNION ALL Select 'M', '03/20/2010'
UNION ALL Select 'M', '04/04/2010' UNION ALL Select 'M', '06/01/2010'
UNION ALL Select 'M', '04/10/2009' UNION ALL Select 'M', '04/10/2009'
INSERT INTO @PersonComm
Select E.PersonID , Comm.[CommunicationId]
FROM @Person E
,@Comm Comm
Where E.[Type] = 'E'
INSERT INTO @PersonComm
Select E.PersonID , Comm.[CommunicationId]
FROM @Person E
,@Comm Comm
Where E.[Type] = 'C'
Select EmployeeID,
ClientID,
Year,
[JanuaryC] AS [Jan Calls],
[JanuaryM] AS [Jan Meetings],
[FebruaryC],
[FebruaryM],
[MarchC],
[MarchM],
[AprilC],
[AprilM],
[MayC],
[MayM],
[JuneC],
[JuneM],
[JulyC],
[JulyM],
[AugustC],
[AugustM],
[SeptemberC] ,
[SeptemberM],
[OctoberC] ,
[OctoberM],
[NovemberC],
[NovemberM],
[DecemberC],
[DecemberM]
FROM
(
Select P.PersonId EmployeeID, Client.PersonId ClientID, YEAR(C.Date) Year, DateName(m,C.Date) Month, COUNT(*) Amount, C.Type CommType,
DateName(m,C.Date) + C.Type PivotColumn -- JanuaryC
FROM @Comm C
INNER JOIN @PersonComm PC
ON PC.CommunicationId = C.CommunicationId
INNER JOIN @Person P
ON P.PersonId = PC.PersonId
INNER JOIN @PersonComm PCC
ON PCC.CommunicationId = PC.CommunicationId
INNER JOIN @Person Client
ON Client.PersonId = PCC.PersonId AND Client.Type = 'C'
Where P.Type = 'E'
Group By P.PersonId, CLient.PersonId, YEAR(C.Date), DateName(m,C.Date), C.Type
) SourceTable
PIVOT (
MAX(Amount)
FOR PivotColumn IN
([JanuaryC], [JanuaryM],[FebruaryC], [FebruaryM],[MarchC], [MarchM], [AprilC], [AprilM], [MayC], [MayM], [JuneC], [JuneM], [JulyC], [JulyM],
[AugustC], [AugustM],[SeptemberC] , [SeptemberM],[OctoberC] ,[OctoberM],[NovemberC], [NovemberM], [DecemberC], [DecemberM]
)
)As PivotTable
精彩评论