I have two tables.
Table TDatos - contains ending and starting dates of each month
appointment - contains Valid From and Valid To dates
There is no any relation between the two tables, however, I want to select all appointment.[Valid From] date day differences with each month DatosT.[Month Start]
For example if appointment. [Valid From] is '2022-05-01' a selected data should look something like 120, 89, 61, 30, 0, -31 ... (for all 12 months of the year or more...)
I have done that using loop, but is there a way to do that without looping ?
/* declarations ... */
SET #Counter=1
WHILE ( #Counter <= 12)
BEGIN
set #month_start = (select top 1 month_start_date from TDatos where MonthNumber = #Counter)
insert into #AllRecords
select
DATEADD(day, [#month_start, Valid From]) as NewDays
from appointment
END
use a recursive query or a RVC...
WITH
T AS (select top 1 month_start_date, 1 AS N from TDatos where MonthNumber = 1
UNION ALL
select top 1 month_start_date, N + 1 from TDatos where MonthNumber = N + 1)
insert into #AllRecords
select DATEADD(day, month_start, [Valid From])
from appointment CROSS JOIN T;
Im in current need of making a query that returns based on a week all the days between the week but I need to use them as columns so that I can use rows per day for that week, I took an example I found here on forums
;WITH TestData(N, Order_Date, Net_Amount) AS (
SELECT 1 N, CAST(GETDATE() AS DATE) Order_Date, RAND() * 100 Net_Amount
UNION ALL
SELECT N+1 N, CAST(GETDATE()-N/5 AS DATE) Order_Date, RAND(CHECKSUM(NEWID())) * 100 Net_Amount FROM TestData
WHERE N < 20
)
SELECT TestData.Order_Date, TestData.Net_Amount INTO #Order FROM TestData
SET DATEFIRST 1
;WITH Days(N,DayOfTheWeek) AS (
SELECT 1 N, DATEADD(DAY, 1-DATEPART(WEEKDAY, GETDATE()), CONVERT(DATE,GETDATE())) DayOfTheWeek
UNION ALL
SELECT N+1 N,DATEADD(DAY, 1, DayOfTheWeek) DayOfTheWeek FROM Days
WHERE N < 7
)
SELECT d.DayOfTheWeek, SUM(Net_Amount) TotalAmount
FROM Days d
LEFT JOIN #Order ON d.DayOfTheWeek = Order_Date
GROUP BY d.DayOfTheWeek
This is my end result
But I need to display the days of the week in columns instead of rows
Result expected
thx for your help
Currently I'm trying to join a date table to a ledger table so I can fill the gaps of the ledger table whenever there are no transactions in certain instances (e.g. there are transactions on March 1st and in March 3rd, but no transaction in March 2nd. And by joining both tables March 2nd would appear in the ledger table but with 0 for the variable we're analyzing.)
The challenge is that I can't create a Date object/table/dimension because I don't have permissions to create tables in the database. Therefore I've been generating a date sequence with this code:
DECLARE #startDate date = CAST('2016-01-01' AS date),
#endDate date = CAST(GETDATE() AS date);
SELECT DATEADD(day, number - 1, #startDate) AS [Date]
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY n.object_id
)
FROM sys.all_objects n
) S(number)
WHERE number <= DATEDIFF(day, #startDate, #endDate) + 1;
So, is there the possibility to join both tables into the same statement? Let's say the ledger table looks like this:
SELECT
date,cost
FROM ledger
I'd assume it can be done by using a subquery but I don't know how.
Thank you.
There is a very good article by Aaron Bertrand showing several methods for generating a sequence of numbers (or dates) in SQL Server: Generate a set or sequence without loops – part 1.
Try them out and see for yourself which is faster or more convenient to you. (spoiler - Recursive CTE is rather slow)
Once you've picked your preferred method you can wrap it in a CTE (common-table expression).
Here I'll use your method from the question
WITH
CTE_Dates
AS
(
SELECT
DATEADD(day, number - 1, #startDate) AS dt
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY n.object_id
)
FROM sys.all_objects n
) S(number)
WHERE number <= DATEDIFF(day, #startDate, #endDate) + 1
)
SELECT
...
FROM
CTE_Dates
LEFT JOIN Ledger ON Ledger.dt = CTE_Dates.dt
;
You can use your generated date sequence as a CTE and LEFT JOIN that to your ledger table. For example:
DECLARE #startDate date = CAST('2020-02-01' AS date);
DECLARE #endDate date = CAST(GETDATE() AS date);
WITH dates AS (
SELECT DATEADD(day, number - 1, #startDate) AS [Date]
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY n.object_id
)
FROM sys.all_objects n
) S(number)
WHERE number <= DATEDIFF(day, #startDate, #endDate) + 1
)
SELECT dates.Date, COALESCE(ledger.cost, 0)
FROM dates
LEFT JOIN (VALUES ('2020-02-02', 14), ('2020-02-05', 10)) AS ledger([Date], [cost]) ON dates.Date = ledger.Date
Output:
Date cost
2020-02-01 0
2020-02-02 14
2020-02-03 0
2020-02-04 0
2020-02-05 10
2020-02-06 0
Demo on dbfiddle
I have an MSSQL database and I want to get a value for each day/ week/ month in separate queries.
I got this working just fine except for intervals where there is no data, it wont return anything. And since im putting this in a graph, I want it to display a 0 or a NULL at least instead of jumping days or weeks etc.
I dont know if it will be different for each query but here is my daily query:
select CAST(Placements.CreatedOn AS DATE) AS
date,SUM(Placements.CommissionPerc * (Placements.PlacementFee / 100)) AS value
from [placements]
where [Placements].[CreatedOn] >= '2018-06-07' and [Placements].[CreatedOn] < '2018-06-12'
group by CAST(Placements.CreatedOn AS DATE)
order by CAST(Placements.CreatedOn AS DATE) ASC
This returns a result like:
So it returns 0 for when the data is actually 0 but when its missing, theres nothing like for days 9, 10 and 12
How can i fix this? thanks
Using a recursive CTE you can generate a list of dates.
Which can then be used to LEFT JOIN your table.
Example:
WITH DATES2018 AS
(
SELECT CAST('2018-01-01' AS DATE) AS [date]
UNION ALL
SELECT DATEADD(day, 1, [date])
FROM DATES2018
WHERE [date] < CAST('2018-12-31' AS DATE)
)
SELECT
d.[Date],
SUM(p.CommissionPerc * (p.PlacementFee / 100.0)) AS [value]
FROM DATES2018 AS d
LEFT JOIN [Placements] AS p ON CAST(p.CreatedOn AS DATE) = d.[Date]
WHERE d.[Date] BETWEEN '2018-06-07' AND '2018-06-11'
GROUP BY d.[Date]
ORDER BY d.[Date] ASC
OPTION (MAXRECURSION 366)
But you could also just add a new permanent table with all dates.
And use that table to left join your table.
Btw, if variables are used for the start and end date then that SQL can be optimized.
DECLARE #StartDate DATE = '2018-06-07';
DECLARE #EndDate DATE = '2018-06-11';
WITH DATES AS
(
SELECT #StartDate AS [date]
UNION ALL
SELECT DATEADD(day, 1, [date])
FROM DATES
WHERE [date] < #EndDate
)
SELECT
d.[Date],
SUM(p.CommissionPerc * (p.PlacementFee / 100.0)) AS [value]
FROM DATES AS d
LEFT JOIN [Placements] AS p
ON p.CreatedOn BETWEEN CAST(#StartDate AS DATETIME) AND CAST(DATEADD(day, 1, #EndDate) AS DATETIME) AND
CAST(p.CreatedOn AS DATE) = d.[Date]
GROUP BY d.[Date]
ORDER BY d.[Date] ASC
OPTION (MAXRECURSION 0)
A permanent calendar table would be best but here's and example that uses a CTE to create the dates needed for a LEFT JOIN. This uses a maximum of 1,000 days but can be extended as needed.
DECLARE
#StartDate date = '2018-06-07'
, #EndDate date = '2018-06-12';
WITH
t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
,t1k AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1 AS num FROM t10 AS a CROSS JOIN t10 AS b CROSS JOIN t10 AS c)
,calendar AS (SELECT DATEADD(day, num, #StartDate) AS calendar_date
FROM t1k
WHERE num <= DATEDIFF(day, #StartDate, #EndDate)
)
SELECT
calendar.calendar_date AS date
, SUM( COALESCE(Placements.CommissionPerc * (Placements.PlacementFee / 100),0 ) ) AS value
FROM calendar
LEFT JOIN [placements] ON [Placements].[CreatedOn] = calendar.calendar_date
GROUP BY calendar.calendar_date
ORDER BY calendar.calendar_date ASC;
I have a table like:
id month cost
------------------
1 Jan 200
1 Mar 204
1 May 200
1 Dec 201
I need an output like( order by month including the other months of a year-displaying all 12 months):
to month cost
------------------
1 Jan 200
NULL Feb NULL
1 Mar 204
....
....
....
1 Dec 201
any idea or solution how to do this in TSQL?
Thanks!
edit:: month is extracted from a datetime value.
in real world i'll have to show previous 12 months from last month in a DESC order! any suggestion for that?
Try building a reference table of months, and JOINing on it. It's the quickest way to do this with months in varchar datatype.
declare #foo table (id int, [mon] varchar(100), cost int)
declare #mon table (mon varchar(100), orderWeight int)
INSERT INTO #mon (mon, orderWeight)
VALUES ('Jan',1), ('Feb',2),('Mar',3),('Apr',4),('May',5),('Jun',6),('Jul',7),
('Aug',8),('Sep',9),('Oct',10),('Nov',11),('Dec',12)
INSERT INTO #foo(id, [mon], cost)
VALUES ( 1 ,'Jan' , 200),
( 1 ,'Mar', 204),
( 1 ,'May' , 200),
( 1 ,'Dec' , 201)
select f.id,
m.[mon] ,
f.cost
from #mon as m
left join #foo as f on m.mon = f.mon
order by m.orderWeight
Results:
Your ordering will now be guaranteed with the order by orderWeight.
Sample table
create table mytable(id int, dt datetime, cost money)
insert mytable values
(1,GETDATE()-10,200),
(1,GETDATE()-40,204),
(1,GETDATE()-100,200),
(1,GETDATE()-200,201);
The query, using SQL Server 2008 specific syntax, and sorted properly
select
t.id [to],
CONVERT(char(3),dateadd(month,-M.N,L.PVT),7) [Month],
sum(t.cost) totalCost
from (select PVT=dateadd(month,datediff(month,0,getdate())-1,0)) L
cross join (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11)) M(N)
left join mytable t
on t.dt >= dateadd(month,-M.N,L.PVT)
and t.dt < dateadd(month,-M.N+1,L.PVT)
group by t.id, right(CONVERT(char(9),dt,6),6), M.N, L.PVT
order by M.N
What it does:
right(CONVERT(char(9),dt,6),6) converts a date into the format 'DD MMM YY', we only need the MMM YY part
In the SELECT, we further extract only the 3-char month from it, using LEFT( , 3)
The subquery L has a single record and column, PVT, which is the first date of the last month
The number series 0-11 is used to create the month values for the last 12 months, using the formula dateadd(month,-M.N,L.PVT)
The range t.dt >= .. and t.dt < .. finds data for a single month
How about this?
The result contains month and year, but you can strip it as you want.
;with months
as
(
select dateadd(month, -1, dateadd(day, datediff(day, 0, getdate()), 0)) as m
union all
select dateadd(month, -1, m)
from months
where m > dateadd(month, -12, getdate())
)
-- Testdata
,yourTable(id,somedate,cost)
as
(
select 1, '2011-01-03', 200
union all
select 1, '2011-03-06', 204
union all
select 1, '2010-05-09', 200
union all
select 1, '2010-05-19', 201
union all
select 1, '2010-12-02', 201
)
-- end testdata
select yt.id
,datename(month,coalesce(yt.somedate, m.m)) as [month]
,datename(year,coalesce(yt.somedate, m.m)) as [year]
--,yt.cost
,sum(yt.cost) as cost
from months m
left join yourTable yt
on datepart(year, yt.someDate) = DATEPART(year, m.m)
and datepart(month, yt.someDate) = DATEPART(month, m.m)
group by
yt.id
,datename(month,coalesce(yt.somedate, m.m))
,datename(year,coalesce(yt.somedate, m.m))
,m.m
order by m.m desc
Edit: Altered solution to support sum.
Remove the group by-section and alter the comment of cost, to get the old solution.