How to INSERT data into multiple columns from multiple queries? - sql-server

This SQL Server stored procedure takes four parameters to show a table order by dates between FromDate and ToDate as follows:
CREATE PROCEDURE GetLedger
(#FromDate date,
#ToDate date,
#Supplier int,
#MOP int)
AS
BEGIN
DECLARE #ExpTABLE TABLE
(
RowNo int,
TranDate date,
NetExpense float
)
INSERT INTO #ExpTABLE
SELECT
ROW_NUMBER() OVER (ORDER BY TranDate), TranDate, SUM(NetAmount)
FROM
Expenditure
WHERE
TranDate BETWEEN #FromDate AND #ToDate
AND SupplierID = #Supplier
AND MOP = #MOP
GROUP BY
TranDate
DECLARE #Data TABLE
(
RDate DATE,
Expense float
PRIMARY KEY (RDate)
)
WHILE (#FromDate <= #ToDate)
BEGIN
INSERT INTO #Data (RDate)
VALUES (#FromDate)
SELECT #FromDate = DATEADD(DAY, 1, #FromDate)
END
WHILE (#FromDate <= #ToDate)
BEGIN
INSERT INTO #Data (Expense)
SELECT NetExpense
FROM #ExpTABLE
WHERE TranDate = #FromDate
SELECT #FromDate = DATEADD(DAY, 1, #FromDate)
END
SELECT * FROM #Data
END
--EXEC GetLedger '2020-03-01' ,'2020-03-31',2,2
The data in #ExpTABLE is like this:
RowNo TranDate NetExpense
------------------------------
1 2020-03-15 35
Now, I am trying to INSERT this NetExpense of '2020-03-15' in #Data Table in the respective date while the rest NetExpense of Other dates remain null.
This INSERT query:
WHILE (#FromDate <= #ToDate)
BEGIN
INSERT INTO #Data (Expense)
SELECT NetExpense
FROM #ExpTABLE
WHERE TranDate = #FromDate
SELECT #FromDate = DATEADD(DAY, 1, #FromDate)
END
is inserting NULL in all dates including '2020-03-15'. What am I missing here?

In order to have a row for every date in your range it would be helpful to use a Calendar table. There are lots of resources available on how to do that like this one. Another option is to create your a calendar table on the fly as a common table expression (CTE) like this.
When developing a stored procedure I like to get it working just as a regular SQL statement and once I have that I can transform it into a stored procedure. So here is the regular SQL statement that should get you going.
DECLARE
#FromDate DATE
, #ToDate DATE
, #Supplier INT
, #MOP INT;
-- set these values to whatever you would pass to your stored procedure
SET #FromDate = '2020-03-01';
SET #ToDate = '2020-03-31';
SET #Supplier = 2;
SET #MOP = 3;
DECLARE #Expenditure TABLE
(
ID INT NOT NULL
, TranDate DATE NOT NULL
, SupplierID INT NOT NULL
, MOP INT NOT NULL
, NetAmount DECIMAL(10, 2) NOT NULL
);
INSERT INTO #Expenditure (ID, TranDate, SupplierID, MOP, NetAmount)
VALUES
(1, '02/28/2020', 2, 3, 200.12)
, (2, '03/15/2020', 2, 3, 125.10)
, (3, '03/15/2020', 2, 3, 74.90)
, (4, '03/17/2020', 3, 3, 150.32)
, (5, '03/18/2020', 2, 3, 250.78)
;WITH cteCalendar (MyDate) AS
(
SELECT CONVERT(DATE, #FromDate) AS MyDate
UNION ALL
SELECT DATEADD(DAY, 1, MyDate)
FROM cteCalendar
WHERE DATEADD(DAY, 1, MyDate) <= #ToDate
)
SELECT
ROW_NUMBER() OVER (ORDER BY x.TranDate) AS [RowNo]
, x.TranDate
, x.NetExpense
FROM (
SELECT
c.MyDate AS TranDate
, SUM(e.NetAmount) AS [NetExpense]
FROM cteCalendar c
LEFT JOIN #Expenditure e ON c.MyDate = e.TranDate
AND e.SupplierID = #Supplier
AND e.MOP = #MOP
GROUP BY c.MyDate
) x;
Here is a demo of the code.

Related

Can I "left join" days between 2 dates in sql server?

There is a table in SQL Server where data is entered day by day. In this table, data is not filled in some days.
Therefore, there are no records in the table.
Sample: dataTable
I need to generate a report like the one below from this table.
Create a table with all the days of the year. I know that I can output a report by "joining" the "dataTable" table.
But this solution seems a bit strange to me.
Is there another way?
the code i use for temp date table
CREATE TABLE tempDate (
calendarDate date,
PRIMARY KEY (calendarDate)
)
DECLARE
#start DATE= '2021-01-01',
#dateCount INT= 730,
#rowNumber INT=1
WHILE (#rowNumber < #dateCount)
BEGIN
INSERT INTO tempDate values (DATEADD(DAY, #rowNumber, #start))
set #rowNumber=#rowNumber+1
END
GO
select * from tempDate
This is how I join using this table
SELECT
*
FROM
tempDate td WITH (NOLOCK)
LEFT JOIN dataTable dt WITH (NOLOCK) ON dt.reportDate = td.calendarDate
WHERE
td.calendarDate BETWEEN '2021-09-05' AND '2021-09-15'
Create a table with all the days of the year. I know that I can output a report by "joining" the "dataTable" table.
This is the way. You can generate that "table" on the fly if you really want to, but normally the best way is to simply have a calendar table.
You can use common expression tables for dates. The code you need:
IF(OBJECT_ID('tempdb..#t') IS NOT NULL)
BEGIN
DROP TABLE #t
END
CREATE TABLE #t
(
id int,
dt date,
dsc varchar(100),
)
INSERT INTO #t
VALUES
(1, '2021.09.08', 'a'),
(1, '2021.09.09', 'b'),
(1, '2021.09.12', 'c')
DECLARE #minDate AS DATE
SET #minDate = (SELECT MIN(dt) FROM #t)
DECLARE #maxDate AS DATE
SET #maxDate = (SELECT MAX(dt) FROM #t)
;WITH cte
AS
(
SELECT #minDate AS [dt]
UNION ALL
SELECT DATEADD(DAY, 1, [dt])
FROM cte
WHERE DATEADD(DAY, 1, [dt])<=#maxDate
)
SELECT
ISNULL(CAST(t.id AS VARCHAR(10)), '') AS [id],
cte.dt AS [dt],
ISNULL(t.dsc, 'No record has been entered in the table.') AS [dsc]
FROM
cte
LEFT JOIN #t t on t.dt=cte.dt
The fastest method is to use a numbers table, you can get a date list between 2 dates with that:
DECLARE #Date1 DATE, #Date2 DATE
SET #Date1 = '20200528'
SET #Date2 = '20200625'
SELECT DATEADD(DAY,number+1,#Date1) [Date]
FROM master..spt_values
WHERE type = 'P'
AND DATEADD(DAY,number+1,#Date1) < #Date2
If you go go in LEFT JOIN this select, whit your table, you have the result that you want.
SELECT *
FROM (SELECT DATEADD(DAY,number+1,#Date1) [Date]
FROM master..spt_values WITH (NOLOCK)
WHERE type = 'P'
AND DATEADD(DAY,number+1,#Date1) < #Date2 ) as a
LEFT JOIN yourTable dt WITH (NOLOCK) ON a.date = dt.reportDate
WHERE td.[Date] BETWEEN '2021-09-05' AND '2021-09-15'

Group data by interval of 15 minutes and use cross tab

I am trying to write a query based on datetime and weekday in sql server where my output should be like :
My table descriptions are:
**Branch**(DateKey integer,
BranchName varchar2(20),
TransactionDate datetime,
OrderCount integer)
**Date**(DateKey integer PrimaryKey,
DayNameofWeek varchar2(15))
This is the raw data I have
So, this is quite a long shot but I solved it the following way:
I created a table valued function, which would take a date as a parameter and find all 15-minute intervals during that day.
For each day it would go from 00:00, to 00:15, 00:30 up to 23:30, 23:45, and 23:59. It also returns each interval start time and end time, since we will need to use this for every row in your branch table to check if they fall into that time slot and if so, count it in.
This is the function:
create function dbo.getDate15MinIntervals(#date date)
returns #intervals table (
[interval] int not null,
[dayname] varchar(20) not null,
interval_start_time datetime not null,
interval_end_time datetime not null
)
as
begin
declare #starttime time = '00:00';
declare #endtime time = '23:59';
declare #date_start datetime;
declare #date_end datetime;
declare #min datetime;
select #date_start = cast(#date as datetime) + cast(#starttime as datetime), #date_end = cast(#date as datetime) + cast(#endtime as datetime);
declare #minutes table ([date] datetime)
insert into #minutes values (#date_start), (#date_end) -- begin, end of the day
select #min = DATEADD(mi, 0, #date_start)
while #min < #date_end
begin
select #min = DATEADD(mi, 1, #min)
insert into #minutes values (#min)
end
insert into #intervals
select ([row]-1)/15+1 intervalId, [dayname], min(interval_time) interval_start_time
> -- **NOTE: This line is the only thing you need to change:**
, DATEADD(ms, 59998, max(interval_time)) interval_end_time
from
(
select row_number() over(order by [date]) as [row], [date], datename(weekday, [date]) [dayname], [date] interval_time
from #minutes
) t
group by ([row]-1)/15+1, [dayname]
order by ([row]-1)/15+1
return
end
--example of calling it:
select * from dbo.getDate15MinIntervals('2017-07-14')
Then, I am querying your branch table (you don't really need the Date table, the weekday now you have it in the function but even if not, there's a DATENAME function in SQL Server, starting with 2008 that you can use.
I would query your table like this:
select branchname, [dayname], ISNULL([11:30], 0) as [11:30], ISNULL([11:45], 0) as [11:45], ISNULL([12:00], 0) as [12:00], ISNULL([12:45], 0) as [12:45]
from
(
select intervals.[dayname]
, b.branchname
, convert(varchar(5), intervals.interval_start_time, 108) interval_start_time -- for hh:mm format
, sum(b.ordercount) ordercount
from branch b cross apply dbo.getDate15MinIntervals(CAST(b.TransactionDate as date)) as intervals
where b.transactiondate between interval_start_time and interval_end_time
group by intervals.[dayname], b.branchname, intervals.interval_start_time, intervals.interval_end_time
) t
pivot ( sum(ordercount) for interval_start_time in ( [11:30], [11:45] , [12:00], [12:45] )) as p
Please note I have used in the PIVOT function only the intervals I can see in the image you posted, but of course you could write all 15-minute intervals of the day manually - you would just need to write them once in the pivot and once in the select statement - or optionally, generate this statement dynamically.

Getting values from Database instead of using Declare

I am using
declare #starttime datetime = '2015-10-28 10:00',
#endtime datetime = '2015-10-28 12:00',
#interval int = 30
I would like to retrieve these values from a table instead of declaring them as static values. I have a separate table that has these 3 values (Startdate, Enddate, interval).
I tried creating a temp table an then inserting values into it but the naming convention fails to work if I want to use the vales in my further query. My entire query is this and in this i want the startdate, enddate,interval to be fetched from the database instead of being static.
create table #booking (start datetime, [end] datetime)
insert into #booking values
('2015-10-28 08:00','2015-10-28 08:30'),
('2015-10-28 10:00','2015-10-28 10:30'),
('2015-10-28 10:30','2015-10-28 11:00')
declare #starttime datetime = '2015-10-28 08:00',
#endtime datetime = '2015-10-28 12:00',
#interval int = 30,
#slots int
select #slots = datediff(minute, #starttime, #endtime)/#interval
SELECT TOP (#slots) N=IDENTITY(INT, 1, 1)
INTO #Numbers
FROM master.dbo.syscolumns a CROSS JOIN master.dbo.syscolumns b;
select
dateadd(minute,((n-1)*#interval),#starttime) as start,
dateadd(minute,(n*#interval),#starttime) as [end]
into
#slots
from
#numbers
select s.*, b.* from #slots s
left join #booking b
on s.start = b.start and s.[end] = b.[end]
where b.start is null
drop table #numbers, #booking, #slots
I'm not so sure I understand the question, but assuming you can fetch a single row from that table, you could just do this:
declare #starttime datetime
,#endtime datetime
,#interval int
select #starttime = starttime
,#endtime = endtime
,#interval = interval
from yourTable
where <condition>
You can do something like:
SELECT
#starttime = Startdate
, #endtime = Enddate
, #interval = interval
FROM YourTableHere

write a query to fill the above table from 1 Jan, 2008 to 31 Dec 2014

Create a table with the following columns
DateId, Date, Day_Name, Day_of_Week, Day_of_Month, Day_of_Year,
Mon… _Name, Quarter_of_Year, Year_Name,Year
write a query to fill the above table from 1 Jan, 2008 to 31 Dec 2014
DECLARE #DATE smalldatetime
DECLARE #DATESTART smalldatetime
DECLARE #DATEEND smalldatetime
SET #DATESTART = '01-01-2008'
SET #DATEEND = '12-31-2014'
--SET #DATE = #DATESTART
DECLARE #days int
DECLARE #index int = 1
SET #days= DATEPART(dayofyear, #DATEEND) - DATEPART(dayofyear, #DATESTART)
SELECT #days
WHILE()
INSERT INTO [BI_Database].[dbo].[Date_
Range]
([Date],[Day_Name],[Day_of_Week],[Day_of…
VALUES(<Date, datetime,>,<Day_Name, nvarchar(50),>,<Day_of_Week, smallint,>,<Day_of_Month, smallint,>,<Day_of_Year, smallint,>,<Month_Name, nvarchar(50),>
,<Month_of_Year, smallint,>,<Quarter_Name, nvarchar(50),>,<Quarter_of_Year, nvarchar(50),>,<Year_Name, nvarchar(50),>,<Year, smallint,>)
This should do it:
DECLARE #DATESTART smalldatetime
DECLARE #DATEEND smalldatetime
SET #DATESTART = '01-01-2008'
SET #DATEEND = '12-31-2014'
;WITH cte4 As (Select 0 As N Union All Select 1 Union All Select 2 Union All Select 4)
, cte64 As (Select 0 As N From cte4 n0, cte4 n1, cte4 n2)
, cte4k As (Select ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) As N From cte64 n0, cte64 n1)
, cteDate As (Select N, CAST(#DATESTART+N-1 As smalldatetime) As date_ From cte4k)
INSERT INTO [BI_Database].[dbo].[Date_Range]
([Date],Day_Name,Day_of_Week,Day_of_Week,Day_of_Month,Day_of_Year,Month_Name,Month_of_Year,
Quarter_Name,Quarter_of_Year,Year_Name,[Year])
SELECT
N As DateId,
date_ As [Date],
DATENAME(WEEKDAY, date_) As Day_Name,
DATEPART(WEEKDAY, date_) As Day_of_Week,
Day(date_) As Day_of_Month,
DATEPART(DAYOFYEAR, date_) As Day_of_Year,
DATENAME(MONTH, date_) As Month_Name,
MONTH(date_) As Month_of_Year,
DATENAME(QUARTER, date_) As Quarter_Name,
DATEPART(QUARTER, date_) As Quarter_of_Year,
DATENAME(YEAR, date_) As Year_Name,
YEAR(date_) As [Year]
FROM cteDate
WHERE date_ BETWEEN #DATESTART AND #DATEEND
Note: Only works for SQL Server 2005+,
Try this, unfortunately I had no clue what to populate year_name with. I used a temporary table, this can be replaced by your own table
create table #t
(DateId int identity(1,1), Date date, Day_Name varchar(8),
Day_of_Week smallint, Day_of_Month smallint, Day_of_Year int,
Mon_Name varchar(12), Quarter_of_Year smallint, Year_Name varchar(30),
Year int)
insert #t
(Date,Day_Name,Day_of_Week,Day_of_Month,Day_of_Year,Mon_Name,
Quarter_of_Year, Year_Name, Year)
select Date1 Date
,datename(weekday, date1) Day_Name
,datediff(day, 0, date1) %7 + 1 Day_of_Week
,day(date1) Day_of_Month
,datepart(dy, date1) Day_of_Year
,datepart(mm, date1) Mon_Name
,datepart(qq, date1) Quarter,
null Year_Name, -- don't know what you want here
year(date1) year
from
master..spt_values v1
cross join
(select 0 mul union all select 1 union all select 2) m
cross apply
(select dateadd(day, v1.number+ 2048 * mul,'2008-01-01') date1) a
where date1 <= '2014-12-31'
and type = 'P'
select * from #t
drop table #t

SQL Server related question

I have this thing that i need to do and some advices will be greatly appreciated.
I have a SQL server table with some phone calls.For each phone call i have the start and end time.
What i need to accomplish: a stored procedure which for a certain period of time, let's say 5 hours at a x interval, lets say 2 minutes returns the number of connected calls.
Something like:
Interval Nr of Calls Connected
01-01-2010 12:00:00 - 01-01-2010 12:05:00 30
01-01-2010 12:05:01 - 01-01-2010 12:10:00 10
.............
Which will be the fastest way to do that? Thank you for your help
This will work for intervals that have calls ...
Declare #datetimestart datetime
Declare #interval int
Set #datetimestart = '2009-01-01 12:00:00'
Set #interval = 5 --in minutes
Select
[start_interval], [end_interval] , count([start_interval]) as [calls]
From
(
Select
DateAdd( Minute,Floor(DateDiff(Minute,#datetimestart,[date])/#interval)*#interval
,#datetimestart) ,
DateAdd( Minute,#interval + Floor(DateDiff(Minute,#datetimestart,[date])/#interval)*#interval
,#datetimestart)
From yourTable
) As W([start_interval],[end_interval])
group by [start_interval], [end_interval]
This will work for all intervals regardless of number of calls..
Declare #datetimestart datetime, #datetimeend datetime, #datetimecurrent datetime
Declare #interval int
Set #datetimestart = '2009-01-01 12:00:00'
Set #interval = 10
Set #datetimeend = (Select max([date]) from yourtable)
SET #datetimecurrent = #datetimestart
declare #temp as table ([start_interval] datetime, [end_interval] datetime)
while #datetimecurrent < #datetimeend
BEGIN
insert into #temp select (#datetimecurrent), dateAdd( minute, #interval, #datetimecurrent)
set #datetimecurrent = dateAdd( minute, #interval, #datetimecurrent)
END
Select
*
From
(
Select
[start_interval],[end_interval], count(d.[start_time])
From #temp t left join yourtable d on d.[start_time] between t.[start_interval] and t.[end_interval]
) As W([start_interval],[end_interval], [calls])
I Altered Gaby's example a little to do What you expected
Declare #datetimeend datetime
,#datetimecurrent datetime
,#interval int
Set #interval = 10
Set #datetimeend = (Select max([end_time]) from Calls)
SET #datetimecurrent = '2010-04-17 14:20:00'
declare #temp as table ([start_interval] datetime, [end_interval] datetime)
while #datetimecurrent < #datetimeend
BEGIN
insert into #temp select (#datetimecurrent), dateAdd( minute, #interval, #datetimecurrent)
set #datetimecurrent = dateAdd( minute, #interval, #datetimecurrent)
END
Select
[start_interval],[end_interval], count(d.id) [COUNT]
From #temp t
left join Calls d on
d.end_time >= t.start_interval
AND d.start_time <= t.end_interval
GROUP BY [start_interval],[end_interval]
used this to create the table and fill it
CREATE TABLE dbo.Calls
(
id int NOT NULL IDENTITY (1, 1),
start_time datetime NOT NULL,
end_time datetime NULL,
caller nvarchar(50) NULL,
receiver nvarchar(50) NULL
) ON [PRIMARY]
GO
ALTER TABLE dbo.Calls ADD CONSTRAINT
PK_Calls PRIMARY KEY CLUSTERED
(
id
) ON [PRIMARY]
GO
DECLARE #I INT
SET #I = 0
WHILE #I < 100
BEGIN
INSERT INTO Calls
(start_time, end_time)
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-10,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-9,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-9,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-8,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-8,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-7,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-7,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-6,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-6,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-5,GETDATE()))
UNION
SELECT
DATEADD(HOUR,-#I,DATEADD(MINUTE,-5,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-4,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-4,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-3,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-3,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-2,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-2,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-1,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-1,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-0,GETDATE()));
SET #I = #I + 1
END
Done in SQL Server 2008
but the script would work in other versions
I would use a Numbers pivot table to get the time intervals and then count all calls which overlapped the interval:
SELECT Intervals.IntervalStart
,Intervals.IntervalEnd
,COUNT(*)
FROM (
SELECT DATEADD(MINUTE, Numbers * 2, #StartTime) AS IntervalStart
,DATEADD(MINUTE, (Numbers + 1) * 2, #StartTime) AS IntervalEnd
FROM Numbers
WHERE Numbers BETWEEN 0 AND (5 * 60 / 2)
) AS Intervals
LEFT JOIN Calls
ON Calls.CallEnd >= Intervals.IntervalStart
AND Calls.CallStart < Intervals.IntervalEnd
GROUP BY Intervals.IntervalStart
,Intervals.IntervalEnd
To get the empty intervals, you would need to LEFT JOIN to this from another "Intervals" derived table.
How about this approach:
select Year(StartTime) as Year, Month(StartTime) as Month, Day(StartTime) as Day, datepart(hh, StartTime) as Hour, datepart(mm, StartTime) / 2 as TwoMinuteSegment, count(*)
from MyTable
where StartDate between '01-01-2010 12:00:00' and '01-01-2010 17:00:00'
group by Year(StartTime), Month(StartTime), Day(StartTime), datepart(hh, StartTime), datepart(mm, StartTime) / 2

Resources