Using a User Defined Function in a View - sql

I wrote the code for a View which needs to call a user defined function which returns a table to join with it. The problem here is passing the parameter that this functions needs straight out of my view.
Below is the code of my view:
select
GG.Gid,
GG.StockType StockType,
COALESCE(STC.Contract, 0) ContractId,
COALESCE(C.[$Refex], null) ContractRefex,
ST.[$Refex] StockTypeRefex
from
(
select
G.Gid,
coalesce(max(G.GEStockType), max(G.EAStockType)) StockType--,
--case when coalesce(G.GEStockType, G.EAStockType) is null then null else coalesce(G.GEStartDate, G.EAStartDate) end StartDate
from
(
select
G.Gid, SI.StockType EAStockType, SI.[Date] EAStartDate, null GEStockType, null GEStartDate
from Goods G
inner join SiteIn SI on G.SiteIn=SI.[$Id]
union
select G.Gid, null EAStockType, null EAStartDate, GE.StockType, GE.EventOn
from
(
Select
GE.Gid, max(GE.EventOn) GEStartDate
from GoodsEvent GE
where GE.IsDeleted=0 and GE.[Type]='ST' and GE.EventOn < GETDATE()
group by Gid
) G
inner join GoodsEvent GE on GE.Gid=G.Gid
and G.GEStartDate=GE.EventOn
and GE.[Type]='ST'
) G
group by G.Gid
) GG
left outer join StockType ST on ST.[$Id]=GG.StockType
inner join (SELECT * FROM [dbo].StockTypeContractGetClosestStartDate(ST.[$Id]))
STC on GG.StockType = STC.[Parent]
inner join Contract C On STC.Contract = C.[$Id]
And this is the code of my function:
CREATE FUNCTION StockTypeContractGetClosestStartDate
(
#ParentId int
)
RETURNS #StockTypeContract TABLE
(
[StartDate] [DateTime] null,
[Parent] [int] not null,
[Contract] [int] null
)
AS
BEGIN
INSERT #StockTypeContract
SELECT TOP 1 STC.StartDate , STC.[$ParentId] , STC.Contract
from StockTypeContract STC
where STC.[$ParentId] = #ParentId AND STC.StartDate <= GETDATE()
order by STC.StartDate desc
RETURN
END
It gives me an error when trying to pass ST.[$Id] to my function, the error is "The multi-part identifier ST.$Id could not be bound".
Is there any work-around for this?

You actually needs CROSS or OUTER APPLY. And from SO too
....
left outer join StockType ST on ST.[$Id]=GG.StockType
CROSS APPLY
[dbo].StockTypeContractGetClosestStartDate(ST.[$Id])
...
(I've simplified parenthesis here BTW, probably wrongly)
Your problem is "get a resultset from StockTypeContractGetClosestStartDate per ST.[$Id]

If I am correct your only inserting one record in the return table of your function, if that is the case then you can rebuild the function as a scalar function, this returns only one value and should solve the multi-part problem.
At the moment your trying to join with a possible "multi valued id".
see http://technet.microsoft.com/en-us/library/ms186755.aspx for the scalar function

Related

How can I perform the Count function with a where clause?

I have my database setup to allow a user to "Like" or "Dislike" a post. If it is liked, the column isliked = true, false otherwise (null if nothing.)
The problem is, I am trying to create a view that shows all Posts, and also shows a column with how many 'likes' and 'dislikes' each post has. Here is my SQL; I'm not sure where to go from here. It's been a while since I've worked with SQL and everything I've tried so far has not given me what I want.
Perhaps my DB isn't setup properly for this. Here is the SQL:
Select trippin.AccountData.username, trippin.PostData.posttext,
trippin.CategoryData.categoryname, Count(trippin.LikesDislikesData.liked)
as TimesLiked from trippin.PostData
inner join trippin.AccountData on trippin.PostData.accountid = trippin.AccountData.id
inner join trippin.CategoryData on trippin.CategoryData.id = trippin.PostData.categoryid
full outer join trippin.LikesDislikesData on trippin.LikesDislikesData.postid =
trippin.PostData.id
full outer join trippin.LikesDislikesData likes2 on trippin.LikesDislikesData.accountid =
trippin.AccountData.id
Group By (trippin.AccountData.username), (trippin.PostData.posttext), (trippin.categorydata.categoryname);
Here's my table setup (I've only included relevant columns):
LikesDislikesData
isliked(bit) || accountid(string) || postid(string
PostData
id(string) || posttext || accountid(string)
AccountData
id(string) || username(string)
CategoryData
categoryname(string)
Problem 1: FULL OUTER JOIN versus LEFT OUTER JOIN. Full outer joins are seldom what you want, it means you want all data specified on the "left" and all data specified on the "right", that are matched and unmatched. What you want is all the PostData on the "left" and any matching Likes data on the "right". If some right hand side rows don't match something on the left, then you don't care about it. Almost always work from left to right and join results that are relevant.
Problem 2: table alias. Where ever you alias a table name - such as Likes2 - then every instance of that table within the query needs to use that alias. Straight after you declare the alias Likes2, your join condition refers back to trippin.LikesDislikesData, which is the first instance of the table. Given the second one in joining on a different field I suspect that the postid and accountid are being matched on the same row, therefore it should be AND together, not a separate table instance. EDIT reading your schema closer, it seems this wouldn't be needed at all.
Problem 3: to solve you Counts problem separate them using CASE statements. Count will add the number of non NULL values returned for each CASE. If the likes.liked = 1, then return 1 otherwise return NULL. The NULL will be returned if the columns contains a 0 or a NULL.
SELECT trippin.PostData.Id, trippin.AccountData.username, trippin.PostData.posttext,
trippin.CategoryData.categoryname,
SUM(CASE WHEN likes.liked = 1 THEN 1 ELSE 0 END) as TimesLiked,
SUM(CASE WHEN likes.liked = 0 THEN 1 ELSE 0 END) as TimesDisLiked
FROM trippin.PostData
INNER JOIN trippin.AccountData ON trippin.PostData.accountid = trippin.AccountData.id
INNER JOIN trippin.CategoryData ON trippin.CategoryData.id = trippin.PostData.categoryid
LEFT OUTER JOIN trippin.LikesDislikesData likes ON likes.postid = trippin.PostData.id
-- remove AND likes.accountid = trippin.AccountData.id
GROUP BY trippin.PostData.Id, (trippin.AccountData.username), (trippin.PostData.posttext), (trippin.categorydata.categoryname);
Then "hide" the PostId column in the User Interface.
Instead of selecting Count(trippin.LikesDislikesData.liked) you could put in a select statement:
Select AccountData.username, PostData.posttext, CategoryData.categoryname,
(select Count(*)
from LikesDislikesData as likes2
where likes2.postid = postdata.id
and likes2.liked = 'like' ) as TimesLiked
from PostData
inner join AccountData on PostData.accountid = AccountData.id
inner join CategoryData on CategoryData.id = PostData.categoryid
USE AdventureWorksDW2008R2
GO
SET NOCOUNT ON
GO
/*
Default
*/
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
GO
BEGIN TRAN
IF OBJECT_ID('tempdb.dbo.#LikesDislikesData') IS NOT NULL
BEGIN
DROP TABLE #LikesDislikesData
END
CREATE TABLE #LikesDislikesData(
isLiked bit
,accountid VARCHAR(50)
,postid VARCHAR(50)
);
IF OBJECT_ID('tempdb.dbo.#PostData') IS NOT NULL
BEGIN
DROP TABLE #PostData
END
CREATE TABLE #PostData(
postid INT IDENTITY(1,1) NOT NULL
,accountid VARCHAR(50)
,posttext VARCHAR(50)
);
IF OBJECT_ID('tempdb.dbo.#AccountData') IS NOT NULL
BEGIN
DROP TABLE #AccountData
END
CREATE TABLE #AccountData(
accountid INT
,username VARCHAR(50)
);
IF OBJECT_ID('tempdb.dbo.#CategoryData') IS NOT NULL
BEGIN
DROP TABLE #CategoryData
END
CREATE TABLE #CategoryData(
categoryname VARCHAR(50)
);
INSERT INTO #AccountData VALUES ('1', 'user1')
INSERT INTO #PostData VALUES('1','this is a post')
INSERT INTO #LikesDislikesData (isLiked ,accountid, postid)
SELECT '1', P.accountid, P.postid
FROM #PostData P
WHERE P.posttext = 'this is a post'
SELECT *
FROM #PostData
SELECT *
FROM #LikesDislikesData
SELECT *
FROM #AccountData
SELECT COUNT(L.isLiked) 'Likes'
,P.posttext
,A.username
FROM #PostData P
JOIN #LikesDislikesData L
ON P.accountid = L.accountid
AND L.IsLiked = 1
JOIN #AccountData A
ON P.accountid = A.accountid
GROUP BY P.posttext, A.username
SELECT X.likes, Y.dislikes
FROM (
(SELECT COUNT(isliked)as 'likes', accountid
FROM #LikesDislikesData
WHERE isLiked = 1
GROUP BY accountid
) X
JOIN
(SELECT COUNT(isliked)as 'dislikes', accountid
FROM #LikesDislikesData
WHERE isLiked = 0
GROUP BY accountid) Y
ON x.accountid = y.accountid)
IF (XACT_STATE() = 1 AND ERROR_STATE() = 0)
BEGIN
COMMIT TRAN
END
ELSE IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRAN
END
How do you think about the solution? We create a new table SummaryReport(PostID,AccountID,NumberOfLikedTime,NumberOfDislikedTimes).
An user clicks on LIKE or DISLIKE button we update the table. After that, you can query as you desire. Another advantage, the table can be served reporting purpose.

Query, subquery and using as variables from subquery

Is it not possible to use the "as [item] and then use the item variable in the query.
For example:
select c.category as [category],c.orderby as [CatOrder], m.masterno, m.master
,-- select OUT (select count(*) from rentalitem ri with (nolock),
rentalitemstatus ris with (nolock),
rentalstatus rs with (nolock)
where ri.rentalitemid = ris.rentalitemid
and ris.rentalstatusid = rs.rentalstatusid
and ri.masterid = m.masterid
and rs.statustype in ('OUT', 'INTRANSIT', 'ONTRUCK')) as [qtyout]
,-- select OWNED owned=
(select top 1 mwq.qty
from masterwhqty mwq
where mwq.masterid = m.masterid)
, -([owned]-[qtyout]) as [Variance]
from master m
inner join category c on c.categoryid=m.categoryid and c.categoryid=#category
inner join inventorydepartment d on c.inventorydepartment=#department
I cannot seem to use qtyout or owned when calculating variance. How can I do that?
You can also use a table variable and then reference that table variable like you are trying to do above....here's an example from MSDN
USE AdventureWorks2012;
GO
DECLARE #MyTableVar table(
EmpID int NOT NULL,
OldVacationHours int,
NewVacationHours int,
ModifiedDate datetime);
UPDATE TOP (10) HumanResources.Employee
SET VacationHours = VacationHours * 1.25,
ModifiedDate = GETDATE()
OUTPUT inserted.BusinessEntityID,
deleted.VacationHours,
inserted.VacationHours,
inserted.ModifiedDate
INTO #MyTableVar;
--Display the result set of the table variable.
SELECT EmpID, OldVacationHours, NewVacationHours, ModifiedDate
FROM #MyTableVar;
GO
--Display the result set of the table.
SELECT TOP (10) BusinessEntityID, VacationHours, ModifiedDate
FROM HumanResources.Employee;
GO
need to move your calculated fields into a subquery, and then use them by their alias in the outer query.
select subquery.*, -([owned]-[qtyout]) as [Variance]
from
(
select c.category as [category],c.orderby as [CatOrder], m.masterno, m.master
,-- select OUT (select count(*) from rentalitem ri with (nolock),
rentalitemstatus ris with (nolock),
rentalstatus rs with (nolock)
where ri.rentalitemid = ris.rentalitemid
and ris.rentalstatusid = rs.rentalstatusid
and ri.masterid = m.masterid
and rs.statustype in ('OUT', 'INTRANSIT', 'ONTRUCK')) as [qtyout]
,-- select OWNED owned=
(select top 1 mwq.qty
from masterwhqty mwq
where mwq.masterid = m.masterid) as [owned]
from master m
inner join category c on c.categoryid=m.categoryid and c.categoryid=#category
inner join inventorydepartment d on c.inventorydepartment=#department
) as subquery
YOu need to use a subquery:
select t.*,
([owned]-[qtyout]) as [Variance]
from (<something like your query here
) t
You query, even without the comments, doesn't quite make sense (select OUT (select . . . for isntance). But, the answer to your question is to define the base variables in a subquery or CTE and then subsequently use them.
And, you are calling the difference "variance". Just so you know, you are redefining the statistical meaning of the term (http://en.wikipedia.org/wiki/Variance), which is based on the squares of the differences.

Only one expression can be specified in the select list when the subquery is not introduced with EXISTS

this is my query
Create FUNCTION [dbo].[CountUses](#couponid INT)
RETURNS INT
AS
BEGIN
RETURN
(
SELECT c.Id,
c.Name,
c.CreateDate,
Count(cu.id) NofUses
FROM Coupon as c
JOIN CouponUse as cu
ON c.id = cu.couponid
GROUP BY c.Id,
c.Name,
c.CreateDate
)
END
its giving the error Only one expression can be specified in the select list when the subquery is not introduced with EXISTS. where is the problem ?
Aside from the main answer, I'd also appreciate any comments you may have about optimizing my query...
If you want your function to return more than one value, then you need to look at Table-Valued Functions.
These types of functions return a table and not just one value. Your current function is set up as a scalar function so it can only return one value.
If you want a scalar value - let's say just the count then your function will be similar to this:
Create FUNCTION [dbo].[CountUses](#couponid INT)
RETURNS INT
AS
BEGIN
RETURN
(
SELECT Count(cu.id) NofUses --- this can only return one column
FROM Coupon as c
JOIN CouponUse as cu
ON c.id = cu.couponid
WHERE cu.couponid = #couponid
)
END
If you intention is to return a table of data, then your function will be similar to this:
Create FUNCTION [dbo].[CountUses](#couponid INT)
RETURNS #new_table table
(
id int,
name varchar(50),
CreateDate datetime,
NofUsers int
)
AS
BEGIN
INSERT INTO #new_table
SELECT c.Id,
c.Name,
c.CreateDate,
Count(cu.id) NofUses
FROM Coupon as c
JOIN CouponUse as cu
ON c.id = cu.couponid
WHERE cu.couponid = #couponid
GROUP BY c.Id, c.Name, c.CreateDate
RETURN
END
This will fix the issue:
Create FUNCTION [dbo].[CountUses](#couponid INT)
RETURNS TABLE
AS
RETURN
(
SELECT c.Id,
c.Name,
c.CreateDate,
Count(cu.id) NofUses
FROM Coupon as c
JOIN CouponUse as cu
ON c.id = cu.couponid
GROUP BY c.Id,
c.Name,
c.CreateDate
)

Cross join on condition

I have a Stored Proc query below which involves returning partial delimited search string. E.g.searching passing a search string of 'wis,k' will return all results with ID that has 'wis' and 'k' in them. I am using a function and a cross join for this but the problem if attaching the cross join will prevent loading all my data which I will need to when I load this SPROC. I was thinking if a conditioned Cross Join is possible such that when my search string variable '#ReceiptNo' is null then I will omit the Cross Join and allow all my data to be displayed. Please kindly advice. Thanks.
Portion of my SPROC:
FROM [Transact] T
LEFT JOIN [Outlet] O On (T.Outlet_Code = O.Code)
LEFT JOIN [SystemCode] SC on (CONVERT(NVARCHAR,T.Mode) = SC.Code)
CROSS JOIN DBO.SPLIT(#ReceiptNo , ',') --SPLIT function to seperate delimited string
Where
(
CardNo In
(
Select [CardNo]
FROM [Card]
WHERE [CardNo] = #CardNo
AND [DeletedBy] IS NULL
AND [DeletedOn] IS NULL
AND [MemberID] = #MemberId
)
)
and
(
(T.TransactDate Between #TransactDateFrom And #TransactDateTo
or #TransactDateFrom is null
or #TransactDateTo is null
)
and (T.TransactDate >= #TransactDateFrom
or #TransactDateFrom is null)
and (T.TransactDate <= #TransactDateTo
or #TransactDateTo is null)
and
(
(',' + #Mode +',' LIKE '%,' + CONVERT(VARCHAR, T.Mode) + ',%')
or #Mode is null
)
and (T.ReceiptNo LIKE '%' + VAL + '%') --This is the 'LIKE' condition to return desired search string results
or (#TransactDateFrom is null
and #TransactDateTo is null
and #Mode is null
and #Outlet_Code is null
and #ReceiptNo is null
)
)
Group by T.AutoID, TransactDate,TransactTime, SC.Name, O.Name
, ReceiptNo, AmountSpent, TransactPoints, VoidOn
You need to take care of NULL and set it to any constant value. Modify CROSS JOIN to (read notes below query):
CROSS JOIN (SELECT ISNULL(Portion, 1) AS Portion FROM DBO.SPLIT(#ReceiptNo , ',')) TTT
In query above, Portion is column returned by DBO.SPLIT function. Change its name to appropriate and add more columns (with ISNULL) if needed.
Am I missing something or You can simply use LEFT JOIN instead of CROSS JOIN? Also, You might consider putting DBO.SPLIT function result into temporary table, index it and then use it in your CROSS/LEFT JOIN.
EDIT#1: I can't find any reason why You should not change CROSS JOIN to LEFT JOIN, as it will process less rows when #RecepitNo is not NULL.

How to create a function that returns the first value of sorted list in SQL?

I'm writing a SQL function that returns the first value of a sorted list, but I really don't know how to start? The function has only one parameter which is the listing number ( VRI.Listing_Number )
Using select statement, I have:
SELECT TOP 1
--VRI.Listing_RID, VRI.Listing_Number, VRI.Listing_Price, CH.Old_Price, CH.Date_Time_Changed
CH.Old_Price
FROM dbo.View_Report_Information_Tables VRI WITH (NOLOCK)
INNER JOIN dbo.MLS_Change_History CH WITH (NOLOCK) ON
VRI.Listing_RID = CH.Listing_RID
INNER JOIN dbo.MLS_Change_History_Type CHT WITH (NOLOCK) ON
CH.Transaction_RID = CHT.Transaction_RID
WHERE CHT.Change_Type_Display = 3 AND
VRI.Listing_RID = CH.LISTING_RID
ORDER BY CH.Transaction_RID DESC
This sql query returns the last price changes from a list of prices.
I'm really new to sql, so I even don't understand quite well the syntax. For example, I looked up the CH.Old_Price, and I saw it TYPE_NAME is numeric, but it also has length and precision, scale.... What should my return value for this function?
Any idea?
Thanks,
CREATE FUNCTION dbo.FirstPriceChange(#Listing_Number int)
RETURNS MONEY
WITH RETURNS NULL ON NULL INPUT
AS
BEGIN
RETURN (
SELECT TOP 1
CH.Old_Price
FROM dbo.View_Report_Information_Tables VRI WITH (NOLOCK)
INNER JOIN dbo.MLS_Change_History CH WITH (NOLOCK) ON
VRI.Listing_RID = CH.Listing_RID
INNER JOIN dbo.MLS_Change_History_Type CHT WITH (NOLOCK) ON
CH.Transaction_RID = CHT.Transaction_RID
WHERE CHT.Change_Type_Display = 3 AND
VRI.Listing_RID = CH.LISTING_RID AND
VRI.Listing_Number = #Listing_Number
ORDER BY CH.Transaction_RID DESC
)
END
GO
Sample usage:
SELECT
VRI.Listing_RID, VRI.col1, VRI.Col2,
dbo.FirstPriceChange(VRI.Listing_Number) AS FirstPriceChange
FROM dbo.View_Report_Information_Tables VRI
Notes:
RETURNS MONEY it returns a money type
WITH RETURNS NULL ON NULL INPUT if the input is null, just return null
The returns is a single value, which is from the subquery, with the VRI.Listing_Number filter added
assuming the select statement you posted returns the correct data, the syntax of the function is rather simple:
CREATE FUNCTION <Inline_Function_Name, sysname, FunctionName>
(
-- Add the parameters for the function here
#Listing_Number int,
)
RETURNS TABLE
AS
RETURN
(
-- Add the SELECT statement with parameter references here
)
you can also get this by right-clicking Functions in SSMS -> Object Explorer -> -> Programmability and choosing the right type of function. My sample above assumes that this returns an entire row of data (which seems to be the case). I also assumed that VRI.Listing_Number is an int.
SELECT TOP 1 FROM (
SELECT *
FROM dbo.View_Report_Information_Tables VRI WITH (NOLOCK)
INNER JOIN dbo.MLS_Change_History CH WITH (NOLOCK) ON
VRI.Listing_RID = CH.Listing_RID
INNER JOIN dbo.MLS_Change_History_Type CHT WITH (NOLOCK) ON
CH.Transaction_RID = CHT.Transaction_RID
WHERE CHT.Change_Type_Display = 3 AND
VRI.Listing_RID = CH.LISTING_RID
ORDER BY CH.Transaction_RID DESC
) AS `aaa`