best way to group date in a range - sql

Just wondering if I have two fields in a table named modified date and created date, they either have a date populate or is null. What I would like to know is the best way to count the number of occurrences and group them into a particular range like for example 0-7 days, 8-14 days, 15- 30 days etc.
I was thinking about using
sum(case when modifieddate between getdate()-7 and getdate() then 1 else 0 end)
Is this the best way to do it or is there a better way for each date range specified above. Same would go for the created date

Build a table containing the ranges on the fly, then access your table and count:
select
mindays,
maxdays,
(
select count(*)
from mytable t
where datediff(day, coalesce(t.modifieddate, t.createddate), getdate() )
between ranges.mindays and ranges.maxdays
) as cnt
from
(
select 0 as mindays, 7 as maxdays
union all
select 8 as mindays, 14 as maxdays
union all
select 15 as mindays, 30 as maxdays
) ranges;

Related

Finding Active Clients By Date

I'm having trouble writing a recursive function that would count the number of active clients on any given day.
Say I have a table like this:
Client
Start Date
End Date
1
1-Jan-22
2
1-Jan-22
3-Jan-22
3
3-Jan-22
4
4-Jan-22
5-Jan-22
5
4-Jan-22
6-Jan-22
6
7-Jan-22
9-Jan-22
I want to return a table that would look like this:
Date
NumActive
1-Jan-22
2
2-Jan-22
2
3-Jan-22
3
4-Jan-22
4
5-Jan-22
4
6-Jan-22
3
7-Jan-22
3
8-Jan-22
3
9-Jan-22
4
Is there a way to do this? Ideally, I'd have a fixed start date and go to today's date.
Some pieces I have tried:
Creating a recursive date table
Truncated to Feb 1, 2022 for simplicity:
WITH DateDiffs AS (
SELECT DATEDIFF(DAY, '2022-02-02', GETDATE()) AS NumDays
)
, Numbers(Numbers) AS (
SELECT MAX(NumDays) FROM DateDiffs
UNION ALL
SELECT Numbers-1 FROM Numbers WHERE Numbers > 0
)
, Dates AS (
SELECT
Numbers
, DATEADD(DAY, -Numbers, CAST(GETDATE() -1 AS DATE)) AS [Date]
FROM Numbers
)
I would like to be able to loop over the dates in that table, such as by modifying the query below for each date, such as by #loopdate. Then UNION ALL it to a larger final query.
I'm now stuck as to how I can run the query to count the number of active users:
SELECT
COUNT(Client)
FROM clients
WHERE [Start Date] >= #loopdate AND ([End Date] <= #loopdate OR [End Date] IS NULL)
Thank you!
You don't need anything recursive in this particular case, you need as a minimum a list of dates in the range you want to report on, ideally a permanent calendar table.
for purposes of demonstration you can create something on the fly, and use it like so, with the list of dates something you outer join to:
with dates as (
select top(9)
Convert(date,DateAdd(day, -1 + Row_Number() over(order by (select null)), '20220101')) dt
from master.dbo.spt_values
)
select d.dt [Date], c.NumActive
from dates d
outer apply (
select Count(*) NumActive
from t
where d.dt >= t.StartDate and (d.dt <= t.EndDate or t.EndDate is null)
)c
See this Demo Fiddle

Sort Numbers in varchar value in SQL Server

My Goal is to load a monthly-daily tabular presentation of sales data with sum total and other average computation at the bottom,
I have one data result set with one column that is named as 'Day' which corresponds to the days of the month, with automatic datatype of int.
select datepart(day, a.date ) as 'Day'
On my second result set, is the loading of the sum at the bottom, it happens that the word 'Sum' is aligned to the column of Day, and I used Union All TO COMBINE the result set together, expected result set is something to this like
day sales
1 10
2 20
3 30
4 10
5 20
6 30
.
.
.
31 10
Sum 130
What I did is to convert the day value, originally in int to varchar datatype. this is to successfully join columns and it did, the new conflict is the sorting of the number
select * from #SalesDetailed
UNION ALL
select * from #SalesSum
order by location, day
Assuming your union query returns the correct results, just messes up the order, you can use case with isnumeric in the order by clause to manipulate your sort:
SELECT *
FROM
(
SELECT *
FROM #SalesDetailed
UNION ALL
SELECT *
FROM #SalesSum
) u
ORDER BY location,
ISNUMERIC(day) DESC,
CASE WHEN ISNUMERIC(day) = 1 THEN cast(day as int) end
The isnumeric will return 1 when day is a number and 0 when it's not.
Try this
select Day, Sum(Col) as Sales
from #SalesDetailed
Group by Day With Rollup
Edit (Working Sample) :
select
CASE WHEN Day IS NULL THEN 'SUM' ELSE STR(Day) END as Days,
Sum(Sales) from
(
Select 1 as Day , 10 as Sales UNION ALL
Select 2 as Day , 20 as Sales
) A
Group by Day With Rollup
EDIT 2:
select CASE WHEN Day IS NULL THEN 'SUM' ELSE STR(Day) END as Days,
Sum(Sales) as Sales
from #SalesDetailed
Group by Day With Rollup

Smoothing out a result set by date

Using SQL I need to return a smooth set of results (i.e. one per day) from a dataset that contains 0-N records per day.
The result per day should be the most recent previous value even if that is not from the same day. For example:
Starting data:
Date: Time: Value
19/3/2014 10:01 5
19/3/2014 11:08 3
19/3/2014 17:19 6
20/3/2014 09:11 4
22/3/2014 14:01 5
Required output:
Date: Value
19/3/2014 6
20/3/2014 4
21/3/2014 4
22/3/2014 5
First you need to complete the date range and fill in the missing dates (21/3/2014 in you example). This can be done by either joining a calendar table if you have one, or by using a recursive common table expression to generate the complete sequence on the fly.
When you have the complete sequence of dates finding the max value for the date, or from the latest previous non-null row becomes easy. In this query I use a correlated subquery to do it.
with cte as (
select min(date) date, max(date) max_date from your_table
union all
select dateadd(day, 1, date) date, max_date
from cte
where date < max_date
)
select
c.date,
(
select top 1 max(value) from your_table
where date <= c.date group by date order by date desc
) value
from cte c
order by c.date;
May be this works but try and let me know
select date, value from test where (time,date) in (select max(time),date from test group by date);

Counting an already counted column in SQL (db2)

I'm pretty new to SQL and have this problem:
I have a filled table with a date column and other not interesting columns.
date | name | name2
2015-03-20 | peter | pan
2015-03-20 | john | wick
2015-03-18 | harry | potter
What im doing right now is counting everything for a date
select date, count(*)
from testtable
where date >= current date - 10 days
group by date
what i want to do now is counting the resulting lines and only returning them if there are less then 10 resulting lines.
What i tried so far is surrounding the whole query with a temp table and the counting everything which gives me the number of resulting lines (yeah)
with temp_count (date, counter) as
(
select date, count(*)
from testtable
where date >= current date - 10 days
group by date
)
select count(*)
from temp_count
What is still missing the check if the number is smaller then 10.
I was searching in this Forum and came across some "having" structs to use, but that forced me to use a "group by", which i can't.
I was thinking about something like this :
with temp_count (date, counter) as
(
select date, count(*)
from testtable
where date >= current date - 10 days
group by date
)
select *
from temp_count
having count(*) < 10
maybe im too tired to think of an easy solution, but i can't solve this so far
Edit: A picture for clarification since my english is horrible
http://imgur.com/1O6zwoh
I want to see the 2 columned results ONLY IF there are less then 10 rows overall
I think you just need to move your having clause to the inner query so that it is paired with the GROUP BY:
with temp_count (date, counter) as
(
select date, count(*)
from testtable
where date >= current date - 10 days
group by date
having count(*) < 10
)
select *
from temp_count
If what you want is to know whether the total # of records (after grouping), are returned, then you could do this:
with temp_count (date, counter) as
(
select date, counter=count(*)
from testtable
where date >= current date - 10 days
group by date
)
select date, counter
from (
select date, counter, rseq=row_number() over (order by date)
from temp_count
) x
group by date, counter
having max(rseq) >= 10
This will return 0 rows if there are less than 10 total, and will deliver ALL the results if there are 10 or more (you can just get the first 10 rows if needed with this also).
In your temp_count table, you can filter results with the WHERE clause:
with temp_count (date, counter) as
(
select date, count(distinct date)
from testtable
where date >= current date - 10 days
group by date
)
select *
from temp_count
where counter < 10
Something like:
with t(dt, rn, cnt) as (
select dt, row_number() over (order by dt) as rn
, count(1) as cnt
from testtable
where dt >= current date - 10 days
group by dt
)
select dt, cnt
from t where 10 >= (select max(rn) from t);
will do what you want (I think)

SQL Query to list records for last 7 days

I need query to list all the records for the date, incase if records are not present then query should list 0
SELECT Count(C.ConversionStatusID) Visits, CONVERT(VARCHAR(10),ActionDate,110) ActionDate
FROM Conversion C
WHERE C.ConversionStatusID = 2 AND
ActionDate Between DateAdd(day,-7,GetDate()) AND GETDATE()
GROUP BY CONVERT(VARCHAR(10),ActionDate,110)
Order BY CONVERT(VARCHAR(10),ActionDate,110) DESC
Expected output should always result 7 records, Sample result should be as below
ActionDate Visits
01-09-2015 1
01-08-2015 5
01-07-2015 0
01-06-2015 0
01-05-2015 3
01-04-2015 8
01-03-2015 0
Thanks in advance
you need to have date table for the last 7 days, you can then do left join
with cte (value, n)
as
(
select DATEADD(DAY, DATEDIFF(day,0,getdate()),0) as value,1 as n
UNION ALL
SELECT DATEADD(day, -1, value) as value, n+1
from cte
where n < 7
)
select CONVERT(VARCHAR(10),cte.value,110) as ActionDate , Count(C.ConversionStatusID) Visits
from cte
left join Conversion C
ON CONVERT(VARCHAR(10),C.ActionDate,110) = CONVERT(VARCHAR(10),cte.value,110)
and C.ConversionStatusID = 2
GROUP BY CONVERT(VARCHAR(10),cte.value,110)
Order BY CONVERT(VARCHAR(10),cte.value,110) DESC
Something like this will work, but you're going to have to play around with what should be a date and what should be a datetime. It's not at all clear where or when you want a time component. In particular, your WHERE clause seems to contradict the rest of your query since it doesn't strip the time. For example, if GETDATE() is January 12 at 2 PM, is January 11 at 1 PM one day ago or two? What about January 5 at 1 PM, then, because the WHERE clause is stripping that off.
If a date is 00:00 to 23:59 (although the WHERE clause is still off):
;WITH Dates ([Date]) AS (
SELECT CAST(DATEADD(DAY,-1,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-2,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-3,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-4,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-5,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-6,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-7,GETDATE()) AS DATE))
SELECT D.Date,
COALESCE(Count(C.ConversionStatusID),0) Visits
FROM Dates D
LEFT JOIN Conversion C
ON D.[Date] = CAST(C.ActionDate AS Date)
WHERE C.ConversionStatusID = 2 AND
ActionDate Between DateAdd(day,-7,GetDate()) AND GETDATE()
GROUP BY CONVERT(VARCHAR(10),ActionDate,110)
Order BY CONVERT(VARCHAR(10),ActionDate,110) DESC
You could get away with not using a CTE if you had a Numbers or tally table, but for something this small it really won't matter that much.