SQL to find the most recent account transaction for each customer - sql

EDIT: I'm using SQL Server
I looked around for an example of this but couldn't find anything so I'll start a new thread...
I have 3 tables.
Account
AccountID
FirstName
LastName
AccountEnroll
AccountEnrollID
AccountID
AccountTypeID
EnrollDate
AccountType
AccountTypeID
AccountType
The AccountEnroll table is a bridge table to track each customer's enrollment history. I want to use the "EnrollDate" column to determine the current account type for each customer. I need to write a SELECT statement that can display AccountID, FirstName, LastName, (current)AccountType.
I am having trouble getting my resultset to display only the MAX(EnrollDate) record for each customer.

You can use common table expressions to do this pretty simply.
with cte as (
select A.FirstName, A.LastName, AT.AccountType, AE.EnrollDate, row_number() over (partition by AE.AccountID order by EnrollDate desc) as [rn]
from Account as A
inner join AccountEnrolled as AE
on A.AccountId = AE.AccountId
inner join AccountType as AT
on AE.AccountTypeId = AT.AccountTypeId
)
select FirstName, LastName, AccountType, EnrollDate
from cte
where rn = 1

Try this:
SELECT
a.AccountID, a.FirstName, a.LastName,
at.AccountType AS 'Current AccountType'
FROM Account a
INNER JOIN
(
SELECT AccountID, MAX(EnrollDate) MaxDate
FROM AccountEnroll
GROUP BY AccountID
) t
INNER JOIN AccountEnroll ae ON ae.AccountID = t.AccountID
AND ae.EnrollDate = t.MaxDate
INNER JOIN AccountType at ON ae.AccountTypeID = at.AccountTypeID

You could use a correlated sub-query:
SELECT A.AccountId, A.FirstName, A.LastName, AT.AccountType
FROM Account A
JOIN AccountEnroll AE
ON A.AccountId = AE.AccountId
JOIN AccountType AT
ON AE.AccountTypeId = AT.AccountTypeId
WHERE NOT EXISTS (
SELECT 1
FROM AccountEnroll
WHERE AccountId = AE.AccountId
AND EnrollDate > AE.EnrollDate
)

Related

Stored procedure that selects from multiple group the results then order each group in descending order?

Here is the list of tables:
My Tables
I will like to create a stored procedure that will use the athleteId as parameter for the query and select all the columns in the result table and a few columns in each of the other tables Group the results by the EventName of the Event Table then order the groups by the Mark in descending order from the Result table and select the last record for each event.
I have search the net and tried various queries but cant seem to get the results I need.
This is hat I created. It retrieves all the info I need but I just don't seem to be able to group, order and select the lowest mark.
SELECT
a.Id as AthleteId, a.FirstName AS FirstName, a.LastName AS LastName,
a.BirthDate AS BirthDate, a.IsMale AS Male,
a.Phone AS Phone, a.Email AS Email,
p.FirstName AS ParentName, p.LastName AS ParentSurname,
p.Phone AS ParentPhone, p.Email AS ParentEmail,
ad.Street1 AS Street1, ad.Street2 AS Street2, ad.Town AS Town,
ad.Parish AS Parish, ad.Country AS Country,
s.SchoolName AS School, s.Phone AS SchoolPhone,
s.[Location] AS SchoolLocation,
c.FirstName AS CoachName, c.LastName AS CoachSurname,
c.Phone AS CoachPhone, c.Email AS CoachEmail,
m.MeetName AS MeetName,
m.StartDate AS StartDate, m.EndDate AS EndDate, m.[Location] AS MeetLocation,
e.EventName AS EventName,
r.Mark AS EventMark, r.Wind AS Wind, r.PerfDate AS PerfDate
FROM
dbo.Result r
INNER JOIN
dbo.[Event] e ON e.Id = r.EventId
INNER JOIN
dbo.Meet m ON m.Id = r.MeetId
INNER JOIN
dbo.Athlete a
JOIN
dbo.Parent p ON p.Id = a.ParentId
JOIN
dbo.[Address] ad ON ad.Id = a.AddressId
JOIN
dbo.Coach c ON c.Id = a.CoachId
JOIN
dbo.School s ON s.Id = a.SchoolId
ON a.Id = r.AthleteId
WHERE
r.AthleteId = #AthleteId
When I try adding the group by it shows an error.
Can anyone help me with this?
A quick-and-dirty way to do this in SQL Server is:
select top (1) with ties . . .
. . .
. . .
order by row_number() over (partition by r.eventid order by r.mark desc)

Count row and get latest row by date from multiple tables

I have 2 tables, Customer and CustomerActivity as showed in the picture below:
I want to output a table that:
has all columns from Customer table where CustomerType = 'Existing Customer', plus 2 more columns:
totalActivity (count activityID) - shows total activity number of each customer.
latestActivity (max checkinTime) - shows the most recent activity datetime
So far I have these 2 queries but I don't know how to combine/join and filter them to get what I need. Anyone can help with 1 query (and some explanation would be perfect)
SELECT customerId, firstName, birthDate, customerType
FROM Customer
WHERE Customer.customerType = 'Existing Customer'
SELECT t1.activityId, t1.checkinTime, t1.customerId
FROM CustomerActivity t1
inner join (
SELECT customerId, max(checkinTime) as Lastest
FROM CustomerActivity
group by customerId
) t2 on t1.customerId = t2.customerId and t1.checkinTime = t2.Lastest
You're actually close. Here is what your query should look like:
SELECT
c.customerId,
c.firstName,
c.lastName,
c.birthDate,
c.customerType,
ca.totalActivity,
ca.latestActivity
FROM Customer c
INNER JOIN(
SELECT
customerId,
latestActivity = MAX(checkinTime),
totalActivity = COUNT(*)
FROM CustomerActivity
GROUP BY customerId
) ca
ON ca.customerId = c.customerId
WHERE
c.customerType = 'Existing Customer'
The subquery (inside the INNER JOIN) retrieves the total number of activities by using COUNT(*) and latest activity using MAX(checkinTime) of each customer. After that, you would want to join it to the Customer table on customerId. You then add a WHERE clause to filter for 'Existing Customer' only.
I haven't tested it against an actual schema, but something like this should work (this approach will show customers even if they have no activity, simply change the left join to an inner join if you only want customers with activity):
SELECT c.CustomerID
, c.FirstName
, c.BirthDate
, c.CustomerType
, COUNT(ca.ActivityID) AS TotalActivity
, MAX(ca.CheckinTime) AS MostRecentActivity
FROM Customer c
LEFT JOIN CustomerActivity ca ON c.CustomerID = ca.CustomerID
WHERE c.CustomerType = 'Existing Customer'
GROUP BY c.CustomerID
, c.FirstName
, c.BirthDate
, c.CustomerType
You can get what you want without group by, by using row_number() and window fu instead:
SELECT c.*, ca.numActivities, ca.activityId as LastActivity
FROM Customer c JOIN
(select ca.*,
count(*) over (partition by ca.CustomerId) as numActivities
row_number() over (partition by ca.CustomerId order by checkinTime desc) as seqnum
from CustomerActivity ca
) ca
on c.customerId = ca.customerId and ca.seqnum = 1
WHERE c.customerType = 'Existing Customer';
This version will let you get whatever columns you like from the most recent activity row.
EDIT:
In your original question, I thought you wanted the latest activity. If you just want the latest datetime, then aggregation works:
SELECT c.*, ca.numActivities, ca.lastActivityDateTime
FROM Customer c JOIN
(select ca.*,
count(*) as numActivities
max(checkinTime) as lastActivityDateTime
from CustomerActivity ca
) ca
on c.customerId = ca.customerId
WHERE c.customerType = 'Existing Customer';
Select c.customerId, c.firstName, c.lastName, c.birthDate, c.customerType, gca.latestCheckIn, gca.count
from customer as c,
(select ca.customerId, max(ca.checkInTime) as latestCheckIn, count(*) as checkinCount
from customerActivity as ca
group by ca.customerId) as gca
where gca.customerId = c.customerId AND c.customerType = 'Existing Customer'
If you clarify more about customer with no activity, one can change the query to using left join

Getting individual counts of last three distinct rows in column of data retrieved from multiple tables

I have a query which returns several rows of data (in datetime format) of a single column obtained by performing JOINS on multiple SQL Tables. The Data obtained is a DateTime type and now I just want the individual count of latest three dates probably the count of lat three distinct dates as it sorted from earliest to latest.
SQL Query
SELECT
ST.EffectiveDate
FROM Person.Contact C
INNER JOIN Sales.SalesPerson SP
ON C.ContactID = SP.SalesPersonID
FULL OUTER JOIN Sales.SalesTerritory ST
ON ST.TerritoryID = SP.TerritoryID
The above query returns around 200 rows of data but I want the count for each of three latest dates possibly bottom three
I would do this with top and group by:
SELECT TOP 3 ST.EffectiveDate, COUNT(*) as cnt
FROM Person.Contact C INNER JOIN
Sales.SalesPerson SP
ON C.ContactID = SP.SalesPersonID FULL OUTER JOIN
Sales.SalesTerritory ST
ON ST.TerritoryID = SP.TerritoryID
GROUP BY ST.EffectiveDate
ORDER BY ST.EffectiveDate DESC
added another query to get the latest 3 distinct dates
SELECT count(1)
FROM Person.Contact C
INNER JOIN Sales.SalesPerson SP
ON C.ContactID = SP.SalesPersonID
FULL OUTER JOIN Sales.SalesTerritory ST
ON ST.TerritoryID = SP.TerritoryID
WHERE ST.effectivedate in (select distinct top 3 effectivedate
from salesterritory
order by effectivedate desc)
Or if you need to see the counts for the 3 dates broken out
SELECT st.effectivedate, count(1)
FROM Person.Contact C
INNER JOIN Sales.SalesPerson SP
ON C.ContactID = SP.SalesPersonID
FULL OUTER JOIN Sales.SalesTerritory ST
ON ST.TerritoryID = SP.TerritoryID
WHERE ST.effctivedate in (select distinct top 3 effectivedate
from salesterritory
order by effectivedate desc)
GROUP BY st.effectivedate
You can also use the analytic RANK function. This query will number the latest date as 1, the next latest as 2, and so forth:
SELECT
ST.EffectiveDate,
ROW_NUMBER() OVER (ORDER BY ST.EffectiveDate DESC) AS DateRank
FROM Person.Contact C
INNER JOIN Sales.SalesPerson SP ON C.ContactID = SP.SalesPersonID
FULL OUTER JOIN Sales.SalesTerritory ST ON ST.TerritoryID = SP.TerritoryID
You can't use the ranked value in the WHERE clause, so you'll need to take the query above and make it a subquery or a common table expression (CTE).
Subquery version:
SELECT EffectiveDate, COUNT(*)
FROM (
SELECT
ST.EffectiveDate,
ROW_NUMBER() OVER (ORDER BY ST.EffectiveDate DESC) AS DateRank
FROM Person.Contact C
INNER JOIN Sales.SalesPerson SP ON C.ContactID = SP.SalesPersonID
FULL OUTER JOIN Sales.SalesTerritory ST ON ST.TerritoryID = SP.TerritoryID
) DateList
WHERE DateRank <= 3
GROUP BY EffectiveDate
CTE version:
WITH DateList AS (
SELECT
ST.EffectiveDate,
ROW_NUMBER() OVER (ORDER BY ST.EffectiveDate DESC) AS DateRank
FROM Person.Contact C
INNER JOIN Sales.SalesPerson SP ON C.ContactID = SP.SalesPersonID
FULL OUTER JOIN Sales.SalesTerritory ST ON ST.TerritoryID = SP.TerritoryID
)
SELECT EffectiveDate, COUNT(*)
FROM DateList
WHERE DateRank <= 3
GROUP BY EffectiveDate
If you're dealing SQL Server 2005 and above, you could even try this:
;with cte as
(
SELECT
ST.EffectiveDate
FROM Person.Contact C
INNER JOIN Sales.SalesPerson SP
ON C.ContactID = SP.SalesPersonID
FULL OUTER JOIN Sales.SalesTerritory ST
ON ST.TerritoryID = SP.TerritoryID
)
Select EffectiveDate, count(1)
from cte
where EffectiveDate in (select distinct top 3 effectivedate
from cte
order by EffectiveDate desc)
group by EffectiveDate
Though untested, it should work; it my be unnecessarily elaborate though.

SQL Combining two queries

The below code selects records from the two tables where both the email and dob match another record (all duplicates..)
SELECT
AccountName,
EmailAddress,
DateOfBirth
FROM
(
SELECT
a.AccountName,
a.EmailAddress,
u.DateOfBirth,
COUNT(*) over (partition by a.EmailAddress, u.DateOfBirth) AS cnt
FROM Account AS a
JOIN [User] AS u ON a.AccountID = u.AccountID
) ua
WHERE cnt > 1
AND EmailAddress IS NOT null
AND DateOfBirth IS NOT null
ORDER BY EmailAddress, DateOfBirth
I also want to add to this table, a field within another table called 'Audit'. We can join them using the LoginID, however the LoginID has a one to many relationship in the Audit table. i.e. a LoginID can have many Audits.
I want to add the Audit StartDate column. The following query allows me to identify the latest Audit by date.
SELECT a.LoginID as AuditLoginID,
MAX(StartDate) as StartDate
FROM Audit as a
GROUP BY a.LoginID
ORDER BY a.StartDate
Would anyone be able to suggest how I can combine these two queries, so that my original query has a join to the Audit table, displaying a 'StartDate' column of the latest audit start date?
You should consider using a correlated subquery. That will avoid building another database object to support this query, and it's a relatively standard SQL construct.
Example:
SELECT
AccountName,
EmailAddress,
DateOfBirth
FROM
(
SELECT
a.AccountName,
a.EmailAddress,
u.DateOfBirth,
a.LoginID,
COUNT(*) over (partition by a.EmailAddress, u.DateOfBirth) AS cnt
FROM Account AS a
JOIN [User] AS u ON a.AccountID = u.AccountID
) ua
join Audit as a
on a.LoginID = au.LoginID
WHERE cnt > 1
AND EmailAddress IS NOT null
AND DateOfBirth IS NOT null
AND a.startdate = (SELECT MAX(StartDate) as StartDate
FROM Audit as b
WHERE b.LoginID = a.LoginID)
ORDER BY EmailAddress, DateOfBirth
Here's an expansion on my comment:
CREATE VIEW MostRecentLogins AS
(
SELECT a.LoginID as AuditLoginID,
MAX(StartDate) as StartDate
FROM Audit as a
GROUP BY a.LoginID
)
Then, you can join the MostRecentLogins view into your other query. It's not clear from your post which column would be the counterpart to LoginId (from the Audit table) but the query would then look something like this:
SELECT a.AccountName,
a.EmailAddress,
u.DateOfBirth,
MRL.StartDate
FROM
(
SELECT a.AccountName,
a.EmailAddress,
u.DateOfBirth,
COUNT(*) over (partition by a.EmailAddress, u.DateOfBirth) AS cnt
FROM Account AS a
JOIN [User] AS u
ON a.AccountID = u.AccountID
) ua
INNER JOIN MostRecentLogins MRL
ON MRL.LoginID = a.LoginID -- not sure what column should be on the RHS of this..
WHERE cnt > 1
AND EmailAddress IS NOT null
AND DateOfBirth IS NOT null
ORDER BY EmailAddress, DateOfBirth

SQL Server 2005 Query remove duplicates via date

I searched and searched and can't seem to figure out this issue:
We have three tables which have data I need to collect and show in a view.
SELECT
C.FirstName, C.LastName,
aspnet_Membership.LoweredEmail,
MAX(Bill.Code) AS BCodes,
MAX(Bill.BillDate)
FROM
dbo.Client C
INNER JOIN
dbo.Bill ON C.Id = Bill.BId
INNER JOIN
dbo.aspnet_Membership ON aspnet_Membership.UserId = C.UserGUID
WHERE
((Bill.Code='ASDF'
OR Bill.Code='XYZ'
OR Bill.Code='QWE'
OR Bill.Code='JKL')
AND C.LastName!='Unassigned')
GROUP BY
LastName, FirstName, LoweredEmail, Code, BDate
Client table has: FirstName LastName and UserGuid
Bill table has: BCode, BillDate
aspnet_Membership table has: E-mail, UserId
RESULTS:
FirstName LastName E-mail BCode BillDate
FName1 Lname1 fname#isp.com XYZ 2010-05-13 00:00:00.000
Fname2 Lname2 fname2#isp2.com XYZ 2010-06-05 00:00:00.000
Fname2 Lname2 fname2#isp2.com ASD 2008-09-17 12:01:45.407
As you can see Fname2 shows up twice, only difference is in the BCode and BillDate.
How can I make this go with the latest date so I get Fname2 record with Bcode of XYZ with date of 2010-06-05.
Any help would be appreciated, thank you in advance.
Seeing that you're using SQL Server 2005, I would probably use a CTE (Common Table Expression) to do this - something like:
;WITH MyData AS
(
SELECT
c.FirstName, c.LastName,
asp.LoweredEmail,
b.Code AS BCodes, b.BillDate,
ROW_NUMBER() OVER (PARTITION BY c.LastName,c.FirstName
ORDER BY BillDate DESC) AS 'RowNum'
FROM
dbo.Client c
INNER JOIN
dbo.Bill b ON C.Id = b.BId
INNER JOIN
dbo.aspnet_Membership asp ON asp.UserId = c.UserGUID
WHERE
b.Code IN ('ASDF', 'JKL', 'QWE', 'XYZ')
AND c.LastName != 'Unassigned'
)
SELECT
FirstName, LastName, LoweredEmail, BCodes, BillDate
FROM
MyData
WHERE
RowNum = 1
This CTE with the ROW_NUMBER() clause will:
"partition" your data by (FirstName,LastName) - each pair of those values gets a new sequential "row number"
order those values within each partition by descending BillDate
So the resulting set of data has each newest entry for any (FirstName,LastName) group with RowNum = 1 - and that's the data I'm selecting from that CTE.
Does that work for you??
Perform a second join (using a LEFT JOIN) to find a later row in Bill table, and then filter any results where that join succeeds:
SELECT
C.FirstName, C.LastName,
aspnet_Membership.LoweredEmail,
MAX(Bill.Code) AS BCodes,
MAX(Bill.BillDate)
FROM dbo.Client C
INNER JOIN dbo.Bill
ON C.Id=Bill.BId
INNER JOIN dbo.aspnet_Membership
ON aspnet_Membership.UserId=C.UserGUID
LEFT JOIN dbo.Bill b2
ON Bill.BId = b2.BId and
b2.Code in ('ASDF','XYZ','QWE','JKL') and
b2.BDate > Bill.BDate
WHERE
b2.BId is null and
((Bill.Code='ASDF'
OR Bill.Code='XYZ'
OR Bill.Code='QWE'
OR Bill.Code='JKL')
AND C.LastName!='Unassigned')
GROUP BY LastName, FirstName, LoweredEmail, Code, BDate