Count the number of rows each month in SQL Server - sql

I'm using SQL Server. I've a following table Orders:
Orders (Id, ItemId, CustomerId, Quantity, OrderDateTime)
I want to count the number of orders each month. I've written 2 of the following query.
Query #1:
SELECT
MONTH(OrderDateTime) AS MonthCol,
YEAR(OrderDateTime) AS YearCol,
COUNT(id) AS OrderCount
FROM
Orders
WHERE
OrderDateTime >= '2000' AND OrderDateTime <= '2018'
GROUP BY
YEAR(OrderDateTime), MONTH(OrderDateTime)
ORDER BY
YearCol, MonthCol
Query #2:
SELECT
DATEPART(mm, OrderDateTime) AS Month,
COUNT(*) AS OrderCount
FROM
Orders
WHERE
OrderDateTime >= '2000' AND OrderDateTime <= '2018'
GROUP BY
DATEPART(mm, OrderDateTime)
Issue with both queries is that I'm not getting the columns with 0 orders. How will I get it?

SQL will not give you data about months and year which do not exist as rows. To get 0 order rows you'd need to right join the results with a calendar table containing all needed months and years or you can also use a tally table.
Select T.MonthCol, T.YearCol,OrderCount= COALESCE(OrderCount,0)
from
(
SELECT MONTH(OrderDateTime) AS MonthCol, YEAR(OrderDateTime) AS YearCol, count(id) AS OrderCount
FROM Orders
WHERE OrderDateTime >= '2000' AND OrderDateTime <= '2018'
GROUP BY YEAR(OrderDateTime), MONTH(OrderDateTime)
ORDER BY YearCol, MonthCol)
P
RIGHT JOIN
(
select * from
( values (2000),(2001),(2002),(2003),(2004),(2005),(2006),(2007),(2008))v(YearCol)
cross join
( values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12))u(MonthCol)
)T
on P.MonthCol=T.MonthCol
and P.YearCol=T.YearCol

I would be inclined to use a recursive CTE for this -- this gives pretty easy flexibility on the range you want:
with dates as (
select cast('2000-01-01' as date) dte
union all
select dateadd(month, 1, dte)
from dates
where dte < '2018-12-01'
)
select year(OrderDateTime) AS year,
month(OrderDateTime) AS month,
count(o.id) as OrderCount
from dates left join
orders o
on d.OrderDateTime >= dates.dte and
d.OrderDateTime < dateadd(month, 1, dates.dte)
group by year(OrderDateTime), month(OrderDateTime)
order by year(OrderDateTime), month(OrderDateTime)
option (maxrecursion 0);
Notes:
This uses the JOIN to do the filtering. This makes it safer to change the range that you are looking for.
I find the year() and month() functions to be more convenient datepart().
When using date parts, spell them out. Why waste brain power trying to remember if mm really means months or minutes?
I added an order by. Presumably you want the results in chronological order.

Related

SQL How can I get a count of messages going out by month

I have a table that sends out messages, I would like to get a total count of the messages that have been going out month by month over the last year . I am new to SQL so I am having trouble with it . I am using MSSQL 2012 this is my sql
SELECT sentDateTime, MessageID, status AS total, CONVERT(NVARCHAR(10), sentDateTime, 120) AS Month
FROM MessageTable
WHERE CAST(sentDateTime AS DATE) > '2017-04-01'
GROUP BY CONVERT(NVARCHAR(10), sentDateTime, 120), sentDateTime, MessageID, status
ORDER BY Month;
I think the month() and year() functions are more convenient than datepart() for this purpose.
I would go for:
select year(sentDateTime) as yr, month(sentDateTime) as mon, count(*)
from MessageTable
where sentDateTime > '2017-04-01'
group by year(sentDateTime), month(sentDateTime)
order by min(sentDateTime);
Additional notes:
Only include the columns in the select that you care about. This would be the ones that define the month and the count.
Only include the columns in the group by that you care about. Every combination of the expressions in the group by found in the data define a column.
There is no need to convert sentDateTime to a date explicitly for the comparison.
The order by orders the results by time. Using the min() is a nice convenience.
Including the year() makes sure you don't make a mistake -- say by including data from 2018-04 with 2017-04.
-- this selects the part of the date you are looking for, replace this with the date format you are using, this should give you what you are looking for
SELECT DATEPART(mm, GETDATE())
SELECT COUNT(DATEPART(mm, sentDateTime)), MessageID, status
From MessageTable where Cast(sentDateTime as date) > '2017-04-01'
group by DATEPART(mm, sentDateTime), MessageID, status
order by DATEPART(mm, sentDateTime)
You can group by the month number of the sentDateTime with the function DATEPART(MONTH, sentDateTime). The next select will also yield results if no message was sent for a particular month (total = 0).
;WITH PossibleMonths AS
(
SELECT
M.PossibleMonth
FROM
(VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) M(PossibleMonth)
),
MonthTotals AS
(
select
COUNT(1) AS Total,
DATEPART(MONTH, sentDateTime) [Month]
From
MessageTable
where
Cast(sentDateTime as date) > '2017-04-01'
group by
DATEPART(MONTH, sentDateTime)
)
SELECT
P.PossibleMonth,
Total = ISNULL(T.Total, 0)
FROM
PossibleMonths AS P
LEFT JOIN MonthTotals AS T ON P.PossibleMonth = T.Month

Joining 3 grouped values

I have 3 queries that counts for each company, number of rows during certain month, from 3 different tables, and returning the same columns :qty, month and company_name.
Instead of this I need to return 1 table with same 3 columns but qty must sum the value of all the 3 separated queries.
Can you suggest the best way to join or execute it in which I will not loose speed of execution.
Here is example of one of queries, the other 2 queries has exactly the same syntax, just instead T_CUSTSK, they use T_CUSTSK2 and T_CUSTSK3:
SELECT
COUNT(*) as qty,
DATEPART (MONTH, [start_date]) AS [month],
T_SYSCOM.company_name
FROM
T_CUSTSK
INNER JOIN
T_SYSCOM ON T_CUSTSK.company_id = T_SYSCOM.company_id
WHERE
DATEPART (MONTH, [start_date]) = 12
GROUP BY
DATEPART (MONTH, [start_date]), T_SYSCOM.company_name
ORDER BY
month, qty DESC
Union all will combine your queries.
You can then wrap your statement with another query that sums:
select sum(qty), month, company_name from (
Select count(*) as qty, datepart(month, [start_date]) as [month], T_SYSCOM.company_name
from T_CUSTSK
INNER JOIN T_SYSCOM
ON T_CUSTSK.company_id=T_SYSCOM.company_id
where datepart(month, [start_date])=12
group by datepart(month, [start_date]), T_SYSCOM.company_name
order by month, qty DESC
union all
<second query>
union all
<third query> )
group by month, company_name
Try this you could do UNION ALL of all the T_CUSTSKx tables in the subquery and join that result once with T_SYSCOM table.
I'm assuming the start_date is in T_CUSTSK table
Select count(*) as qty, datepart(month, [start_date]) as [month], T2.company_name
from (
SELECT company_id,start_date FROM T_CUSTSK
UNION ALL
SELECT company_id,start_date FROM T_CUSTSK2
UNION ALL
SELECT company_id,start_date FROM T_CUSTSK3
) T1
INNER JOIN #T_SYSCOM T2
ON T1.company_id=T2.company_id
where datepart(month, [start_date])=12
group by datepart(month, [start_date]), T2.company_name
order by month, qty DESC
kindly let me know if this works

Use a view to determine if a previous date exists

Currently, I have an orders table that I want to extract information from. There are 4 main fields I am looking for: Order no, customer no, date1, and date 2.
Date 1 is the minimum date in the table for a specific customer number prior to the date associated with the order number within a 12 month period (that's not the order number date).
Date 2 is the minimum date in the table for a specific customer number prior to date1 within a 12 month period (that's not date 1)
Here is what I have so far but it doesn't seem to be pulling in any data. Any help is appreciated!
With CTE as
(SELECT Order_no, customer_no, MIN(order_date) AS date1
FROM dbo.orders
WHERE (order_date >= DATEADD(month, - 12, order_date)
and (order_date < DATEADD(day, - 1, order_date)))
GROUP BY Order_no, customer_no)
Select x.order_no, x.customer_no, min(x.order_date) as date2, cte.date1
from dbo.orders x
LEFT OUTER JOIN CTE ON cte.order_no=x.order_no
where x.order_date > DATEADD(month, - 12, cte.date1)
AND x.order_date < DATEADD(DAY, - 1, cte.date1)
Group by x.order_no, x.customer_no, cte.date1
You need to figure out what is filtering based on the data you have. This is more about data meaning than syntax.
Start by making sure the cte is returning any records. and then make sure by checking the data that they are th records you wanted.
With CTE as
(SELECT Order_no, customer_no, MIN(order_date) AS date1
FROM dbo.orders
WHERE (order_date >= DATEADD(month, - 12, order_date)
and (order_date < DATEADD(day, - 1, order_date)))
GROUP BY Order_no, customer_no)
select * from CTE
Once you are sure of this try with just the join
With CTE as
(SELECT Order_no, customer_no, MIN(order_date) AS date1
FROM dbo.orders
WHERE (order_date >= DATEADD(month, - 12, order_date)
and (order_date < DATEADD(day, - 1, order_date)))
GROUP BY Order_no, customer_no)
Select *
from dbo.orders x
LEFT OUTER JOIN CTE ON cte.order_no=x.order_no
Then add the where criteria back in one at a time. This is the basic technique you use when the query does not havea a syntax issue but is not returning the correct data.
Based on the comments, this is what I would try next:
SELECT Order_no, customer_no,order_date, DATEADD(month, - 12, order_date) as ordermonth, DATEADD(day, - 1, order_date) as orderday
FROM dbo.orders
This will tell you why none of your records meet the criteria.

How to get a minimum value and the year it represents

I have a list of orders in a table. these all have a date against them. How do I write a query to return the minimum orders in a year and the associated year.
SELECT Max(YearCounts.[CountForYear]) AS [MinForYear]
FROM ( SELECT COUNT(PK) AS [CountForYear] FROM Orders
WHERE DATEPART( year , TransactionDate) > '2002'
GROUP BY DATEPART( Year, TransactionDate ) ) YearCounts
So I am looking for 98008 orders in 2003 as an example
Thanks for the answers, I checked them out, the sub query option executed fastest. I take on board the single quotes comments, thanks for the assistance.
Just use TOP 1
SELECT TOP 1 YearCounts.[CountForYear] AS [MinForYear],
YearCounts.[YEAR]
FROM ( SELECT COUNT(PK) AS [CountForYear],
DATEPART( year , TransactionDate) as [YEAR] FROM Orders
WHERE DATEPART( year , TransactionDate) > '2002'
GROUP BY DATEPART( Year, TransactionDate ) ) YearCounts
ORDER BY YearCounts.[CountForYear]
You can do this without a subquery, just by using top and order by:
SELECT TOP 1 DATEPART(year, TransactionDate), COUNT(PK) AS CountForYear
FROM Orders o
WHERE DATEPART(year , TransactionDate) > 2002
GROUP BY DATEPART(Year, TransactionDate )
ORDER BY COUNT(PK) DESC ;
You can also write this using the year function (which I personally find easier to read):
SELECT TOP 1 YEAR(TransactionDate), COUNT(PK) AS CountForYear
FROM Orders o
WHERE YEAR(TransactionDate) > 2002
GROUP BY YEAR(TransactionDate)
ORDER BY COUNT(PK) DESC ;
Your original query had single quotes around '2002'. This is unnecessary. You should express numeric constants without single quotes. Only use single quotes for string and date constants.

SQL Query for hours of the day

I have a SQL Server 2008 database with a table that has a column (datetime datatype) recording when an order is placed.
I would like to run a query that will give a breakdown of total amount of orders per hour on any given date or date range but am unsure of the best way to write the query.
It should be something along the lines of:
SELECT DATEPART(HOUR, OrderDate) AS [Hour], COUNT(*) AS [Count]
FROM [Orders]
WHERE OrderDate BETWEEN #StartDate AND #EndDate --or whatever criteria
GROUP BY DATEPART(HOUR, OrderDate)
You can use DATEPART and GROUP BY to get this:
SELECT DATEPART(Hour, DateField), COUNT(*)
FROM Orders
WHERE Date = #DateParam -- or your range check here
GROUP BY DATEPART(Hour, Datefield)
I believe it would be as simple as this:
SELECT
[Hour] = DATEPART(HOUR,DateTimeField)
,Orders = COUNT(*)
FROM
tblORDERS
GROUP BY
DATEPART(HOUR,DateTimeField)
You're probably looking for something like:
;WITH OrdersPerHour AS
(
SELECT
CAST(OrderDate AS DATE) 'OrderDate',
DATEPART(HOUR, OrderDate) AS 'Hour',
SalesAmount
FROM
dbo.YourOrderTable
)
SELECT
OrderDate, Hour,
COUNT(*), -- number of orders
SUM(SalesAmount) -- sum of the amounts for those orders
FROM
OrdersPerHour
WHERE
OrderDate = '20120113' -- or whatever you're looking for
GROUP BY
OrderDate, Hour
Not sure if you mean the total sum of the order amounts - or the number of orders - when you say breakdown of total amount of orders per hour - if you need the count, just use COUNT(*) instead of the SUM(SalesAmount) I put in my query.