Query based on Count, Time frame, and location - sql

Ok so I need to write a query that I am probably making much more complicated than it needs to be but I could use some help.
I need to select records of clients that have not been seen for a year or longer, have seen us more than once but can be only once if it is not at certain locations.
So what I have so far is:
WITH CTE AS
(
SELECT
client_id,
location_id,
employee_id,
create_timestamp,
ROW_NUMBER() OVER(PARTITION BY person_id ORDER BY create_timestamp DESC) AS ROW
FROM
client_Appointment
)
SELECT
c.client_id,
COUNT(*)
FROM
CTE AS ce
INNER JOIN person AS c
ON p.person_id= ce.client_id
INNER JOIN employee_mstr AS em
ON em.employee_id = ce.empoyee_id
INNER JOIN location_mstr AS lm
ON lm.location_id = ce.location_id
WHERE
ce.create_timestamp <= CONVERT(VARCHAR(10), DATEADD(Year,-1,GETDATE()), 120)
GROUP BY
p.person_id
HAVING
COUNT(*) > 1
I'm unsure where to go from here. Also this does not get me all the info I need and if I add that information to the select clause I have to use it in group by which means I don't get all the needed records.
Thanks

So you want only clients who have not been seen in a year or more,
then clients that have either one visit NOT at certain locations OR more than one visit. Did I get that right?
Note: Just replace (VALUES(1),(2),(3)) with your table name
WITH CTE_visits
AS
(
SELECT
c.client_id,
COUNT(*) AS total_visits,
SUM(
CASE
WHEN ce.location_id IN (SELECT ID FROM (VALUES(1),(2),(3)) AS A(ID)) THEN 0 --so when it is a certain location then do NOT count it
ELSE 1 --if it is not at the certain locations, then count it
END
) AS visits_not_at_certain_locations
FROM
client_Appointment AS ce
INNER JOIN person AS c
ON p.person_id= ce.client_id
INNER JOIN employee_mstr AS em
ON em.employee_id = ce.empoyee_id
INNER JOIN location_mstr AS lm
ON lm.location_id = ce.location_id
CROSS APPLY(SELECT client_id, MAX(create_timestamp) last_visit FROM client_Appointment WHERE client_id = ce.client_id GROUP BY client_id) CA --find most recent visit for each client_id
WHERE
ce.create_timestamp <= CONVERT(VARCHAR(10), DATEADD(Year,-1,GETDATE()), 120) --remember this only counts visits over a year ago
AND last_visit <= CONVERT(VARCHAR(10), DATEADD(Year,-1,GETDATE()), 120) --says only return client_id's who's last visit is more than a year ago
GROUP BY
p.person_id
)
SELECT *
FROM CTE_visits
WHERE visits_not_at_certain_locations = 1 --seen once NOT at certain locations
OR total_visits > 1 --seen more than once at any location

Related

Getting a SUM of the values in INNER JOIN adds up duplicate values

I am running a query which is counting the records on monthly basis from the table.
I am trying to add one extra column called "TotalPrice", I need a sum of all the prices from 'settle' table.
The problem I am facing is because of INNER JOIN, 'SUM' of the prices is adding up multiple prices due to duplicate records which the INNER JOIN is returning. Is there a way to avoid it and get a SUM of the prices from unique records ?
SELECT
CONCAT(year(datetime), '-', month(datetime)) AS YearMonth,
COUNT (DISTINCT a.id) AS TOTAL, SUM(total_price) AS TotalPrice
FROM settle AS a with (nolock)
INNER JOIN transfers b with (nolock) ON b.settleId = a.id
INNER JOIN Fdata AS c with (nolock) ON c.id= b.data
GROUP BY CONCAT(year(datetime), '-', month(datetime))
Thanks in advance.
sql server 2008 onwards:
with CTE as -- A CTE alows us to manipulate the data before we use it, like a derived table
(
select datetime, id, total_price,
row_number() over(partition by id, datetime order by total_price) as rn -- This creates a row number for each combo of id and datetime that appears
FROM settle AS a with (nolock)
INNER JOIN transfers b with (nolock) ON b.settleId = a.id
INNER JOIN Fdata AS c with (nolock) ON c.id= b.data
)
SELECT CONCAT(year(datetime), '-', month(datetime)) AS YearMonth,
COUNT (DISTINCT a.id) AS TOTAL,
SUM(total_price) AS TotalPrice
from CTE
where rn = 1 -- that row_number we created? This selects only the first one, removing duplicates
group by CONCAT(year(datetime), '-', month(datetime))

Group by Month, return 0 if no record found

I want to fetch records from database table for last 12 months. Here is what I tried so far.
SELECT COUNT(s.id), date_part('month', s.viewed_at) month_number
FROM statistics_maps_view as s
INNER JOIN maps as m
ON s.maps_id=m.id Where m.users_id = $users_id group by month_number ORDER BY month_number DESC LIMIT 12
I know It'll group the records month wise. but is there a way to add Count = 0 if there is no record for a particular month?
The group by clause will not create entries where there's no data, as you've seen. What you could do is left join this entire result with another result set that has all the entries you want - e.g., one you dynamically generate with generate_series:
SELECT generate_series AS month_number, cnt
FROM GENERATE_SERIES(1,12) g
LEFT JOIN (SELECT COUNT(s.id) AS cnt,
DATE_PART('month', s.viewed_at) AS month_number
FROM statistics_maps_view s
INNER JOIN maps m ON s.maps_id = m.id
WHERE m.users_id = $users_id
GROUP BY month_number) s ON g.generate_series = s.month_number
ORDER BY 1 ASC

Very hard greatest n per group query

I have a very complexe query here, I try to give you an overview about the necessary tables here:
RPG
RPGCharacter
RPGPost
User
We have X Chars per RPG, x Posts per Char. 1 User can have X Chars, but 1 Char only depens on 1 User.
What I want is a query in which I got the last post per RPG within information about the Username who wrote this, the character and the RPG itself addition to a number how much RPGPosts per RPG we have (total).
This is how far I solved it until now:
SELECT c.RPGID, c.Name, DateTime, r.Name, u.Username, t.count
FROM dbo.RPGCharacter c inner join
(
SELECT CharacterID,
MAX(DateTime) MaxDate
FROM RPGPost
GROUP BY CharacterID
) MaxDates ON c.RPGCharacterID = MaxDates.CharacterID
INNER JOIN RPGPost p ON MaxDates.CharacterID = p.CharacterID
AND MaxDates.MaxDate = p.DateTime
Inner join RPG r on c.RPGID = r.RPGID
Inner join [User] u on u.UserID = c.OwnerID
inner join (Select RPG.RPGID, Count(*) as Count from RPGPost
inner join RPGCharacter on RPGPost.CharacterID = RPGCharacter.RPGCharacterID
inner join RPG on RPG.RPGID = RPGCharacter.RPGID
where RPGPost.IsDeleted = 0
Group by RPG.RPGID) t on r.RPGID = t.RPGID
Order by DateTime desc
Result : http://abload.de/image.php?img=16iudw.jpg
This query gives me all I want but has an Errors:
1) It gives me the last post per Character, but I need the last Post per RPG
Does this help? This should give you the last post per CharacterID in the RPGPost table and include the total number of posts for that CharacterID.
WITH RankedPost AS (
SELECT
P.PostID,
P.CharacterID,
P.DateTime
RANK() OVER (
PARTITION BY CharacterID,
ORDER BY DateTime DESC) Rank,
RANK() OVER (
PARTITION BY CharacterID,
ORDER BY DateTime ASC) Count
FROM RPGPost P)
SELECT
P.DateTime
P.CharacterID,
P.Count
FROM RankedPost P
WHERE
RankedPost.Rank = 0;

SQL - Getting multiple counts with criteria

I have three tables that store customers, customer visits to a store, and store reviews:
Customers
ID
BirthDate...etc.
CustomerVisits
Customer_ID
Store_ID
VisitDate
Reviews
Store_ID
Customer_ID
Rating
What I need to get in a (hopefully) a single SQL statement is a count of all time visitors per store, count of visitors within the last 30 days per store, average customer age per store, and average review score per store. I need to be able to do this for several stores at once using an IN clause like where Store_ID IN (1,2,3). I know I could create a temp table and loop through store_ids, running multiple selects, but would rather do this in a single select if that is possible.
Thanks in advance!
You could perform each count in a subquery as follows:
SELECT Stores.Store_ID,
review.AvgRating,
cv.VisitsLast20days,
cv.TotalVisits,
cv.AvgCustomerAge
FROM Stores
LEFT JOIN
( SELECT Store_ID, [AvgRating] = AVG(Rating)
FROM Reviews
GROUP BY Store_ID
) review
ON review.Store_ID = Stores.Store_ID
LEFT JOIN
( SELECT CustomerVisits.Store_ID,
[VisitsLast30Days] = COUNT(CASE WHEN CustomerVisits.VisitDate >= DATEADD(DAY, -30, CURRENT_TIMESTAMP) THEN 1 END),
[TotalVisits] = COUNT(*),
[AvgCustomerAge] = AVG(DATEDIFF(DAY, Customer.BirthDate, CURRENT_TIMESTAMP)) / 365.25
FROM CustomerVisits
INNER JOIN Customer
ON Customer.Customer_ID = CustomerVisits.Customer_ID
GROUP BY CustomerVisits.Store_ID
) cv
ON cv.Store_ID = Stores.Store_ID;
I have assumed you have a table called stores to do this, and used LEFT JOINs on the assumption that not every store has a visit or a review.
I've also used a fairly crude method of calculating the average age of a customer, but given it is only for an average, and not actually working out an accurate age for an individual I doubt it will adversely affect the results
Try:
select s.Store_ID,
count(distinct v.Customer_ID) all_time_visitors,
count(distinct case when datediff(d, v.VisitDate, getdate()) <= 30 then v.Customer_ID end) 30day_visitors,
avg(datediff(yy, c.BirthDate, getdate())) avg_customer_age,
max(r.avg_rating) avg_rating
from Stores s
left join CustomerVisits v on s.Store_ID = v.Store_ID
left join Customers c on v.Customer_ID = c.Customer_ID
left join (select Store_ID, avg(Rating) avg_rating
from Reviews
group by Store_ID) r on s.Store_ID = r.Store_ID
where s.Store_ID in (1,2,3) /*amend as required*/
group by s.Store_ID

Stuck on what is a simple query

If i have 2 tables one for people and one for holidays. Everytime someone goes on holiday the date gets entered in the holiday table. How would I query this so it shows the person name from the persons table if they have been on more then say 2 holidays between 1st of jan 2010 and the 6th of june 2010? This seems simple but I cant seem to do it.
If all you want is the list of names of people taking 2 or more days between those two dates:
SELECT people.name
FROM people
WHERE EXISTS (
SELECT count(*)
FROM days_taken
WHERE people.person_id=days_taken.person_id AND
days_taken.vacation_date BETWEEN date1 AND date2
HAVING count(*)>=2
)
If you want the name and the number of days:
SELECT people.name,count(*)
FROM people JOIN days_taken ON people.person_id=days_taken.person_id
WHERE days_taken.vacation_date BETWEEN date1 AND date2
GROUP BY people.name
HAVING count(*)>=2
SELECT people.name, COUNT(*) c
FROM people INNER JOIN holidays
ON people.user_id = holidays.user_id
WHERE holidays.departure_date BETWEEN date1 AND date2
GROUP BY people.name
HAVING c > 2
SELECT p1.name, p2.num_holidays
FROM people p1
INNER JOIN
(
SELECT people.user_id, COUNT(*) as num_holidays
FROM people
INNER JOIN holidays ON (people.user_id = holidays.user_id)
WHERE holidays.departure_date BETWEEN date1 AND date2
GROUP BY people.user_id
HAVING COUNT(*) > 2
)p2 ON (p2.user_id = p1.user_id)
For SQL Server 2005 onwards, you can use the analytical function Count() OVER
select p.*, h.C
from person p inner join
(
select distinct person_id, C = COUNT(*) over (partition by person_id)
from holiday
where holiday_date between '20100101' and '20100606'
) h on h.person_id = p.person_id and h.C >= 2