Conversion failed while using ids "where ID in (...)", nvarchar to int - sql

I have a query in MSSQL 2008 like:
IF OBJECT_Id('tempdb..#AccessibleFacilities') IS NOT NULL DROP TABLE #AccessibleFacilities
SELECT u.Userid
, AccesibleFacilityIds = dbo.GetCommaDelimitedString(upf.Facility_Id)
INTO #AccessibleFacilities
FROM Users u
INNER join UserProfileFacilities upf on upf.UserProfile_Id = up.Id
WHERE LOWER(u.Userid) = LOWER(#userId)
GROUP BY u.Userid
This query returns AccessibleFacilityIds like ",1,2,3,4,5,6,". Please note that I am not able to modify GetCommaDelimitedString function.
What I actually need to do is that using those facility ids to reach provs like below:
INSERT INTO #AccessibleProvs
SELECT Userid = #userId
, AccessibleProvIds = dbo.GetCommaDelimitedString(distinct p.Id)
FROM Provs p
inner join ProvFacs pf on p.Id = pf.Provider_Id
WHERE pf.Facility_Id in
(select a.AccesibleFacilityIds from #AccessibleFacilities a)
However, it gives me an error like:
Conversion failed when converting the nvarchar value ',1,2,3,4,5,6,'
to data type int.
I tried removing the comma signs at the start and end like below to fix it, but it did not help:
...
where pf.Facility_Id in (
select SUBSTRING(a.AccesibleFacilityIds,2,LEN(a.AccesibleFacilityIds)-2)
from #AccessibleFacilities a
)
Any advice would be appreciated. Thanks.

Instead of converting Facility_Id into a comma delimited string, why not keep it as a usable column in your temp table?
if object_Id('tempdb..#AccessibleFacilities') is not null drop table #AccessibleFacilities;
select
u.UserId
, upf.Facility_Id
into #AccessibleFacilities
from Users u
inner join UserProfileFacilities upf
on upf.UserProfile_Id = up.Id
Then use it as you did with in() or with exists():
insert into #AccessibleProvs
select
UserId = #userId
, AccessibleProvIds = dbo.GetCommaDelimitedString(distinct p.Id)
from Provs p
inner join ProvFacs pf
on p.Id = pf.Provider_Id
where exists (
select 1
from #AccessibleFacilities a
where a.Facility_Id = pf.Facility_Id
--and a.UserId = #UserId -- Do you need to check Facility_Id by User?
)
If you have the value for #UserId in the beginning, you could limit your temp table usage to just the user you need. Hopefully this code is not meant for use in some sort of cursor or other loop.

Related

Recieving error when updating with subquery within SSMS

I've run into an error where my subquery returns more values than the permitted "1".
I'm trying to update Table [I] with the.QUERY value from Table [P].
Both tables are from different databases. They have the same value in column ID.
And I want to try out ID's 100-150 as a test first.
UPDATE I
SET I.metadata02 = (SELECT CAST([XML]AS xml)
.query(N'/inkoopfacturen/inkoopfactuur/jaar')
.value('.', 'varchar(30)')
FROM [Archive190404132717].[dbo].[tblArchiveInvoices])
FROM tblindex AS I
INNER JOIN [Archive190404132717].[dbo].[tblArchiveInvoices] AS P
ON I.ID = P.ID
WHERE
I.tasknumber BETWEEN '100' and '150'
OK, seems like what you are actually after is actually just this:
UPDATE I
SET I.metadata02 = CAST([XML] AS xml).query(N'/inkoopfacturen/inkoopfactuur/jaar').value('.', 'varchar(30)')
FROM tblindex I
INNER JOIN [Archive190404132717].[dbo].[tblArchiveInvoices] P ON I.ID = P.ID
WHERE I.tasknumber BETWEEN '100' AND '150';
There's no need for the subquery, and the 2nd reference to tblArchiveInvoices; you've already joined to it.
Why are you using both a subquery and join? I assume you want a correlated subquery:
UPDATE I
SET I.metadata02 = (SELECT CAST([XML]AS xml)
.query(N'/inkoopfacturen/inkoopfactuur/jaar')
.value('.', 'varchar(30)')
FROM [Archive190404132717].[dbo].[tblArchiveInvoices]
WHERE I.ID = P.ID
)
FROM tblindex I
WHERE I.tasknumber BETWEEN '100' and '150';
Also, a field called tasknumber should really be stored as a number. The comparison as strings can be misleading. If it is a number, drop the single quotes. If it is a string, you should realize that '10001 meets the WHERE conditions.
maybe you're trying something like this:
UPDATE I
SET metadata02 = X
FROM tblindex AS I
INNER JOIN (SELECT Id, CAST([XML]AS xml).query(N'/inkoopfacturen/inkoopfactuur/jaar').value('.', 'varchar(30)') AS X
FROM [Archive190404132717].[dbo].[tblArchiveInvoices] ) AS P
ON I.ID = P.ID
WHERE I.tasknumber BETWEEN '100' and '150'

changing temp tables to declaring them

Just a question regards to temp tables and declaring table. If I change the temp tables 'ChangedData' and 'PackageDatatoProcess' to their own variables '#ChangedData' and '#PackageDatatoProcess', can I ask how I am suppose to change the select into statement as I have not quite done this before. Virtually I told that we can declare tables rather than using the select into but just need a bit of help with this:
select distinct * into #PackageDataToProcess from #ChangedData pp
outer apply (
select pk.Reference, pjl.PackageToJournalLinkId, j.CreatedDate, pccl.PackageCostChangeLogId from Jet2Holidays.dbo.Package pk
inner join Jet2Holidays.dbo.PackageToJournalLink pjl on pk.PackageId = pjl.PackageId
inner join Jet2Holidays.dbo.Journal j on pjl.JournalId = j.JournalId
and j.PrincipalName= iif(#AllowNonSupportChanges = 0, 'HolidaysSupport', j.PrincipalName)
inner join Jet2Holidays.dbo.BusinessProcess bp on pjl.BusinessProcessId = bp.BusinessProcessId
and bp.[Description] = iif(#AllowNonSupportChanges = 0, 'CallCentreAction', bp.[Description])
left outer join Jet2Holidays.dbo.PackageCostChangeLog pccl on pccl.PackageToJournalLinkId = pjl.PackageToJournalLinkId
where pk.Reference = pp.PackageReference
and pp.JournalID = pjl.JournalId
) as packageData
First, you declare your variable tables like so:
DECLARE #PackageDataToProcess TABLE
(
Reference UNIQUEIDENTIFIER
, PackageToJournalLinkId INT
, CreatedDate DATETIME
, PackageCostChangeLogId INT
, {other columns here}
)
DECLARE #ChangedData TABLE
(
Reference UNIQUEIDENTIFIER
, PackageToJournalLinkId INT
, CreatedDate DATETIME
, PackageCostChangeLogId INT
, {other columns here}
)
At this point you can populate your #ChangedData table like so:
INSERT #ChangedData ( Reference, PackageToJournalLinkId, CreatedDate, PackageCostChangeLogId, {other columns})
SELECT Reference, PackageToJournalLinkId, CreatedDate, PackageCostChangeLogId, {other columns}
FROM ChangedDataSource -- Table, Procedure, Function
And then you can run your code by substituting #PackageDataToProcess for #PackageDataToProcess. Here it is again with a slight re-write (from the original):
INSERT #PackageDataToProcess
select distinct pp.* from #ChangedData pp
outer apply (
select pk.Reference, pjl.PackageToJournalLinkId, j.CreatedDate, pccl.PackageCostChangeLogId from Jet2Holidays.dbo.Package pk
inner join Jet2Holidays.dbo.PackageToJournalLink pjl on pk.PackageId = pjl.PackageId
inner join Jet2Holidays.dbo.Journal j on pjl.JournalId = j.JournalId
and j.PrincipalName= iif(#AllowNonSupportChanges = 0, 'HolidaysSupport', j.PrincipalName)
inner join Jet2Holidays.dbo.BusinessProcess bp on pjl.BusinessProcessId = bp.BusinessProcessId
and bp.[Description] = iif(#AllowNonSupportChanges = 0, 'CallCentreAction', bp.[Description])
left outer join Jet2Holidays.dbo.PackageCostChangeLog pccl on pccl.PackageToJournalLinkId = pjl.PackageToJournalLinkId
where pk.Reference = pp.PackageReference
and pp.JournalID = pjl.JournalId
) as packageData
Your declared tabled will go out of scope in a similar manner to the way your non-global temporary table do.

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.

SQL Text Matching Query Tuning

I'm trying to do some free text search matching, and wondering if I can improve this query (using MSSQL 2008):
#FreeText is a table, where each row is a search word
DECLARE #WordCount = (SELECT COUNT(*) from #FreeText)
SELECT p.ID
FROM Product p
OUTER APPLY
(
SELECT COUNT(ID) as MatchCount
FROM Product pm
INNER JOIN #FreeText ft
ON pm.txt like '%'+ft.text+'%'
WHERE pm.ID = p.ID
AND (SELECT TOP 1 [text] FROM #FreeText) IS NOT NULL
)MC
WHERE MatchCount = #WordCount
So I'm wondering if there is any way to avoid the "FROM Product pm" in the outer apply?
I cannot always INNER JOIN #FreeText because sometimes we don't use free text searching.
Any thoughts or tips would be greatly appreciated, also let me know if I can clarify anything. Thanks in advance.
P.S. I do know that MS SQL has a FREETEXT() search, but I unfortunately cannot use that at the moment.
Here's a query without OUTER APPLY, that returns all results when there are no search critera.
DECLARE #FreeText TABLE
(
[text] varchar(200)
)
INSERT INTO #FreeText SELECT 'a'
INSERT INTO #FreeText SELECT 'c'
-- what, null? No.
DELETE FROM #FreeText WHERE [text] is null
DECLARE #WordCount int
SET #WordCount = (SELECT Count(*) FROM #FreeText)
SELECT p.ID
FROM Product p
LEFT JOIN #FreeText ft
ON p.txt like '%' + ft.text + '%'
WHERE ft.text is not null OR #WordCount = 0
GROUP BY p.ID
HAVING COUNT(*) = #WordCount OR #WordCount = 0
Note: it would be my preference to not use the "freetext" query when there is not any freetext criteria - instead use another query (simpler). If you choose to go that route - go back to an INNER JOIN and drop the OR #WordCount = 0 x2.

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`