SQL Count for a Date Column - sql

I have a table that containts a set of columns one of it is a Date column.
I need to count how many occurrences of the values of that column refer to the same month. And return if for one month, that count sums more than 3.
For example:
____________________
| DATE | .... |
---------------------
1998-09-02
1998-09-03
1998-10-03
1998-10-04
This must return no value. Because it doesn't have the necessary number of repetitions.
But this it does:
____________________
| DATE | .... |
---------------------
1998-09-02
1998-09-03
1998-09-12
1998-09-14
1998-10-02
1998-11-21
For the november month.
Is for an Oracle DB.

SELECT
COUNT(date)
, TRUNC(DATE,'MON')
FROM TABLE
GROUP BY TRUNC(DATE,'MON')
HAVING COUNT(DATE) > 3

create table x (date_col date);
insert into x values (date '1998-09-02');
insert into x values (date '1998-09-03');
insert into x values (date '1998-09-12');
insert into x values (date '1998-09-14');
insert into x values (date '1998-10-02');
insert into x values (date '1998-11-21');
SELECT TRUNC(date_col,'MM'), count(*)
FROM x
GROUP BY TRUNC(date_col,'MM')
HAVING count(*) > 3;

So if 3 coloums contain 1999-01-xx you want to get that fetched ?
SELECT YEAR(date), MONTH(date)
FROM table GROUP BY YEAR(date), MONTH(date)
HAVING COUNT(*) > 3
If you need all the rows that contain the upper result it should look something like that
SELECT * FROM table
INNER JOIN (
SELECT YEAR(date) as y, MONTH(date) as m
FROM table GROUP BY YEAR(date), MONTH(date)
HAVING COUNT(*) > 3
) as virtualTable
ON virtualTable.y = YEAR(date) AND virtualTable.m = MONTH(date)

This example will help :
create table d1
( event_date date, event_description varchar2(100));
insert into d1 values (sysdate,'Phone Call');
insert into d1 values (sysdate,'Letter');
insert into d1 values (sysdate-50,'Interview');
insert into d1 values (sysdate-50,'Dinner with parents');
insert into d1 values (sysdate-100,'Birthday');
insert into d1 values (sysdate-100,'Holiday');
insert into d1 values (sysdate-100,'Interview');
insert into d1 values (sysdate-100,'Phone Call');
commit;
select * from d1;
EVENT_DATE EVENT_DESCRIPTION
------------------------- -----------------------------------------------
04-MAR-10 14.47.58 Phone Call
04-MAR-10 14.47.58 Letter
13-JAN-10 14.47.58 Interview
13-JAN-10 14.47.58 Dinner with parents
24-NOV-09 14.47.58 Birthday
24-NOV-09 14.47.58 Holiday
24-NOV-09 14.47.58 Interview
24-NOV-09 14.47.58 Phone Call
8 rows selected
You can see that Nov-09 is the only month which more than 3 events.
Referring back to your original question, which was And return if for one month, that count sums more than 3. The following SQL aggregate will work.
select trunc(event_date,'MONTH'),count('x') from d1
having count('x') > 3 group by trunc(event_date,'MONTH')
Alternatively, use to_char to convert the Date type to a Char with a MON-YYYY picture as follows :
select to_char(trunc(event_date,'MONTH'),'MON-YYYY') month,
count('x') no_of_occurances from d1 having count('x') > 3 group trunc(event_date,'MONTH')

Ideally you should create a stored procedure that accepts the two criteria you need, Month(integer) and limit(integer)
In a parameterized procedure that executes the following
SELECT MONTH(Date) AS TheMonth, COUNT(MONTH(Date)) AS TheMonthCount
FROM MyTable
GROUP BY MONTH(Date)
HAVING (COUNT(MONTH(Date)) > #limit) AND (MONTH(Date) = #month)
To also output the relevant month you could use the following
SELECT CAST(YEAR(Date) AS NVARCHAR) + '.' +
CAST(MONTH(Date) AS NVARCHAR) AS 'The ',
MONTH(Date ) AS TheMonth, COUNT(MONTH(Date)) AS TheMonthCount
FROM Audit_Entry
GROUP BY MONTH(Date),
CAST(YEAR(Date) AS NVARCHAR) + '.' +
CAST(MONTH(Date) AS NVARCHAR)
HAVING (COUNT(MONTH(Date)) > #limit) AND (MONTH(Date) = #month)

This should work for mysql and mssql:
SELECT MONTH(date), Sum(MONTH(date))
FROM table
GROUP BY date
HAVING Sum(MONTH(date)) > 3

I am not sure which database you are using.
In MySQL query will be similar to the method proposed by #THEn
On SQL server you have other interesting possibilities.
Read the this article for more details.

You could use Oracle's EXTRACT method :
select theMonth, sum(monthCount)
from (
select
extract(MONTH FROM t.theDateColumn) as theMonth,
1 as monthCount
)
group by theMonth
having sum(monthCount) >= 3
I don't have an Oracle database at hand at the moment, so this code may not work as is - I apologize for this.

Could be wrong but a guess:
SELECT SUM(date) FROM table
GROUP BY date where SUM(date) > 3

Related

Irregular grouping of timestamp variable

I have a table organized as follows:
id lateAt
1231235 2019/09/14
1242123 2019/09/13
3465345 NULL
5676548 2019/09/28
8986475 2019/09/23
Where lateAt is a timestamp of when a certain loan's payment became late. So, for each current date - I need to look at these numbers daily - there's a certain amount of entries which are late for 0-15, 15-30, 30-45, 45-60, 60-90 and 90+ days.
This is my desired output:
lateGroup Count
0-15 20
15-30 22
30-45 25
45-60 32
60-90 47
90+ 57
This is something I can easily calculate in R, but to get the results back to my BI dashboard I'd have to create a new table in my database, which I don't think is a good practice. What is the SQL-native approach to this problem?
I would define the "late groups" using a range, the join against the number of days:
with groups (grp) as (
values
(int4range(0,15, '[)')),
(int4range(15,30, '[)')),
(int4range(30,45, '[)')),
(int4range(45,60, '[)')),
(int4range(60,90, '[)')),
(int4range(90,null, '[)'))
)
select grp, count(t.user_id)
from groups g
left join the_table t on g.grp #> current_date - t.late_at
group by grp
order by grp;
int4range(0,15, '[)') creates a range from 0 (inclusive) and 15 (exclusive)
Online example: https://rextester.com/QJSN89445
The quick and dirty way to do this in SQL is:
SELECT '0-15' AS lateGroup,
COUNT(*) AS lateGroupCount
FROM my_table t
WHERE (CURRENT_DATE - t.lateAt) >= 0
AND (CURRENT_DATE - t.lateAt) < 15
UNION
SELECT '15-30' AS lateGroup,
COUNT(*) AS lateGroupCount
FROM my_table t
WHERE (CURRENT_DATE - t.lateAt) >= 15
AND (CURRENT_DATE - t.lateAt) < 30
UNION
SELECT '30-45' AS lateGroup,
COUNT(*) AS lateGroupCount
FROM my_table t
WHERE (CURRENT_DATE - t.lateAt) >= 30
AND (CURRENT_DATE - t.lateAt) < 45
-- Etc...
For production code, you would want to do something more like Ross' answer.
You didn't mention which DBMS you're using, but nearly all of them will have a construct known as a "value constructor" like this:
select bins.lateGroup, bins.minVal, bins.maxVal FROM
(VALUES
('0-15',0,15),
('15-30',15.0001,30), -- increase by a small fraction so bins don't overlap
('30-45',30.0001,45),
('45-60',45.0001,60),
('60-90',60.0001,90),
('90-99999',90.0001,99999)
) AS bins(lateGroup,minVal,maxVal)
If your DBMS doesn't have it, then you can probably use UNION ALL:
SELECT '0-15' as lateGroup, 0 as minVal, 15 as maxVal
union all SELECT '15-30',15,30
union all SELECT '30-45',30,45
Then your complete query, with the sample data you provided, would look like this:
--- example from SQL Server 2012 SP1
--- first let's set up some sample data
create table #temp (id int, lateAt datetime);
INSERT #temp (id, lateAt) values
(1231235,'2019-09-14'),
(1242123,'2019-09-13'),
(3465345,NULL),
(5676548,'2019-09-28'),
(8986475,'2019-09-23');
--- here's the actual query
select lateGroup, count(*) as Count
from #temp as T,
(VALUES
('0-15',0,15),
('15-30',15.0001,30), -- increase by a small fraction so bins don't overlap
('30-45',30.0001,45),
('45-60',45.0001,60),
('60-90',60.0001,90),
('90-99999',90.0001,99999)
) AS bins(lateGroup,minVal,maxVal)
) AS bins(lateGroup,minVal,maxVal)
where datediff(day,lateAt,getdate()) between minVal and maxVal
group by lateGroup
order by lateGroup
--- remove our sample data
drop table #temp;
Here's the output:
lateGroup Count
15-30 2
30-45 2
Note: rows with null lateAt are not counted.
I think you can do it all in one clear query :
with cte_lategroup as
(
select *
from (values(0,15,'0-15'),(15,30,'15-30'),(30,45,'30-45')) as t (mini, maxi, designation)
)
select
t2.designation
, count(*)
from test t
left outer join cte_lategroup t2
on current_date - t.lateat >= t2.mini
and current_date - lateat < t2.maxi
group by t2.designation;
With a preset like yours :
create table test
(
id int
, lateAt date
);
insert into test
values (1231235, to_date('2019/09/14', 'yyyy/mm/dd'))
,(1242123, to_date('2019/09/13', 'yyyy/mm/dd'))
,(3465345, null)
,(5676548, to_date('2019/09/28', 'yyyy/mm/dd'))
,(8986475, to_date('2019/09/23', 'yyyy/mm/dd'));

JOIN tables ON DATE = NUMBER?

I am trying to join two tables in Oracle SQL. One table has a DATE data type which represents a date(go figure) the other has an NUMBER data type which represents a month. I need to join the tables on the DATE's month and the NUMBER. I tried TO_CHAR() but it didn't work. Any suggestions?
Oracle's EXTRACT() function may do the trick ( https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions050.htm ). Suppose we have 2 tables, populated with test data, like so:
create table numbers_ (num_ number);
create table dates_ (date_ date);
begin
for i in 1 .. 12
loop
insert into numbers_ values (i);
end loop;
insert into dates_ values ('15-JUL-2017');
insert into dates_ values ('16-AUG-2017');
insert into dates_ values ('17-SEP-2017');
end;
/
We can use EXTRACT to get the "months" from the dates_ table:
select extract (month from date_) from dates_;
EXTRACT(MONTHFROMDATE_)
7
8
9
Use the "extracted" months for joining the tables:
select *
from
numbers_ N,
( select extract( month from date_ ) month from dates_ ) D
where N.num_ = D.month;
-- output
NUM_ MONTH
7 7
8 8
9 9
If you need more columns from the dates_ table, add them into the subquery (and to the main SELECT clause). Example:
select
N.num_
, D.date_
, D.month
from
numbers_ N,
( select
extract( month from date_ ) month
, date_
from dates_ ) D
where N.num_ = D.month;
(See also: dbfiddle)
Or - better (as #Wernfried Domscheit suggested):
select
N.num_
, D.date_
from
numbers_ N join dates_ D
on extract(month from D.date_) = N.num_ ;

How to find MAX of COUNT Result for Relation

I have Table which consist of PatientId which is Int and Date which is Date Data Type.
It does look like following
patientId Date
101 01/01/2001
102 01/02/2001
103 01/03/2002
104 01/03/2004
105 01/03/2004
106 01/04/2004
And My Desired Result would give me
Count Year
3 2004
since it has the most patients, also it we have two year that has the same number of patients then we should have both year displayed with Number of patients that they had.
Thank you.
Use YEAR function to extract year from your date column. Use extracted year in group by to get the count of Year
select TOP 1 year([Date]),count(1) as [Count]
from Yourtable
Group by year([Date])
Order by [Count] desc
Another way would be using DATEPART
select TOP 1 Datepart(year,[Date]),count(1) as [Count]
from Yourtable
Group by Datepart(year,[Date])
Order by [Count] desc
The DATEPART function is your friend in this case. However, to get all of the rows in case of a tie, a simple TOP will not work. In this case, a different coding method is needed.
You could use a RANK() command, but that is more complex than this calls for. Instead, use a Common Table Expression (CTE).
Here, I set up a table for testing. Since I need two years with the same count of rows, I extended your sample into 2005
CREATE TABLE MyTable (
custID INT,
[Date] DATE
)
TRUNCATE TABLE MyTable;
INSERT INTO MyTable
VALUES
(101, '01/01/2001'),
(102, '01/02/2001'),
(103, '01/03/2002'),
(104, '01/03/2004'),
(105, '01/03/2004'),
(106, '01/04/2004'),
(107, '02/01/2005'),
(108, '02/02/2005'),
(109, '10/10/2005');
This is the CTE I created, which summarizes the data into its year counts, and the queries against the CTE.
WITH MyData AS (
SELECT
DATEPART(year, [Date]) AS [Year],
COUNT(*) AS ct
FROM MyTable
GROUP BY Datepart(year, [Date])
)
-- Now we issue the SELECT statement against the CTE itself
SELECT *
FROM MyData
WHERE ct = (SELECT MAX(ct) FROM MyData)
And here is the output:
Year ct
2004 3
2005 3

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)

How do I compare dates in one SQL table to a range defined in another table?

I have one table holding events and dates:
NAME | DOB
-------------------
Adam | 6/26/1999
Barry | 7/18/2005
Daniel| 1/18/1984
I have another table defining date ranges as either start or end times, each with a descriptive code:
CODE | DATE
---------------------
YearStart| 6/28/2013
YearEnd | 8/14/2013
I am trying to write SQL that will find all Birthdates that fall between the start and end of the times described in the second table. The YearStart will always be in June, and the YearEnd will always be in August. My thought was to try:
SELECT
u.Name
CAST(MONTH(u.DOB) AS varchar) + '/' + CAST(DAY(u.DOB) AS varchar) as 'Birthdate',
u.DOB as 'Birthday'
FROM
Users u
WHERE
MONTH(DOB) = '7' OR
(MONTH(DOB) = '6' AND DAY(DOB) >= DAY(SELECT d.Date FROM Dates d WHERE d.Code='YearStart')) OR
(MONTH(DOB) = '8' AND DAY(DOB) <= DAY(SELECT d.Date FROM Dates d WHERE d.Code='YearEnd')))
ORDER BY
MONTH(DOB) ASC, DAY(DOB) ASC
But this doesn't pass, I'm guessing because there is no guarantee that the internal SELECT statement will return only one row, so cannot be parsed as a datetime. How do I actually accomplish this query?
This seems strange and I still feel like we're missing a relevant piece of the requirements, but look at the following. It seems from your description that the years are irrelevant and you want birthdays that fall between the given months/days.
SELECT
t1.Name, t1.DOB
FROM
t1
JOIN t2 AS startDate ON (startDate.Code = 'YearStart')
JOIN t2 AS endDate ON (endDate.Code = 'YearEnd')
WHERE
STUFF(CONVERT(varchar, t1.DOB, 112), 1, 4, '') BETWEEN
STUFF(CONVERT(varchar, startDate.[Date], 112), 1, 4, '')
AND
STUFF(CONVERT(varchar, endDate.[Date], 112), 1, 4, '')
Try using a PIVOT to get the years on the same row, like this. This will return only 'Bob'
DECLARE #Names TABLE(
NAME VARCHAR(20),
DOB VARCHAR(10));
DECLARE #Dates TABLE(
CODE VARCHAR(20),
THEDATE VARCHAR(10));
INSERT #Names (NAME,DOB) VALUES ('Adam', '6/26/1999');
INSERT #Names (NAME,DOB) VALUES ('Daniel', '1/18/1984');
INSERT #Names (NAME,DOB) VALUES ('Bob', '7/1/2013');
INSERT #Dates (CODE,THEDATE) VALUES ('YearStart', '6/28/2013');
INSERT #Dates (CODE,THEDATE) VALUES ('YearEnd', '8/14/2013');
SELECT * FROM #Names;
SELECT * FROM #Dates;
SELECT n.*
FROM #Names AS n
INNER JOIN (
SELECT
1 AS YearTypeId
, [YearStart]
, [YearEnd]
FROM ( SELECT [CODE]
, THEDATE
FROM #Dates
) p PIVOT ( MIN(THEDATE)
FOR [CODE]
IN ([YearStart],[YearEnd])
) AS pvt) AS y
ON
n.DOB >= y.YearStart
AND n.DOB <= y.YearEnd
From the last paragraph in your question, I am assuming that the Dates table have one YearStart and one YearEnd row for each year, correct? If so, your SQL query should include the year you are interrested in.
Also, even if "date" is not strictly speaking a reserved word for SQL Server (see Reserved Keywords for Transact SQL), you should avoid using such column names since, for example, ODBC does not allow them.
But to do something with only the information that you have already provided, you could do something like this to get the birthday celebrants for the last year defined in Dates (providing there really is both a YearStart and YearEnd entry for that year):
SELECT DISTINCT <the rest as in your example>
FROM Users u
WHERE u.DOB >= (SELECT max(d1.Date) FROM Dates d1 WHERE d1.Code = 'YearStart')
AND u.DOB <= (SELECT max(d2.Date) FROM Dates d2 WHERE d2.Code = 'YearEnd')
ORDER BY u.DOB;
The main difference between the query above (which I have not tested - this is just to show the principle) and your post is that I trust the datetime type (or whichever variant of it that you have used in the database) to work as intended. What I mean by that is that the database engine is well aware (well, it should be) of which of two full dates is the earliest and the latest - you do not have to extract and compare their components separately.
/Bosse