Get list of counts by date - sql

I have two tables. One containing a list of applications. And another one containing counts associated to them every week. Now I want to get as a result the app name and the count for this week and the previous. Let me explain this.
app:
+----+-------------+
| id | name |
+----+-------------+
| 1 | Office 2007 |
+----+-------------+
| 2 | Office 2010 |
+----+-------------+
| 3 | Office 2013 |
+----+-------------+
count:
+----+--------+-------+------------+
| id | app_id | count | date |
+----+--------+-------+------------+
| 1 | 1 | 200 | 2016-01-11 |
+----+--------+-------+------------+
| 2 | 2 | 500 | 2016-01-11 |
+----+--------+-------+------------+
| 3 | 3 | 750 | 2016-01-11 |
+----+--------+-------+------------+
| 4 | 1 | 180 | 2016-01-18 |
+----+--------+-------+------------+
| 5 | 2 | 378 | 2016-01-18 |
+----+--------+-------+------------+
| 6 | 3 | 1000 | 2016-01-18 |
+----+--------+-------+------------+
And this is the result I need. I need all the applications with the count of this week and the previous:
+-------------+-----------------+-----------------+
| app | count_this_week | count_prev_week |
+-------------+-----------------+-----------------+
| Office 2007 | 180 | 200 |
+-------------+-----------------+-----------------+
| Office 2010 | 378 | 500 |
+-------------+-----------------+-----------------+
| Office 2013 | 1000 | 750 |
+-------------+-----------------+-----------------+
A script runs every week which fills the count table. And now I need to get a report also on a weekly basis.
Honestly I'm a bit lost as I don't know how to declare the conditions for the columns.

You can try to group first by DATEPART(WEEK,C.date),name and then split the counts into 2 columns using another GROUP BY. Something like this
EDIT
If there are exactly 1 record per week per app, you can do with just one group by like this.
SELECT
appname,
SUM(CASE WHEN weekno = 0 THEN sumcount ELSE 0 END) as thisweek,
SUM(CASE WHEN weekno = 1 THEN sumcount ELSE 0 END) as lastweek
FROM
(
SELECT
DATEPART(WEEK,CURRENT_TIMESTAMP) - DATEPART(WEEK,C.date) as weekno,
name as appname,
count as sumcount
FROM App A
INNER JOIN CountTable C ON A.[id] = C.[app_id]
WHERE DATEPART(WEEK,C.date) BETWEEN DATEPART(WEEK,CURRENT_TIMESTAMP) - 1 AND DATEPART(WEEK,CURRENT_TIMESTAMP)
)T
GROUP BY appname
Query
SELECT
appname,
SUM(CASE WHEN weekno = 0 THEN sumcount ELSE 0 END) as thisweek,
SUM(CASE WHEN weekno = 1 THEN sumcount ELSE 0 END) as lastweek
FROM
(
SELECT
DATEPART(WEEK,CURRENT_TIMESTAMP) - DATEPART(WEEK,C.date) as weekno,
name as appname,
SUM(count) as sumcount
FROM App A INNER JOIN CountTable C ON A.[id] = C.[app_id]
WHERE DATEPART(WEEK,C.date) BETWEEN DATEPART(WEEK,CURRENT_TIMESTAMP) - 1 AND DATEPART(WEEK,CURRENT_TIMESTAMP)
GROUP BY DATEPART(WEEK,C.date),name
) AS T
GROUP BY appname
SQL Fiddle
Output
| appname | thisweek | lastweek |
|-------------|----------|----------|
| Office 2007 | 180 | 200 |
| Office 2010 | 378 | 500 |
| Office 2013 | 1000 | 750 |

You can use this generic query with a variable for the current week day:
DECLARE #week date = '2016-01-18';
WITH data AS (
SELECT a.name, c.[count]
, w = CASE WHEN c.[date] = #week THEN 0 ELSE 1 END
FROM #Counts c
INNER JOIN #Apps a ON c.app_id = a.id
WHERE [date] = #week OR [date] = DATEADD(day, -7, #week)
)
SELECT App = name, count_this_week = [0], count_prev_week = [1]
FROM data d
PIVOT (
MAX([count])
FOR w IN ([0], [1])
) p
Output:
App count_this_week count_prev_week
Office 2007 180 200
Office 2010 378 500
Office 2013 1000 750
Your data:
DECLARE #Apps TABLE ([id] int, [name] varchar(11));
DECLARE #Counts TABLE([id] int, [app_id] int, [count] int, [date] date);
INSERT INTO #Apps([id], [name])
VALUES
(1, 'Office 2007'),
(2, 'Office 2010'),
(3, 'Office 2013')
;
INSERT INTO #Counts([id], [app_id], [count], [date])
VALUES
(1, 1, 200, '2016-01-11'),
(2, 2, 500, '2016-01-11'),
(3, 3, 750, '2016-01-11'),
(4, 1, 180, '2016-01-18'),
(5, 2, 378, '2016-01-18'),
(6, 3, 1000, '2016-01-18')
;

SELECT *
FROM count
JOIN app ON app.id=count.app_id
WHERE date BETWEEN '2016-01-18' AND '2016-01-11'

Related

Identify two rows with 1 year or more of difference

I have a table called finance that I store all payment of the customer. The main columns are: ID,COSTUMERID,DATEPAID,AMOUNTPAID.
What I need is a list of dates by COSTUMERID with dates of its first payment and any other payment that is grater than 1 year of the last one. Example:
+----+------------+------------+------------+
| ID | COSTUMERID | DATEPAID | AMOUNTPAID |
+----+------------+------------+------------+
| 1 | 1 | 2015-01-10 | 10 |
| 2 | 1 | 2016-01-05 | 30 |
| 2 | 1 | 2017-02-20 | 30 |
| 3 | 2 | 2016-03-15 | 100 |
| 4 | 2 | 2017-02-15 | 100 |
| 5 | 3 | 2017-05-01 | 25 |
+----+------------+------------+------------+
What I expect as result:
+------------+------------+
| COSTUMERID | DATEPAID |
+------------+------------+
| 1 | 2015-01-01 |
| 1 | 2017-02-20 |
| 2 | 2016-03-15 |
| 3 | 2017-05-01 |
+------------+------------+
Costumer 1 have 2 dates: the first one + one more that have more then 1 year after the last one.
I hope I make my self clear.
I think you just want lag():
select t.*
from (select t.*,
lag(datepaid) over (partition by customerid order by datepaid) as prev_datepaid
from t
) t
where prev_datepaid is null or
datepaid > dateadd(year, 1, prev_datepaid);
Gordon's solution is correct, as long as you are only looking at the previous row (previous payment) diff, but I wonder if Antonio is looking for payments greater than one year from the last 1 year payment, in which case this becomes a more complex problem to solve. Take the following example:
CREATE TABLE #Test (
CustomerID smallint
,DatePaid date
,AmountPaid smallint )
INSERT INTO #Test
SELECT 1, '2015-1-10', 10
INSERT INTO #Test
SELECT 1, '2016-1-05', 30
INSERT INTO #Test
SELECT 1, '2017-2-20', 30
INSERT INTO #Test
SELECT 1, '2017-6-30', 50
INSERT INTO #Test
SELECT 1, '2018-3-5', 50
INSERT INTO #Test
SELECT 1, '2018-5-15', 50
INSERT INTO #Test
SELECT 2, '2016-3-15', 100
INSERT INTO #Test
SELECT 2, '2017-6-15', 100
WITH CTE AS (
SELECT
CustomerID
,DatePaid
,LAG(DatePaid) OVER (PARTITION BY CustomerID ORDER BY DatePaid) AS PreviousPaidDate
,AmountPaid
FROM #Test )
SELECT
*
,-DATEDIFF(DAY, DatePaid, PreviousPaidDate) AS DayDiff
,CASE WHEN DATEDIFF(DAY, PreviousPaidDate, DatePaid) >= 365 THEN 1 ELSE 0 END AS Paid
FROM CTE
Row number 5 is > 1 year from the last 1 year payment, but subtracting from previous row doesn't address this. This may or may not matter but I wanted to point it out in case that is what he means.

SQL Server: how to do 3 months' partition?

I want to count the number of rows in the partition 0-3months. Months are specified by MYMONTH in the format such that 201601 for 2016 January. I am using SQL Server 2014. How can I do the partition over 3 months?
SELECT COUNT(*),
COUNT(*)
/
(COUNT(*) OVER (PARTITION
BY MYMONTH RANGE BETWEEN 3 MONTH PRECEDING AND CURRENT MONTH))
FROM myData
Sample
| Month | Value | ID |
-------------------------|
| 201601 | 1 | X |
| 201601 | 1 | Y |
| 201601 | 1 | Y |
| 201602 | 1 | Z |
| 201603 | 1 | A |
| 201604 | 1 | B |
| 201605 | 1 | C |
| 201607 | 1 | E |
| 201607 | 10 | EE |
| 201607 | 100 | EEE|
Counts
| Month | Count | Count3M | Count/Count3M |
-------------------------------------------
| 201601| 3 | 3 | 3/3 |
| 201602| 1 | 4 | 1/4 |
| 201603| 1 | 5 | 1/5 |
| 201604| 1 | 6 | 1/6 |
| 201605| 1 | 4 | 1/4 |
| 201607| 3 | 5 | 3/5 |
You can try this (MSSQL 2012):
Sample data
CREATE TABLE mytable(
MONT INTEGER NOT NULL
,Value INTEGER NOT NULL
,ID VARCHAR(5) NOT NULL
);
INSERT INTO mytable(MONT,Value,ID) VALUES (201601,1,'X');
INSERT INTO mytable(MONT,Value,ID) VALUES (201601,1,'Y');
INSERT INTO mytable(MONT,Value,ID) VALUES (201601,1,'Y');
INSERT INTO mytable(MONT,Value,ID) VALUES (201602,1,'Z');
INSERT INTO mytable(MONT,Value,ID) VALUES (201603,1,'A');
INSERT INTO mytable(MONT,Value,ID) VALUES (201604,1,'B');
INSERT INTO mytable(MONT,Value,ID) VALUES (201605,1,'C');
INSERT INTO mytable(MONT,Value,ID) VALUES (201607,1,'E');
INSERT INTO mytable(MONT,Value,ID) VALUES (201607,10,'EE');
INSERT INTO mytable(MONT,Value,ID) VALUES (201607,100,'EEE');
Query 1
SELECT MONT, RC, RC+ LAG(RC,3,0) OVER ( ORDER BY MONT)+ LAG(RC,2,0) OVER ( ORDER BY MONT) + LAG(RC,1,0) OVER ( ORDER BY MONT) AS RC_3M_PREC -- + COALESCE( LEAD(RC) OVER ( ORDER BY MONT),0) AS RC_3M
FROM (SELECT MONT
, COUNT(*) RC
FROM mytable
GROUP BY MONT
) A
Output:
MONT RC RC_3M_PREC
----------- ----------- -----------
201601 3 3
201602 1 4
201603 1 5
201604 1 6
201605 1 4
201607 3 6
Or using what you proposed (option ROWS ... PRECEDING):
Query 2:
SELECT MONT, RC
, COALESCE(SUM(RC) OVER (ORDER BY MONT ROWS BETWEEN 3 PRECEDING AND CURRENT ROW),0) AS RC_3M
FROM (SELECT MONT
, COUNT(*) RC
FROM mytable
GROUP BY MONT
) A
Output:
MONT RC RC_3M
----------- ----------- -----------
201601 3 3
201602 1 4
201603 1 5
201604 1 6
201605 1 4
201607 3 6
If you want to count rows in the previous three months, just use conditional aggregation. You do need a way to enumerate the months:
SELECT COUNT(*),
SUM(CASE WHEN yyyymm_counter <= 3 THEN 1 ELSE 0 END)
FROM (SELECT md.*,
DENSE_RANK() OVER (ORDER BY MYMONTH DESC) as yyyymm_counter
FROM myData md
) md;
Another way without the subquery converts the month value to an actual date. Let me assume that it is a string:
SELECT COUNT(*),
SUM( CASE WHEN DATEDIFF(month, CAST(MYMONTH + '01' as DATE), GETDATE()) <= 3
THEN 1 ELSE 0
END)
FROM MyData;
I've left the / out of the answer. You need to be aware that SQL Server does integer division, so you may not get the results you want -- unless you convert values to non-integer number (I would suggest multiplying by 1.0 or using 1.0 instead of 1 in the queries).

SQL - Count number of itemactive within a date rate

I have a dataset of Resources, Projects, StartDate and EndDate.
Each Resource can be utilised by multiple projects.
I want to get a count of the number of projects that are using a resource in each quarter.
So if project starts in Q1 of a particular year and ends in Q3 that year, and project2 starts in Q2 and ends in Q3, I want to get a count of 2 projects for Q2, since during Q1 both project1 and project2 were active.
Here is my dataset:
create table Projects
(Resource_Name varchar(20)
,Project_Name varchar(20)
,StartDate varchar(20)
,EndDate varchar(20)
)
insert into Projects values('Resource 1','Project A','15/01/2013','1/11/2014')
insert into Projects values('Resource 1','Project B','1/03/2013','1/09/2016')
insert into Projects values('Resource 1','Project C','1/04/2013','1/09/2015')
insert into Projects values('Resource 1','Project D','1/06/2013','1/03/2016')
insert into Projects values('Resource 1','Project E','15/01/2013','1/09/2015')
insert into Projects values('Resource 1','Project F','3/06/2013','1/11/2015')
And here is the result I'm looking for:
Resource Name| Year | Quarter|Active Projects
Resource 1 2013 1 2
Resource 1 2013 2 6
Using tally table:
Using the dates from Projects, generate a list of all quarters and their start dates and end dates, in this example, that is CteQuarter(sd, ed). After that, you simply need to JOIN the Projects table to CteQuarter for overlapping dates. Then finally, GROUP BY using the YEAR and Quarter part of the date.
SQL Fiddle
WITH CteYear(yr) AS(
SELECT number
FROM master..spt_values
WHERE
type = 'P'
AND number >= (SELECT MIN(YEAR(CONVERT(DATE, StartDate, 103))) FROM Projects)
AND number <= (SELECT MAX(YEAR(CONVERT(DATE, EndDate, 103))) FROM Projects)
),
CteQuarter(sd, ed) AS(
SELECT
DATEADD(QUARTER, q.n - 1, DATEADD(YEAR, cy.yr - 1900, 0)),
DATEADD(DAY, -1, DATEADD(QUARTER, q.n, DATEADD(YEAR, cy.yr - 1900, 0)))
FROM CteYear AS cy
CROSS JOIN(
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
) AS q(n)
)
SELECT
p.Resource_Name,
[Year] = DATEPART(YEAR, q.sd),
[Quarter] = DATEPART(QUARTER, q.sd),
[Active Projects] = COUNT(*)
FROM Projects p
INNER JOIN CteQuarter q
ON CONVERT(DATE, StartDate, 103) <= q.ed
AND CONVERT(DATE, EndDate, 103) >= q.sd
GROUP BY
p.Resource_Name,
DATEPART(YEAR, q.sd),
DATEPART(QUARTER, q.sd)
ORDER BY
p.Resource_Name,
DATEPART(YEAR, q.sd),
DATEPART(QUARTER, q.sd)
Notes:
Here is a great way to check for overlapping dates.
Some common date routines
RESULT:
| Resource_Name | Year | Quarter | Active Projects |
|---------------|------|---------|-----------------|
| Resource 1 | 2013 | 1 | 3 |
| Resource 1 | 2013 | 2 | 6 |
| Resource 1 | 2013 | 3 | 6 |
| Resource 1 | 2013 | 4 | 6 |
| Resource 1 | 2014 | 1 | 6 |
| Resource 1 | 2014 | 2 | 6 |
| Resource 1 | 2014 | 3 | 6 |
| Resource 1 | 2014 | 4 | 6 |
| Resource 1 | 2015 | 1 | 5 |
| Resource 1 | 2015 | 2 | 5 |
| Resource 1 | 2015 | 3 | 5 |
| Resource 1 | 2015 | 4 | 3 |
| Resource 1 | 2016 | 1 | 2 |
| Resource 1 | 2016 | 2 | 1 |
| Resource 1 | 2016 | 3 | 1 |
You can do this by determining the first and last quarters when a project is active, and then using cumulative sum. In SQL Server 2012+, this looks like
select resource_name, yyyyq,
(sum(sum(s)) over (partition by resource_name order by yyyyq) -
sum(sum(e)) over (partition by resource_name order by yyyyq) +
e
) as activeProjects
from ((select resource_name, datepart(year, startdate) + datepart(quarter, startdate) as yyyyq, 1 as s, 0 as e
from projects
) union all
(select resource_name, datepart(year, enddate) + datepart(quarter, enddate), 0 as s, 1 as e
from projects
)
) yq
group by resource_name, yyyyq;
In earlier versions, you can do something similar with cross apply.

SQL Server 2008 How can I SELECT rows with Value by GROUP(ing) other column?

Hi guys I'm a beginner at SQL so please bear with me.. :)
My question is as follows.
I got this table:
DateTime ID Year Month Value Cost
-------------------|------|--------|-------|-------|--------|
1-1-2013 00:00:01 | 1 | 2013 | 1 | 30 | 90 |
1-1-2013 00:01:01 | 1 | 2013 | 1 | 0 | 0 |
1-1-2013 00:02:01 | 1 | 2013 | 1 | 1 | 3 |
1-2-2013 00:00:01 | 1 | 2013 | 2 | 2 | 6 |
1-2-2013 00:01:01 | 1 | 2013 | 2 | 3 | 9 |
1-2-2013 00:02:01 | 1 | 2013 | 2 | 4 | 12 |
1-3-2013 00:00:01 | 1 | 2013 | 3 | 5 | 15 |
1-3-2013 00:01:01 | 1 | 2013 | 3 | 6 | 18 |
1-3-2013 00:02:01 | 1 | 2013 | 3 | 7 | 21 |
Now what I'm trying to get is this result
Year Month Value Cost
|--------|-------|-------|--------|
| 2013 | 1 | 1 | 3 |
| 2013 | 2 | 4 | 12 |
| 2013 | 3 | 7 | 21 |
As you can see I'm trying to GROUP BY the [Month] and the [Year] and to get the last [Value] for every [Month].
Now as you can understand from the result I do not try to get the MAX() value from the [Value] column but the last value for every [Month] and that is my issue..
Thanks in advance
PS
I was able to GROUP BY the [Year] and the [Month] but as I understand that when I adding the [Value] column the GROUP BY is not effecting the result, as the SQL need more spcification on the value you what the SQL to get..
Instead of using row_number(), you can also use rank(). Using rank() might give you multiple values within the same year and month, see this post.
Because of this, a group by is added.
SELECT
[Year],
[Month],
[Value],
[Cost]
FROM
(
SELECT
[Year],
[Month],
[Value],
[Cost],
Rank() OVER (PARTITION BY [Year], [Month] ORDER BY [DateTime] DESC) AS [Rank]
FROM [t1]
) AS [sub]
WHERE [Rank] = 1
GROUP BY
[Year],
[Month],
[Value],
[Cost]
ORDER BY
[Year] ASC,
[Month] ASC
As stated in the comments, this might still return multiple records for a single month. Therefor the ORDER BY statement can be extended, based on the desired functionality:
Rank() OVER (PARTITION BY [Year], [Month] ORDER BY [DateTime] DESC, [Value] DESC, [Cost] ASC) AS [Rank]
Switching the order of [Value] and [Cost] or ASC <> DESC will influence the rank and because of that the result.
Since you are using SQL Server 2008, you can use row_number() to get the result:
select year, month, value, cost
from
(
select year, month, value, cost,
row_number() over(partition by year, month order by datetime desc) rn
from yourtable
) src
where rn = 1
See SQL Fiddle with Demo
Or you can use a subquery to get this (note: with this version if you have more than one record with the same max datetime per month then you will return each record:
select t1.year, t1.month, t1.value, t1.cost
from yourtable t1
inner join
(
select max(datetime) datetime
from yourtable
group by year, month
) t2
on t1.datetime = t2.datetime
See SQL Fiddle with Demo
Both give the same result:
| YEAR | MONTH | VALUE | COST |
-------------------------------
| 2013 | 1 | 1 | 3 |
| 2013 | 2 | 4 | 12 |
| 2013 | 3 | 7 | 21 |

Sql query: Link data to all days between two given dates

I have a table with that looks like
Date | BookID | Rating | Review
2012-10-12 | 2 | 3 | 3
2012-10-13 | 2 | 7 | 9
2012-10-16 | 3 | 4 | 2
Each day, a book can receive a number of rating and review
I am trying to write a query that given two dates, it displays the rating and review for each book, for EACH day in between the two given dates. Even when that day has no rating or no review I still want to display the date and the BookID
For example, in the table above, I want the query to return something like
Date | BookID | Rating | Review
2012-10-12 | 2 | 3 | 3
2012-10-13 | 2 | 7 | 9
2012-10-14 | 2 | null | null
2012-10-15 | 2 | null | null
2012-10-16 | 3 | 4 | 2
How can I do this please?
--set up test data
DECLARE #Reviews TABLE([Date] date, BookId int, Rating int, Review int)
INSERT INTO #Reviews VALUES
('2012-10-12', 2, 3, 3),
('2012-10-13', 2, 7, 9),
('2012-10-16', 3, 4, 2)
--setup query parameters
DECLARE #StartDate date,
#EndDate date
SELECT #StartDate = '2012-10-12',
#EndDate = '2012-10-16'
--run query
;WITH IntervalSet AS
(
SELECT #StartDate AS [Date]
UNION ALL
SELECT DATEADD(DAY,1,[Date]) AS [Date]
FROM IntervalSet
WHERE DATEADD(DAY,1,[Date])<=#EndDate
)
SELECT I.[Date], B.BookId, R.Rating, R.Review
FROM
(
SELECT BookId, Min([Date]) as EarliestDate
FROM #Reviews
GROUP BY BookId
) B
JOIN IntervalSet I
ON I.[Date] >= b.EarliestDate
LEFT
JOIN #Reviews R
ON R.[Date] = I.[Date]
AND R.BookId = B.BookId
ORDER BY I.[Date], B.BookId
OPTION(MAXRECURSION 0)
This gives the results:
Date BookId Rating Review
---------- ----------- ----------- -----------
2012-10-12 2 3 3
2012-10-13 2 7 9
2012-10-14 2 NULL NULL
2012-10-15 2 NULL NULL
2012-10-16 2 NULL NULL
2012-10-16 3 4 2
That's one extra row than the results you posted, but I believe is correct - it doesn't return any rows for a bookid before the first review which looks like what you were after
Something like this:
SQLFIDDLEEXAMPLE
WITH mycte AS
(
SELECT cast('2012-01-01' AS datetime) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue + 1 < '2014-12-31'
)
SELECT
CONVERT(char(10), mycte.DateValue,126) as Date,
b.BookID,
(SELECT Rating FROM tbl WHERE BookID = b.BookID
AND Date = mycte.DateValue) as Rating,
(SELECT Review FROM tbl WHERE BookID = b.BookID
AND Date = mycte.DateValue) as Review
FROM mycte, (SELECT distinct BookID
FROM tbl) b
WHERE mycte.DateValue >= '2012-10-12'
AND mycte.DateValue <= '2012-10-15'
OPTION (MAXRECURSION 0)
Result:
| DATE | BOOKID | RATING | REVIEW |
-----------------------------------------
| 2012-10-12 | 2 | 3 | 3 |
| 2012-10-12 | 3 | (null) | (null) |
| 2012-10-13 | 2 | 7 | 9 |
| 2012-10-13 | 3 | (null) | (null) |
| 2012-10-14 | 2 | (null) | (null) |
| 2012-10-14 | 3 | (null) | (null) |
| 2012-10-15 | 2 | (null) | (null) |
| 2012-10-15 | 3 | (null) | (null) |