Split function in T-SQL - sql

I have table with field that contains coma separated string. I whant create query thats return follow result:
I want to create query that return split text. For exapmle:
Data in table:
| ID | Names |
| ------------------------------------ | -------------------------------- |
| 63F5D993-3AC9-4EEA-8007-B669542BAD9A | John Smith,Kerry King,Tom Arraya |
Required result:
ID | Names
------------------------------------ | -----------
63F5D993-3AC9-4EEA-8007-B669542BAD9A | John Smith
------------------------------------- | -----------
63F5D993-3AC9-4EEA-8007-B669542BAD9A | Kerry King
------------------------------------- | -----------
63F5D993-3AC9-4EEA-8007-B669542BAD9A | Tom Arraya
I found "split" function for T-SQL but its works not quite right for my case. I can't execute it like this:
SELECT dbo.Split(dbo.name, ',') FROM dbo.Mytable
It can execute only follows:
SELECT * FROM dbo.Split('John Smith,Kerry King,Tom Arraya', ',')
But it does not suit me.
Also i attempted write cursor:
DECLARE #bkb varchar(256)
DECLARE #Bkb_Cursor CURSOR
SET #Bkb_Cursor = CURSOR SCROLL FOR
SELECT bkb.best_know_by
FROM [sugarcrm_cmsru_dev].[dbo].[contacts] c
left JOIN [dbo].[email_addr_bean_rel] eb
ON eb.[bean_id] = c.[id]
JOIN [dbo].[email_addresses] ea
ON ea.[id] = eb.[email_address_id]
JOIN [dbo].[contacts_cstm] ccs
ON eb.bean_id = ccs.id_c
left JOIN [dbo].[BestKnowBy$] bkb
ON c.[campaign_id] =bkb.Con_id
where c.deleted = 0;
OPEN #Bkb_Cursor;
FETCH NEXT FROM #Bkb_Cursor
INTO #bkb;
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT dbo.Splitfn(#bkb);
FETCH NEXT FROM #Bkb_Cursor INTO #bkb;
END
CLOSE #Bkb_Cursor;
DEALLOCATE #Bkb_Cursor;
GO
But it is not worked. I get error "Column "dbo" is not allowed in this context, and the user-defined function or aggregate "dbo.Splitfn" could not be found."
How can I solve this problem?
My query looks like follow:
SELECT c.[id],
bkb.best_know_by
FROM [sugarcrm_cmsru_dev].[dbo].[contacts] c
left JOIN [dbo].[email_addr_bean_rel] eb
ON eb.[bean_id] = c.[id]
JOIN [dbo].[email_addresses] ea
ON ea.[id] = eb.[email_address_id]
JOIN [dbo].[contacts_cstm] ccs
ON eb.bean_id = ccs.id_c
left JOIN [dbo].[BestKnowBy$] bkb
ON c.[campaign_id] =bkb.Con_id
where c.deleted = 0;
The bkb.best_know_by field contains comma separated string. How can i use "Cross Apply" in this case?

You need to use CROSS APPLY in combination with your table's result set which will execute the function for every row:
SELECT st.ID, spl.value
FROM SplitTest st
CROSS APPLY string_split(st.Names, ',') spl
Edit:
With regard to the addition of your query to your question, you could do the following:
;WITH CTE_Query AS (
SELECT c.[id],
bkb.best_know_by
FROM [sugarcrm_cmsru_dev].[dbo].[contacts] c
left JOIN [dbo].[email_addr_bean_rel] eb
ON eb.[bean_id] = c.[id]
JOIN [dbo].[email_addresses] ea
ON ea.[id] = eb.[email_address_id]
JOIN [dbo].[contacts_cstm] ccs
ON eb.bean_id = ccs.id_c
left JOIN [dbo].[BestKnowBy$] bkb
ON c.[campaign_id] =bkb.Con_id
where c.deleted = 0
)
SELECT cte.id, spl.value
FROM CTE_Query AS cte
CROSS APPLY string_split(cte.best_know_by, ',') spl

Cross Apply will do the trick
Select A.ID
,Names = B.Item -- << Return Field from your Split Function
From YourTable A
Cross Apply (Select * from dbo.Split(A.Names, ',') ) B
With your query
SELECT c.[id]
,S.* --<< Removed bkb.best_know_by and Replaced with S.* (don't know your Split() Return Field)
FROM [sugarcrm_cmsru_dev].[dbo].[contacts] c
LEFT JOIN [dbo].[email_addr_bean_rel] eb ON eb.[bean_id] = c.[id]
JOIN [dbo].[email_addresses] ea ON ea.[id] = eb.[email_address_id]
JOIN [dbo].[contacts_cstm] ccs ON eb.bean_id = ccs.id_c
LEFT JOIN [dbo].[BestKnowBy$] bkb ON c.[campaign_id] =bkb.Con_id
Cross Apply dbo.Split(bkb.best_know_by,',') S
where c.deleted = 0;

Related

LEFT OUTER JOIN not always matching

I'm starting with a SQL query with a couple of joins and I'm getting the exact data I expect. This is what the current query is.
SELECT DISTINCT o.OrganizationHierarchyUnitLevelFourCd, o.OrganizationHierarchyUnitLevelThreeNm, o.OrganizationHierarchyUnitLevelFourNm
FROM Lab_Space l
JOIN Worker w ON l.Contact_WWID = w.WWID AND w.Employee_Status_Code = 'A'
JOIN Org_Hierarchy o ON o.OrganizationHierarchyUnitLevelThreeNm IS NOT NULL AND w.Org_Hierarchy_Unit_Cd = o.OrganizationHierarchyUnitCd
ORDER BY o.OrganizationHierarchyUnitLevelThreeNm, o.OrganizationHierarchyUnitLevelFourNm
This ends up with a row like
1234 | Finance | IT
Now I've created a new table, where I'm tracking whether or not I want to include the organization in my output. That table just has two columns, an org ID and a bit field. So I thought I could LEFT OUTER JOIN, since the second table won't have data on all orgs, so I expanded the query to this:
SELECT DISTINCT o.OrganizationHierarchyUnitLevelFourCd, o.OrganizationHierarchyUnitLevelThreeNm, o.OrganizationHierarchyUnitLevelFourNm, v.Include
FROM Lab_Space l
JOIN Worker w ON l.Contact_WWID = w.WWID AND w.Employee_Status_Code = 'A'
JOIN Org_Hierarchy o ON o.OrganizationHierarchyUnitLevelThreeNm IS NOT NULL AND w.Org_Hierarchy_Unit_Cd = o.OrganizationHierarchyUnitCd
LEFT OUTER JOIN Validation_Email_Org_Unit_Inclusion v ON o.OrganizationHierarchyUnitCd = v.OrganizationHierarchyUnitCd
ORDER BY o.OrganizationHierarchyUnitLevelThreeNm, o.OrganizationHierarchyUnitLevelFourNm
The problem I have is now I end up with rows like so:
1234 | Finance | IT | NULL
1234 | Finance | IT | 1
Since the Validation_Email_Org_Unit_Inclusion table includes a 1 for the 1234 org, I would expect to just get a single row with a value of 1, not include the row with NULL.
What have I done wrong?
You output OrganizationHierarchyUnitLevelFourCd but currently join on OrganizationHierarchyUnitCd. Join on the same column you output to get the corresponding value.
SELECT DISTINCT o.OrganizationHierarchyUnitLevelFourCd, ...
...
LEFT OUTER JOIN Validation_Email_Org_Unit_Inclusion v ON o.OrganizationHierarchyUnitLevelFourCd = v.OrganizationHierarchyUnitCd
...

Select MAX() or Select TOP 1 on Join

I'm working with the following code to only get one associated person per case, using the MAX Associated Type to get the top 1.
Associated Type is not a GUID, rather looks like:
Responsible Party, Primary Physician, etc.
It just so happens that Responsible Party is the last alphabetical option, so it's a lucky workaround. Not every case has a responsible party, however, and if there isn't a responsible party, the next top associated person is 'good enough' and will be highlighted as a data error anyway.
The result shows every single associated person (rather than top 1), but shows all of them as Responsible Party, which is not true. What am I doing wrong here?
FROM T_LatestIFSP Ltst
LEFT OUTER JOIN (
SELECT
Clas.ClientCase_ID,
MAX(Astp.AssociatedType) AS AssociatedType
FROM
T_ClientAssociatedPerson Clas
Inner Join T_AssociatedType Astp
ON Clas.AssociatedType_ID = Astp.AssociatedType_ID
GROUP BY Clas.ClientCase_ID
) AS Astp ON Ltst.ClientCase_ID = Astp.ClientCase_ID
LEFT OUTER JOIN T_ClientAssociatedPerson Clas
on Clas.ClientCase_ID = Astp.ClientCase_ID
LEFT OUTER JOIN T_AssociatedPerson Aspr
ON Aspr.AssociatedPerson_ID = Clas.AssociatedPerson_ID
To get AssocId in the select, you have to do a self join.
LEFT OUTER JOIN
(your subselect with max(AssociatedType) in it) AS Astp
INNER JOIN T_AssociatedType AS Astp2
ON (whatever the primary key is on that table)
Then you can add astp2.AssociationTypeId to the original SELECT.
You can try this query.
Make rn from your order condition in CASE WHEN
You can use Rank with window function to make rank number in subquery, then get rnk=1 data row.
;WITH CTE AS (
SELECT ClientCase_ID,
AssociatedPerson_ID,
AssociatedPersonType,
AssociatedType_ID,
RANK() OVER(PARTITION BY ClientCase_ID ORDER BY rn desc,AssociatedPerson_ID) rnk
FROM (
SELECT t1.ClientCase_ID,
t1.AssociatedPerson_ID,
t1.AssociatedPersonType,
t1.AssociatedType_ID,
(CASE
WHEN t1.AssociatedPersonType = 'ResPonsible Party' then 16
WHEN t1.AssociatedPersonType = 'Primary Physician' then 15
ELSE 14
END) rn
FROM T t1
INNER JOIN T t2 ON t1.ClientCase_ID = t2.AssociatedPerson_ID
UNION ALL
SELECT t2.AssociatedPerson_ID,
t1.AssociatedPerson_ID,
t1.AssociatedPersonType,
t2.AssociatedType_ID,
(CASE
WHEN t2.AssociatedPersonType = 'ResPonsible Party' then 16
WHEN t2.AssociatedPersonType = 'Primary Physician' then 15
ELSE 14
END) rn
FROM T t1
INNER JOIN T t2 ON t1.ClientCase_ID = t2.AssociatedPerson_ID
) t1
)
select DISTINCT ClientCase_ID,AssociatedPerson_ID,AssociatedPersonType,AssociatedType_ID
FROM CTE
WHERE rnk = 1
sqlfiddle
Also, you can try to use CROSS APPLY with value instead of UNION ALL
;with CTE AS (
SELECT v.*, (CASE
WHEN v.AssociatedPersonType = 'ResPonsible Party' then 16
WHEN v.AssociatedPersonType = 'Primary Physician' then 15
ELSE 14
END) rn
FROM T t1
INNER JOIN T t2 ON t1.ClientCase_ID = t2.AssociatedPerson_ID
CROSS APPLY (VALUES
(t1.ClientCase_ID,t1.AssociatedPerson_ID,t1.AssociatedPersonType, t1.AssociatedType_ID),
(t2.AssociatedPerson_ID,t1.AssociatedPerson_ID,t2.AssociatedPersonType, t2.AssociatedType_ID)
) v (ClientCase_ID,AssociatedPerson_ID,AssociatedPersonType,AssociatedType_ID)
)
SELECT distinct ClientCase_ID,AssociatedPerson_ID,AssociatedPersonType,AssociatedType_ID
FROM
(
SELECT *,
RANK() OVER(PARTITION BY ClientCase_ID ORDER BY rn desc,AssociatedPerson_ID) rnk
FROM CTE
) t1
WHERE rnk = 1
sqlfiddle
Note
you can add your customer order number in CASE WHEN
[Results]:
| ClientCase_ID | AssociatedPerson_ID | AssociatedPersonType | AssociatedType_ID |
|---------------|---------------------|----------------------|-------------------|
| 01 | 01 | ResPonsible Party | 16 |
| 02 | 03 | Physician Therapist | 24 |
I solved the problem with the following code:
LEFT OUTER JOIN T_ClientAssociatedPerson Clas
on Clas.ClientCase_ID = Ltst.ClientCase_ID
and
CASE
WHEN Clas.AssociatedType_ID = 16 AND Clas.ClientCase_ID = Ltst.ClientCase_ID THEN 1
WHEN Clas.AssociatedType_ID <> 16 AND Clas.AssociatedType_ID = (
SELECT TOP 1 Clas.AssociatedType_ID
FROM T_ClientAssociatedPerson Clas
WHERE Clas.ClientCase_ID = Ltst.ClientCase_ID
ORDER BY AssociatedType_ID DESC
) THEN 1
ELSE 0
END = 1

SQL - Fallback to default language when translate does not exist

Recording texts in different languages in a table (tbl_i18n) with the following structure:
text_FK | language_FK | value
-----------------------------
1 | 1 | hello
1 | 2 | hallo
2 | 1 | world
3 | 1 | test
gives texts of a specific language (id = 2) with a simple join like:
SELECT [value] FROM tbl_i18n i
JOIN tbl_products p ON p.text_id = i.text_FK
JOIN tbl_languages l ON l.id = i.language_FK AND i.language FK = 2;
and the result is:
value
-------
hallo
How could aforementioned select statement changed so we could have got a default language and when translate for a text fields does not exist their fallback text will be shown and the result will became:
value
-------
hallo
world
test
LEFT JOIN the language table twice. The first time for wanted language, the second time for fallback value. Use COALESCE to pick wanted language if available, otherwise fallback language.
SELECT coalesce(l1.[value], l2.[value])
FROM tbl_i18n i
JOIN tbl_products p ON p.text_id = i.text_FK
LEFT JOIN tbl_languages l1 ON l.id = i.language_FK AND i.language_FK = 2
LEFT JOIN tbl_languages l2 ON l.id = i.language_FK AND i.language_FK = 1;
I think in simple english you want the highest available language_FK for each text_FK.
WITH X AS (
SELECT *
,ROW_NUMBER() OVER (PARTITION BY text_FK ORDER BY language_FK DESC)rn
FROM TableName
)
SELECT *
FROM X
WHERE X.rn = 1

Using the COUNT with multiple tables

I want to count the number of different sheep, and I want it in one table.
Like this;
Ewes | Rams | Lambs
8 | 5 | 12
The query I try is this, but it doesn't work;
SELECT COUNT(e.EweID) AS 'Ewe', COUNT(r.RamID) AS 'Ram', COUNT(l.LambID) AS 'Lamb'
FROM Sheep s
INNER JOIN Ewe e ON s.SheepID = e.EweID
INNER JOIN Ram r ON s.SheepID = r.RamID
INNER JOIN Lamb l ON s.SheepID = l.LambID
WHERE s.FarmerID = '123'
I don't get what I'm doing wrong, this is my database ERD;
I don't think you need a FROM here at all:
select
(select count(*) from Ram where Famerid = 123) as RamCount,
(select count(*) from Ewe where Famerid = 123) as Count,
(select count(*) from Lamb where Famerid = 123) as LambCount
(There is no relationship between the rows you are counting, do don't try and create one. Instead count each separately, wrapping it all in an outer select keeps everything in a single result row.)
I think that the problem here is that you don't need an INNER JOIN but an OUTER JOIN ...
SELECT COUNT(CASE WHEN e.EweID IN NOT NULL THEN e.EweID ELSE 0 END) AS 'Ewe', COUNT(r.RamID) AS 'Ram', COUNT(l.LambID) AS 'Lamb'
FROM Sheep s
LEFT OUTER JOIN Ewe e ON s.SheepID = e.EweID
LEFT OUTER JOIN Ram r ON s.SheepID = r.RamID
LEFT OUTER JOIN Lamb l ON s.SheepID = l.LambID
WHERE s.FarmerID = '123'
Take a look even at the case statement that I've added inside the first count(Ewe), to see a way to handle nulls in the count .
The Left Outer Join logical operator returns each row that satisfies
the join of the first (top) input with the second (bottom) input. It
also returns any rows from the first input that had no matching rows
in the second input. The nonmatching rows in the second input are
returned as null values. If no join predicate exists in the Argument
column, each row is a matching row.
Use correlated sub-selects to do the counting:
SELECT (select COUNT(*) from Ewe e where s.SheepID = e.EweID) AS 'Ewe',
(select COUNT(*) from Ram r where s.SheepID = r.RamID) AS 'Ram',
(select COUNT(*) from Lamb l where s.SheepID = l.LambID) AS 'Lamb'
FROM Sheep s
WHERE s.FarmerID = '123'
And you can also simply remove the WHERE clause to get all farms' counts.
DECLARE #Count1 INT;
SELECT #Count1 = COUNT(*)
FROM dbo.Ewe;
DECLARE #Count2 INT;
SELECT #Count2 = COUNT(*)
FROM dbo.Ram;
DECLARE #Count3 INT;
SELECT #Count3 = COUNT(*)
FROM dbo.Lamb;
SELECT #Count1 AS 'Ewe' ,
#Count2 AS 'Ram' ,
#Count3 AS 'Lamb'

How to check intersection of subqueries in query?

I have the next query:
SELECT c.client_code, a.account_num, m.account_close_date, u.uso, m.product_name
FROM accounts a INNER JOIN Clients c ON c.id = a.client_id INNER JOIN
Uso u ON c.uso_id = u.uso_id INNER JOIN Magazine m ON a.account_id = m.account_id
and I need to compare product_name with input parameter.
product_name and input parameter #s are comma-delimited strings.
I use next split function:
ALTER FUNCTION [dbo].[Split]
(
#s VARCHAR(max),
#split CHAR(1)
)
RETURNS #temptable TABLE (items VARCHAR(MAX))
AS
BEGIN
DECLARE #x XML
SELECT #x = CONVERT(xml,'<root><s>' + REPLACE(#s,#split,'</s><s>') + '</s></root>');
INSERT INTO #temptable
SELECT [Value] = T.c.value('.','varchar(20)')
FROM #X.nodes('/root/s') T(c);
RETURN
END;
I think that I need to check the intersection of tables, which I will receive after split of product_name and after split of input parameter. I trid to do this:
WHERE (select * from dbo.Split(m.product_name, ';')
INTERSECT select * from dbo.Split('product1;product2',';'))
is not null
But it does not work quite right. Please, help me.
INTERSECT requires the same column output and is used like UNION or EXCEPT: not in the WHERE clause
Just JOIN onto the udf
...
INNER JOIN
Magazine m ON a.account_id = m.account_id
INNER JOIN
dbo.Split(#parameter, ';') CSV ON m.productname = CSV.items
If you need to split m.productname, if you can't fix the design, use CROSS APPLY
...
INNER JOIN
Magazine m ON a.account_id = m.account_id
CROSS APPLY
dbo.Split(m.productname, ';') WTF
INNER JOIN
dbo.Split(#parameter, ';') CSV ON WTF.items = CSV.items
However, JOIN and INTERSECT give different results if #parameter has duplicated values. Add a DISTINCT to the UDF for example to get around this. Or change the udf JOIN into EXISTS