Summary field values from some period - sql

In one table I have currency rate in some time period(five years). In another table, I have calculated data.
I have the task. I need to get statistics by every week summary from joined tables
I am having this at the moment:
DECLARE #Category_RentHouse INT = 3;
DECLARE #Category_Parents INT = 5;
DECLARE #Category_Salary INT = 9;
DECLARE #TestDateStart DATE = '2012-01-01';
DECLARE #TestDateFinish DATE = '2012-01-07';
select Weeks, SUM(Cash_Usd) TotalMoney
from (select CAST(RateDate AS DATE) Weeks,
CASE WHEN CategoryID = #Category_RentHouse THEN (TransactionAmount*(-1))
WHEN CategoryID = #Category_Parents THEN TransactionAmount
WHEN CategoryID = #Category_Salary THEN CAST((TransactionAmount /
RateValue) AS MONEY)
ELSE CAST((TransactionAmount*(-1) / RateValue) AS MONEY)
END AS Cash_Usd
FROM (select * from Marathon.dbo.Transactions T
LEFT JOIN IntermediateM.dbo.Rates R ON T.TransactionDate = R.RateDate) Y
) RR
WHERE Weeks BETWEEN #TestDateStart AND #TestDateFinish
GROUP BY DATEPART(week, Weeks), Weeks
ORDER BY Weeks
And result of run this small code is
But it would better if in fields Weeks and TotalSumm I will get the next:
Weeks TotalSumm
2012-01-07 -552...
2012-01-14 ....

I think you just need to fix the select and group by:
SELECT MIN(Dte) as Weeks, SUM(Cash_Usd) TotalMoney
FROM (SELECT CAST(RateDate AS DATE) as Dte,
(CASE WHEN CategoryID = #Category_RentHouse THEN (TransactionAmount*(-1))
WHEN CategoryID = #Category_Parents THEN TransactionAmount
WHEN CategoryID = #Category_Salary THEN CAST((TransactionAmount / RateValue) AS MONEY)
ELSE CAST((TransactionAmount*(-1) / RateValue) AS MONEY)
END) AS Cash_Usd
FROM Marathon.dbo.Transactions T LEFT JOIN
IntermediateM.dbo.Rates R
ON T.TransactionDate = R.RateDate
WHERE Weeks BETWEEN #TestDateStart AND #TestDateFinish
) RT
GROUP BY DATEPART(week, Weeks)
ORDER BY Weeks

Related

Counting New Unique Values in Growing Time Window

I have a large table of users (as a guid), some associated values, and a time stamp of when each row was inserted. A user might be associated with many rows in this table.
guid | <other columns> | insertdate
I want to count for each month: how many unique new users were inserted. It's easy to do manually:
select count(distinct guid)
from table
where insertdate >= '20060201' and insertdate < '20060301'
and guid not in (select guid from table where
insertdate >= '20060101' and insertdate < '20060201')
How could this be done for each successive month in sql?
I thought to use a rank function to associate clearly each guid with a month:
select guid,
,dense_rank() over ( order by datepart(YYYY, insertdate),
datepart(m, t.TransactionDateTime)) as MonthRank
from table
and then iterate upon each rank value:
declare #no_times int
declare #counter int = 1
set #no_times = select count(distinct concat(datepart(year, t.TransactionDateTime),
datepart(month, t.TransactionDateTime))) from table
while #no_times > 0 do
(
select count(*), #counter
where guid not in (select guid from table where rank = #counter)
and rank = #int + 1
#counter += 1
#no_times -= 1
union all
)
end
I know this strategy is probably the wrong way to go about things.
Ideally, I would like a result set to look like this:
MonthRank | NoNewUsers
I would be extremely interested and grateful if a sql wizard could point me in the right direction.
SELECT
DATEPART(year,t.insertdate) AS YearNum
,DATEPART(mm,t.insertdate) as MonthNum
,COUNT(DISTINCT guid) AS NoNewUsers
,DENSE_RANK() OVER (ORDER BY COUNT(DISTINCT t.guid) DESC) AS MonthRank
FROM
table t
LEFT JOIN table t2
ON t.guid = t2.guid
AND t.insertdate > t2.insertdate
WHERE
t2.guid IS NULL
GROUP BY
DATEPART(year,t.insertdate)
,DATEPART(mm,t.insertdate)
Use a left join to see if the table ever existed as a prior insert date and if they didn't then count them using aggregation like you normally would. If you want to add a rank to see which month(s) have the highest number of new users then you can use your DENSE_RANK() function but because you are already grouping by want you want you do not need a partition clause.
If you want the first time that a guid entered, then your query doesn't exactly work. You can get the first time with two aggregations:
select year(first_insertdate), month(first_insertdate), count(*)
from (select t.guid, min(insertdate) as first_insertdate
from t
group by t.guid
) t
group by year(first_insertdate), month(first_insertdate)
order by year(first_insertdate), month(first_insertdate);
If you are looking for counting guids each time they skip a month, then you can use lag():
select year(insertdate), month(insertdate), count(*)
from (select t.*,
lag(insertdate) over (partition by guid order by insertdate) as prev_insertdate
from t
) t
where prev_insertdate is null or
datediff(month, prev_insertdate, insertdate) >= 2
group by year(insertdate), month(insertdate)
order by year(insertdate), month(insertdate);
I solved it with the terrible while loop, then a friend helped me to solve it more efficiently in another way.
The loop version:
--ranked by month
select t.TransactionID
,t.BuyerUserID
,concat(datepart(year, t.InsertDate), datepart(month,
t.InsertDate)) MonthRankName
,dense_rank() over ( order by datepart(YYYY, t.InsertDate),
datepart(m, t.InsertDate)) as MonthRank
into #ranked
from table t;
--iteratate
declare #counter int = 1
declare #no_times int
select #no_times = count(distinct concat(datepart(year, t.InsertDate),
datepart(month, t.InsertDate))) from table t;
select count(distinct r.guid) as NewUnique, r.Monthrank into #results
from #ranked r
where r.MonthRank = 1 group by r.MonthRank;
while #no_times > 1
begin
insert into #results
select count(distinct rt.guid) as NewUnique, #counter + 1 as MonthRank
from #ranked r
where rt.guid not in
(
select rt2.guid from #ranked rt2
where rt2.MonthRank = #counter
)
and rt.MonthRank = #counter + 1
set #counter = #counter+1
set #no_times = #no_times-1
end
select * from #results r
This turned out to run pretty slowly (as you might expect)
What turned out to be faster by a factor of 10 was this method:
select t.guid,
cast (concat(datepart(year, min(t.InsertDate)),
case when datepart(month, min(t.InsertDate)) < 10 then
'0'+cast( datepart(month, min(t.InsertDate)) as varchar(10))
else cast (datepart(month, min(t.InsertDate)) as varchar(10)) end
) as int) as MonthRankName
into #NewUnique
from table t
group by t.guid;
select count(1) as NewUniques, t.MonthRankName from #NewUnique t
group by t.MonthRankName
order by t.MonthRankName
Simply identifying the very first month each guid appears, then counting the number of these occurring each month. With a bit of a hack to get YearMonth formatted nicely (this seems to be more efficient than format([date], 'yyyyMM') but need to experiment more on that.

Performance issues in T-SQL function

I have a table which provides me daily inventory status. I.e Inventory Item X, on a particular date had Y amount of quantity. I have created a function to obtain the purchase price on a particular day based on the last purchases.
When I run the query on the dailyinventorystatus table it completes within 3 minute for date > 2014-01-01. However, when I add in the function as a subquery it causes huge performance issues. It has been over 1.5 hours and the query is still running.
How do I improve this?
Here is the query:
SELECT
*,
RWReports.dbo.FindPurchasePrice(InventoryKey, Date , warehouse) as SalesPurchasePrice
FROM
DailyInventoryStatus
WHERE
Warehouse IN ('NYC,', 'CHICAGO', 'CHINA', 'ATLANTA')
AND Date >= '2014-01-01'
Here is the function:
CREATE FUNCTION [dbo].[FindPurchasePrice]
(#InventoryKey varchar(8), #InDate Date , #Warehouse varchar(30))
RETURNS REAL
AS
BEGIN
DECLARE #oPurchasePrice AS REAL ;
SELECT TOP (1)
#oPurchasePrice = UnitPurchasePrice
FROM
PurchaseTransactions
WHERE
InventoryKey = #InventoryKey
AND TransactionDate <= #InDate
AND Warehouse = #Warehouse
ORDER BY
TransactionDate DESC;
IF #oPurchasePrice IS NULL
SELECT
#oPurchasePrice = mw.cost
FROM
Rentalworks.dbo.masterwh mw
JOIN
Rentalworks.dbo.warehouse w ON w.warehouseid = mw.warehouseid
AND mw.masterid = #InventoryKey
AND w.warehouse = #Warehouse;
RETURN #oPurchasePrice;
END;
GO
This is how you could possibly convert this into an inline table valued function.
CREATE FUNCTION [dbo].[FindPurchasePrice]
(
#InventoryKey varchar(8)
, #InDate Date
, #Warehouse varchar(30)
)
RETURNS TABLE
AS
RETURN
SELECT ISNULL(pt.UnitPurchasePrice ,mw.cost) AS PurchasePrice
FROM Rentalworks.dbo.masterwh mw
JOIN Rentalworks.dbo.warehouse w on w.warehouseid = mw.warehouseid
AND mw.masterid = #InventoryKey
AND w.warehouse = #Warehouse
OUTER APPLY
(
SELECT TOP (1) UnitPurchasePrice
FROM PurchaseTransactions
WHERE InventoryKey = #InventoryKey
AND TransactionDate <= #InDate
AND Warehouse=#Warehouse
ORDER BY TransactionDate DESC
) pt
I of course can't test this but it syntax checks fine.
Now to include that in your original select statement you would do something like this.
SELECT dis.*
, fp.PurchasePrice
FROM DailyInventoryStatus dis
CROSS APPLY dbo.FindPurchasePrice(dis.InventoryKey, dis.Date, dis.warehouse) fp
WHERE Warehouse IN ('NYC,', 'CHICAGO', 'CHINA', 'ATLANTA')
AND Date >= '2014-01-01'
Here's one way to re-write without the function by incorporating all the logic into a single query:
with data as (
select
dis.*, pt.TransactionDate, pt.UnitPurchasePrice,
row_number() over (
partition by dis.InventoryKey, dis.Warehouse
order by TransactionDate desc
) as TransNumber
from
DailyInventoryStatus dis left outer join
PurchaseTransactions pt
on pt.InventoryKey = dis.InventoryKey
and pt.Warehouse = dis.Warehouse
and pt.TransactionDate < dis.Date
where dis.Date >= ?
)
select
*,
coalesce(
UnitPurchasePrice,
(
select mw.cost
from
Rentalworks.dbo.masterwh mw inner join
Rentalworks.dbo.warehouse w
on w.warehouseid = mw.warehouseid
where mw.masterid = data.InventoryKey
and w.warehouse = data.Warehouse
)
) as PurchasePrice
from data
where TransNumber = 1

Running Total of Open Accounts SQL Server 2008 by Month

I have found loads of articles similar to what I’m attempting to do but just cannot seem to replicate it to work.
I have a transactions table that is as below :
AccountNo Status DateComplete DateDue State
1147331 OPEN 18/02/2011 10/03/2011 R
1146787 OPEN 07/04/2011 05/04/2011 R
16511 OPEN 29/09/2003 29/09/2003 R
22571 OPEN 15/10/2003 15/10/2003 R
2852403 OPEN 10/11/2014 10/11/2014 R
2851890 OPEN 17/11/2014 17/11/2014 R
1147331 CLOSED 24/06/2011 27/06/2011 R
1146787 CLOSED 14/06/2011 14/06/2011 R
2852403 CLOSED 11/08/2015 11/08/2015 F
2851890 CLOSED 18/08/2015 18/08/2015 F
I don’t have access to change any of the data so have to work with what I have. The State column shows if a status is completed or pending. So when it is an ‘F’ its will have a future date in date due So I am using a CASE WHEN State = 'F' THEN DateDue WHEN State = 'R' THEN DateComplete END AS Date statement (forgive me if syntax is out its just an example).
What I am trying to achieve is a running total by month of open accounts. I’ve found guides on cumulative counts by month but cant find one by month for running total. The end output I need is (not based on above figures)
Month OpenAccounts
Jan-14 1
Feb-14 3
Mar-14 2
Apr-14 5
Will happily answer any questions If I haven’t been clear. (I have had to repost this question after deleting the original).
;with CTE as -- Get all dates with month year combo
( select CASE [STATE] WHEN 'F' THEN DateDue
WHEN 'R' THEN DateComplete END AS TransDate
from #Account
where Status = 'OPEN'
)
select DATENAME(mm,T1.TransDate) +'-'+
CAST (YEAR(T1.TransDate) AS VARCHAR(20)) AS Month,
count(distinct T2.TransDate) as runsum
from CTE as T2
cross join CTE T1
where 1= case when DATENAME(mm,T1.TransDate) +'-'+
CAST (YEAR(T1.TransDate) AS VARCHAR(20)) =
DATENAME(mm,T2.TransDate) +'-'+
CAST (YEAR(T2.TransDate) AS VARCHAR(20))
and T2.TransDate < T1.TransDate then 1
when T2.TransDate <= T1.TransDate then 1
end
group by DATENAME(mm,T1.TransDate) +'-'+
CAST (YEAR(T1.TransDate) AS VARCHAR(20))
order by runsum asc
DEMO
Try This :
DECLARE #table TABLE
(
mon INT,
yr INT,
cnt INT,
runningtotal INT
)
DECLARE #runningtotal INT
INSERT INTO #table
(mon,
yr,
cnt)
SELECT Datepart(MONTH, yourcolumn) AS mon,
Datepart(YEAR, yourcolumn) AS yr,
Count(*) cnt
FROM yourtable
GROUP BY Datepart(MONTH, yourcolumn),Datepart(YEAR, yourcolumn)
UPDATE #table
SET #runningtotal = #runningtotal + cnt,
runningtotal = #runningtotal
SELECT mon,
yr,
runningtotal
FROM #table

SQL Query in CRM Report

A "Case" in CRM has a field called "Status" with four options.
I'm trying to
build a report in CRM that fills a table with every week of the year (each row is a different week), and then counts the number of cases that have each Status option (the columns would be each of the Status options).
The table would look like this
Status 1 Status 2 Status 3
Week 1 3 55 4
Week 2 5 23 5
Week 3 14 11 33
So far I have the following:
SELECT
SUM(case WHEN status = 1 then 1 else 0 end) Status1,
SUM(case WHEN status = 2 then 1 else 0 end) Status2,
SUM(case WHEN status = 3 then 1 else 0 end) Status3,
SUM(case WHEN status = 4 then 1 else 0 end) Status4,
SUM(case WHEN status = 5 then 1 else 0 end) Status5
FROM [DB].[dbo].[Contact]
Which gives me the following:
Status 1 Status 2 Status 3
2 43 53
Now I need to somehow split this into 52 rows for the past year and filter these results by date (columns in the Contact table). I'm a bit new to SQL queries and CRM - any help here would be much appreciated.
Here is a SQLFiddle with my progress and sample data: http://sqlfiddle.com/#!2/85b19/1
Sounds like you want to group by a range. The trick is to create a new field that represents each range (for you one per year) and group by that.
Since it also seems like you want an infinite range of dates, marc_s has a good summary for how to do the group by trick with dates in a generic way: SQL group by frequency within a date range
So, let's break this down:
You want to make a report that shows, for each contact, a breakdown, week by week, of the number of cases registered to that contact, which is divided into three columns, one for each StateCode.
If this is the case, then you would need to have 52 date records (or so) for each contact. For calendar like requests, it's always good to have a separate calendar table that lets you query from it. Dan Guzman has a blog entry that creates a useful calendar table which I'll use in the query.
WITH WeekNumbers AS
(
SELECT
FirstDateOfWeek,
-- order by first date of week, grouping calendar year to produce week numbers
WeekNumber = row_number() OVER (PARTITION BY CalendarYear ORDER BY FirstDateOfWeek)
FROM
master.dbo.Calendar -- created from script
GROUP BY
FirstDateOfWeek,
CalendarYear
), Calendar AS
(
SELECT
WeekNumber =
(
SELECT
WeekNumber
FROM
WeekNumbers WN
WHERE
C.FirstDateOfWeek = WN.FirstDateOfWeek
),
*
FROM
master.dbo.Calendar C
WHERE
CalendarDate BETWEEN '1/1/2012' AND getutcdate()
)
SELECT
C.FullName,
----include the below if the data is necessary
--Cl.WeekNumber,
--Cl.CalendarYear,
--Cl.FirstDateOfWeek,
--Cl.LastDateOfWeek,
'Week: ' + CAST(Cl.WeekNumber AS VARCHAR(20))
+ ', Year: ' + CAST(Cl.CalendarYear AS VARCHAR(20)) WeekNumber
FROM
CRM.dbo.Contact C
-- use a cartesian join to produce a table list
CROSS JOIN
(
SELECT
DISTINCT WeekNumber,
CalendarYear,
FirstDateOfWeek,
LastDateOfWeek
FROM
Calendar
) Cl
ORDER BY
C.FullName,
Cl.WeekNumber
This is different from the solution Ben linked to because Marc's query only returns weeks where there is a matching value, whereas you may or may not want to see even the weeks where there is no activity.
Once you have your core tables of contacts split out week by week as in the above (or altered for your specific time period), you can simply add a subquery for each StateCode to see the breakdown in columns as in the final query below.
WITH WeekNumbers AS
(
SELECT
FirstDateOfWeek,
WeekNumber = row_number() OVER (PARTITION BY CalendarYear ORDER BY FirstDateOfWeek)
FROM
master.dbo.Calendar
GROUP BY
FirstDateOfWeek,
CalendarYear
), Calendar AS
(
SELECT
WeekNumber =
(
SELECT
WeekNumber
FROM
WeekNumbers WN
WHERE
C.FirstDateOfWeek = WN.FirstDateOfWeek
),
*
FROM
master.dbo.Calendar C
WHERE
CalendarDate BETWEEN '1/1/2012' AND getutcdate()
)
SELECT
C.FullName,
--Cl.WeekNumber,
--Cl.CalendarYear,
--Cl.FirstDateOfWeek,
--Cl.LastDateOfWeek,
'Week: ' + CAST(Cl.WeekNumber AS VARCHAR(20)) +', Year: ' + CAST(Cl.CalendarYear AS VARCHAR(20)) WeekNumber,
(
SELECT
count(*)
FROM
CRM.dbo.Incident I
INNER JOIN CRM.dbo.StringMap SM ON
I.StateCode = SM.AttributeValue
INNER JOIN
(
SELECT
DISTINCT ME.Name,
ME.ObjectTypeCode
FROM
CRM.MetadataSchema.Entity ME
) E ON
SM.ObjectTypeCode = E.ObjectTypeCode
WHERE
I.ModifiedOn >= Cl.FirstDateOfWeek
AND I.ModifiedOn < dateadd(day, 1, Cl.LastDateOfWeek)
AND E.Name = 'incident'
AND SM.AttributeName = 'statecode'
AND SM.LangId = 1033
AND I.CustomerId = C.ContactId
AND SM.Value = 'Active'
) ActiveCases,
(
SELECT
count(*)
FROM
CRM.dbo.Incident I
INNER JOIN CRM.dbo.StringMap SM ON
I.StateCode = SM.AttributeValue
INNER JOIN
(
SELECT
DISTINCT ME.Name,
ME.ObjectTypeCode
FROM
CRM.MetadataSchema.Entity ME
) E ON
SM.ObjectTypeCode = E.ObjectTypeCode
WHERE
I.ModifiedOn >= Cl.FirstDateOfWeek
AND I.ModifiedOn < dateadd(day, 1, Cl.LastDateOfWeek)
AND E.Name = 'incident'
AND SM.AttributeName = 'statecode'
AND SM.LangId = 1033
AND I.CustomerId = C.ContactId
AND SM.Value = 'Resolved'
) ResolvedCases,
(
SELECT
count(*)
FROM
CRM.dbo.Incident I
INNER JOIN CRM.dbo.StringMap SM ON
I.StateCode = SM.AttributeValue
INNER JOIN
(
SELECT
DISTINCT ME.Name,
ME.ObjectTypeCode
FROM
CRM.MetadataSchema.Entity ME
) E ON
SM.ObjectTypeCode = E.ObjectTypeCode
WHERE
I.ModifiedOn >= Cl.FirstDateOfWeek
AND I.ModifiedOn < dateadd(day, 1, Cl.LastDateOfWeek)
AND E.Name = 'incident'
AND SM.AttributeName = 'statecode'
AND SM.LangId = 1033
AND I.CustomerId = C.ContactId
AND SM.Value = 'Canceled'
) CancelledCases
FROM
CRM.dbo.Contact C
CROSS JOIN
(
SELECT
DISTINCT WeekNumber,
CalendarYear,
FirstDateOfWeek,
LastDateOfWeek
FROM
Calendar
) Cl
ORDER BY
C.FullName,
Cl.WeekNumber

SQL grouping and running total of open items for a date range

I have a table of items that, for sake of simplicity, contains the ItemID, the StartDate, and the EndDate for a list of items.
ItemID StartDate EndDate
1 1/1/2011 1/15/2011
2 1/2/2011 1/14/2011
3 1/5/2011 1/17/2011
...
My goal is to be able to join this table to a table with a sequential list of dates,
and say both how many items are open on a particular date, and also how many items are cumulatively open.
Date ItemsOpened CumulativeItemsOpen
1/1/2011 1 1
1/2/2011 1 2
...
I can see how this would be done with a WHILE loop,
but that has performance implications. I'm wondering how
this could be done with a set-based approach?
SELECT COUNT(CASE WHEN d.CheckDate = i.StartDate THEN 1 ELSE NULL END)
AS ItemsOpened
, COUNT(i.StartDate)
AS ItemsOpenedCumulative
FROM Dates AS d
LEFT JOIN Items AS i
ON d.CheckDate BETWEEN i.StartDate AND i.EndDate
GROUP BY d.CheckDate
This may give you what you want
SELECT DATE,
SUM(ItemOpened) AS ItemsOpened,
COUNT(StartDate) AS ItemsOpenedCumulative
FROM
(
SELECT d.Date, i.startdate, i.enddate,
CASE WHEN i.StartDate = d.Date THEN 1 ELSE 0 END AS ItemOpened
FROM Dates d
LEFT OUTER JOIN Items i ON d.Date BETWEEN i.StartDate AND i.EndDate
) AS x
GROUP BY DATE
ORDER BY DATE
This assumes that your date values are DATE data type. Or, the dates are DATETIME with no time values.
You may find this useful. The recusive part can be replaced with a table. To demonstrate it works I had to populate some sort of date table. As you can see, the actual sql is short and simple.
DECLARE #i table (itemid INT, startdate DATE, enddate DATE)
INSERT #i VALUES (1,'1/1/2011', '1/15/2011')
INSERT #i VALUES (2,'1/2/2011', '1/14/2011')
INSERT #i VALUES (3,'1/5/2011', '1/17/2011')
DECLARE #from DATE
DECLARE #to DATE
SET #from = '1/1/2011'
SET #to = '1/18/2011'
-- the recusive sql is strictly to make a datelist between #from and #to
;WITH cte(Date)
AS (
SELECT #from DATE
UNION ALL
SELECT DATEADD(day, 1, DATE)
FROM cte ch
WHERE DATE < #to
)
SELECT cte.Date, sum(case when cte.Date=i.startdate then 1 else 0 end) ItemsOpened, count(i.itemid) ItemsOpenedCumulative
FROM cte
left join #i i on cte.Date between i.startdate and i.enddate
GROUP BY cte.Date
OPTION( MAXRECURSION 0)
If you are on SQL Server 2005+, you could use a recursive CTE to obtain running totals, with the additional help of the ranking function ROW_NUMBER(), like this:
WITH grouped AS (
SELECT
d.Date,
ItemsOpened = COUNT(i.ItemID),
rn = ROW_NUMBER() OVER (ORDER BY d.Date)
FROM Dates d
LEFT JOIN Items i ON d.Date BETWEEN i.StartDate AND i.EndDate
GROUP BY d.Date
WHERE d.Date BETWEEN #FilterStartDate AND #FilterEndDate
),
cumulative AS (
SELECT
Date,
ItemsOpened,
ItemsOpenedCumulative = ItemsOpened
FROM grouped
WHERE rn = 1
UNION ALL
SELECT
g.Date,
g.ItemsOpened,
ItemsOpenedCumulative = g.ItemsOpenedCumulative + c.ItemsOpened
FROM grouped g
INNER JOIN cumulative c ON g.Date = DATEADD(day, 1, c.Date)
)
SELECT *
FROM cumulative