can you please assist
I have a query that shows number of teachers, the site the visited and when date they visited. This query looks at all teachers visits for the past week.
I want to split the dateattended field into columns to show daily visit for the past week. Below is how it looks.
EmployeeNumber Name HomeSite Site Attended Day Attended
TP-000322789 Samuel Mohlamnyane Teacher Port Elizabeth 2014-10-18 07:23
TP-000148774 Jean Smoothie Teacher Hennopsview 2014-10-13 08:55
TP-000148774 Jean Smoothie Teacher Hennopsview 2014-10-16 08:43
TP-000148122 Anthony Mike Teacher Tzaneen 2014-10-19 09:19
TP-000148122 Anthony Mike Teacher Tzaneen 2014-10-15 08:26
TP-000328452 Geneve Gorridon Teacher Tzaneen 2014-10-14 07:44
TP-000346529 Edmos Dube Teacher Melrose 2014-10-18 07:47
TP-000321374 Anita Rene Classen Teacher Johannesburg 2014-10-17 07:57
TP-000324511 Anthonysia White Teacher Durbanville 2014-10-15 07:53
TP-000324511 Anthonysia White Teacher Durbanville 2014-10-18 12:26
TP-000327471 Moses Mathebula Teacher Polokwane 2014-10-13 05:50
TP-000148194 Nonhlanhla Ndlovu Teacher Vereeniging 2014-10-15 07:06
TP-000323383 Lerato Manyanka Teacher Bedfordview 2014-10-13 07:26
TP-000323383 Lerato Manyanka Teacher Bedfordview 2014-10-16 06:51
TP-000323384 Lerato Manyanka Teacher Bedfordview 2014-10-17 08:57
Now I want to split Day attended to show date in different columns from yesterday going down to the last seven days.
Below is the code I used to get the above result set. And how the result should look like.
EmployeeNumber Name HomeSite Site Attended Day 1 Day2 Day 3 Day 4 Day 5 Day 6 Day 7
TP-000148194 Nonhlanhla Ndlovu Teacher Vereeniging 2014-10-15 07:06
TP-000323383 Lerato Manyanka Teacher Bedfordview 2014-10-17 08:57 2014-10-16 06:51 2014-10-13 07:26
SELECT mdet.MemRefNo AS 'EmployeeNumber'
, cont.FirstName + ' ' + cont.LastName AS Name
, s.Name AS 'HomeSite'
, Attend.VisitedSite AS 'Site Attended'
, Attend.Weekdays AS 'Day Attended'
FROM MemberDetail mdet
INNER JOIN MembershipHistory mhis ON mdet.CurrentMembershipID = mhis.ID32
INNER JOIN contacts cont ON cont.GUID = mdet.ContactGUID
INNER JOIN Sites s ON s.id = cont.HomeSiteID
INNER JOIN Packages pg ON pg.ID = mhis.PackageID
CROSS APPLY
(
SELECT min(a1.attenddate) AS Weekdays , a1.contactguid, a1.SiteID , s.Name as VisitedSite FROM dbo.attendance a1
INNER JOIN Sites s ON s.id = a1.Siteid
WHERE DATEDIFF(DAY,a1.attenddate,GETDATE()) <= 7
and ContactGuid = mdet.ContactGuid
AND a1.isswipesuccessful = 1
GROUP BY a1.ContactGuid, DATEPART(DW, a1.attenddate),a1.SiteID , s.Name
) Attend
WHERE pg.Description LIKE '%Teacher%'
I think this is the query you want, it uses a pivot with the results from your existing query as source through a common table expression. Ideally the code could be merged into one query, but as you didn't provide any test data from the source tables, but only the output I didn't try to rewrite it. Note that if a person visited the same site more than one time in a single day, only the latest time will be shown.
-- using original query as source
;WITH visits AS (
SELECT mdet.MemRefNo AS 'EmployeeNumber'
, cont.FirstName + ' ' + cont.LastName AS Name
, s.Name AS 'HomeSite'
, Attend.VisitedSite AS 'Site Attended'
, Attend.Weekdays AS 'Day Attended'
FROM MemberDetail mdet
INNER JOIN MembershipHistory mhis ON mdet.CurrentMembershipID = mhis.ID32
INNER JOIN contacts cont ON cont.GUID = mdet.ContactGUID
INNER JOIN Sites s ON s.id = cont.HomeSiteID
INNER JOIN Packages pg ON pg.ID = mhis.PackageID
CROSS APPLY
(
SELECT min(a1.attenddate) AS Weekdays , a1.contactguid, a1.SiteID , s.Name as VisitedSite FROM dbo.attendance a1
INNER JOIN Sites s ON s.id = a1.Siteid
WHERE DATEDIFF(DAY,a1.attenddate,GETDATE()) <= 7
and ContactGuid = mdet.ContactGuid
AND a1.isswipesuccessful = 1
GROUP BY a1.ContactGuid, DATEPART(DW, a1.attenddate),a1.SiteID , s.Name
) Attend
WHERE pg.Description LIKE '%Teacher%'
)
-- query to produce results
SELECT
EmployeeNumber,
Name,
HomeSite,
[Site Attended],
[1] AS 'Day 1',
[2] AS 'Day 2',
[3] AS 'Day 3',
[4] AS 'Day 4',
[5] AS 'Day 5',
[6] AS 'Day 6',
[7] AS 'Day 7'
FROM (
SELECT *, DATEDIFF(day, [Day Attended], GETDATE()) diff
FROM visits
WHERE [Day Attended] > (GETDATE()-7) -- adjust this to limit date range
) a
PIVOT (
MAX([Day Attended]) FOR [diff] in ([1],[2],[3],[4],[5],[6],[7])
) AS Pivoted;
It would probably be trivial to modify the existing query to get the desired results.
Sample SQL Fiddle (using the sample output data as source).
Sample results:
EmployeeNumber Name HomeSite Site Attended Day 1 Day 2 Day 3 Day 4 Day 5 Day 6 Day 7
-------------------- ---------------------------------------- -------------------- -------------------- ----------------------- ----------------------- ----------------------- ----------------------- ----------------------- ----------------------- -----------------------
TP-000148122 Anthony Mike Teacher Tzaneen NULL 2014-10-19 09:19:00.000 NULL NULL NULL 2014-10-15 08:26:00.000 NULL
TP-000148194 Nonhlanhla Ndlovu Teacher Vereeniging NULL NULL NULL NULL NULL 2014-10-15 07:06:00.000 NULL
TP-000148774 Jean Smoothie Teacher Hennopsview NULL NULL NULL NULL 2014-10-16 08:43:00.000 NULL NULL
TP-000321374 Anita Rene Classen Teacher Johannesburg NULL NULL NULL 2014-10-17 07:57:00.000 NULL NULL NULL
TP-000322789 Samuel Mohlamnyane Teacher Port Elizabeth NULL NULL 2014-10-18 07:23:00.000 NULL NULL NULL NULL
TP-000323383 Lerato Manyanka Teacher Bedfordview NULL NULL NULL 2014-10-17 08:57:00.000 2014-10-16 06:51:00.000 NULL NULL
TP-000324511 Anthonysia White Teacher Durbanville NULL NULL 2014-10-18 12:26:00.000 NULL NULL 2014-10-15 07:53:00.000 NULL
TP-000346529 Edmos Dube Teacher Melrose NULL NULL 2014-10-18 07:47:00.000 NULL NULL NULL NULL
Related
I want to get the last diploma for each employee :
SELECT * FROM (SELECT e.employeeid,afd.AdminFileId ,afd.Title,ss.Name,YearObtained,afd.CreateDate,
ROW_NUMBER() OVER (PARTITION BY afd.AdminFileId ORDER BY afd.CreateDate desc) AS rn
FROM AF_Degree afd
LEFT JOIN AF_AdminFile aaf ON afd.AdminFileId = aaf.AdminFileId
LEFT JOIN schools_School ss ON afd.SchoolId = ss.ID
LEFT JOIN employee e ON e.adminfileid=aaf.AdminFileId) AS D WHERE D.employeeid=109
The result of the above query :
Case of EmployeeId= 109 :
employeeid AdminFileId Title School YearObtained CreateDate
107 149971 Intercambio universitario; Programa Erasmus ( meses) Universidad Politécnica de Cartagena 2008 2018-05-14 03:45:41.6436995
107 149971 Student exchange Umeå University 2008 2018-06-27 16:01:53.8213765
107 149971 Erasmus Program (12months) POLITECNICA CARTAGENA 2006 2018-06-27 16:01:53.8213765
Case of EmployeeId= 139 :
employeeid AdminFileId Title School YearObtained CreateDate
139 145555 Electronic Business Engineering Czech University of Life Sciences Prague 2007 2018-05-14 03:45:41.6436995
139 145555 Entrepreneurship and Management Umeå University 2009 2015-06-23 17:30:31.3100000
139 145555 Ingeniería técnica en informática de gestión Czech University of Life Sciences Prague 2009 2015-06-23 17:30:31.3100000
For EmoloyeeId=109 the expected result is :
EmployeeId AdminFileId Title School YearObtained CreateDate
107 149971 Student exchange Umeå University 2008 2018-06-27 16:01:53.8213765
For EmoloyeeId=139 the expected result is :
EmployeeId AdminFileId Title
139 7198 Ingeniería técnica en informática de gestión Czech University of Life Sciences Prague 2009 2015-06-23 17:30:31.3100000
How to get the last diploma obtained for an employee based on the YearObtained and the CreateDate especially when we have two diplomas in the same year and having the same CreateDate like in the two cases mentioned above and their related expected output ?
Just add WHERE d.rn = 1 to the outer query. Also may need to change the partition just a bit to ... ORDER BY YearObtained DESC, afd.CreateDate DESC)...
SELECT *
FROM (
SELECT e.employeeid,afd.AdminFileId ,afd.Title,ss.Name,YearObtained,afd.CreateDate,
ROW_NUMBER() OVER (PARTITION BY afd.AdminFileId ORDER BY afd.CreateDate desc) AS rn
FROM AF_Degree afd
LEFT JOIN AF_AdminFile aaf ON afd.AdminFileId = aaf.AdminFileId
LEFT JOIN schools_School ss ON afd.SchoolId = ss.ID
LEFT JOIN employee e ON e.adminfileid=aaf.AdminFileId
) AS D
WHERE d.rn = 1
That will get you one row. However, if there's the same YearObtained and CreateDate then it's not guaranteed which record you will get. You may need to add a third column to the ORDER BY in the partition such as an ID or something unique.
What I need to do is get a Cost breakout for each grouping, aggregated by day. Also, only taking the top N per the whole date range. I'm probably not explaining this well so let me give examples. Say my table schema and data looks like this:
SoldDate Product State Cost
----------------------- --------------------- --------- ------
2017-07-11 01:00:00.000 Apple NY 6
2017-07-11 07:00:00.000 Banana NY 1
2017-07-11 07:00:00.000 Banana NY 1
2017-07-12 01:00:00.000 Pear NY 2
2017-07-12 03:00:00.000 Olive TX 1
2017-07-12 16:00:00.000 Banana NY 1
2017-07-13 22:00:00.000 Apple NY 6
2017-07-13 22:00:00.000 Apple NY 6
2017-07-13 23:00:00.000 Banana NY 1
Call this table SoldProduce.
Now what I'm looking for is to group by Day, Product and State but for each day, only take the top two of the group NOT the top of that particular day. Anything else gets lumped under 'other'.
So in this case, our top two groups with the greatest Cost are Apple-NY and Banana-NY. So those are the two that should show up in the output only. Anything else is under 'Other'
So in the end this is the desired output:
SoldDay Product State Total Cost
----------------------- --------------------- --------- ------
2017-07-11 00:00:00.000 Apple NY 6
2017-07-11 00:00:00.000 Banana NY 2
2017-07-11 00:00:00.000 OTHER OTHER 0
2017-07-12 00:00:00.000 OTHER OTHER 3
2017-07-12 00:00:00.000 Banana NY 1
2017-07-13 00:00:00.000 Apple NY 12
2017-07-13 00:00:00.000 Banana NY 1
2017-07-13 00:00:00.000 OTHER OTHER 0
Note how on the 12th Pear and Olive were lumped under other. Even though it outsold Banana on that day. This is because I want the Top N selling groups for the whole range, not just on a day by day basis.
I did a lot of googleing a way to make a query to get this data but I'm not sure if it's the best way:
WITH TopX AS
(
SELECT
b.Product,
b.State,
b.SoldDate,
b.Cost,
DENSE_RANK() OVER (ORDER BY GroupedCost DESC) as [Rank]
FROM
(
SELECT
b.Product,
b.State,
b.SoldDate,
b.Cost,
SUM(b.Cost) OVER (PARTITION BY b.Product, b.State) as GroupedCost
FROM
SoldProduce b WITH (NOLOCK)
) as b
)
SELECT
DATEADD(d,DATEDIFF(d,0,SoldDate),0),
b.Product,
b.State,
SUM(b.Cost)
FROM
TopX b
WHERE
[Rank] <= 2
GROUP BY
DATEADD(d,DATEDIFF(d,0,SoldDate),0),
b.Product,
b.State
UNION ALL
SELECT
DATEADD(d,DATEDIFF(d,0,SoldDate),0),
null,
null,
SUM(b.Cost)
from
TopX b
WHERE
[Rank] > 2
GROUP BY
DATEADD(d,DATEDIFF(d,0,SoldDate),0)
Step 1) Create a common query that first projects the cost that the row would be has we just grouped by Product and State. Then it does a second projection to rank that cost 1-N where 1 has the greatest grouped cost.
Step 2) Call upon the common query, grouping by day and restricting to rows <= 2. This is the Top elements. Then union the other category to this, or anything ranked > 2.
What do you guys think? Is this an efficient solution? Could I do this better?
Edit:
FuzzyTrees suggestion benchmarks better than mine.
Final query used:
WITH TopX AS
(
SELECT
TOP(2)
b.Product,
b.State
FROM
SoldProduce b
GROUP BY
b.Product,
b.State
ORDER BY
SUM(b.Cost)
)
SELECT
DATEADD(d,DATEDIFF(d,0,SoldDate),0),
coalesce(b.Product, 'Other') Product,
coalesce(b.State, 'Other') State,
SUM(b.Cost)
FROM
SoldProduce a
LEFT JOIN TopX b ON
(a.Product = b.Product OR (a.Product IS NULL AND b.Product IS NULL)) AND
(a.State = b.State OR (a.State IS NULL AND b.State IS NULL))
GROUP BY
DATEADD(d,DATEDIFF(d,0,SoldDate),0),
coalesce(b.Product, 'Other') Product,
coalesce(b.State, 'Other') State,
ORDER BY DATEADD(d,DATEDIFF(d,0,SoldDate),0)
-- Order by optional. Just for display purposes.
--More effienct to order in code for the final product.
--Don't use I/O if you don't have to :)
I suggest using a plain group by without window functions for your TopX view:
With TopX AS
(
select top 2 Product, State
from SoldProduce
group by Product, State
order by sum(cost) desc
)
Then you can left join to your TopX view and use coalesce to determine which products fall into the Other group
select
coalesce(TopX.Product, 'Other') Product,
coalesce(TopX.State, 'Other') State,
sum(Cost),
sp.SoldDate
from SoldProduce sp
left join TopX on TopX.Product = sp.Product
and TopX.State = sp.State
group by
coalesce(TopX.Product, 'Other'),
coalesce(TopX.State, 'Other'),
SoldDate
order by SoldDate
Note: This query will not return 0 counts
I am trying to grab a series of dates and the corresponding values (if any) that exist in my database.
I have two parameters - today (date using getDate()) - and a number of days (integer). For this example, I'm using the value 10 for the days.
Code to get the sequential dates for 10 days after today:
SELECT top 10 DATEADD(DAY, ROW_NUMBER()
OVER (ORDER BY object_id), REPLACE(getDate(),'-','')) as Alldays
FROM sys.all_objects
I now need to look up several values for each day in the sequential days code, which may or may not exist in the time table (we assume 8 hours for all dates, unless otherwise specified). The lookup would be on the field recordDateTime. If no "hours" value exists in the table cap_time for that date, I need to return a default value of 8 as the number of hours. Here's the base query:
SELECT u.FullName as UserName, d2.department,
recordDateTime, ISNULL(hours,8) as hours
FROM cap_time c
left join user u on c.userID = u.userid
left join dept d2 on u.deptID = d2.DeptID
WHERE c.userid = 38 AND u.deptID = 1
My end result for the next 10 days should be something like:
Date (sequential), Department, UserName, Number of Hours
I can accomplish this using TSQL and a temp table, but I'd like to see if this can be done in a single statement. Any help is appreciated.
Without any DDL or sample data it's hard to determine exactly what you need.
I think this will get you pretty close (note my comments):
-- sample data
------------------------------------------------------------------------------------------
DECLARE #table TABLE
(
fullName varchar(10),
department varchar(10),
[hours] tinyint,
somedate date
);
INSERT #table VALUES
('bob', 'sales', 5, getdate()+1),
('Sue', 'marketing', 3, getdate()+2),
('Sue', 'sales', 12, getdate()+4),
('Craig', 'sales', 4, getdate()+8),
('Joe', 'sales', 18, getdate()+9),
('Fred', 'sales', 10, getdate()+10);
--SELECT * FROM #table
;
-- solution
------------------------------------------------------------------------------------------
WITH alldays([day]) AS -- logic to get your dates for a LEFT date table
(
SELECT TOP (10)
CAST(DATEADD
(
DAY,
ROW_NUMBER() OVER (ORDER BY object_id),
getdate()
) AS date)
FROM sys.all_objects
)
SELECT d.[day], t.fullName, department, [hours] = ISNULL([hours], 8)
FROM alldays d
LEFT JOIN #table t ON d.[day] = t.somedate;
Results:
day fullName department hours
---------- ---------- ---------- -----
2017-04-12 bob sales 5
2017-04-13 Sue marketing 3
2017-04-14 NULL NULL 8
2017-04-15 Sue sales 12
2017-04-16 NULL NULL 8
2017-04-17 NULL NULL 8
2017-04-18 NULL NULL 8
2017-04-19 Craig sales 4
2017-04-20 Joe sales 18
2017-04-21 Fred sales 10
Maybe a subquery and the in statement, like:
SELECT u.FullName as UserName, d2.department,
recordDateTime, ISNULL(hours,8) as hours
FROM cap_time c
left join user u on c.userID = u.userid
left join dept d2 on u.deptID = d2.DeptID
WHERE c.userid = 38 AND u.deptID = 1 and recordDateTime in
(SELECT top 10 DATEADD(DAY, ROW_NUMBER()
OVER (ORDER BY object_id), REPLACE(getDate(),'-','')) as Alldays
FROM sys.all_objects)
So I am learning about TSQL and have been given an assignment to do an App that details stuff about a certain series of movies. One of the things I would like to add to the finished product is a DATEDIFF where I have the birthdate of the main actor and the Premiere date of the movie but would like to return the age of the actor at the time of the Premiere.
My question is, HOW do I specify the dates, given a DATEDIFF statement if those dates already exist in a table?
Examples of the tables follow.
Bond
BondID FirstName LastName Birthdate
1 Sean Connery 8/25/1930
2 George Lazenby 9/5/1939
3 Roger Moore 10/14/1927
4 Timothy Dalton 3/21/1946
5 Pierce Brosnan 5/16/1953
6 Daniel Craig 3/2/1968
Film
ID BondID FilmTitle Premiere
1 1 Dr. No 1963-05-08
2 1 From Russia With Love 1974-04-08
3 1 Goldfinger 1964-12-22
4 1 Thunderball 1965-12-29
Here is what I have presently.
SELECT b.Birthdate, f.Premiere, b.FirstName, b.LastName, f.FilmTitle
FROM Bond b INNER JOIN FilmID f ON b.BondID = f.BondID
WHERE b.BondID = 1
SELECT DATEDIFF(YEAR, GETDATE(), GETDATE()) AS "YearDif"
WHERE GETDATE() = Bond.Birthdate AND GETDATE() = f.Premiere
Data
Birthdate Premiere FirstName LastName FilmTitle
1 1930-08-25 1963-05-08 Sean Connery Dr. No
2 1930-08-25 1974-04-08 Sean Connery From Russia With Love
3 1930-08-25 1964-12-22 Sean Connery Goldfinger
4 1930-08-25 1965-12-29 Sean Connery Thunderball
I would like to add a column that states the age of the actor at the time of the premiere...I think I have a handle on that one... But...
I get that the second part of the DATEDIFF part isn't correct, but how do I tell the DATEDIFF where to get the date values?
Any help would be wonderous!
Your query:
SELECT DATEDIFF(YEAR, GETDATE(), GETDATE()) AS "YearDif"
WHERE GETDATE() = Bond.Birthdate AND GETDATE() = f.Premiere
will not work for what you want. GETDATE() will only return today's date so in essence the above will only return a row if the birthdate and the premiere were today at the very moment you ran the query.
What you need to do is is the QUERY section below:
SAMPLE DATA:
IF OBJECT_ID('tempdb..#Bond') IS NOT NULL
DROP TABLE #Bond
CREATE TABLE #Bond
(BondID INT ,FirstName VARCHAR(20),LastName VARCHAR(20),Birthdate DATE)
INSERT INTO #Bond
VALUES
(1 ,'Sean' ,'Connery' ,'8/25/1930')
,(2 ,'George' ,'Lazenby' ,'9/5/1939')
,(3 ,'Roger' ,'Moore' ,'10/14/1927')
,(4 ,'Timothy' ,'Dalton' ,'3/21/1946')
,(5 ,'Pierce' ,'Brosnan' ,'5/16/1953')
,(6 ,'Daniel' ,'Craig' ,'3/2/1968')
IF OBJECT_ID('tempdb..#Film') IS NOT NULL
DROP TABLE #Film
CREATE TABLE #Film
(ID INT , BondID INT , FilmTitle VARCHAR(50), Premiere DATE)
INSERT INTO #Film
VALUES (1 , 1,'Dr. No' ,'1963-05-08')
,(2 , 1,'From Russia With Love','1974-04-08')
,(3 , 1,'Goldfinger' ,'1964-12-22')
,(4 , 1,'Thunderball' ,'1965-12-29')
QUERY:
SELECT b.Birthdate, f.Premiere, b.FirstName, b.LastName, f.FilmTitle , DATEDIFF(YEAR, b.Birthdate, Premiere) AS AgeOfActor
FROM #Bond b INNER JOIN #Film f ON b.BondID = f.BondID
RESULT:
RESPONSE TO COMMENT:
you can always count the days and then devide by 365:
SELECT b.Birthdate, f.Premiere, b.FirstName, b.LastName, f.FilmTitle , DATEDIFF(day, b.Birthdate, Premiere)/365 AS AgeOfActor
FROM #Bond b INNER JOIN #Film f ON b.BondID = f.BondID
or for more accuracy you can do it in seconds :P
SELECT b.Birthdate, f.Premiere, b.FirstName, b.LastName, f.FilmTitle , DATEDIFF(second, b.Birthdate, Premiere)/(365.25*24*60*60) AS AgeOfActor
FROM #Bond b INNER JOIN #Film f ON b.BondID = f.BondID
Such things can be done in SQL in just one step. There is no need to store the intermediate results anywhere to pass them later on.
You just need to pass your column values from the table into the function call like that:
SELECT
b.Birthdate, f.Premiere, b.FirstName, b.LastName, f.FilmTitle,
DATEDIFF(YEAR, b.Birthdate, f.Premiere) AS actor_age_at_premiere
FROM
Bond b
INNER JOIN FilmID f
ON b.BondID = f.BondID
Assuming that an actor was born before the premiere date of a film he/she played in (which is a fair assumption) the result will be a positive Integer.
If you'd like to include the actor age as of now, then your third argument would be GETDATE() instead of Premiere column:
DATEDIFF(Year, b.Birthdate, GETDATE()) AS actor_age_current
I have looked at the following relevant posts:
How to create a PivotTable in Transact/SQL?
SQL Server query - Selecting COUNT(*) with DISTINCT
SQL query to get field value distribution
Desire: The have data change from State #1 to State #2.
Data: This data is a collection of the year(s) in which a person (identified by their PersonID) has been recorded performing a certain activity, at a certain place.
My data currently looks as follows:
State #1
Row | Year | PlaceID | ActivityID | PersonID
001 2011 Park Read 201a
002 2011 Library Read 202b
003 2012 Library Read 202b
004 2013 Library Read 202b
005 2013 Museum Read 202b
006 2011 Park Read 203c
006 2010 Library Read 203c
007 2012 Library Read 204d
008 2014 Library Read 204d
Edit (4/2/2014): I decided that I want State #2 to just be distinct counts.
What I want my data to look like:
State #2
Row | PlaceID | Column1 | Column2 | Column3
001 Park 2
002 Library 1 1 1
003 Museum 1
Where:
Column1: The count of the number of people that attended the PlaceID to read on only one year.
Column2: The count of the number of people that attended the PlaceID to read on two different years.
Column3: The count of the number of people that attended the PlaceID to read on three different years.
In the State #2 schema, a person cannot be counted in more than one column for each row (place). If a person reads at a particular place for 2010, 2011, 2012, they appear in Row 001, Column3 only. However, that person can appear in other rows, but once again, in only one column of that row.
My methodology (please correct me if I am doing this wrong):
I believe that the first step is to extract distinct counts of the number of years each person attended the place to perform the activity of interest (please correct me on this methodology if incorrect).
As such, this is where I am with the T-SQL:
SELECT
PlaceID
,PersonID
,[ActivityID]
,COUNT(DISTINCT [Year]) AS UNIQUE_YEAR_COUNT
FROM (
SELECT
Year
,PlaceID
,ActivityID
,PersonID
FROM [my].[linkeddatabasetable]
WHERE ActivityID = 'Read') t1
GROUP BY
PlaceID
,PersonID
,[ActivityID]
ORDER BY 1,2
Unfortunately, I do not know where to take it from here.
I think you have two options.
A traditional pivot
select placeID,
, Column1 = [1]
, Column2 = [2]
, Column3 = [3]
from
(
SELECT
PlaceID
,COUNT(DISTINCT [Yearvalue]) AS UNIQUE_YEAR_COUNT
FROM (
SELECT
yearValue
,PlaceID
,ActivityID
,PersonID
FROM #SO
WHERE ActivityID = 'Read') t1
GROUP BY
PlaceID
,PersonID
,[ActivityID]) up
pivot (count(UNIQUE_YEAR_COUNT) for UNIQUE_YEAR_COUNT in ([1],[2],[3]) ) as pvt
or as a case/when style pivot.
select I.PlaceID
, Column1 = count(case when UNIQUE_YEAR_COUNT = 1 then PersonID else null end)
, Column2 = count(case when UNIQUE_YEAR_COUNT = 2 then PersonID else null end)
, Column3 = count(case when UNIQUE_YEAR_COUNT = 3 then PersonID else null end)
from (
SELECT
PlaceID
, PersonID
,COUNT(DISTINCT [Yearvalue]) AS UNIQUE_YEAR_COUNT
FROM (
SELECT
yearValue
,PlaceID
,ActivityID
,PersonID
FROM #SO
WHERE ActivityID = 'Read') t1
GROUP BY
PlaceID
,PersonID
,[ActivityID]) I
group by I.PlaceID
Since you are in Access I would think using an aggregate functions would do the work.
Try with DCOUNT() to begin with http://office.microsoft.com/en-us/access-help/dcount-function-HA001228817.aspx.
Replace your count() with dcount("year", "linkeddatabasetable", "placeid=" & [placeid])