Use coalesce in a subquery while creating a view - subquery

I am currently trying to make a view in order to show the name of the clients that have purchased a car (new or used) and need to show "NA" when the client name is NULL.
IF OBJECT_ID ('V_PrixSuperieurMoyenne','V') IS NOT NULL
DROP VIEW V_PrixSuperieurMoyenne;
GO
CREATE VIEW V_PrixSuperieurMoyenne
AS
(
SELECT
CS.[State],
CI.ListPrice,
CI.CarInventoryID,
COALESCE(CONCAT(C.Name, ' ', C.FirstName), 'NA') AS 'Nom du Client',
(
SELECT AVG(CI.ListPrice)
FROM CarInventory CI
INNER JOIN CarState CS ON CS.CarStateID = CI.CarStateID
WHERE CS.State = 'New'
) AS 'Prix Moyen Neuf'
FROM
CarInventory CI
INNER JOIN CarState CS ON CS.CarStateID = CI.CarStateID
LEFT JOIN Invoice I ON I.CarInventoryID = CI.CarInventoryID
LEFT JOIN Client C ON C.ClientID = I.ClientID
GROUP BY
CS.State,
CI.ListPrice,
CI.CarInventoryID,
CONCAT(C.Name , ' ', C.FirstName)
)
GO
SELECT *
FROM V_PrixSuperieurMoyenne
ORDER BY 1,2
GO
Here is my code, however it seems like the COALESCE doesn't work as when I try to run the code, when the names are NULL, it only shows an empty string. I need it to show 'NA'.

The CONCAT function will return ' ' when C.Name and C.FirstName are NULL. However, if you replace CONCAT with string additions, you will get NULL as you expect, because a single NULL value in a line of string additions nulls out the entire string.
-- Returns 'NA' when C.Name or C.FirstName are NULL.
COALESCE(C.Name + ' ' + C.FirstName,'NA')
You could also replace COALESCE with ISNULL since you're only checking nullity of a single item:
ISNULL(C.Name +' ' + C.FirstName,'NA')
Be sure to make this change in your GROUP BY, too.
COALESCE is typically used when you want to check whether multiple items are NULL.

Related

Problems with the BQL "IN<>" statement

The requirement I have is to get a list of all discount codes defined in an instance and which ones a particular customer is currently assigned to, in the case given CustomerID=28. I further have to include only discount codes that naturally will be applicable to customers. There are only 3 of these; "Customer", "Customer and Item", "Customer and Item price Class". These are ARDiscount.ApplicableTo containing "CU", "CP","CI"
Select a.CompanyID, a.DiscountID, a.DiscountSequenceID, b.ApplicableTo, c.CustomerID
From DiscountSequence a
Join ARDiscount b On a.CompanyID = b.CompanyID and a.DiscountID = b.DiscountID
Left Outer Join DiscountCustomer c On a.CompanyID = c.CompanyID
And a.DiscountID = c.DiscountID
And a.DiscountSequenceID = c.DiscountSequenceID
And (IsNull(c.CustomerID,0) = 0 OR c.CustomerID = 72)
Where a.CompanyID = 2
And b.ApplicableTo In ('CU','CP','CI')
Order By a.DiscountID, a.DiscountSequenceID
I created data view delegate to return the 4 columns I need to display and in the view I created
to read the data like the SQL query above I used the BQL "IN<>" statement like this. The method was taken directlty from a blog post found here :
https://asiablog.acumatica.com/2017/11/sql-in-operator-in-bql.html
Object[] applicableTovalues = new String[] { "CP","CI","CU" }; // Customer and Price Class // Customer and Item// Customer
var Results = PXSelectJoin<DiscountSequence
, InnerJoin<ARDiscount, On<DiscountSequence.discountID, Equal<ARDiscount.discountID>>
, LeftJoin<DiscountCustomer, On<DiscountSequence.discountID, Equal<DiscountCustomer.discountID>,
And<DiscountSequence.discountSequenceID, Equal<DiscountCustomer.discountSequenceID>,
And<Where<DiscountCustomer.customerID, Equal<Current<Customer.bAccountID>>,
Or<DiscountCustomer.customerID, IsNull>>>>>>>
, Where<DiscountSequence.discountID, IsNotNull
, And<ARDiscount.applicableTo, In<Required<ARDiscount.applicableTo>>>>
, OrderBy<Asc<DiscountSequence.discountID, Asc<DiscountSequence.discountSequenceID>>>
>.Select(Base, applicableTovalues);
The problem is that the resulting SQL server select statement caught with TRACE only includes the first of the three IN values (''CU'') leaving (CI and CU) out.
I was expecting all three values in the IN statement like this : AND [ARDiscount].[ApplicableTo] IN ( ''CP'', ''CI'', ''CU'')
exec sp_executesql N'SELECT [DiscountSequence].[DiscountID], [DiscountSequence].[DiscountSequenceID], [DiscountSequence].[LineCntr],
<snip>
[DiscountCustomer].[CreatedDateTime], [DiscountCustomer].[LastModifiedByID], [DiscountCustomer].[LastModifiedByScreenID], [DiscountCustomer].[LastModifiedDateTime]
FROM [DiscountSequence] [DiscountSequence] INNER JOIN [ARDiscount] [ARDiscount] ON ( [ARDiscount].[CompanyID] = 2) AND [DiscountSequence].[DiscountID] = [ARDiscount].[DiscountID]
LEFT JOIN [DiscountCustomer] [DiscountCustomer] ON ( [DiscountCustomer].[CompanyID] = 2) AND [DiscountSequence].[DiscountID] = [DiscountCustomer].[DiscountID]
AND [DiscountSequence].[DiscountSequenceID] = [DiscountCustomer].[DiscountSequenceID] AND ( [DiscountCustomer].[CustomerID] = #P0 OR [DiscountCustomer].[CustomerID] IS NULL )
WHERE ( [DiscountSequence].[CompanyID] = 2)
AND ( [DiscountSequence].[DiscountID] IS NOT NULL
AND [ARDiscount].[ApplicableTo] IN ( ''CU''))
ORDER BY [DiscountSequence].[DiscountID], [DiscountSequence].[DiscountSequenceID]
OPTION(OPTIMIZE FOR UNKNOWN) /* AR.30.30.00 */',N'#P0 int',#P0=39
The issue is passing the array into the 'params' parameter. It thinks that you are passing a list of parameters into the bql query instead of a single array as a parameter.
If you cast it as follows it should work:
.Select(Base, (object)applicableTovalues);

Nested subquery in FOR ALL ENTRIES

Consultant sent me this code example, here is something he expects to get
SELECT m1~vbeln_im m1~vbelp_im m1~mblnr smbln
INTO CORRESPONDING FIELDS OF TABLE lt_mseg
FROM mseg AS m1
INNER JOIN mseg AS m2 ON m1~mblnr = m2~smbln
AND m1~mjahr = m2~sjahr
AND m1~zeile = m2~smblp
FOR ALL ENTRIES IN lt_vbfa
WHERE
AND m2~bwart = '102'
AND 0 = ( select SUM( ( CASE
when SHKZG = 'S' THEN 1
when SHKZG = 'H' THEN -1
else 0
END ) *MENGE ) MENGE
into lt_mseg-summ
from mseg
where
VBELN_IM = m1~vbeln_im
and VBELP_IM = m1~vbelp_im
).
The problem is I don't see how that should work in current syntax. I think about deriving internal select and using it as condition to main one, but is there a proper way to write this nested construction?
As i get it, if nested statement = 0, then main query executes. The problem here is the case inside nested statement. Is it even possible in ABAP? And in my opinion this check could be used outside from main SQL query.
Any suggestions are welcome.
the logic that you were given is part of Native/Open SQL and has some shortcomings that you need to be aware of.
the statement you are showing has to be placed between EXEC SQL and ENDEXEC.
the logic is platform dependent.
there is no syntax checking performed between the EXEC and ENDEXEC
the execution of this bypasses the database buffering process, so its slower
To me, I would investigate a better way to capture the data that performs better outside of open/native sql.
If you want to move forward with this type of logic, below are a couple of links which should be helpful. There is an example select using a nested select with a case statement.
Test Program
Example Logic
This is probably what you need, it works at least since ABAP 750.
SELECT vbeln UP TO 100 ROWS
FROM vbfa
INTO TABLE #DATA(lt_vbfa).
DATA(rt_vbeln) = VALUE range_vbeln_va_tab( FOR GROUPS val OF <line> IN lt_vbfa GROUP BY ( low = <line>-vbeln ) WITHOUT MEMBERS ( sign = 'I' option = 'EQ' low = val-low ) ).
SELECT m1~vbeln_im, m1~vbelp_im, m1~mblnr, m2~smbln
INTO TABLE #DATA(lt_mseg)
FROM mseg AS m1
JOIN mseg AS m2
ON m1~mblnr = m2~smbln
AND m1~mjahr = m2~sjahr
AND m1~zeile = m2~smblp
WHERE m2~bwart = '102'
AND m1~vbeln_im IN ( SELECT vbelv FROM vbfa WHERE vbelv IN #rt_vbeln )
GROUP BY m1~vbeln_im, m1~vbelp_im, m1~mblnr, m2~smbln
HAVING SUM( CASE m1~shkzg WHEN 'H' THEN 1 WHEN 'S' THEN -1 ELSE 0 END * m1~menge ) = 0.
Yes, aggregating and FOR ALL ENTRIES is impossible in one SELECT, but you can trick the system with range and subquery. Also you don't need three joins for summarizing reversed docs, your SUM subquery is redundant here.
If you need to select documents not only by delivery number but also by position this will be more complicated for sure.

MS SQL - How to get list of comma/semi-colon separated list of e-mail addresses searched in from a large string

How to extract the list of all email addresses available in a string, as a comma/semicolon separated list
SELECT dbo.getEmailAddresses('this is misc andrew#g.com')
--output andrew#g.com
SELECT dbo.getEmailAddresses('this is misc andrew#g.com and a medium text returning %John#acme.com')
--output andrew#g.com; John#acme.com
Here is a UDF for this. Correct this line SET #MailTempl='[A-Za-z0-9_.-] if you need additional (for example non-English symbols in an E-mail address). Here is a full list of allowed characters
CREATE FUNCTION [dbo].[getEmailAddresses] (#Str varchar(8000))
RETURNS varchar(8000) AS
BEGIN
declare #i int, #StartPos int,#AtPos int,#EndPos int;
declare #MailList varchar(8000);
declare #MailTempl varchar(100);
SET #MailList=NULL;
SET #MailTempl='[A-Za-z0-9_.-]'; --allowing symbols in e-mail not including #
SET #AtPos=PATINDEX('%'+#MailTempl+'#'+#MailTempl+'%',#Str)+1;
While #AtPos>1
begin
--go left
SET #i=#AtPos-1;
while (substring(#Str,#i,1) like #MailTempl) SET #i=#i-1;
SET #StartPos=#i+1;
--go right
SET #i=#AtPos+1;
while (substring(#Str,#i,1) like #MailTempl) SET #i=#i+1;
SET #EndPos=#i-1;
SET #MailList=isnull(#MailList+';','')+Substring(#Str,#StartPos,#EndPos-#StartPos+1);
--prepare for the next round
SET #Str=substring(#Str,#EndPos+1,LEn(#Str));
SET #AtPos=PATINDEX('%'+#MailTempl+'#'+#MailTempl+'%',#Str)+1;
end;
RETURN #MailList;
END
try this:
create function getEmailAddresses
(
#test varchar(max)
)
returns varchar(max)
As
BEGIN
declare #emaillist varchar(max)
--SET #test=' this is it by it a#b.com dsdkjl dsaldkj a#b.com dasdlk c#bn.com dsafhjkf anand#p.com d fdajf s#s.com .'
;WITH CTE as(
select reverse(left(reverse(left(#test,CHARINDEX('.com',#test)+3)),charindex(' ',reverse(left(#test,CHARINDEX('.com',#test)+3))))) as emailids,
right(#test,len(#test)-(CHARINDEX('.com',#test)+3)) rem
union all
select CASE WHEN len(rem)>2 then reverse(left(reverse(left(rem,CHARINDEX('.com',rem)+3)),charindex(' ',reverse(left(rem,CHARINDEX('.com',rem)+3))))) else 'a' end as emailids ,
CASE WHEN len(rem) > 2 then right(rem,len(rem)-(CHARINDEX('.com',rem)+3)) else 'a' end rem
from CTE where LEN(rem)>2
)
select #emaillist =STUFF((select ','+emailids from CTE for XML PATH('')),1,1,'')
return #emaillist
END
select dbo.getEmailAddresses('this is it by it a#b.com dsdkjl dsaldkj a#b.com dasdlk c#bn.com dsafhjkf anand#p.com d fdajf s#s.com .')
Try this
Declare #str varchar(max) = 'this is misc andrew#g.com and a medium text returning John#acme.com'
;With Cte AS(
SELECT
Items = Split.a.value('.', 'VARCHAR(100)')
FROM
(
SELECT
CAST('<X>' + REPLACE(#str, ' ' , '</X><X>') + '</X>' AS XML) AS Splitdata
) X
CROSS APPLY Splitdata.nodes('/X') Split(a) )
SELECT Email = STUFF((
SELECT ';'+ Items
FROM Cte
Where Items
LIKE '[A-Z0-9]%[#][A-Z]%[.][A-Z]%'
FOR XML PATH('')),1,1,'')
Result
EmailAddress
andrew#g.com;John#acme.com
N.B.~ You may need to do the following
a) As per your requirement , you need to make a TVF (Table valued function). You can refer the article on Split Function in Sql Server using Set base approach
b) The Email validation like clause is OK to work with but for more complex requirement you may have to enhance it.
c)If needed, you may have to clean the data before applying the filtration clause. e.g. %John#acme.com is an invalid email.so remove the "%" sign and then apply the filtering clause.
But as some one mentioned that, it is better not to do the string splitting/ manipulation much in Sql Server side and I agree to his point , so here is a C# code for achieving the same
static void Main(string[] args)
{
string str = "this is misc andrew#g.com and a medium text returning John#acme.com";
var result = GetValidEmails(str).Aggregate((a,b)=>a+";" + b);
Console.WriteLine(result);
Console.ReadKey();
}
private static List<string> GetValidEmails(string input)
{
List<string> lstValidEmails = new List<string>();
string regexPattern = #"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$";
foreach (string email in input.Split(' '))
{
if (new Regex(regexPattern, RegexOptions.IgnoreCase).IsMatch(email))
{
lstValidEmails.Add(email);
}
}
return lstValidEmails;
}
Hope this will be helpful.

Creating an associative array using a while loop for select list?

I am dynamically generating a selectlist in Drupal and I want to create an associative array to populate the node ID as the value, and the title of the node as the option.
By default, it makes the value for each option the index of the select list. This is no good because the select list is dynamic, meaning the values won't be in the same order.
I used drupal_map_assoc to make the value the same as the option, but I have queries based on the value stored in this field, so if someone updates the value stored, the queries won't match.
<option value="Addison Reserve Country Club Inc.">Addison Reserve Country Club Inc.</option>
I want to replace the value with the Node ID also pulled with the query.
$sql = 'SELECT DISTINCT title, nid FROM {node} WHERE type = \'accounts\' ';
$result = db_query($sql);
while ($row = db_fetch_array($result)) {
$return[] = $row['title'];
//Trying to do something like 'Addison Reserve Country Club' => '2033' - where 2033 is the nid
}
$return = drupal_map_assoc($return);
I think you just want to do this inside the loop:
$return[$row['nid']] = $row['title'];
Based on your comment, you would also want to do an array_flip() right after the loop, but I think your comment may just have it backwards.

linq to entity Contains() and nested queries

i've a trouble with linq, i'll explain on example :
i have a database table called Employee which got FirstName and LastName columns,
and a method to search employees which gets a nameList list as argument, the elements in this list are names formatted like this one "Fred Burn", or this1 "Grim Reaper",
already tryed these approaches with no luck =[
//just all employees
var allData = from emp in Context.Employee select emp;
var test1 = from emp in allData
where(emp.FirstName + " " + emp.LastName).Contains
("" + ((from n in nameList select n).FirstOrDefault()))
select emp;
var test2 = (from emp in allData
where (emp.FirstName + " " + emp.LastName)
== ((from n in nameList select n).FirstOrDefault())
select emp);
var test3 = from emp in allData
where (from n in nameList select n).Contains
(emp.FirstName + " " + emp.LastName)
select emp;
first and second queries give : {"Unable to create a constant value of type 'Closure type'. Only primitive types ('such as Int32, String, and Guid') are supported in this context."} exceptionand third : {"LINQ to Entities does not recognize the method 'Boolean Contains[String](System.Collections.Generic.IEnumerable`1[System.String], System.String)' method, and this method cannot be translated into a store expression."}
would be glad to hear your suggestions :)
Thank You!
p.s.
yea i know it's possible to split names in list and compare them separately, but still curious why wont these queries work.
I assume nameList in this case is an in memory collection and that you are trying to use the LINQ to SQL trick creating a SQL "IN" clause inside of the Entity Framework. Unfortunately, EF doesn't support this syntax (yet). Depending on the number of records you are trying to match, you may be able to run separate queries for each child you are desiring. Alternatively, you could build an entitysql query using concatenation to append the multiple items from the nameList as separate OR clauses in the WHERE operation.

Resources