How to add an additional column to the result set returned by a SP without modifying the SP? - sql

I have a Stored Procedure (SP), named myStoredProcedure, returning me such output based on startDate and endDate user-defined parameters:
PrimaryName SecondaryName Volume
A B 20
C D 30
A D 50
...
So, Volume represents the sum of all the cases between the dates defined.
In another SP, named mySecondStoredProcedure, I am using the first SP to get the result there. However, my problem is that I need an additional attribute in my output, which is year, I want to see year based volumes. Therefore, the output I would like to see is something like that
assume startDate: 2014, endDate: 2015:
PrimaryName SecondaryName Volume Year
A B 12 2014
C D 14 2014
A D 20 2014
A B 8 2015
C D 16 2015
A D 30 2015
...
I am not allowed to modify myStoredProcedure. Therefore I build a while loop in the second SP to receive it. My code is like:
declare #temp_table table
(
PrimaryGroup varchar(10),
SecondaryGroup varchar(10),
Volume int
)
while #startDate < #endDate
begin
insert into #temp_table
exec myStoredProcedure #startDate #endDate
set #startDate = DATEADD(YEAR,1,#startDate)
end
select * from #temp_table
This is giving me the result without the year column. I need a year column like I showed in my example output above. I could not find a way to add it. There is no primary key in the result set returned by myStoredProcedure. Also, SQL Server 2008 does not let me add a year column in #temp_table, saying that fields are not matching. How can I add the year column properly? Any help would be appreciated!
EDIT: When I add year column in the definition of #temp_table, the error I receive: Column name or number of supplied values does not match table definition.

You're close with the syntax you currently have, you'll just need to add the year to the temp table and supply it after calling the stored procedure. In addition, you will also need to specify the columns being inserted (a practice well worth getting in the habit of) as your procedure doesn't return the same number of columns.
declare #temp_table table
(
PrimaryGroup varchar(10),
SecondaryGroup varchar(10),
Volume int,
Year int
)
while #startDate < #endDate
begin
insert into #temp_table (PrimaryGroup, SecondaryGroup, Volume)
exec myStoredProcedure #startDate #endDate
Update #temp_table
Set Year = #StartDate
Where Year Is Null
set #startDate = DATEADD(YEAR,1,#startDate)
end
select * from #temp_table

Add a Year column to your temp table, and apply the structured insert
declare #temp_table table
(
PrimaryGroup varchar(10),
SecondaryGroup varchar(10),
Volume int,
Year int
)
while #startDate < #endDate
begin
insert into #temp_table (PrimaryName,SecondaryName,Volume)
exec myStoredProcedure #startDate #endDate
Update #temp_table set Year = #startDate where Year is Null
set #startDate = DATEADD(YEAR,1,#startDate)
end
select * from #temp

Create a second table variable that will hold the result:
declare #result_table table
(
Year int,
PrimaryGroup varchar(10),
SecondaryGroup varchar(10),
Volume int
)
Then in the while loop after fetching the result into #temp_table:
insert into #result_table
select <year>, PrimaryGroup, SecondaryGroup, Volume from #temp_table;
truncate #temp_table;

Related

Returning the following season and year

For a particular season and year when it started I have to get the following season and year when it starts, e.g. for Summer 2021 I should get Autumn 2021, and for Winter 2021 I should get Spring 2022.
"Seasons" table has the "Order" column which indicates the order of seasons in a year, so it looks like this:
SeasonID
Order
Name
1
1
Spring
2
2
Summer
3
3
Autumn
4
4
Winter
Remark: "Order" column may seem redundant, but this is actually simplified/adapted version of the problem I have, where "Order" column is neccesary.
I have a stored procedure that has #Year and #SeasonID as input parameters. I have to get following season/year in #FollowingSeasonID and #FollowingYear parameters, which I use later in the stored procedure. I'm not sure if I've used the best technique for that:
(...)
DECLARE #FollowingSeasonID int;
DECLARE #FollowingYear int;
if object_id('tempdb..#Years') is not null drop table #Years
CREATE TABLE #Years ([Year] int)
INSERT INTO #Years ([Year]) VALUES (#Year), (#Year + 1)
SELECT #FollowingSeasonID = FollowingSeasonID, #FollowingYear = FollowingYear
FROM
(SELECT SeasonID,
[Year],
LEAD(SeasonID) OVER (ORDER BY [Year], Order) AS FollowingSeasonID,
LEAD([Year]) OVER (ORDER BY [Year], Order) AS FollowingYear
FROM Seasons
CROSS JOIN #Years) t
WHERE SeasonID = #SeasonID AND [Year] = #Year
(...)
Is there a better way to do that? Could it be achieved in just one query?
I need those values in multiple stored procedures/views/... so I wanted to extract that part of code if a function. Scalar valued function can't return two values, so I have to create a table valued function (and I have to use table variable instead of temp table #Years). However, is there a better way to do that instead of having a table valued function that always returns just one row?
You can wrap this code in UDF and you should be able to get seasonId, year
DECLARE #season table(SeasonId int, SeasonOrder int, Name varchar(10))
INSERT INTo #season values
(1, 1 ,'Spring')
,(2, 2 ,'Summer')
,(3, 3 ,'Autumn')
,(4, 4 ,'Winter');
DECLARE #FollowingSeasonID int;
DECLARE #FollowingYear int;
DECLARE #year int = 2021, #SeasonId int = 2;
;WITH CTE_YearSeason AS
(
SELECT y,s.* FROM #season as s
CROSS APPLY
(VALUES (#year), (#year+1)
) as t(y)
), cte_ranking as
(
SELECT *, row_number() over (order by y,SeasonOrder) as rnk
FROM CTE_YearSeason)
SELECT SeasonId, Y as year
FROM cte_ranking as c
where c.rnk = (SELECT c1.rnk
from cte_ranking as c1
where c1.SeasonId = #SeasonId and c1.y = #year) +1
SeasonId
year
3
2021
Unless I've misunderstood, given a single seasonId you just need the following Id and reset it to 1 and increment the year if necessary?
declare #seasonid int=1, #Year int=2021
select FollowingSeasonID, #Year + yr FollowingYear
from (
select *,
IsNull(Lead(SeasonId) over(order by [order]),1) FollowingSeasonID,
case when Lead(SeasonId) over(order by [order]) is null then 1 else 0 end yr
from seasons
)s
where SeasonId=#SeasonId
If you don't want to use a TVF you could just use this as a view/cte, joining on currentId and return the followingId and yr value to add to your current year.

New to SQL - Why is my Insert into trying to insert NULL into primary key?

What I want to do is insert a range of dates into multiple rows for customerID=1. I have and insert for dbo.Customer(Dates), specifying my that I want to insert a record into the Dates column for my Customer table, right? I am getting error:
Cannot insert the value NULL into column 'CustomerId', table 'dbo.Customers'
Sorry if I am way off track here. I have looked at similar threads to find out what I am missing, but I'm not piecing this together. I am thinking it wants to overwrite the existing customer ID as NULL, but I am unsure why exactly since I'm specifying dbo.Customer(Dates) and not the existing customerID for that record.
declare #date_Start datetime = '03/01/2011'
declare #date_End datetime = '10/30/2011'
declare #date datetime = #date_Start
while #date <= #date_End
begin
insert into dbo.Customer(Dates) select #date
if DATEPART(dd,#date) = 0
set #date = DATEADD(dd, -1, DATEADD(mm,1,#date))
else
set #date = DATEADD(dd,1,#date)
end
select * from dbo.Customer
The primary key is customerId, but you are not inserting a value.
My guess is that you declared it as a primary key with something like this:
customerId int primary key,
You want it to be an identity column, so the database assigns a value:
customerId int identity(1, 1) primary key
Then, you don't need to assign a value into the column when you insert a new row -- the database does it for you.
Your Customer table has a column named CustomerId and which column is NOT Nullable so you have to provide that column value as well. If your column type is Int try the bellow code:
declare #date_Start datetime = '03/01/2011'
declare #date_End datetime = '10/30/2011'
declare #date datetime = #date_Start
DECLARE #cusId INT
SET #cusId = 1
while #date <= #date_End
begin
insert into dbo.Customer(CustomerId, Dates) select #cusId, #date
if DATEPART(dd,#date) = 0
set #date = DATEADD(dd, -1, DATEADD(mm,1,#date))
else
set #date = DATEADD(dd,1,#date)
SET #cusId = #cusId + 1;
end
select * from dbo.Customer
thank you for the feedback. I think I'm scrapping this and going to go with creating a separate table to JOIN. Not sure why I didn't start doing that before

Stored procedure in Microsoft SQL Server conversion before inserting

I am trying to create a stored procedure that does manipulation of parameter passed in before inserting it into my table. One of the columns in my table is called DATE_CHANGED and basically what I gg to do here is to change a passed date parameter like December 1st 2017 to 20171201. This is an int value.
I wrote a stored procedure like this:
CREATE PROCEDURE date_generate
#startDate DATE
AS
BEGIN
DECLARE #DATE_KEY INT
#DATE_KEY = CONVERT(INT, FORMAT(#startDate, 'YYYYMMDD')
INSERT INTO table date_key = #DATE_KEY
END
However I get an error
Incorrect syntax near '#DATE_KEY
Are local variable declared only used for SQL query statement like
select *
from table
where date_key = #DATE_Key?
There is more than one error.
Use SET to assign values to a variable.
Have a look at INSERT statement too.
CREATE PROCEDURE date_generate
#startDate date
AS
BEGIN
DECLARE #DATE_KEY int;
SET #DATE_KEY = CONVERT(int, format(#startDate, 'YYYYMMDD'));
INSERT INTO DATE_CHANGED (date_key)
VALUES (#DATE_KEY);
END
This seems really strange. You don't even need a local variable. Based on your code, you could write:
create procedure date_generate (
#startDate date
) as
begin
insert into table (date_key)
values ( convert(int, format(#startDate, 'YYYYMMDD')) );
end; -- date_generate
Or, I might write:
create procedure date_generate (
#startDate date
) as
begin
insert into table (date_key)
values ( year(#startDate) * 10000 + month(#startDate) * 100 + day(#startDate) );
end;
Why you would have a table with a single date on each row doesn't really make sense to me. Why you would be storing that "date" as an integer also doesn't make sense.
As far as I've understood, your stored procedure accepts a DATE as a parameter, but you need to do an INSERT with an INT.
You can easily convert a DATE to a VARCHAR and then to a INT, this way:
DECLARE #DateASInt INT = CAST(CONVERT(VARCHAR(8), #startDate, 112) AS INT);
So, your stored procedure will be like this:
CREATE PROCEDURE date_generate
#startDate date
AS
BEGIN
INSERT INTO date_key
VALUES (CAST(CONVERT(VARCHAR(8), #startDate, 112) AS INT));
END

SQL Query help needed - Multiple rows in 1st table should match to multiple table in 2nd table

Problem Illustration
I am trying to find that magical query to generate summary information. I have mapped my problem into fictitious illustration. I have 'WaterLeakage%' table which records leakage occurred in hotel rooms over several year.
I have another table which records WaterConsumption in liters for each table.
Now i have to find actual water leakage in liters for given room number over given date range.
Basically i have to group several rows in 'WaterLeakage%' table to several rows in 'WaterConsumption' table. I am trying to figure out magical efficient query to find this. Unable to find it, please help.
DECLARE #START_DATE_PARAM DATE = '01/10/2017';
DECLARE #END_DATE_PARAM DATE = '01/31/2017';
DECLARE #ROOM_NUMBER INT = 101;
IF (EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '#WATER_CONSUMPTION'))
DROP TABLE #WATER_CONSUMPTION;
IF (EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '#WATER_LEAKAGE_PER'))
DROP TABLE #WATER_LEAKAGE_PER;
--Table for daily daily water consumption per room
CREATE TABLE #WATER_CONSUMPTION(
ROOM_NUMBER INT,
UDAY DATE,
WATER_CONSUMPTION_LITER INT
)
--Table for water leakage percent per room for date range
CREATE TABLE #WATER_LEAKAGE_PER
(
ROOM_NUMBER INT,
START_DATE DATE,
END_DATE DATE,
WATER_LEAKAGE_PERCENT INT
)
-- Raw Data
INSERT INTO #WATER_LEAKAGE_PER(ROOM_NUMBER,START_DATE,END_DATE,WATER_LEAKAGE_PERCENT)
VALUES(101,'2017/01/01','2017/01/02',5),
(102,'2017/01/01','2017/01/05',10),
(101,'2017/01/04','2017/02/06',10);
-- Raw Data
INSERT INTO #WATER_CONSUMPTION
VALUES(101,'2017/01/01',100),
(101,'2017/01/02',100),
(101,'2017/01/03',100),
(101,'2017/01/04',100),
(101,'2017/01/05',100),
(101,'2017/01/06',100),
(102,'2017/01/01',100),
(102,'2017/01/02',100),
(102,'2017/01/03',100),
(102,'2017/01/04',100),
(102,'2017/01/05',100);
DECLARE #TotalLeak REAL = 0;
SELECT * FROM #WATER_CONSUMPTION;
SELECT * FROM #WATER_LEAKAGE_PER;
SELECT * FROM #WATER_CONSUMPTION T1 JOIN (SELECT * FROM #WATER_LEAKAGE_PER WHERE ROOM_NUMBER=#ROOM_NUMBER) T2
ON (T1.ROOM_NUMBER=T2.ROOM_NUMBER AND T1.UDAY >= T2.START_DATE AND T1.UDAY <= T2.END_DATE);
DROP TABLE #WATER_CONSUMPTION;
DROP TABLE #WATER_LEAKAGE_PER;
I am very close to solution now. Basically i changed my thinking. I will join reverse now.
BEGIN
--Input Parameters for calculating water wastage between date range
DECLARE #START_DATE_PARAM DATE = '01/10/2017';
DECLARE #END_DATE_PARAM DATE = '01/31/2017';
--Table for daily daily water consumption per room
CREATE TABLE #WATER_CONSUMPTION(
ROOM_NUMBER INT,
UDAY DATE,
WATER_CONSUMPTION_LITER INT
)
--Table for water leakage percent per room for date range
CREATE TABLE #WATER_LEAKAGE_PER
(
ROOM_NUMBER INT,
START_DATE DATE,
END_DATE DATE,
WATER_LEAKAGE_PERCENT INT,
LEAKAGE_PER_DAY_IN_LITER INT
)
-- Leakage in liter per room for each day, This will have multiple entries for room and date if room number and date is available in multiple date ranges, ex. in #WATER_CONSUMPTION table for room number 101 we have multiple entries with overlapping dates
CREATE TABLE #DAY_WISE_LEAKAGE
(
ROOM_NUMBER INT,
LDATE DATE,
LEAKAGE_IN_LITER INT
)
-- Raw Data
INSERT INTO #WATER_LEAKAGE_PER(ROOM_NUMBER,START_DATE,END_DATE,WATER_LEAKAGE_PERCENT)
VALUES(101,'2017/01/15','2017/01/18',30),
(102,'2017/01/15','2017/01/18',10),
(101,'2017/01/15','2017/02/13',5);
-- Raw Data
INSERT INTO #WATER_CONSUMPTION
VALUES(101,'01/01/2017',1001),
(101,'01/02/2017',1001),
(101,'01/03/2017',1001),
(101,'01/04/2017',1001),
(101,'01/05/2017',1001),
(101,'01/06/2017',1001),
(101,'01/07/2017',1001),
(101,'01/08/2017',1001),
(101,'01/09/2017',1001),
(101,'01/10/2017',1001),
(101,'01/11/2017',1001),
(101,'01/12/2017',1001),
(101,'01/13/2017',1001),
(101,'01/14/2017',1001),
(101,'01/15/2017',1001),
(101,'01/16/2017',1001),
(101,'01/17/2017',1001),
(101,'01/18/2017',1001),
(101,'01/19/2017',1001),
(101,'01/20/2017',1001),
(101,'01/21/2017',1001),
(101,'01/22/2017',1001),
(101,'01/23/2017',1001),
(101,'01/24/2017',1001),
(101,'01/25/2017',1001),
(101,'01/26/2017',1001),
(101,'01/27/2017',1001),
(101,'01/28/2017',1001),
(101,'01/29/2017',1001),
(101,'01/30/2017',1001),
(101,'01/31/2017',1001);
DECLARE #ROOM_NUMBER INT
DECLARE #START_DATE DATE
DECLARE #END_DATE DATE
DECLARE #WATER_LEAKAGE_PERCENT INT
-- cursor for calculating water wastage pre date range per day available in #WATER_LEAKAGE_PER table
DECLARE WATER_LEAKAGE_PER_CURSOR CURSOR FOR
SELECT ROOM_NUMBER,START_DATE,END_DATE,WATER_LEAKAGE_PERCENT FROM #WATER_LEAKAGE_PER
OPEN WATER_LEAKAGE_PER_CURSOR
FETCH NEXT FROM WATER_LEAKAGE_PER_CURSOR
INTO #ROOM_NUMBER, #START_DATE ,#END_DATE, #WATER_LEAKAGE_PERCENT
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #TOTAL_WATER_USED_FOR_DATE_RANGE INT=0;
DECLARE #NUMBER_OF_DAYS INT=0;
DECLARE #LEAKAGE_PER_DAY_IN_LITER INT=0;
-- Total Liters of water used for 1 date range
SELECT #TOTAL_WATER_USED_FOR_DATE_RANGE =SUM(WATER_CONSUMPTION_LITER),#NUMBER_OF_DAYS=COUNT(1) FROM #WATER_CONSUMPTION WHERE ROOM_NUMBER=#ROOM_NUMBER AND UDAY BETWEEN #START_DATE AND #END_DATE;
-- Liters of water leakage per day for selevted date range in cursor
SELECT #LEAKAGE_PER_DAY_IN_LITER=((#TOTAL_WATER_USED_FOR_DATE_RANGE*#WATER_LEAKAGE_PERCENT)/100)/#NUMBER_OF_DAYS;
UPDATE #WATER_LEAKAGE_PER SET LEAKAGE_PER_DAY_IN_LITER = #LEAKAGE_PER_DAY_IN_LITER WHERE ROOM_NUMBER=#ROOM_NUMBER AND START_DATE = #START_DATE AND END_DATE=#END_DATE AND WATER_LEAKAGE_PERCENT=#WATER_LEAKAGE_PERCENT;
-- generate dates and water leakage, this will be used for actual calculation of water leakage in date range.
;WITH n AS
(
SELECT TOP (DATEDIFF(DAY, #START_DATE, #END_DATE) + 1)
n = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects
)
INSERT INTO #DAY_WISE_LEAKAGE SELECT #ROOM_NUMBER, DATEADD(DAY, n-1, #START_DATE),#LEAKAGE_PER_DAY_IN_LITER
FROM n;
FETCH NEXT FROM WATER_LEAKAGE_PER_CURSOR
INTO #ROOM_NUMBER, #START_DATE ,#END_DATE, #WATER_LEAKAGE_PERCENT
END
CLOSE WATER_LEAKAGE_PER_CURSOR;
DEALLOCATE WATER_LEAKAGE_PER_CURSOR;
-- Average of Liters of water leakage per Room number.
SELECT ROOM_NUMBER,SUM(LEAKAGE_IN_LITER) FROM #DAY_WISE_LEAKAGE WHERE LDATE BETWEEN #START_DATE_PARAM AND #END_DATE_PARAM GROUP BY ROOM_NUMBER;
DROP TABLE #WATER_CONSUMPTION;
DROP TABLE #WATER_LEAKAGE_PER;
DROP TABLE #DAY_WISE_LEAKAGE
END

How to return a list of courses between 2 months in SQL Server 2012 Management Studio?

How to create a stored procedure in SQL Server 2012 that returns a list of courses running between 2 months?
I've written code something like this:
create procedure final_RTrainerqualification
(#TrainerID char(10),
#Coursecode char(4) OUTPUT,
#qualcode nvarchar(30),
#coursedate datetime output)
DECLARE #MinDate DATE = '20160401',
#MaxDate DATE = '20160601';
SELECT coursedate
FROM dbo.RTrainerqualification
WHERE coursedate >= #MinDate
AND coursedate < #MaxDate;`
It should be returning the list of courses that run between these 2 dates mentioned but I am new to stored procedure so my question is how do I assign coursedates to the courses and make it return the list?
Edit- used T-SQL to recreate the code
#Coursecount smallint
declare #Coursedatebeg datetime, #Coursedateend datetime, #CourseCode char(4),#TrainerID char(10);
select #Coursedatebeg = '2015-04-20'
select #Coursedateend = '2015-06-20';
while #Coursedatebeg <= #Coursedateend
begin
select #CourseCode = #Coursedateend;
select #Coursecount = count(*) from RCourseInstance
where CourseCode between 'R222' and 'R224';
if #CourseCount <> 0
begin
Print 'Courses running between April and June 2015 ' ;
select Coursedate,CourseCode from RCourseInstance as t
inner join Coursedate as d on t.Coursedate = d.Coursedate
inner join CourseCode as c on c.CourseCode = t.CourseCode
where CourseCode between 'R222' and 'R224';
end
else
print 'No courses are running between these dates ' ;
set #Coursedatebeg = #Coursedatebeg + 2;
end
It is returning the print statement but also declaring that invalid object name Coursedate
what have I done incorrectly here?
Firstly, use ISO date strings to avoid regional settings issues. You are only returning the coursedate in the SELECT. If you want further information such as the coursecode then add it into the SELECT. i.e.
DECLARE #MinDate DATE = '2016-04-01',
#MaxDate DATE = '2016-06-01'
SELECT coursedate, coursecode, *
FROM dbo.RTrainerqualification
WHERE coursedate >= #MinDate
AND coursedate < #MaxDate;
The OUTPUT variables are not required - these are needed when you want to return a single value rather than a list.
This is fundamental SQL stuff so I would strongly recommend you do some reading on this. There is a simple stored procedure tutorial here to start off with.