How to generate row between dates range in sql if for date don't have a record?









up vote
0
down vote

favorite












I'm not very familiar with SQL Server.



I have 3 tables, which relate to each other as one to many. So for one History, there are many Applications, and one Application can have many ApplicationAttributes.



I need to generate additional rows between dates range if for dates isn't exist any rows in database. If for given Imei already exist record, then need to take enrollTotalToday from previous row. For Id need to take last Id and just increment it. If for given dates ranges didn't exist previous rows, then just need to paste 0.



Please can anybody help me?



Histories table:



Id Imei CreationDate DeviceId
--------------------------------------------------
1 ProductionDevice299 2018-11-04 1
7 ProductionDevice299 2018-11-07 1


Applications table:



Id Name DeviceHistoryId
--------------------------------------
1 Enrollment.cone 1
2 DPC_OWNERS 1
3 OTHER_APPS 1
6 Enrollment.emp 7
7 DPC_OWNERS 7


ApplicationAttributes table:



Id Key Value DeviceApplicationId
------------------------------------------------
1 EnrolledTotal 2 1
2 LoginsTotal 5 2
3 OtherAttribt1 8 3
4 OtherAttribt2 12 3
5 OtherAttribt3 17 3
6 EnrolledTotal 21 6
7 LoginsTotal 25 7


Expected result for range(2018-11-02, 2018-11-09):



Id Imei CreationDate DeviceId EnrollTotalToday EnrollTotalYesterday
8 ProductionDevice299 2018-11-02 1 0 0
9 ProductionDevice299 2018-11-03 1 0 0
1 ProductionDevice299 2018-11-04 1 2 0
10 ProductionDevice299 2018-11-05 1 2 0
11 ProductionDevice299 2018-11-06 1 2 0
7 ProductionDevice299 2018-11-07 1 21 2
12 ProductionDevice299 2018-11-08 1 21 0
13 ProductionDevice299 2018-11-09 1 21 0


My current SQL query:



WITH [CTE] AS 
(
SELECT
[Extent1].[Id] AS [id],
[Extent1].[Imei] AS [imei],
CAST([Extent1].[CreationDate] AS DATE) AS [lastSeenOnline],
[Extent1].[DeviceId] AS [deviceId],
SUM(CAST([Extent4].[Value] AS BIGINT)) as [enrollTotalToday],
LAG(SUM(CAST([Extent4].[Value] AS BIGINT)), 1, 0) OVER (PARTITION BY [Extent1].[Imei] ORDER BY [Extent1].[CreationDate]) AS [enrollTotalYesterday]
FROM
[DeviceManagement].[dbo].[DeviceHistory] AS [Extent1]
INNER JOIN
(SELECT
[Imei], MAX([CreationDate]) AS MaxDate
FROM
[dbo].[DeviceHistory]
GROUP BY
[Imei], CAST([CreationDate] AS DATE)) [Extent2] ON [Extent1].[Imei] = [Extent2].[Imei] AND [Extent1].[CreationDate] = [Extent2].MaxDate
INNER JOIN
[DeviceManagement].[dbo].[DeviceApplication] AS [Extent3] ON [Extent3].DeviceHistoryId = [Extent1].id
INNER JOIN
[DeviceManagement].[dbo].[DeviceApplicationAttribute] [Extent4] ON [Extent4].DeviceApplicationId = [Extent3].id AND [Extent4].[Key] = 'EnrolledTotal'
GROUP BY
[Extent1].Id, [Extent1].Imei, [Extent1].CreationDate, [Extent1].DeviceId
)
SELECT *
FROM [CTE]
WHERE ([CTE].[lastSeenOnline] >= Convert(datetime, '2018-11-02' ))
AND ([CTE].[lastSeenOnline] <= Convert(datetime, '2018-11-09' ))









share|improve this question























  • Take a look at this similar question for how to generate a series of dates between 2 given dates: stackoverflow.com/q/28400012/361842
    – JohnLBevan
    Nov 10 at 17:04










  • @JohnLBevan in this example he want to insert into database some values. In my case I need to generate values on fly without inserting in database.
    – BorHunter
    Nov 10 at 17:10










  • @BorHunter well, really, the difference would be to comment out the insert into in between the CTE and the select.
    – Aaron Bertrand
    Nov 11 at 13:26














up vote
0
down vote

favorite












I'm not very familiar with SQL Server.



I have 3 tables, which relate to each other as one to many. So for one History, there are many Applications, and one Application can have many ApplicationAttributes.



I need to generate additional rows between dates range if for dates isn't exist any rows in database. If for given Imei already exist record, then need to take enrollTotalToday from previous row. For Id need to take last Id and just increment it. If for given dates ranges didn't exist previous rows, then just need to paste 0.



Please can anybody help me?



Histories table:



Id Imei CreationDate DeviceId
--------------------------------------------------
1 ProductionDevice299 2018-11-04 1
7 ProductionDevice299 2018-11-07 1


Applications table:



Id Name DeviceHistoryId
--------------------------------------
1 Enrollment.cone 1
2 DPC_OWNERS 1
3 OTHER_APPS 1
6 Enrollment.emp 7
7 DPC_OWNERS 7


ApplicationAttributes table:



Id Key Value DeviceApplicationId
------------------------------------------------
1 EnrolledTotal 2 1
2 LoginsTotal 5 2
3 OtherAttribt1 8 3
4 OtherAttribt2 12 3
5 OtherAttribt3 17 3
6 EnrolledTotal 21 6
7 LoginsTotal 25 7


Expected result for range(2018-11-02, 2018-11-09):



Id Imei CreationDate DeviceId EnrollTotalToday EnrollTotalYesterday
8 ProductionDevice299 2018-11-02 1 0 0
9 ProductionDevice299 2018-11-03 1 0 0
1 ProductionDevice299 2018-11-04 1 2 0
10 ProductionDevice299 2018-11-05 1 2 0
11 ProductionDevice299 2018-11-06 1 2 0
7 ProductionDevice299 2018-11-07 1 21 2
12 ProductionDevice299 2018-11-08 1 21 0
13 ProductionDevice299 2018-11-09 1 21 0


My current SQL query:



WITH [CTE] AS 
(
SELECT
[Extent1].[Id] AS [id],
[Extent1].[Imei] AS [imei],
CAST([Extent1].[CreationDate] AS DATE) AS [lastSeenOnline],
[Extent1].[DeviceId] AS [deviceId],
SUM(CAST([Extent4].[Value] AS BIGINT)) as [enrollTotalToday],
LAG(SUM(CAST([Extent4].[Value] AS BIGINT)), 1, 0) OVER (PARTITION BY [Extent1].[Imei] ORDER BY [Extent1].[CreationDate]) AS [enrollTotalYesterday]
FROM
[DeviceManagement].[dbo].[DeviceHistory] AS [Extent1]
INNER JOIN
(SELECT
[Imei], MAX([CreationDate]) AS MaxDate
FROM
[dbo].[DeviceHistory]
GROUP BY
[Imei], CAST([CreationDate] AS DATE)) [Extent2] ON [Extent1].[Imei] = [Extent2].[Imei] AND [Extent1].[CreationDate] = [Extent2].MaxDate
INNER JOIN
[DeviceManagement].[dbo].[DeviceApplication] AS [Extent3] ON [Extent3].DeviceHistoryId = [Extent1].id
INNER JOIN
[DeviceManagement].[dbo].[DeviceApplicationAttribute] [Extent4] ON [Extent4].DeviceApplicationId = [Extent3].id AND [Extent4].[Key] = 'EnrolledTotal'
GROUP BY
[Extent1].Id, [Extent1].Imei, [Extent1].CreationDate, [Extent1].DeviceId
)
SELECT *
FROM [CTE]
WHERE ([CTE].[lastSeenOnline] >= Convert(datetime, '2018-11-02' ))
AND ([CTE].[lastSeenOnline] <= Convert(datetime, '2018-11-09' ))









share|improve this question























  • Take a look at this similar question for how to generate a series of dates between 2 given dates: stackoverflow.com/q/28400012/361842
    – JohnLBevan
    Nov 10 at 17:04










  • @JohnLBevan in this example he want to insert into database some values. In my case I need to generate values on fly without inserting in database.
    – BorHunter
    Nov 10 at 17:10










  • @BorHunter well, really, the difference would be to comment out the insert into in between the CTE and the select.
    – Aaron Bertrand
    Nov 11 at 13:26












up vote
0
down vote

favorite









up vote
0
down vote

favorite











I'm not very familiar with SQL Server.



I have 3 tables, which relate to each other as one to many. So for one History, there are many Applications, and one Application can have many ApplicationAttributes.



I need to generate additional rows between dates range if for dates isn't exist any rows in database. If for given Imei already exist record, then need to take enrollTotalToday from previous row. For Id need to take last Id and just increment it. If for given dates ranges didn't exist previous rows, then just need to paste 0.



Please can anybody help me?



Histories table:



Id Imei CreationDate DeviceId
--------------------------------------------------
1 ProductionDevice299 2018-11-04 1
7 ProductionDevice299 2018-11-07 1


Applications table:



Id Name DeviceHistoryId
--------------------------------------
1 Enrollment.cone 1
2 DPC_OWNERS 1
3 OTHER_APPS 1
6 Enrollment.emp 7
7 DPC_OWNERS 7


ApplicationAttributes table:



Id Key Value DeviceApplicationId
------------------------------------------------
1 EnrolledTotal 2 1
2 LoginsTotal 5 2
3 OtherAttribt1 8 3
4 OtherAttribt2 12 3
5 OtherAttribt3 17 3
6 EnrolledTotal 21 6
7 LoginsTotal 25 7


Expected result for range(2018-11-02, 2018-11-09):



Id Imei CreationDate DeviceId EnrollTotalToday EnrollTotalYesterday
8 ProductionDevice299 2018-11-02 1 0 0
9 ProductionDevice299 2018-11-03 1 0 0
1 ProductionDevice299 2018-11-04 1 2 0
10 ProductionDevice299 2018-11-05 1 2 0
11 ProductionDevice299 2018-11-06 1 2 0
7 ProductionDevice299 2018-11-07 1 21 2
12 ProductionDevice299 2018-11-08 1 21 0
13 ProductionDevice299 2018-11-09 1 21 0


My current SQL query:



WITH [CTE] AS 
(
SELECT
[Extent1].[Id] AS [id],
[Extent1].[Imei] AS [imei],
CAST([Extent1].[CreationDate] AS DATE) AS [lastSeenOnline],
[Extent1].[DeviceId] AS [deviceId],
SUM(CAST([Extent4].[Value] AS BIGINT)) as [enrollTotalToday],
LAG(SUM(CAST([Extent4].[Value] AS BIGINT)), 1, 0) OVER (PARTITION BY [Extent1].[Imei] ORDER BY [Extent1].[CreationDate]) AS [enrollTotalYesterday]
FROM
[DeviceManagement].[dbo].[DeviceHistory] AS [Extent1]
INNER JOIN
(SELECT
[Imei], MAX([CreationDate]) AS MaxDate
FROM
[dbo].[DeviceHistory]
GROUP BY
[Imei], CAST([CreationDate] AS DATE)) [Extent2] ON [Extent1].[Imei] = [Extent2].[Imei] AND [Extent1].[CreationDate] = [Extent2].MaxDate
INNER JOIN
[DeviceManagement].[dbo].[DeviceApplication] AS [Extent3] ON [Extent3].DeviceHistoryId = [Extent1].id
INNER JOIN
[DeviceManagement].[dbo].[DeviceApplicationAttribute] [Extent4] ON [Extent4].DeviceApplicationId = [Extent3].id AND [Extent4].[Key] = 'EnrolledTotal'
GROUP BY
[Extent1].Id, [Extent1].Imei, [Extent1].CreationDate, [Extent1].DeviceId
)
SELECT *
FROM [CTE]
WHERE ([CTE].[lastSeenOnline] >= Convert(datetime, '2018-11-02' ))
AND ([CTE].[lastSeenOnline] <= Convert(datetime, '2018-11-09' ))









share|improve this question















I'm not very familiar with SQL Server.



I have 3 tables, which relate to each other as one to many. So for one History, there are many Applications, and one Application can have many ApplicationAttributes.



I need to generate additional rows between dates range if for dates isn't exist any rows in database. If for given Imei already exist record, then need to take enrollTotalToday from previous row. For Id need to take last Id and just increment it. If for given dates ranges didn't exist previous rows, then just need to paste 0.



Please can anybody help me?



Histories table:



Id Imei CreationDate DeviceId
--------------------------------------------------
1 ProductionDevice299 2018-11-04 1
7 ProductionDevice299 2018-11-07 1


Applications table:



Id Name DeviceHistoryId
--------------------------------------
1 Enrollment.cone 1
2 DPC_OWNERS 1
3 OTHER_APPS 1
6 Enrollment.emp 7
7 DPC_OWNERS 7


ApplicationAttributes table:



Id Key Value DeviceApplicationId
------------------------------------------------
1 EnrolledTotal 2 1
2 LoginsTotal 5 2
3 OtherAttribt1 8 3
4 OtherAttribt2 12 3
5 OtherAttribt3 17 3
6 EnrolledTotal 21 6
7 LoginsTotal 25 7


Expected result for range(2018-11-02, 2018-11-09):



Id Imei CreationDate DeviceId EnrollTotalToday EnrollTotalYesterday
8 ProductionDevice299 2018-11-02 1 0 0
9 ProductionDevice299 2018-11-03 1 0 0
1 ProductionDevice299 2018-11-04 1 2 0
10 ProductionDevice299 2018-11-05 1 2 0
11 ProductionDevice299 2018-11-06 1 2 0
7 ProductionDevice299 2018-11-07 1 21 2
12 ProductionDevice299 2018-11-08 1 21 0
13 ProductionDevice299 2018-11-09 1 21 0


My current SQL query:



WITH [CTE] AS 
(
SELECT
[Extent1].[Id] AS [id],
[Extent1].[Imei] AS [imei],
CAST([Extent1].[CreationDate] AS DATE) AS [lastSeenOnline],
[Extent1].[DeviceId] AS [deviceId],
SUM(CAST([Extent4].[Value] AS BIGINT)) as [enrollTotalToday],
LAG(SUM(CAST([Extent4].[Value] AS BIGINT)), 1, 0) OVER (PARTITION BY [Extent1].[Imei] ORDER BY [Extent1].[CreationDate]) AS [enrollTotalYesterday]
FROM
[DeviceManagement].[dbo].[DeviceHistory] AS [Extent1]
INNER JOIN
(SELECT
[Imei], MAX([CreationDate]) AS MaxDate
FROM
[dbo].[DeviceHistory]
GROUP BY
[Imei], CAST([CreationDate] AS DATE)) [Extent2] ON [Extent1].[Imei] = [Extent2].[Imei] AND [Extent1].[CreationDate] = [Extent2].MaxDate
INNER JOIN
[DeviceManagement].[dbo].[DeviceApplication] AS [Extent3] ON [Extent3].DeviceHistoryId = [Extent1].id
INNER JOIN
[DeviceManagement].[dbo].[DeviceApplicationAttribute] [Extent4] ON [Extent4].DeviceApplicationId = [Extent3].id AND [Extent4].[Key] = 'EnrolledTotal'
GROUP BY
[Extent1].Id, [Extent1].Imei, [Extent1].CreationDate, [Extent1].DeviceId
)
SELECT *
FROM [CTE]
WHERE ([CTE].[lastSeenOnline] >= Convert(datetime, '2018-11-02' ))
AND ([CTE].[lastSeenOnline] <= Convert(datetime, '2018-11-09' ))






sql sql-server tsql






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 10 at 17:38









marc_s

565k12610911243




565k12610911243










asked Nov 10 at 17:00









BorHunter

50621535




50621535











  • Take a look at this similar question for how to generate a series of dates between 2 given dates: stackoverflow.com/q/28400012/361842
    – JohnLBevan
    Nov 10 at 17:04










  • @JohnLBevan in this example he want to insert into database some values. In my case I need to generate values on fly without inserting in database.
    – BorHunter
    Nov 10 at 17:10










  • @BorHunter well, really, the difference would be to comment out the insert into in between the CTE and the select.
    – Aaron Bertrand
    Nov 11 at 13:26
















  • Take a look at this similar question for how to generate a series of dates between 2 given dates: stackoverflow.com/q/28400012/361842
    – JohnLBevan
    Nov 10 at 17:04










  • @JohnLBevan in this example he want to insert into database some values. In my case I need to generate values on fly without inserting in database.
    – BorHunter
    Nov 10 at 17:10










  • @BorHunter well, really, the difference would be to comment out the insert into in between the CTE and the select.
    – Aaron Bertrand
    Nov 11 at 13:26















Take a look at this similar question for how to generate a series of dates between 2 given dates: stackoverflow.com/q/28400012/361842
– JohnLBevan
Nov 10 at 17:04




Take a look at this similar question for how to generate a series of dates between 2 given dates: stackoverflow.com/q/28400012/361842
– JohnLBevan
Nov 10 at 17:04












@JohnLBevan in this example he want to insert into database some values. In my case I need to generate values on fly without inserting in database.
– BorHunter
Nov 10 at 17:10




@JohnLBevan in this example he want to insert into database some values. In my case I need to generate values on fly without inserting in database.
– BorHunter
Nov 10 at 17:10












@BorHunter well, really, the difference would be to comment out the insert into in between the CTE and the select.
– Aaron Bertrand
Nov 11 at 13:26




@BorHunter well, really, the difference would be to comment out the insert into in between the CTE and the select.
– Aaron Bertrand
Nov 11 at 13:26












2 Answers
2






active

oldest

votes

















up vote
0
down vote













The best solution in this case is to use calendar table with all dates that you need. Then to select from this table and join the other tables to get the data for the corresponding date.



If you can create this table, you can generate one on the fly. Just select from a table with enough rows to cover the number of days in the desired range (you can combine multiple tables to increase the number of rows if necessary), calculate ROW_NUMBER() and add as many days to your start date (or to the day before to be precise). You need to user TOP clause to select as many rows as days you have in your range. Something like this:



declare @FromDate date = '20180101', @ToDate DATE = '20180228', @Duration int 

set @Duration = DATEDIFF(day, @FromDate, @ToDate)

select top (DATEDIFF(day, @FromDate, @ToDate) + 1) DATEADD(DAY, ROW_NUMBER() over(order by message_id) - 1, @FromDate) as DateFromRange
from sys.messages


To use this technique in your query, you can construct derived table and select from it in your query.



select 
DatesRange.DateFromRange
, SalesPerDate.TotalSales
from (select top (DATEDIFF(day, '20180101', '20180228') + 1) DATEADD(DAY, ROW_NUMBER() over(order by message_id) - 1, '20180101') as DateFromRange from sys.messages) DatesRange
outer apply (select sum(SalesAmount) as TotalSales from Sales where SalesDate = DatesRange.DateFromRange) SalesPerDate


From your query it looks like you are using Entity Framework. Eventually you can generate this range in your app code too, and then join the query result to it. For generating a range you can use Enumerable.Range method, or to do something like that:



Enumerable.Range(0, 1 + end.Subtract(start).Days)
.Select(offset => start.AddDays(offset))
.ToArray();


as described in this question.






share|improve this answer




















  • thank you for answer, but this is not exactly what I need. BTW: it's not an option to generate this rows using C# code, because of I will need to convert IQueryable type to IEnumerable, which will load all data and occur performance issues.
    – BorHunter
    Nov 11 at 8:23

















up vote
0
down vote













First, become comfortable with a generic way to generate the range of days:



DECLARE @start date = '20181102', @end date = '20181109';

;WITH n(n) AS
(
SELECT 0
UNION ALL SELECT n+1
FROM n
WHERE n < DATEDIFF(DAY, @start, @end)
)
SELECT d = DATEADD(DAY, n, @start) FROM n;


Results:



d
==========
2018-11-02
2018-11-03
2018-11-04
2018-11-05
2018-11-06
2018-11-07
2018-11-08
2018-11-09


Currently your query just produces the two rows with dates that exist, but you need to manufacture rows for the 6 other rows that don't, and you do that by using the above as the base and then left joining against it. Given these tables and data (thank you for sample data, but this is a much better form for showing us):



CREATE TABLE #DeviceHistory
(Id int, Imei varchar(64), CreationDate date, DeviceId int);

INSERT #DeviceHistory VALUES
(1,'ProductionDevice299','2018-11-04',1),
(7,'ProductionDevice299','2018-11-07',1);

CREATE TABLE #Applications
(Id int, Name varchar(64), DeviceHistoryId int);

INSERT #Applications VALUES
(1, 'Enrollment.cone',1),
(2, 'DPC_OWNERS' ,1),
(3, 'OTHER_APPS' ,1),
(6, 'Enrollment.emp' ,7),
(7, 'DPC_OWNERS' ,7);

CREATE TABLE #ApplicationAttributes
(Id int, [Key] varchar(64), Value int, DeviceApplicationId int);

INSERT #ApplicationAttributes VALUES
(1, 'EnrolledTotal', 2 ,1),
(2, 'LoginsTotal' , 5 ,2),
(3, 'OtherAttribt1', 8 ,3),
(4, 'OtherAttribt2', 12,3),
(5, 'OtherAttribt3', 17,3),
(6, 'EnrolledTotal', 21,6),
(7, 'LoginsTotal' , 25,7);


Applying the date range to this as follows:



DECLARE @start date = '20181102', @end date = '20181109';

;WITH n(n) AS
(
SELECT 0 UNION ALL SELECT n+1 FROM n WHERE n < DATEDIFF(DAY, @start, @end)
),
d(d) AS
(
SELECT DATEADD(DAY, n, @start) FROM n
),
[CTE] AS (
SELECT
[Extent1].[Id] AS [id],
[Extent1].[Imei] AS [imei],
CAST([Extent1].[CreationDate] AS DATE) AS [lastSeenOnline],
[Extent1].[DeviceId] AS [deviceId],
SUM(CAST([Extent4].[Value] AS BIGINT)) as [enrollTotalToday],

LAG(SUM(CAST([Extent4].[Value] AS BIGINT)), 1, 0) OVER
(PARTITION BY [Extent1].[Imei] ORDER BY [Extent1].[CreationDate])
AS [enrollTotalYesterday]
FROM #DeviceHistory AS [Extent1]
INNER JOIN (
SELECT [Imei], max([CreationDate]) as MaxDate
FROM #DeviceHistory
GROUP BY [Imei], CAST([CreationDate] AS DATE)
) [Extent2] on [Extent1].[Imei] = [Extent2].[Imei]
and [Extent1].[CreationDate] = [Extent2].MaxDate
INNER JOIN
#Applications AS [Extent3]
ON [Extent3].DeviceHistoryId = [Extent1].id
INNER JOIN
#ApplicationAttributes [Extent4]
ON [Extent4].DeviceApplicationId = [Extent3].id
AND [Extent4].[Key] = 'EnrolledTotal'
GROUP BY [Extent1].Id, [Extent1].Imei, [Extent1].CreationDate, [Extent1].DeviceId
)
SELECT
CTE.id,
CreationDate = d.d,
CTE.imei,
CTE.deviceId,
CTE.enrollTotalToday,
CTE.enrollTotalYesterday
FROM d
LEFT OUTER JOIN CTE
ON CTE.LastSeenOnline = d.d
ORDER BY d.d;


Gets you this, which admittedly isn't quite what you're after:




enter image description here




I'm not quite sure exactly what other data might be in these tables and what relationships you're going to use to determine that the missing rows should be populated with this imei and deviceId, or where those ids come from (they're not in your sample data). In this case the Applications table only has a single imei name, and all the data falls within the date range, but I can't imagine your real table is that simple. Other things also don't make sense to me, like why the enrollment for November 4th carries forward to the 5th, but the 7th doesn't carry to the 8th? You might have constructed the desired results because of things you know about the data or business rules you're applying in your head, but readers don't pick up on those, and there might not be a straightforward way to have SQL Server apply them, either.



As an aside, try to avoid using reserved keywords for column names (like Key).






share|improve this answer






















  • Wouldn't it be better to use an already pre-populated calendar table?
    – Zohar Peled
    Nov 11 at 7:09










  • @AaronBertrand Thank you for explanation. According to your questions: this generation needfor data grid in web client. Situation is follow: we have some histories for some devices for example daily, but not each day device send info to server. That's why we need to show for days, when device didn't send info some generated data. I can do this using C# code, but this will take a time in comparison to sql request, which will return all data itself.
    – BorHunter
    Nov 11 at 8:17











  • @AaronBertrand that's why I need additional information, which should set in generated rows.
    – BorHunter
    Nov 11 at 8:18










  • @ZoharPeled Maybe? With 8 rows (or even 100 rows), it’s unlikely to have any impact. Also (1) most people don’t already have a calendar table (or even a numbers table), (2) setting one up adds a lot of potentially useless bulk to the answer, and (3) after much time here this proposal is usually met with “But I don’t want to create a table” or even “I don’t have permission to create a table.”
    – Aaron Bertrand
    Nov 11 at 13:01










  • @BorHunter if you already have the logic to populate the missing data in C#, just do it there. I feel like the time that takes should be negligible in any case, but it probably won’t be less time than adding similar logic in the query (even if we can figure out how to do that without turning this into an expensive cursor).
    – Aaron Bertrand
    Nov 11 at 13:04










Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53241284%2fhow-to-generate-row-between-dates-range-in-sql-if-for-date-dont-have-a-record%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown

























2 Answers
2






active

oldest

votes








2 Answers
2






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
0
down vote













The best solution in this case is to use calendar table with all dates that you need. Then to select from this table and join the other tables to get the data for the corresponding date.



If you can create this table, you can generate one on the fly. Just select from a table with enough rows to cover the number of days in the desired range (you can combine multiple tables to increase the number of rows if necessary), calculate ROW_NUMBER() and add as many days to your start date (or to the day before to be precise). You need to user TOP clause to select as many rows as days you have in your range. Something like this:



declare @FromDate date = '20180101', @ToDate DATE = '20180228', @Duration int 

set @Duration = DATEDIFF(day, @FromDate, @ToDate)

select top (DATEDIFF(day, @FromDate, @ToDate) + 1) DATEADD(DAY, ROW_NUMBER() over(order by message_id) - 1, @FromDate) as DateFromRange
from sys.messages


To use this technique in your query, you can construct derived table and select from it in your query.



select 
DatesRange.DateFromRange
, SalesPerDate.TotalSales
from (select top (DATEDIFF(day, '20180101', '20180228') + 1) DATEADD(DAY, ROW_NUMBER() over(order by message_id) - 1, '20180101') as DateFromRange from sys.messages) DatesRange
outer apply (select sum(SalesAmount) as TotalSales from Sales where SalesDate = DatesRange.DateFromRange) SalesPerDate


From your query it looks like you are using Entity Framework. Eventually you can generate this range in your app code too, and then join the query result to it. For generating a range you can use Enumerable.Range method, or to do something like that:



Enumerable.Range(0, 1 + end.Subtract(start).Days)
.Select(offset => start.AddDays(offset))
.ToArray();


as described in this question.






share|improve this answer




















  • thank you for answer, but this is not exactly what I need. BTW: it's not an option to generate this rows using C# code, because of I will need to convert IQueryable type to IEnumerable, which will load all data and occur performance issues.
    – BorHunter
    Nov 11 at 8:23














up vote
0
down vote













The best solution in this case is to use calendar table with all dates that you need. Then to select from this table and join the other tables to get the data for the corresponding date.



If you can create this table, you can generate one on the fly. Just select from a table with enough rows to cover the number of days in the desired range (you can combine multiple tables to increase the number of rows if necessary), calculate ROW_NUMBER() and add as many days to your start date (or to the day before to be precise). You need to user TOP clause to select as many rows as days you have in your range. Something like this:



declare @FromDate date = '20180101', @ToDate DATE = '20180228', @Duration int 

set @Duration = DATEDIFF(day, @FromDate, @ToDate)

select top (DATEDIFF(day, @FromDate, @ToDate) + 1) DATEADD(DAY, ROW_NUMBER() over(order by message_id) - 1, @FromDate) as DateFromRange
from sys.messages


To use this technique in your query, you can construct derived table and select from it in your query.



select 
DatesRange.DateFromRange
, SalesPerDate.TotalSales
from (select top (DATEDIFF(day, '20180101', '20180228') + 1) DATEADD(DAY, ROW_NUMBER() over(order by message_id) - 1, '20180101') as DateFromRange from sys.messages) DatesRange
outer apply (select sum(SalesAmount) as TotalSales from Sales where SalesDate = DatesRange.DateFromRange) SalesPerDate


From your query it looks like you are using Entity Framework. Eventually you can generate this range in your app code too, and then join the query result to it. For generating a range you can use Enumerable.Range method, or to do something like that:



Enumerable.Range(0, 1 + end.Subtract(start).Days)
.Select(offset => start.AddDays(offset))
.ToArray();


as described in this question.






share|improve this answer




















  • thank you for answer, but this is not exactly what I need. BTW: it's not an option to generate this rows using C# code, because of I will need to convert IQueryable type to IEnumerable, which will load all data and occur performance issues.
    – BorHunter
    Nov 11 at 8:23












up vote
0
down vote










up vote
0
down vote









The best solution in this case is to use calendar table with all dates that you need. Then to select from this table and join the other tables to get the data for the corresponding date.



If you can create this table, you can generate one on the fly. Just select from a table with enough rows to cover the number of days in the desired range (you can combine multiple tables to increase the number of rows if necessary), calculate ROW_NUMBER() and add as many days to your start date (or to the day before to be precise). You need to user TOP clause to select as many rows as days you have in your range. Something like this:



declare @FromDate date = '20180101', @ToDate DATE = '20180228', @Duration int 

set @Duration = DATEDIFF(day, @FromDate, @ToDate)

select top (DATEDIFF(day, @FromDate, @ToDate) + 1) DATEADD(DAY, ROW_NUMBER() over(order by message_id) - 1, @FromDate) as DateFromRange
from sys.messages


To use this technique in your query, you can construct derived table and select from it in your query.



select 
DatesRange.DateFromRange
, SalesPerDate.TotalSales
from (select top (DATEDIFF(day, '20180101', '20180228') + 1) DATEADD(DAY, ROW_NUMBER() over(order by message_id) - 1, '20180101') as DateFromRange from sys.messages) DatesRange
outer apply (select sum(SalesAmount) as TotalSales from Sales where SalesDate = DatesRange.DateFromRange) SalesPerDate


From your query it looks like you are using Entity Framework. Eventually you can generate this range in your app code too, and then join the query result to it. For generating a range you can use Enumerable.Range method, or to do something like that:



Enumerable.Range(0, 1 + end.Subtract(start).Days)
.Select(offset => start.AddDays(offset))
.ToArray();


as described in this question.






share|improve this answer












The best solution in this case is to use calendar table with all dates that you need. Then to select from this table and join the other tables to get the data for the corresponding date.



If you can create this table, you can generate one on the fly. Just select from a table with enough rows to cover the number of days in the desired range (you can combine multiple tables to increase the number of rows if necessary), calculate ROW_NUMBER() and add as many days to your start date (or to the day before to be precise). You need to user TOP clause to select as many rows as days you have in your range. Something like this:



declare @FromDate date = '20180101', @ToDate DATE = '20180228', @Duration int 

set @Duration = DATEDIFF(day, @FromDate, @ToDate)

select top (DATEDIFF(day, @FromDate, @ToDate) + 1) DATEADD(DAY, ROW_NUMBER() over(order by message_id) - 1, @FromDate) as DateFromRange
from sys.messages


To use this technique in your query, you can construct derived table and select from it in your query.



select 
DatesRange.DateFromRange
, SalesPerDate.TotalSales
from (select top (DATEDIFF(day, '20180101', '20180228') + 1) DATEADD(DAY, ROW_NUMBER() over(order by message_id) - 1, '20180101') as DateFromRange from sys.messages) DatesRange
outer apply (select sum(SalesAmount) as TotalSales from Sales where SalesDate = DatesRange.DateFromRange) SalesPerDate


From your query it looks like you are using Entity Framework. Eventually you can generate this range in your app code too, and then join the query result to it. For generating a range you can use Enumerable.Range method, or to do something like that:



Enumerable.Range(0, 1 + end.Subtract(start).Days)
.Select(offset => start.AddDays(offset))
.ToArray();


as described in this question.







share|improve this answer












share|improve this answer



share|improve this answer










answered Nov 10 at 17:39









Andrey Nikolov

1,33129




1,33129











  • thank you for answer, but this is not exactly what I need. BTW: it's not an option to generate this rows using C# code, because of I will need to convert IQueryable type to IEnumerable, which will load all data and occur performance issues.
    – BorHunter
    Nov 11 at 8:23
















  • thank you for answer, but this is not exactly what I need. BTW: it's not an option to generate this rows using C# code, because of I will need to convert IQueryable type to IEnumerable, which will load all data and occur performance issues.
    – BorHunter
    Nov 11 at 8:23















thank you for answer, but this is not exactly what I need. BTW: it's not an option to generate this rows using C# code, because of I will need to convert IQueryable type to IEnumerable, which will load all data and occur performance issues.
– BorHunter
Nov 11 at 8:23




thank you for answer, but this is not exactly what I need. BTW: it's not an option to generate this rows using C# code, because of I will need to convert IQueryable type to IEnumerable, which will load all data and occur performance issues.
– BorHunter
Nov 11 at 8:23












up vote
0
down vote













First, become comfortable with a generic way to generate the range of days:



DECLARE @start date = '20181102', @end date = '20181109';

;WITH n(n) AS
(
SELECT 0
UNION ALL SELECT n+1
FROM n
WHERE n < DATEDIFF(DAY, @start, @end)
)
SELECT d = DATEADD(DAY, n, @start) FROM n;


Results:



d
==========
2018-11-02
2018-11-03
2018-11-04
2018-11-05
2018-11-06
2018-11-07
2018-11-08
2018-11-09


Currently your query just produces the two rows with dates that exist, but you need to manufacture rows for the 6 other rows that don't, and you do that by using the above as the base and then left joining against it. Given these tables and data (thank you for sample data, but this is a much better form for showing us):



CREATE TABLE #DeviceHistory
(Id int, Imei varchar(64), CreationDate date, DeviceId int);

INSERT #DeviceHistory VALUES
(1,'ProductionDevice299','2018-11-04',1),
(7,'ProductionDevice299','2018-11-07',1);

CREATE TABLE #Applications
(Id int, Name varchar(64), DeviceHistoryId int);

INSERT #Applications VALUES
(1, 'Enrollment.cone',1),
(2, 'DPC_OWNERS' ,1),
(3, 'OTHER_APPS' ,1),
(6, 'Enrollment.emp' ,7),
(7, 'DPC_OWNERS' ,7);

CREATE TABLE #ApplicationAttributes
(Id int, [Key] varchar(64), Value int, DeviceApplicationId int);

INSERT #ApplicationAttributes VALUES
(1, 'EnrolledTotal', 2 ,1),
(2, 'LoginsTotal' , 5 ,2),
(3, 'OtherAttribt1', 8 ,3),
(4, 'OtherAttribt2', 12,3),
(5, 'OtherAttribt3', 17,3),
(6, 'EnrolledTotal', 21,6),
(7, 'LoginsTotal' , 25,7);


Applying the date range to this as follows:



DECLARE @start date = '20181102', @end date = '20181109';

;WITH n(n) AS
(
SELECT 0 UNION ALL SELECT n+1 FROM n WHERE n < DATEDIFF(DAY, @start, @end)
),
d(d) AS
(
SELECT DATEADD(DAY, n, @start) FROM n
),
[CTE] AS (
SELECT
[Extent1].[Id] AS [id],
[Extent1].[Imei] AS [imei],
CAST([Extent1].[CreationDate] AS DATE) AS [lastSeenOnline],
[Extent1].[DeviceId] AS [deviceId],
SUM(CAST([Extent4].[Value] AS BIGINT)) as [enrollTotalToday],

LAG(SUM(CAST([Extent4].[Value] AS BIGINT)), 1, 0) OVER
(PARTITION BY [Extent1].[Imei] ORDER BY [Extent1].[CreationDate])
AS [enrollTotalYesterday]
FROM #DeviceHistory AS [Extent1]
INNER JOIN (
SELECT [Imei], max([CreationDate]) as MaxDate
FROM #DeviceHistory
GROUP BY [Imei], CAST([CreationDate] AS DATE)
) [Extent2] on [Extent1].[Imei] = [Extent2].[Imei]
and [Extent1].[CreationDate] = [Extent2].MaxDate
INNER JOIN
#Applications AS [Extent3]
ON [Extent3].DeviceHistoryId = [Extent1].id
INNER JOIN
#ApplicationAttributes [Extent4]
ON [Extent4].DeviceApplicationId = [Extent3].id
AND [Extent4].[Key] = 'EnrolledTotal'
GROUP BY [Extent1].Id, [Extent1].Imei, [Extent1].CreationDate, [Extent1].DeviceId
)
SELECT
CTE.id,
CreationDate = d.d,
CTE.imei,
CTE.deviceId,
CTE.enrollTotalToday,
CTE.enrollTotalYesterday
FROM d
LEFT OUTER JOIN CTE
ON CTE.LastSeenOnline = d.d
ORDER BY d.d;


Gets you this, which admittedly isn't quite what you're after:




enter image description here




I'm not quite sure exactly what other data might be in these tables and what relationships you're going to use to determine that the missing rows should be populated with this imei and deviceId, or where those ids come from (they're not in your sample data). In this case the Applications table only has a single imei name, and all the data falls within the date range, but I can't imagine your real table is that simple. Other things also don't make sense to me, like why the enrollment for November 4th carries forward to the 5th, but the 7th doesn't carry to the 8th? You might have constructed the desired results because of things you know about the data or business rules you're applying in your head, but readers don't pick up on those, and there might not be a straightforward way to have SQL Server apply them, either.



As an aside, try to avoid using reserved keywords for column names (like Key).






share|improve this answer






















  • Wouldn't it be better to use an already pre-populated calendar table?
    – Zohar Peled
    Nov 11 at 7:09










  • @AaronBertrand Thank you for explanation. According to your questions: this generation needfor data grid in web client. Situation is follow: we have some histories for some devices for example daily, but not each day device send info to server. That's why we need to show for days, when device didn't send info some generated data. I can do this using C# code, but this will take a time in comparison to sql request, which will return all data itself.
    – BorHunter
    Nov 11 at 8:17











  • @AaronBertrand that's why I need additional information, which should set in generated rows.
    – BorHunter
    Nov 11 at 8:18










  • @ZoharPeled Maybe? With 8 rows (or even 100 rows), it’s unlikely to have any impact. Also (1) most people don’t already have a calendar table (or even a numbers table), (2) setting one up adds a lot of potentially useless bulk to the answer, and (3) after much time here this proposal is usually met with “But I don’t want to create a table” or even “I don’t have permission to create a table.”
    – Aaron Bertrand
    Nov 11 at 13:01










  • @BorHunter if you already have the logic to populate the missing data in C#, just do it there. I feel like the time that takes should be negligible in any case, but it probably won’t be less time than adding similar logic in the query (even if we can figure out how to do that without turning this into an expensive cursor).
    – Aaron Bertrand
    Nov 11 at 13:04














up vote
0
down vote













First, become comfortable with a generic way to generate the range of days:



DECLARE @start date = '20181102', @end date = '20181109';

;WITH n(n) AS
(
SELECT 0
UNION ALL SELECT n+1
FROM n
WHERE n < DATEDIFF(DAY, @start, @end)
)
SELECT d = DATEADD(DAY, n, @start) FROM n;


Results:



d
==========
2018-11-02
2018-11-03
2018-11-04
2018-11-05
2018-11-06
2018-11-07
2018-11-08
2018-11-09


Currently your query just produces the two rows with dates that exist, but you need to manufacture rows for the 6 other rows that don't, and you do that by using the above as the base and then left joining against it. Given these tables and data (thank you for sample data, but this is a much better form for showing us):



CREATE TABLE #DeviceHistory
(Id int, Imei varchar(64), CreationDate date, DeviceId int);

INSERT #DeviceHistory VALUES
(1,'ProductionDevice299','2018-11-04',1),
(7,'ProductionDevice299','2018-11-07',1);

CREATE TABLE #Applications
(Id int, Name varchar(64), DeviceHistoryId int);

INSERT #Applications VALUES
(1, 'Enrollment.cone',1),
(2, 'DPC_OWNERS' ,1),
(3, 'OTHER_APPS' ,1),
(6, 'Enrollment.emp' ,7),
(7, 'DPC_OWNERS' ,7);

CREATE TABLE #ApplicationAttributes
(Id int, [Key] varchar(64), Value int, DeviceApplicationId int);

INSERT #ApplicationAttributes VALUES
(1, 'EnrolledTotal', 2 ,1),
(2, 'LoginsTotal' , 5 ,2),
(3, 'OtherAttribt1', 8 ,3),
(4, 'OtherAttribt2', 12,3),
(5, 'OtherAttribt3', 17,3),
(6, 'EnrolledTotal', 21,6),
(7, 'LoginsTotal' , 25,7);


Applying the date range to this as follows:



DECLARE @start date = '20181102', @end date = '20181109';

;WITH n(n) AS
(
SELECT 0 UNION ALL SELECT n+1 FROM n WHERE n < DATEDIFF(DAY, @start, @end)
),
d(d) AS
(
SELECT DATEADD(DAY, n, @start) FROM n
),
[CTE] AS (
SELECT
[Extent1].[Id] AS [id],
[Extent1].[Imei] AS [imei],
CAST([Extent1].[CreationDate] AS DATE) AS [lastSeenOnline],
[Extent1].[DeviceId] AS [deviceId],
SUM(CAST([Extent4].[Value] AS BIGINT)) as [enrollTotalToday],

LAG(SUM(CAST([Extent4].[Value] AS BIGINT)), 1, 0) OVER
(PARTITION BY [Extent1].[Imei] ORDER BY [Extent1].[CreationDate])
AS [enrollTotalYesterday]
FROM #DeviceHistory AS [Extent1]
INNER JOIN (
SELECT [Imei], max([CreationDate]) as MaxDate
FROM #DeviceHistory
GROUP BY [Imei], CAST([CreationDate] AS DATE)
) [Extent2] on [Extent1].[Imei] = [Extent2].[Imei]
and [Extent1].[CreationDate] = [Extent2].MaxDate
INNER JOIN
#Applications AS [Extent3]
ON [Extent3].DeviceHistoryId = [Extent1].id
INNER JOIN
#ApplicationAttributes [Extent4]
ON [Extent4].DeviceApplicationId = [Extent3].id
AND [Extent4].[Key] = 'EnrolledTotal'
GROUP BY [Extent1].Id, [Extent1].Imei, [Extent1].CreationDate, [Extent1].DeviceId
)
SELECT
CTE.id,
CreationDate = d.d,
CTE.imei,
CTE.deviceId,
CTE.enrollTotalToday,
CTE.enrollTotalYesterday
FROM d
LEFT OUTER JOIN CTE
ON CTE.LastSeenOnline = d.d
ORDER BY d.d;


Gets you this, which admittedly isn't quite what you're after:




enter image description here




I'm not quite sure exactly what other data might be in these tables and what relationships you're going to use to determine that the missing rows should be populated with this imei and deviceId, or where those ids come from (they're not in your sample data). In this case the Applications table only has a single imei name, and all the data falls within the date range, but I can't imagine your real table is that simple. Other things also don't make sense to me, like why the enrollment for November 4th carries forward to the 5th, but the 7th doesn't carry to the 8th? You might have constructed the desired results because of things you know about the data or business rules you're applying in your head, but readers don't pick up on those, and there might not be a straightforward way to have SQL Server apply them, either.



As an aside, try to avoid using reserved keywords for column names (like Key).






share|improve this answer






















  • Wouldn't it be better to use an already pre-populated calendar table?
    – Zohar Peled
    Nov 11 at 7:09










  • @AaronBertrand Thank you for explanation. According to your questions: this generation needfor data grid in web client. Situation is follow: we have some histories for some devices for example daily, but not each day device send info to server. That's why we need to show for days, when device didn't send info some generated data. I can do this using C# code, but this will take a time in comparison to sql request, which will return all data itself.
    – BorHunter
    Nov 11 at 8:17











  • @AaronBertrand that's why I need additional information, which should set in generated rows.
    – BorHunter
    Nov 11 at 8:18










  • @ZoharPeled Maybe? With 8 rows (or even 100 rows), it’s unlikely to have any impact. Also (1) most people don’t already have a calendar table (or even a numbers table), (2) setting one up adds a lot of potentially useless bulk to the answer, and (3) after much time here this proposal is usually met with “But I don’t want to create a table” or even “I don’t have permission to create a table.”
    – Aaron Bertrand
    Nov 11 at 13:01










  • @BorHunter if you already have the logic to populate the missing data in C#, just do it there. I feel like the time that takes should be negligible in any case, but it probably won’t be less time than adding similar logic in the query (even if we can figure out how to do that without turning this into an expensive cursor).
    – Aaron Bertrand
    Nov 11 at 13:04












up vote
0
down vote










up vote
0
down vote









First, become comfortable with a generic way to generate the range of days:



DECLARE @start date = '20181102', @end date = '20181109';

;WITH n(n) AS
(
SELECT 0
UNION ALL SELECT n+1
FROM n
WHERE n < DATEDIFF(DAY, @start, @end)
)
SELECT d = DATEADD(DAY, n, @start) FROM n;


Results:



d
==========
2018-11-02
2018-11-03
2018-11-04
2018-11-05
2018-11-06
2018-11-07
2018-11-08
2018-11-09


Currently your query just produces the two rows with dates that exist, but you need to manufacture rows for the 6 other rows that don't, and you do that by using the above as the base and then left joining against it. Given these tables and data (thank you for sample data, but this is a much better form for showing us):



CREATE TABLE #DeviceHistory
(Id int, Imei varchar(64), CreationDate date, DeviceId int);

INSERT #DeviceHistory VALUES
(1,'ProductionDevice299','2018-11-04',1),
(7,'ProductionDevice299','2018-11-07',1);

CREATE TABLE #Applications
(Id int, Name varchar(64), DeviceHistoryId int);

INSERT #Applications VALUES
(1, 'Enrollment.cone',1),
(2, 'DPC_OWNERS' ,1),
(3, 'OTHER_APPS' ,1),
(6, 'Enrollment.emp' ,7),
(7, 'DPC_OWNERS' ,7);

CREATE TABLE #ApplicationAttributes
(Id int, [Key] varchar(64), Value int, DeviceApplicationId int);

INSERT #ApplicationAttributes VALUES
(1, 'EnrolledTotal', 2 ,1),
(2, 'LoginsTotal' , 5 ,2),
(3, 'OtherAttribt1', 8 ,3),
(4, 'OtherAttribt2', 12,3),
(5, 'OtherAttribt3', 17,3),
(6, 'EnrolledTotal', 21,6),
(7, 'LoginsTotal' , 25,7);


Applying the date range to this as follows:



DECLARE @start date = '20181102', @end date = '20181109';

;WITH n(n) AS
(
SELECT 0 UNION ALL SELECT n+1 FROM n WHERE n < DATEDIFF(DAY, @start, @end)
),
d(d) AS
(
SELECT DATEADD(DAY, n, @start) FROM n
),
[CTE] AS (
SELECT
[Extent1].[Id] AS [id],
[Extent1].[Imei] AS [imei],
CAST([Extent1].[CreationDate] AS DATE) AS [lastSeenOnline],
[Extent1].[DeviceId] AS [deviceId],
SUM(CAST([Extent4].[Value] AS BIGINT)) as [enrollTotalToday],

LAG(SUM(CAST([Extent4].[Value] AS BIGINT)), 1, 0) OVER
(PARTITION BY [Extent1].[Imei] ORDER BY [Extent1].[CreationDate])
AS [enrollTotalYesterday]
FROM #DeviceHistory AS [Extent1]
INNER JOIN (
SELECT [Imei], max([CreationDate]) as MaxDate
FROM #DeviceHistory
GROUP BY [Imei], CAST([CreationDate] AS DATE)
) [Extent2] on [Extent1].[Imei] = [Extent2].[Imei]
and [Extent1].[CreationDate] = [Extent2].MaxDate
INNER JOIN
#Applications AS [Extent3]
ON [Extent3].DeviceHistoryId = [Extent1].id
INNER JOIN
#ApplicationAttributes [Extent4]
ON [Extent4].DeviceApplicationId = [Extent3].id
AND [Extent4].[Key] = 'EnrolledTotal'
GROUP BY [Extent1].Id, [Extent1].Imei, [Extent1].CreationDate, [Extent1].DeviceId
)
SELECT
CTE.id,
CreationDate = d.d,
CTE.imei,
CTE.deviceId,
CTE.enrollTotalToday,
CTE.enrollTotalYesterday
FROM d
LEFT OUTER JOIN CTE
ON CTE.LastSeenOnline = d.d
ORDER BY d.d;


Gets you this, which admittedly isn't quite what you're after:




enter image description here




I'm not quite sure exactly what other data might be in these tables and what relationships you're going to use to determine that the missing rows should be populated with this imei and deviceId, or where those ids come from (they're not in your sample data). In this case the Applications table only has a single imei name, and all the data falls within the date range, but I can't imagine your real table is that simple. Other things also don't make sense to me, like why the enrollment for November 4th carries forward to the 5th, but the 7th doesn't carry to the 8th? You might have constructed the desired results because of things you know about the data or business rules you're applying in your head, but readers don't pick up on those, and there might not be a straightforward way to have SQL Server apply them, either.



As an aside, try to avoid using reserved keywords for column names (like Key).






share|improve this answer














First, become comfortable with a generic way to generate the range of days:



DECLARE @start date = '20181102', @end date = '20181109';

;WITH n(n) AS
(
SELECT 0
UNION ALL SELECT n+1
FROM n
WHERE n < DATEDIFF(DAY, @start, @end)
)
SELECT d = DATEADD(DAY, n, @start) FROM n;


Results:



d
==========
2018-11-02
2018-11-03
2018-11-04
2018-11-05
2018-11-06
2018-11-07
2018-11-08
2018-11-09


Currently your query just produces the two rows with dates that exist, but you need to manufacture rows for the 6 other rows that don't, and you do that by using the above as the base and then left joining against it. Given these tables and data (thank you for sample data, but this is a much better form for showing us):



CREATE TABLE #DeviceHistory
(Id int, Imei varchar(64), CreationDate date, DeviceId int);

INSERT #DeviceHistory VALUES
(1,'ProductionDevice299','2018-11-04',1),
(7,'ProductionDevice299','2018-11-07',1);

CREATE TABLE #Applications
(Id int, Name varchar(64), DeviceHistoryId int);

INSERT #Applications VALUES
(1, 'Enrollment.cone',1),
(2, 'DPC_OWNERS' ,1),
(3, 'OTHER_APPS' ,1),
(6, 'Enrollment.emp' ,7),
(7, 'DPC_OWNERS' ,7);

CREATE TABLE #ApplicationAttributes
(Id int, [Key] varchar(64), Value int, DeviceApplicationId int);

INSERT #ApplicationAttributes VALUES
(1, 'EnrolledTotal', 2 ,1),
(2, 'LoginsTotal' , 5 ,2),
(3, 'OtherAttribt1', 8 ,3),
(4, 'OtherAttribt2', 12,3),
(5, 'OtherAttribt3', 17,3),
(6, 'EnrolledTotal', 21,6),
(7, 'LoginsTotal' , 25,7);


Applying the date range to this as follows:



DECLARE @start date = '20181102', @end date = '20181109';

;WITH n(n) AS
(
SELECT 0 UNION ALL SELECT n+1 FROM n WHERE n < DATEDIFF(DAY, @start, @end)
),
d(d) AS
(
SELECT DATEADD(DAY, n, @start) FROM n
),
[CTE] AS (
SELECT
[Extent1].[Id] AS [id],
[Extent1].[Imei] AS [imei],
CAST([Extent1].[CreationDate] AS DATE) AS [lastSeenOnline],
[Extent1].[DeviceId] AS [deviceId],
SUM(CAST([Extent4].[Value] AS BIGINT)) as [enrollTotalToday],

LAG(SUM(CAST([Extent4].[Value] AS BIGINT)), 1, 0) OVER
(PARTITION BY [Extent1].[Imei] ORDER BY [Extent1].[CreationDate])
AS [enrollTotalYesterday]
FROM #DeviceHistory AS [Extent1]
INNER JOIN (
SELECT [Imei], max([CreationDate]) as MaxDate
FROM #DeviceHistory
GROUP BY [Imei], CAST([CreationDate] AS DATE)
) [Extent2] on [Extent1].[Imei] = [Extent2].[Imei]
and [Extent1].[CreationDate] = [Extent2].MaxDate
INNER JOIN
#Applications AS [Extent3]
ON [Extent3].DeviceHistoryId = [Extent1].id
INNER JOIN
#ApplicationAttributes [Extent4]
ON [Extent4].DeviceApplicationId = [Extent3].id
AND [Extent4].[Key] = 'EnrolledTotal'
GROUP BY [Extent1].Id, [Extent1].Imei, [Extent1].CreationDate, [Extent1].DeviceId
)
SELECT
CTE.id,
CreationDate = d.d,
CTE.imei,
CTE.deviceId,
CTE.enrollTotalToday,
CTE.enrollTotalYesterday
FROM d
LEFT OUTER JOIN CTE
ON CTE.LastSeenOnline = d.d
ORDER BY d.d;


Gets you this, which admittedly isn't quite what you're after:




enter image description here




I'm not quite sure exactly what other data might be in these tables and what relationships you're going to use to determine that the missing rows should be populated with this imei and deviceId, or where those ids come from (they're not in your sample data). In this case the Applications table only has a single imei name, and all the data falls within the date range, but I can't imagine your real table is that simple. Other things also don't make sense to me, like why the enrollment for November 4th carries forward to the 5th, but the 7th doesn't carry to the 8th? You might have constructed the desired results because of things you know about the data or business rules you're applying in your head, but readers don't pick up on those, and there might not be a straightforward way to have SQL Server apply them, either.



As an aside, try to avoid using reserved keywords for column names (like Key).







share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 10 at 21:10

























answered Nov 10 at 21:02









Aaron Bertrand

205k27357401




205k27357401











  • Wouldn't it be better to use an already pre-populated calendar table?
    – Zohar Peled
    Nov 11 at 7:09










  • @AaronBertrand Thank you for explanation. According to your questions: this generation needfor data grid in web client. Situation is follow: we have some histories for some devices for example daily, but not each day device send info to server. That's why we need to show for days, when device didn't send info some generated data. I can do this using C# code, but this will take a time in comparison to sql request, which will return all data itself.
    – BorHunter
    Nov 11 at 8:17











  • @AaronBertrand that's why I need additional information, which should set in generated rows.
    – BorHunter
    Nov 11 at 8:18










  • @ZoharPeled Maybe? With 8 rows (or even 100 rows), it’s unlikely to have any impact. Also (1) most people don’t already have a calendar table (or even a numbers table), (2) setting one up adds a lot of potentially useless bulk to the answer, and (3) after much time here this proposal is usually met with “But I don’t want to create a table” or even “I don’t have permission to create a table.”
    – Aaron Bertrand
    Nov 11 at 13:01










  • @BorHunter if you already have the logic to populate the missing data in C#, just do it there. I feel like the time that takes should be negligible in any case, but it probably won’t be less time than adding similar logic in the query (even if we can figure out how to do that without turning this into an expensive cursor).
    – Aaron Bertrand
    Nov 11 at 13:04
















  • Wouldn't it be better to use an already pre-populated calendar table?
    – Zohar Peled
    Nov 11 at 7:09










  • @AaronBertrand Thank you for explanation. According to your questions: this generation needfor data grid in web client. Situation is follow: we have some histories for some devices for example daily, but not each day device send info to server. That's why we need to show for days, when device didn't send info some generated data. I can do this using C# code, but this will take a time in comparison to sql request, which will return all data itself.
    – BorHunter
    Nov 11 at 8:17











  • @AaronBertrand that's why I need additional information, which should set in generated rows.
    – BorHunter
    Nov 11 at 8:18










  • @ZoharPeled Maybe? With 8 rows (or even 100 rows), it’s unlikely to have any impact. Also (1) most people don’t already have a calendar table (or even a numbers table), (2) setting one up adds a lot of potentially useless bulk to the answer, and (3) after much time here this proposal is usually met with “But I don’t want to create a table” or even “I don’t have permission to create a table.”
    – Aaron Bertrand
    Nov 11 at 13:01










  • @BorHunter if you already have the logic to populate the missing data in C#, just do it there. I feel like the time that takes should be negligible in any case, but it probably won’t be less time than adding similar logic in the query (even if we can figure out how to do that without turning this into an expensive cursor).
    – Aaron Bertrand
    Nov 11 at 13:04















Wouldn't it be better to use an already pre-populated calendar table?
– Zohar Peled
Nov 11 at 7:09




Wouldn't it be better to use an already pre-populated calendar table?
– Zohar Peled
Nov 11 at 7:09












@AaronBertrand Thank you for explanation. According to your questions: this generation needfor data grid in web client. Situation is follow: we have some histories for some devices for example daily, but not each day device send info to server. That's why we need to show for days, when device didn't send info some generated data. I can do this using C# code, but this will take a time in comparison to sql request, which will return all data itself.
– BorHunter
Nov 11 at 8:17





@AaronBertrand Thank you for explanation. According to your questions: this generation needfor data grid in web client. Situation is follow: we have some histories for some devices for example daily, but not each day device send info to server. That's why we need to show for days, when device didn't send info some generated data. I can do this using C# code, but this will take a time in comparison to sql request, which will return all data itself.
– BorHunter
Nov 11 at 8:17













@AaronBertrand that's why I need additional information, which should set in generated rows.
– BorHunter
Nov 11 at 8:18




@AaronBertrand that's why I need additional information, which should set in generated rows.
– BorHunter
Nov 11 at 8:18












@ZoharPeled Maybe? With 8 rows (or even 100 rows), it’s unlikely to have any impact. Also (1) most people don’t already have a calendar table (or even a numbers table), (2) setting one up adds a lot of potentially useless bulk to the answer, and (3) after much time here this proposal is usually met with “But I don’t want to create a table” or even “I don’t have permission to create a table.”
– Aaron Bertrand
Nov 11 at 13:01




@ZoharPeled Maybe? With 8 rows (or even 100 rows), it’s unlikely to have any impact. Also (1) most people don’t already have a calendar table (or even a numbers table), (2) setting one up adds a lot of potentially useless bulk to the answer, and (3) after much time here this proposal is usually met with “But I don’t want to create a table” or even “I don’t have permission to create a table.”
– Aaron Bertrand
Nov 11 at 13:01












@BorHunter if you already have the logic to populate the missing data in C#, just do it there. I feel like the time that takes should be negligible in any case, but it probably won’t be less time than adding similar logic in the query (even if we can figure out how to do that without turning this into an expensive cursor).
– Aaron Bertrand
Nov 11 at 13:04




@BorHunter if you already have the logic to populate the missing data in C#, just do it there. I feel like the time that takes should be negligible in any case, but it probably won’t be less time than adding similar logic in the query (even if we can figure out how to do that without turning this into an expensive cursor).
– Aaron Bertrand
Nov 11 at 13:04

















 

draft saved


draft discarded















































 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53241284%2fhow-to-generate-row-between-dates-range-in-sql-if-for-date-dont-have-a-record%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







這個網誌中的熱門文章

How to read a connectionString WITH PROVIDER in .NET Core?

In R, how to develop a multiplot heatmap.2 figure showing key labels successfully

Museum of Modern and Contemporary Art of Trento and Rovereto