SQL Subquery using IN Statment with coalesce - sql

I have the following SELECT statmentment
SELECT
UserID
,UserName
,TradingParty
,Email
,[PrivDesc]
,LastLogin
,IsApproved
FROM
cte_getAll allUsers
WHERE
allUsers.TradingParty = COALESCE(#TradingParty, allUsers.TradingParty)
AND allUsers.Username = COALESCE(#Username, allUsers.Username)
AND allUsers.Email = COALESCE(#EmailAddress, allUsers.Email)
AND DATEADD(dd, DATEDIFF(dd, 0, allUsers.[LastLogin]), 0) >= COALESCE(#FromDate, DATEADD(dd, DATEDIFF(dd, 0, allUsers.[LastLogin]), 0))
AND DATEADD(dd, DATEDIFF(dd, 0, allUsers.[LastLogin]), 0) <= COALESCE( #ToDate, DATEADD(dd, DATEDIFF(dd, 0, allUsers.[LastLogin]), 0))
AND allUsers.IsApproved = COALESCE(#AccountActive, allUsers.IsApproved)
AND allUsers.[PrivId] -- IN (SELECT privId from #selectedPriv)
IN (COALESCE((SELECT PrivID FROM #selectedPriv), allUsers.[PrivId]))
EDIT
Prior to this statment i am populating the #selectedPriv temp table according to the users input. If there is not user input therefore the table will contain nothin
On the final AND statement i am trying to find all data where privId is equal to a list of privs which the user has entered. So for example the user eneters 1,2,3,4,5,6 that is stored into a temp table, from this i need to find all the rows from my original table which are associated with these priv.
Here is my issue if i use the commented IN statement it works but since the user is allowed to pass in NULL i need it within a COALESCE, but when i do this i get the message
Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as
an expression.
I havent got a clue on why this is. Any ideas that will help me fix this issue?

If I understand your question correctly, you have a table variable that has values that you want to select from, but if they don't have any values in the table, you don't want the restriction at all.
The reason you're getting the error you're getting is due to COALESCE() only expecting a single value to transform, but your query is returning multiple.
This may be what you're looking for:
AND
(
NOT EXISTS (SELECT * FROM #selectedPriv WHERE PrivID IS NOT NULL)
OR
(
EXISTS (SELECT * FROM #selectedPriv WHERE PrivID IS NOT NULL)
AND allUsers.[PrivId] IN (SELECT PrivID FROM #selectedPriv)
)
)

Since you are populating the temp table #selectedPriv prior to this statement just add an else there -- if the input is null have #selectedPriv =
SELECT DISTINCT PrivId FROM cte_getAll
This should be faster than a more complicated where statement that checks count(*) in #selectedPriv which is the other way to do it.

COALESCE can only take one value for it's first argument, which is why it complains when you get more than one value from that query. You could switch that part for something a bit different using some ANDs and ORs. (Note: untested, but should at least be close.)
AND (((select count(*) from #selectedPriv) = 0)
OR ((select count(*) from #selectedPriv) != 0 AND allUsers.[PrivId] IN (SELECT privId from #selectedPriv)))

coalesce works fine but you can use ORs instead and that might help you with your last AND.
SELECT
UserID,
UserName,
TradingParty,
Email,
[PrivDesc],
LastLogin,
IsApproved
FROM
cte_getAll allUsers
WHERE
allUsers.TradingParty = COALESCE(#TradingParty,allUsers.TradingParty)
AND (#Username IS NULL OR allUsers.Username = #Username)
AND (#EmailAddress IS NULL OR allUsers.Email = #EmailAddress)
AND (#FromDate IS NULL OR DATEADD(dd,DATEDIFF(dd,0,allUsers.[LastLogin]),0) >= #FromDate)
AND (#ToDate IS NULL OR DATEADD(dd,DATEDIFF(dd,0,allUsers.[LastLogin]),0) <= #ToDate)
AND (#AccountActive IS NULL OR allUsers.IsApproved = #AccountActive)
AND (#PrivIDS IS NULL OR allUsers.[PrivId] IN (SELECT PrivID FROM #selectedPriv))

Related

Getting error Invalid column name days in sql server

I am working on sql query, When i run the query i am getting error Invalid column name 'days'., Can anyone please help me, why i am getting error like this, it looks like there something issue with having, but still not able to resolve it, here is my query
SELECT
COUNT(*) AS total
FROM
(
SELECT *, ABS(DATEDIFF(day, GETDATE(), EpStart)) AS [days]
FROM tb_Episode
HAVING [days] <= ''
) AS episodes
WHERE
(episodes.EOEClaimDate is NULL or episodes.EOEClaimDate = '0000-00-00') AND
episodes.PayerType = 'Ep' AND
episodes.EpEnd < '2018-02-05' AND
episodes.CustID = '27'
You can't use in HAVING clause the alias of your field.
Why you use HAVING instead of WHERE?
The same restriction for HAVING exists for WHERE,
So your query will become:
FROM
(
SELECT *, ABS(DATEDIFF(day, GETDATE(), EpStart)) AS [days]
FROM tb_Episode
WHERE ABS(DATEDIFF(day, GETDATE(), EpStart)) <= ''
) AS episodes
According to this answer by #Codo you cannot using a HAVING on an alias/virtual column as each SQL clause is performed in a designated order
FROM clause
WHERE clause
GROUP BY clause
HAVING clause
SELECT clause
ORDER BY clause
So because your column alias is defined after the HAVING clause you get the invalid column name error as the column doesn't technically exist yet.

SQL: Filter records based on record creation date and other criteria

I am struggling to find a better solution to pick unique records from my user call data table.
My table structure is as follows:
SELECT [MarketName],
[WebsiteName] ,
[ID] ,
[UserID],
[CreationDate],
[CallDuration],
[FromPhone] ,
[ToPhone],
[IsAnswered],
[Source]
FROM [dbo].[UserCallData]
There are multiple entries in this table with different and same ID's. I wanted to check if [FromPhone] and [ToPhone] exists multiple times within last 3 months, if yes, I wanted to pick the first record with all columns based on [CreationDate], count the number of occurrences as TotalCallCount and sum the totalCallDuration as a single record. If [FromPhone] and [ToPhone] does not occur multiple times, I wanted to pick all columns as such. I have been able to put up partial query like below. But this doesn't return all columns without including in group by clause and also it doesn't satisfy my entire criteria. Any help on this would be highly appreciated.
select [FromPhone],
MIN([CreationDate]),
[ToPhone],
marketname,
count(*) as TotalCallCount ,
sum(CallDuration) as TotalCallDuration
from [dbo].[UserCallData]
where [CreationDate] >= DATEADD(MONTH, -3, GETDATE())
group by [FromPhone],[ToPhone], marketname
having count([FromPhone]) > 1 and count([ToPhone]) >1
Try to use ROW_NUMBER()
;with cte as
(
select *, ROW_NUMBER() OVER(PARTITION BY FromPhone, ToPhone ORDER BY CreationDate) as RN
from UserCallData
where CreationDate >= DATEADD(MONTH, -3, GETDATE())
),
cte_totals as
(
select C1.FromPhone, C1.ToPhone, COUNT(*) as TotalCallCount, SUM(CallDuration) as TotalCallDuration
from cte C1
where exists(select * from cte C2 where C1.FromPhone = C2.FromPhone and C1.ToPhone = C2.ToPhone and C2.RN > 1)
group by C1.FromPhone, C1.ToPhone
)
select C1.*, TotalCallCount, TotalCallDuration
from cte C1
inner join cte_totals C2 on C1.FromPhone = C2.FromPhone and C1.ToPhone = C2.ToPhone
where C1.RN = 1
I wrote query right in here so it can have some mistakes or mistypes, but the main idea might be clear.
I'm not entirely sure I've understood the question, but if I have the following may be what you want (or be a useful starting point):
SELECT
ucd.FromPhone,
min(ucd.CreationDate) as MinCreationDate,
ucd.ToPhone,
ucd.MarketName,
count(*) as TotalCallCount,
sum(ucd.CallDuration) as TotalCallDuration,
case
when min(ucd.WebsiteName) = max(ucd.WebsiteName) then min(ucd.WebsiteName)
else '* Various'
end as WebsiteName,
case
when min(ucd.ID) = max(ucd.ID) then min(ucd.ID)
else '* Various'
end as ID,
case
when min(ucd.UserID) = max(ucd.UserID) then min(ucd.UserID)
else '* Various'
end as UserID,
case
when min(ucd.IsAnswered) = max(ucd.IsAnswered) then min(ucd.IsAnswered)
else '* Some'
end as IsAnswered,
case
when min(ucd.Source) = max(ucd.Source) then min(ucd.Source)
else '* Various'
end as Source
FROM
dbo.UserCallData ucd
WHERE
ucd.CreationDate >= DATEADD(MONTH, -3, GETDATE())
GROUP BY
ucd.FromPhone,
ucd.ToPhone,
ucd.MarketName
Where we are collapsing rows together, if all the rows agree on a given column (so min(Field) = max(Field)), I return the min(Field) value (which is the same all the others, but avoid problems with needing additional "group by" clauses which would interfere with the other cases). Where they don't all agree, I've returned "* something".
The code assumes that all the columns are text type columns (you haven't said), you may get conversion errors. It also assumes that none of these fields are null. You / we can adapt the code if those assumptions aren't correct. If you aren't able to do that for yourself, let me know about issues, I'll be happy to do what I can.

Main T-SQL WHERE function seems to be wrongly applied to a subquery

This query returns a set of dates from tblValue whose FieldValue is type nvarchar(4000)
SELECT t1.FieldValue FROM (SELECT FieldValue
FROM tblValue
WHERE FieldID = 4) t1
WHERE DateAdd(day, -90, t1.FieldValue) <= GETDATE()
This works, but instead of hard-coding the FieldID of 4, I'd like to get all FieldValues for those which have the type "Expiration".
This query returns 4.
SELECT FieldID FROM tblField WHERE FieldType = 'Expiration'
So, I expect this query's innermost subquery to return 4, and then to have the DateAdd applied only to those Expiration values which are yielded from t1 in the outermost subquery, which is what happens in the working first example.
SELECT t1.FieldValue FROM (SELECT FieldValue
FROM tblValue
WHERE FieldID = (SELECT FieldID FROM tblField WHERE FieldType = 'Expiration')) t1
WHERE DateAdd(day, -90, t1.FieldValue) <= GETDATE()
But I get the error
"Conversion failed when converting date and/or time from character string."
which to me suggests that the DateAdd is being applied to all values of tblValue, not only to those which are yielded by the subquery which returns t1. There is probably a technical reason for it, but it doesn't seem right to me. For some reason
WHERE FieldID = 4) t1
is not equivalent to
WHERE FieldID = (SELECT FieldID FROM tblField WHERE FieldType = 'Expiration')) t1
It just so happens that if I leave off the final WHERE clause of the erroring query I get the same set of dates as in the working query. So t1 should not be presenting any values which the DateAdd should have a problem with. But there it is. I'm puzzled as to why.
This happens because of the particular execution plan that the optimizer produces. Depending on how it chooses to combine the comparison and filtering operations of the various clauses, it can do either one or the other first.
In this case, it's trying to perform the date conversion and comparison before applying the FieldType filter.
It's a well-known issue but inherent to the behavior of the SQL optimizer -- this is a similar issue with a different datatype: https://connect.microsoft.com/SQLServer/feedback/details/333312/error-8114-converting-data-type-varchar-to-numeric
There are ways around this, but they are not always straightforward and usually require you to force specific order of execution.
The below works for me, although I understand that the CASE technique is not always 100% effective. From this fiddle:
SELECT t1.FieldValue FROM (SELECT FieldValue
FROM tblValue
WHERE FieldID = (SELECT FieldID FROM tblField WHERE FieldType = 'Expiration')) t1
WHERE CASE WHEN ISDATE(t1.FieldValue) = 1 THEN DateAdd(day, -90, t1.FieldValue) ELSE '1/1/2900' END <= GETDATE()
I guess you want this?
SELECT * FROM tblValue v
JOIN tblField f ON v.FieldID = f.FieldID
WHERE f.FieldType = 'Expiration' AND DateAdd(day, -90, v.FieldValue) <= GETDATE()
To categorize this as wrongly applied is not fair
You don't get to control which rows TSQL will evaluate
With a hard 4 the optimizer did that first
Without a hard 4 the query optimizer had to be ready for anything and moved it to later
The query optimizer even considers a derived table fair game to optimize
If you just look at the query plan you can see the order
Try
SELECT *
FROM tblValue v
JOIN tblField f
ON v.FieldID = f.FieldID
AND f.FieldType = 'Expiration'
AND DateAdd(day, -90, v.FieldValue) <= GETDATE()

SQL Subquery returned more than 1 value

My query causes the following error:
Msg 512, Level 16, State 1, Procedure Item_insupd, Line 17
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
Query:
INSERT INTO [Total_Database].[dbo].[Item]
(
ItemID,
ItemNo,
ItemDescription,
Notes,
StandardCost,
SalesGLAccountID,
ItemTypeID,
Backorderable
)
(
SELECT [nr],
[nr],
[Latijn]+' '+[Subgroep]+' '+CAST([nr] as VARCHAR(255)),
[NL]+' '+[Vorm]+' '+[Kenmerk]+' '+[Hoogte],[Inkoopprijs],
(4),
(case when [Productgroep]='PB' then 1 else 5 end),
(1)
FROM [ACCESDATA].[dbo].[Planten]
);
I suspect this to happen because my subquery does not contain a WHERE, unfortunately I do not know how to construct a correct WHERE clause.
I suspect the problem is in this string (line 26 in your code):
IF NOT (EXISTS (SELECT G.GLAccountID FROM GLAccnt G INNER JOIN Inserted I ON G.GLAccountID = I.SalesGLAccountID))
OR ((SELECT I.COGSGLAccountID FROM Inserted I) IS NOT NULL) AND NOT (EXISTS (SELECT G.GLAccountID FROM GLAccnt G INNER JOIN Inserted I ON G.GLAccountID = I.COGSGLAccountID))
It looks like (SELECT I.COGSGLAccountID FROM Inserted I) return more than one row, so you're getting an error.
You're treating inserted as a one row table (for example, you're getting parameters from it like SELECT #ItemNo = I.ItemNo, #ItemDescription = I.ItemDescription FROM Inserted I, but inserted table can have more than one row. So in your case I think you have 3 options - check that there's only 1 row in inserted, rewrite trigger as set-based, or use cursor.
Here's sql fiddle with somewhat similar example.
If you really only want to insert one row, then You just add Where at the end, followed by some predicate (logical statement) that will be true for only one row in the table the query is reading from.
INSERT INTO [Total_Database].[dbo].[Item](ItemID,
ItemNo,ItemDescription,Notes,StandardCost,SalesGLAccountID,
ItemTypeID,Backorderable)
SELECT [nr],[nr],[Latijn]+' '+[Subgroep]+' '+CAST([nr] as VARCHAR(255)),
[NL]+' '+[Vorm]+' '+[Kenmerk]+' '+[Hoogte],[Inkoopprijs],(4),
(case when [Productgroep]='PB' then 1 else 5 end),(1)
FROM [ACCESDATA].[dbo].[Planten]
Where [SomeColumnName] = [some Value];
... but when performing an Insert using a select to generate the rows to be inserted, you just type the Select statement instead of the Values() clause, without surrounding parentheses. I think that because you had the surrounding parentheses, the query processor assumed you wanted that to be a single value. Do you want to only insert one row of data? or a whole set of data rows ?
INSERT INTO [Total_Database].[dbo].[Item]
(
ItemID,
ItemNo,
ItemDescription,
Notes,
StandardCost,
SalesGLAccountID,
ItemTypeID,
Backorderable
) IN
(
SELECT [nr],
[nr],
[Latijn]+' '+[Subgroep]+' '+CAST([nr] as VARCHAR(255)),
[NL]+' '+[Vorm]+' '+[Kenmerk]+' '+[Hoogte],[Inkoopprijs],
(4),
(case when [Productgroep]='PB' then 1 else 5 end),
(1)
FROM [ACCESDATA].[dbo].[Planten]
);

Need COUNT from a more complex query

I usually use COUNT within a subquery to grab the desired number, but in this case I need a little help as the query contains too many arguments.
SELECT a.[QueueID]
,a.[CouponID]
,a.[ListingID]
,a.[User_ID]
,b.[CouponID]
,b.[ListingID]
,b.[CouponActive]
,b.[CouponExpire]
,b.[IsDeleted]
,c.[ListingID]
,c.[TypeID]
,c.[LevelID]
,#passedUserID as User_ID
FROM CouponQueue a
JOIN Coupon b
on a.CouponID = b.CouponID
JOIN Listing c
on b.ListingID = c.ListingID
WHERE (a.[User_ID] = #passedUserID)
AND (b.[CouponActive] = 1)
AND (b.[IsDeleted] = 0)
AND (b.[CouponExpire] > DATEADD(dd, -1, GETDATE()) OR b.[CouponExpire] IS NULL)
So lets say this query returns a result of 7 rows. All I need is this number for my VIEW. So I want to limit the ultimate result to a single row so that in the end I get:
[TotalCount] <-- Field name
[7] <-- Result
But not 7 rows of data.. I just need the count from the above query. Still plugging away and trying to learn. I looked at a few other examples but I haven't found one with all the conditions... which is what's messing me up. Please help!
Thank you so much!
Would this work for you?
select count(*) as TotalCOunt from (
SELECT a.[QueueID] /*
,a.[CouponID]
,a.[ListingID]
,a.[User_ID]
,b.[CouponID]
,b.[ListingID]
,b.[CouponActive]
,b.[CouponExpire]
,b.[IsDeleted]
,c.[ListingID]
,c.[TypeID]
,c.[LevelID]
,#passedUserID as User_ID */
FROM CouponQueue a
JOIN Coupon b
on a.CouponID = b.CouponID
JOIN Listing c
on b.ListingID = c.ListingID
WHERE (a.[User_ID] = #passedUserID)
AND (b.[CouponActive] = 1)
AND (b.[IsDeleted] = 0)
AND (b.[CouponExpire] > DATEADD(dd, -1, GETDATE()) OR b.[CouponExpire] IS NULL)
) t
You can remove the columns for the count. They are not actually necessary.
Should be able to just add COUNT(*):
SELECT COUNT(*) as TotalCount
FROM CouponQueue a
JOIN Coupon b
on a.CouponID = b.CouponID
JOIN Listing c
on b.ListingID = c.ListingID
WHERE (a.[User_ID] = #passedUserID)
AND (b.[CouponActive] = 1)
AND (b.[IsDeleted] = 0)
AND (b.[CouponExpire] > DATEADD(dd, -1, GETDATE()) OR b.[CouponExpire] IS NULL)
Good luck.
You can put the below sample on your SQL Developer to run for the count:
SELECT count(*) as totalCount (*-open a parentheses - your original query -close the parentheses*)