I'm studing the Ms SQL AdventureWorks 2014, to model an internal dbase for our company
I usually work on Postgres, and I'm trying to "understand" Ms SQL stored procedures... :-)) BUT ....
The store Procedure [dbo.ufnGetProductListPrice ] SEEMS STRANGE to me.
the SQL code is found here:
(https://dataedo.com/samples/html/AdventureWorks/doc/AdventureWorks_2/functions/dbo_ufnGetProductListPrice_116.html)
CREATE FUNCTION [dbo].[ufnGetProductListPrice](#ProductID [int], #OrderDate [datetime])
RETURNS [money]
AS
BEGIN
DECLARE #ListPrice money;
SELECT #ListPrice = plph.[ListPrice]
FROM [Production].[Product] p
INNER JOIN [Production].[ProductListPriceHistory] plph
ON p.[ProductID] = plph.[ProductID]
AND p.[ProductID] = #ProductID
AND #OrderDate BETWEEN plph.[StartDate] AND COALESCE(plph.[EndDate], CONVERT(datetime, '99991231', 112)); -- Make sure we get all the prices!
RETURN #ListPrice;
END;
The function is making use of the following two tables:
[Production.Product] (https://dataedo.com/samples/html/AdventureWorks/doc/AdventureWorks_2/tables/Production_Product_153.html)
[Production.ProductListPriceHistory] - (https://dataedo.com/samples/html/AdventureWorks/doc/AdventureWorks_2/tables/Production_ProductListPriceHistory_159.html)
In particular my doubts are:
the function is getting the parameter #ProductID.
The same #ProductID is used as the primary key of the Production.Product AND as part of the primary key for the table Production.ProductListPriceHistory
so is seems of no help making a join on Product.[ProductID] = ProductListPriceHistory.[ProductID]
when we can test the ProductID directly on the ProductListPriceHistory.[ProductID]
Why to create such a join ? Seems of no help...
The given #Orderdate datetime received as second parameter, is checked in the JOIN against the following condition
AND #OrderDate BETWEEN plph.[StartDate] AND COALESCE(plph.[EndDate], CONVERT(datetime, '99991231', 112)); -- Make sure we get all the prices!
BUT, if we are calling the store procedure for #Orderdate = 1/1/2022,
considering that [EndDate] could be NULL,
and we could have in ProductListPriceHistory.StartDate two records with our #ProductID, the first with StartDate=1/1/2020 and the second StartDate=1/1/2021,
such "BETWEEN" condition should match both of them, when obviously we would expect the last one ....
is it a bug ?
You are right, there are a number of serious flaws in this code, and I would recommend finding better tutorials instead.
I've noted comments on each flaw
CREATE FUNCTION [dbo].[ufnGetProductListPrice](#ProductID [int], #OrderDate [datetime])
RETURNS [money] -- money is a bad data type due to rounding problems, decimal should be used
-- scalar UDFs are very slow, this should be a inline Table Valued Function
AS
BEGIN
DECLARE #ListPrice money;
SELECT #ListPrice = plph.[ListPrice] -- as you point out, there should be some kind of aggregation
FROM [Production].[Product] p -- as you point out: join is unnecessary
INNER JOIN [Production].[ProductListPriceHistory] plph
ON p.[ProductID] = plph.[ProductID]
AND p.[ProductID] = #ProductID
-- BETWEEN should never be used on dates, use ">= AND <"
AND #OrderDate BETWEEN plph.[StartDate] AND COALESCE(plph.[EndDate], CONVERT(datetime, '99991231', 112)); -- ISNULL is better for performance than COALESCE
RETURN #ListPrice;
END;
A better function would be this
CREATE FUNCTION dbo.ufnGetProductListPrice (
#ProductID int,
#OrderDate datetime
)
RETURNS TABLE
AS RETURN
SELECT TOP (1)
plph.ListPrice
FROM Production.ProductListPriceHistory plph
WHERE plph.ProductID = #ProductID
AND #OrderDate >= plph.StartDate
AND #OrderDate < ISNULL(plph.EndDate, CONVERT(datetime, '99991231', 112))
ORDER BY
plph.StartDate DESC;
Assuming it's possible for there to be multiple active prices (I don't think it's possible in AdventureWorks), then you also need TOP (1) and ORDER BY plph.StartDate DESC. If this is not possible then you can leave that out.
Instead of doing this with a scalar UDF
SELECT p.*, dbo.ufnGetProductListPrice(p.ProductId, GETDATE())
FROM Production.Product p;
You use an APPLY for a TVF
SELECT p.*, plp.*
FROM Production.Product p
OUTER APPLY dbo.ufnGetProductListPrice(p.ProductId, GETDATE()) plp;
An OUTER APPLY works like a LEFT JOIN, which means you get NULL if there are no rows.
#Charlieface
Thanks for your reply. One more note on the function ufnGetProductStandardCost
AdventureWorks is storing historical Retailprices and CostPrices, to let you found the price (list, cost) of a certain product at a certain time. This is why I'm interested in this part of the database
It even offers a "dual" function for Cost prices ufnGetProductStandardCost
https://dataedo.com/samples/html/AdventureWorks/doc/AdventureWorks_2/functions/dbo_ufnGetProductStandardCost_117.html
with the same errors :-(
I think that the idea of ufnGetProductListPrice(ProductID, OrderDate), is to report a SINGLE price or Null if at that given time the price did not exist.
NOT a table or listrow.
Let's just recall the case I supposed before
search OrderDate 1/1/2022
one record with StartDate=1/1/2020 , EndDate=null
one record with StartDate=1/1/2021 , EndDate=null
One idea for such function would be to:
use a subquery to find the MAX() StardDate<#OrderDate for the given #ProductId
use such StartDate to get the correspondig ListPrice in the external query
or give back NULL if we have NO StartDate ListPrice before #OrderDate (e.g #OrderDate=1/1/2019)
What do you think about this query ? Any improvement ?
CREATE FUNCTION dbo.ufnGetProductListPrice (
#ProductID int,
#OrderDate datetime
)
AS
BEGIN
DECLARE #ListPrice money;
SELECT #ListPrice= plph.ListPrice
FROM Production.ProductListPriceHistory plph
WHERE plph.ProductID = #ProductID
AND plhp.StartDate = (
SELECT MAX(StardDate)
FROM Production.ProductListPriceHistory plph1
WHERE plph1.ProductID = #ProductID
AND plhp1.StartDate <= #OrderDate)
RETURN #ListPrice;
END;
Related
For the last few days, I've been reading an ebook on data structures and well, frankly speaking, many things are already gone from my head. Just reviewing them and trying to make clear again. I was going through hash tables and get to familiar with it again. So I know and heard, SQL Server uses hash tables internally and many of the threads of stackoverflow.com and forums.asp.net asked about creating hash tables in SQL Server as it stores temporary data. So let me give an example that I've used in a stored procedure using temp table: (Avoid it and it's too long. Just for an example)
1st:
CREATE PROCEDURE [dbo].[Orders]
#OrderLine int
AS
BEGIN
DECLARE #t1 TABLE(Date1 date,
OrderID VARCHAR(MAX),
EmployeeName VARCHAR(MAX),
DeliveryDate date,
StoreName VARCHAR(MAX),
DeliveryAddress VARCHAR(MAX),
ItemName VARCHAR(MAX),
Quantity FLOAT)
INSERT INTO #t1(Date1, OrderID, EmployeeName, DeliveryDate, StoreName, DeliveryAddress, ItemName, Quantity)
(SELECT DISTINCT
CONVERT(VARCHAR(11), DemandOrder.POCreationDate, 6) AS DemandOrderDate,
DemandOrder.OrderID, EmployeeDetails.EmployeeName,
CONVERT(DATE, DemandOrder.DeliveryDate) AS ExpectedDeliveryDate,
StoreDetails.StoreName,
DemandOrder.DeliveryAddress, Item.ItemName,
DemandOrderLine.Quantity
FROM
DemandOrder
INNER JOIN
DemandOrderLine ON DemandOrder.OrderID = DemandOrderLine.OrderID
INNER JOIN
Item on DemandOrderLine.ItemID=Item.ItemID
INNER JOIN
EmployeeDetails ON EmployeeDetails.EmployeeID = DemandOrder.EmployeeID
INNER JOIN
StoreDetails ON DemandOrderLine.StoreID = StoreDetails.StoreID
WHERE
DemandOrderLine.OrderLine = #OrderLine)
DECLARE #t2 TABLE(Approvedby VARCHAR(MAX))
INSERT INTO #t2(Approvedby)
(SELECT EmployeeDetails.EmployeeName
FROM EmployeeDetails
INNER JOIN DemandOrderLine ON DemandOrderLine.ApprovedBy = EmployeeDetails.EmployeeID)
SELECT DISTINCT
CONVERT(VARCHAR(11), Date1, 6) AS Date,
OrderID, EmployeeName,
CONVERT(VARCHAR(11), DeliveryDate, 6) AS ExpectedDeliveryDate,
StoreName, Approvedby, DeliveryAddress,
ItemName, Quantity
FROM
#t1
CROSS JOIN
#t2
END
Another one, from an example, that says in stored procedure, hash tables can't be used. So here it's:
2nd:
CREATE PROCEDURE TempTable AS ---- It's actually not possible in SP
CREATE table #Color
(
Color varchar(10) PRIMARY key
)
INSERT INTO #color
SELECT 'Red'
UNION
SELECT 'White'
UNION
SELECT 'green'
UNION
SELECT 'Yellow'
UNION
SELECT 'blue'
DROP TABLE #color
CREATE table #Color
(
Color varchar(10) PRIMARY key
)
INSERT INTO #color
SELECT 'Red'
UNION
SELECT 'White'
UNION
SELECT 'green'
UNION
SELECT 'Yellow'
UNION
SELECT 'blue'
DROP TABLE #color
GO
So my question is can I say the 1st one is an example of hash table as it uses temp tables and if not, why can't we use it in the stored procedure? Again, if it's created internally, why do we need to create a hash table again for working purposes (Though it has performance issues, just wondering to know if the above examples serve for the purpose). Thanks.
Note: I faced an interview last month and was discussing about it. That's why making sure if I was correct in my views.
Hash-based algorithms are important for any powerful database. These are used for aggregation and join operations. Hash-based joins have been there since version 7.0 -- which is really old (thanks to Martin Smith). You can read more about them in the documentation.
SQL Server 2014 introduced hash-based indexes for memory optimized tables (see here). These are an explicit use of hash tables. In general, though, the tree-based indexes are more powerful because they can be used in more situations:
For range lookups (including like).
For partial key matches.
For order by.
A hash index can only be used for an exact equality match (and group by).
I know im a little late to the party, but I dont think anyone has directly answered your original question.
The first is an example of a table variable and the second is an example of a local table, both are created in the tempdb
The difference between them is that a table variable is not created in memory and cant have a clustered index.
Also a local (hash) table will stick around until that single connection ends, while a table variable is only available for the batch its declared in.
A global table (using a double hash before it) will be available to all connections and persist until all connections using it are closed.
One final thing, the only reason you cant use that local table in a stored procedure is because it uses the same name twice, even though you've used drop table it evaluates it based on the creates in the batch first. So it wont execute anything and moan it already exists.
DECLARE #SEPERATOR as VARCHAR(1)
DECLARE #SP INT
DECLARE #VALUE VARCHAR(MAX)
SET #SEPERATOR = ','
CREATE TABLE #TempCode (id int NOT NULL)
/**this Region For Storing SiteCode**/
WHILE PATINDEX('%' + #SEPERATOR + '%', #Code ) <> 0
BEGIN
SELECT #SP = PATINDEX('%' + #SEPERATOR + '%' ,#Code)
SELECT #VALUE = LEFT(#Code , #SP - 1)
SELECT #Code = STUFF(Code, 1, #SP, '')
INSERT INTO #TempCode (id) VALUES (#VALUE)
END
I was looking for help to optimize a query I am writing for SQL Server. Given this database schema:
TradeLead object, a record in this table is a small article.
CREATE TABLE [dbo].[TradeLeads]
(
[TradeLeadID] INT NOT NULL PRIMARY KEY IDENTITY(1,1),
Title nvarchar(250),
Body nvarchar(max),
CreateDate datetime,
EditDate datetime,
CreateUser nvarchar(250),
EditUser nvarchar(250),
[Views] INT NOT NULL DEFAULT(0)
)
Here's the cross reference table to link a TradeLead article to an Industry record.
CREATE TABLE [dbo].[TradeLeads_Industries]
(
[ID] INT NOT NULL PRIMARY KEY IDENTITY(1,1),
[TradeLeadID] INT NOT NULL,
[IndustryID] INT NOT NULL
)
Finally, the schema for the Industry object. These are essentially just tags, but a user is unable to enter these. The database will have a specific amount.
CREATE TABLE [dbo].[Industries]
(
IndustryID INT NOT NULL PRIMARY KEY identity(1,1),
Name nvarchar(200)
)
The procedure I'm writing is used to search for specific TradeLead records. The user would be able to search for keywords in the title of the TradeLead object, search using a date range, and search for a TradeLead with specific Industry Tags.
The database will most likely be holding around 1,000,000 TradeLead articles and about 30 industry tags.
This is the query I have come up with:
DECLARE #Title nvarchar(50);
SET #Title = 'Testing';
-- User defined table type containing a list of IndustryIDs. Would prob have around 5 selections max.
DECLARE #Selectedindustryids IndustryIdentifierTable_UDT;
DECLARE #Start DATETIME;
SET #Start = NULL;
DECLARE #End DATETIME;
SET #End = NULL;
SELECT *
FROM(
-- Subquery to return all the tradeleads that match a user's criteria.
-- These fields can be null.
SELECT TradeLeadID,
Title,
Body,
CreateDate,
CreateUser,
Views
FROM TradeLeads
WHERE(#Title IS NULL OR Title LIKE '%' + #Title + '%') AND (#Start IS NULL OR CreateDate >= #Start) AND (#End IS NULL OR CreateDate <= #End)) AS FTL
INNER JOIN
-- Subquery to return the TradeLeadID for each TradeLead record with related IndustryIDs
(SELECT TI.TradeLeadID
FROM TradeLeads_Industries TI
-- Left join the selected IndustryIDs to the Cross reference table to get the TradeLeadIDs that are associated with a specific industry.
LEFT JOIN #SelectedindustryIDs SIDS
ON SIDS.IndustryID = TI.IndustryID
-- It's possible the user has not selected any IndustryIDs to search for.
WHERE (NOT EXISTS(SELECT 1 FROM #SelectedIndustryIDs) OR SIDS.IndustryID IS NOT NULL)
-- Group by to reduce the amount of records.
GROUP BY TI.TradeLeadID) AS SelectedIndustries ON SelectedIndustries.TradeLeadID = FTL.TradeLeadID
With about 600,000 TradeLead records and with an average of 4 IndustryIDs attached to each one, the query takes around 8 seconds to finish on a local machine. I would like to get it as fast as possible. Any tips or insight would be appreciated.
There's a few points here.
Using constructs like (#Start IS NULL OR CreateDate >= #Start) can cause a problem called parameter sniffing. Two ways of working around it are
Add Option (Recompile) to the end of the query
Use dynamic SQL to only include the criteria that the user has asked for.
I would favour the second method for this data.
Next, the query can be rewritten to be more efficient by using exists (assuming the user has entered industry ids)
select
TradeLeadID,
Title,
Body,
CreateDate,
CreateUser,
[Views]
from
dbo.TradeLeads t
where
Title LIKE '%' + #Title + '%' and
CreateDate >= #Start and
CreateDate <= #End and
exists (
select
'x'
from
dbo.TradeLeads_Industries ti
inner join
#Selectedindustryids sids
on ti.IndustryID = sids.IndustryID
where
t.TradeLeadID = ti.TradeLeadID
);
Finally you will want at least one index on the dbo.TradeLeads_Industries table. The following are candidates.
(TradeLeadID, IndustryID)
(IndustryID, TradeLeadID)
Testing will tell you whether one or both is useful.
I am using SQL Server 2008R2 and I have the following scripts.
select * from orderSummaryTotal(#orderid,#sessionid)
select
count(*) as Quantity,
IsNull(Sum(VatAmount),0) As VATAmount,
IsNull(Sum(NetAmount),0) As NetAmount,
IsNull(Sum(GrossAmount),0) as GrossAmount
from tbOrderProduct
where
Orderid = #orderid
and sessionid = #sessionid
When I run the Second Query it returns me values. Namely a Quantity of 3
However when I run the first Query it returns me a Quantity of 0.
The First Query is a Table Valued Function Here is the code.
ALTER FUNCTION [dbo].[OrderSummaryTotal](#orderid varchar, #sessionid uniqueidentifier)
RETURNS TABLE as
RETURN
select
count(*) as Quantity,
IsNull(Sum(VatAmount),0) As VATAmount,
IsNull(Sum(NetAmount),0) As NetAmount,
IsNull(Sum(GrossAmount),0) as GrossAmount
from tbOrderProduct
where
Orderid = #orderid
and sessionid = #sessionid
Both queries are identical but how come one returns a count of 3 and the other does not? Any ideas?
The reason is that you have varchar in your function definition with no length.
Try changing it to something like varchar(8000), or a number large enough to suit your needs.
I'm using SSRS for reporting and executing a stored procedure to generate the data for my reports
DECLARE #return_value int
EXEC #return_value = [dbo].[MYREPORT]
#ComparePeriod = 'Daily',
#OverrideCompareDate = NULL,
#PortfolioId = '5,6',
#OverrideStartDate = NULL,
#NewPositionsOnly = NULL,
#SourceID = 13
SELECT 'Return Value' = #return_value
GO
In the above when I passed #PortfolioId = '5,6' it is giving me wrong inputs
I need all records for portfolio id 5 and 6 also is this correct way to send the multiple values ?
When I execute my reports only giving #PortfolioId = '5' it is giving me 120 records
and when I execute it by giving #PortfolioId = '6' it is giving me 70 records
So when I will give #PortfolioId = '5,6' it should have to give me only 190 records altogether, but it is giving me more no of records I don't understand where I exactly go wrong .
Could anyone help me?
thanks
all code is too huge to paste , i'm pasting relevant code please suggest clue.
CREATE PROCEDURE [dbo].[GENERATE_REPORT]
(
#ComparePeriod VARCHAR(10),
#OverrideCompareDate DATETIME,
#PortfolioId VARCHAR(50) = '2', --this must be multiple
#OverrideStartDate DATETIME = NULL,
#NewPositionsOnly BIT = 0,
#SourceID INT = NULL
) AS
BEGIN
SELECT
Position.Date,
Position.SecurityId,
Position.Level1Industry,
Position.MoodyFacilityRating,
Position.SPFacilityRating,
Position.CompositeFacilityRating,
Position.SecurityType,
Position.FacilityType,
Position.Position
FROM
Fireball_Reporting.dbo.Reporting_DailyNAV_Pricing POSITION WITH (NOLOCK, READUNCOMMITTED)
LEFT JOIN Fireball.dbo.AdditionalSecurityPrice ClosingPrice WITH (NOLOCK, READUNCOMMITTED) ON
ClosingPrice.SecurityID = Position.PricingSecurityID AND
ClosingPrice.Date = Position.Date AND
ClosingPrice.SecurityPriceSourceID = #SourceID AND
ClosingPrice.PortfolioID IN (
SELECT
PARAM
FROM
Fireball_Reporting.dbo.ParseMultiValuedParameter(#PortfolioId, ',') )
This can not be done easily. There's no way to make an NVARCHAR parameter take "more than one value". What I've done before is - as you do already - make the parameter value like a list with comma-separated values. Then, split this string up into its parts in the stored procedure.
Splitting up can be done using string functions. Add every part to a temporary table. Pseudo-code for this could be:
CREATE TABLE #TempTable (ID INT)
WHILE LEN(#PortfolioID) > 0
BEGIN
IF NOT <#PortfolioID contains Comma>
BEGIN
INSERT INTO #TempTable VALUES CAST(#PortfolioID as INT)
SET #PortfolioID = ''
END ELSE
BEGIN
INSERT INTO #Temptable VALUES CAST(<Part until next comma> AS INT)
SET #PortfolioID = <Everything after the next comma>
END
END
Then, change your condition to
WHERE PortfolioId IN (SELECT ID FROM #TempTable)
EDIT
You may be interested in the documentation for multi value parameters in SSRS, which states:
You can define a multivalue parameter for any report parameter that
you create. However, if you want to pass multiple parameter values
back to a data source by using the query, the following requirements
must be satisfied:
The data source must be SQL Server, Oracle, Analysis Services, SAP BI
NetWeaver, or Hyperion Essbase.
The data source cannot be a stored procedure. Reporting Services does
not support passing a multivalue parameter array to a stored
procedure.
The query must use an IN clause to specify the parameter.
This I found here.
I spent time finding a proper way. This may be useful for others.
Create a UDF and refer in the query -
http://www.geekzilla.co.uk/view5C09B52C-4600-4B66-9DD7-DCE840D64CBD.htm
USE THIS
I have had this exact issue for almost 2 weeks, extremely frustrating but I FINALLY found this site and it was a clear walk-through of what to do.
http://blog.summitcloud.com/2010/01/multivalue-parameters-with-stored-procedures-in-ssrs-sql/
I hope this helps people because it was exactly what I was looking for
Either use a User Defined Table
Or you can use CSV by defining your own CSV function as per This Post.
I'd probably recommend the second method, as your stored proc is already written in the correct format and you'll find it handy later on if you need to do this down the road.
Cheers!
I think, below procedure help you to what you are looking for.
CREATE PROCEDURE [dbo].[FindEmployeeRecord]
#EmployeeID nvarchar(Max)
AS
BEGIN
DECLARE #sqLQuery VARCHAR(MAX)
Declare #AnswersTempTable Table
(
EmpId int,
EmployeeName nvarchar (250),
EmployeeAddress nvarchar (250),
PostalCode nvarchar (50),
TelephoneNo nvarchar (50),
Email nvarchar (250),
status nvarchar (50),
Sex nvarchar (50)
)
Set #sqlQuery =
'select e.EmpId,e.EmployeeName,e.Email,e.Sex,ed.EmployeeAddress,ed.PostalCode,ed.TelephoneNo,ed.status
from Employee e
join EmployeeDetail ed on e.Empid = ed.iEmpID
where Convert(nvarchar(Max),e.EmpId) in ('+#EmployeeId+')
order by EmpId'
Insert into #AnswersTempTable
exec (#sqlQuery)
select * from #AnswersTempTable
END
I will do my best to make this question better than my last fiasco. I am getting the dreaded >"cannot find either column "dbo" or the user-defined function or aggregate "dbo.PriMonthAvgPrice", or the name is ambiguous.<
I am attempting to find the avg sales price from the previous month. Here is my UDF:
USE [WoodProduction]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[PriMonthAvgPrice]
(
-- Add the parameters for the function here
#endofmonth datetime,
#begofmonth datetime,
#PlantCode varchar
)
RETURNS decimal (10,2)
AS
BEGIN
-- Declare the return variable here
DECLARE #MonthEndAvgPrice decimal (10,2)
-- Add the T-SQL statements to compute the return value here
SELECT #MonthEndAvgPrice =
(
select
sum(Actual_Sales_Dollars/Actual_Volume)
FROM
woodproduction.dbo.plywood_layup_sales pls
WHERE
Production_Date between #begofmonth and #endofmonth
and actual_volume <> 0
and #PlantCode = pls.Plant_Code
)
-- Return the result of the function
RETURN #MonthEndAvgPrice
END
This is my SELECT statement from my query:
SELECT
DISTINCT
P.[Plant_Number]
,p.plant_name
,pls.plant_code
,(pls.[Budget_Realization]) AS 'BR'
,(pls.[Actual_Volume] ) AS 'AV'
,(pls.[Budget_Volume]) AS 'BV'
--,sum (dpb.[Gross_Production_Per_Hr]) AS 'GPB'
,(p.Production_Volume) AS 'PV'
,CASE
WHEN coalesce (pls.[Actual_Volume],0) = 0 and
coalesce (pls.[Actual_Sales_Dollars],0) = 0
THEN 0
ELSE (pls.[Actual_Sales_Dollars]/pls.[Actual_Volume])
END
AS 'AP'
,pls.production_date
,[dbo].[PriMonthAvgPrice](#endofmonth,#begofmonth, pls.plant_code) AS 'PriMoAvgPrice'
My BASIC understanding is that I HAVE created a Scalar Function. From what I've been reading about my error however, This error returns on TVF's. Is this true? I created a SVF prior to this dealing with just determining a prior month end date so it wasn't as involved as this one where I create the query in the UDF.
Do I need to change this to a TVF? And if so, how do I incorporate SELECT * when I have to join multiple tables along with this?
Thanks in advance.
Aaron
You don't show the from clause, but is the database you created the function in part of it?
Does it work if you fully qualify the name (include the database)?
Have you independently tested the function with:
select [dbo].[PriMonthAvgPrice] ('01/01/2011', '02/01/2011', 'test')
Note: of course you would use some actual values that should return a result.
Please run this and tell us the values returned:
SELECT Actual_Sales_Dollars,Actual_Volume, pls.PLant_code
FROM woodproduction.dbo.plywood_layup_sales pls
WHERE Production_Date between '09-01-2011' and '09-30-2011'
and actual_volume <> 0