DB2 - Ways to get multiple positions for character in a string - sql

So I am having the pleasure of working with a very old database that apparently was setup before the invention of normalization. I have been asked to see if I can come up with a way to make it work right.
The first table actually has something like a real primary key.
example:
ID, Reason
--- ----------
1, Write off
2, Overage
3, OLH
The problem is the other table...
CustomerNum, JobNum, Reasons
------------ ------- ---------------------
42351, 46, X
32313, 456, X
85472, 13, X X
How are these tables joined in their system? Yup, the position of the X's in the line. So if the X is in the first position, it's the reason 1, second position, reason 2, and so on. It's essentially a flat array. And that wouldn't be to bad actually, if they limited it to 1 X per line... (LOCATE('X', REASONS) as XINDEX) but that's not the case. In theory there are 21 possible X's that could be checked on each line.
So I have to give them a recommendation on how to make it work.
One of my first recommendations will be to create a separate table and normalize the tables, however I don't know how well that will go over, or if they would be willing to change their system.
So, I would also like to suggest something like a stored procedure that would be able to go through each line and return the indexes as if they were in a separate table.
I don't know if this is possible, but I'm hopeful.
EDIT
So yeah, I'm really going to push the linking table.
here's the alternative that I've got working from the suggestions:
Select tblCustomers.*,
CASE WHEN SUBSTRING(Reasons,1,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 1)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,2,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 2)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,3,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 3)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,4,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 4)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,5,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 5)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,6,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 6)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,7,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 7)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,8,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 8)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,9,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 9)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,10,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 10)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,11,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 11)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,12,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 12)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,13,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 13)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,14,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 14)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,15,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 15)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,16,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 16)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,17,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 17)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,18,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 18)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,19,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 19)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,20,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 20)) || ', ' ELSE '' END
|| CASE WHEN SUBSTRING(Reasons,21,1) = 'X' THEN RTRIM((SELECT Reason FROM tblReasons WHERE ID = 21)) || ', ' ELSE '' END AS XPOS
From tblCustomers
I'm going to have to play with Marlin's suggestion a bit to see how much it streamlines it, but one thing I do like about this query is that it shows how ridiculous their current layout is, and why they should change it.

I'd really pitch the new linking table, but here is either the meat of the stored procedure, or the way to populate the linking table:
SELECT Customers.CustomerNum, Reasons.ID
FROM Customers, Reasons
WHERE SUBSTR(Customers.Reasons, Reasons.ID, 1) = 'X'

first thing I would do is to create a new column for each possible position of the X (so if there are 10 positions -> 10 new cols) and give those columns meaningful names. then you can write queries easier...

Related

How to replace the phrase "1234 Address" by an empty string for the aliased fields

When I run this code, the fields Old_Address and New_Address are containing 1234 Address. I would like the result to change 1234 Address to an empty string each time found. What is an easy way to do this?
INSERT
INTO OPT_INT.mgnNACHG_Results
(
BRANCH_CD,
ACCOUNT_CD,
RR_CD,
ALT_BRANCH_CD,
[OldAddress],
[NewAddress],
[AddressUpdate],
[OldName],
[NewName],
[NameUpdate]
)
SELECT A.BRANCH_CD,
A.ACCOUNT_CD,
A.RR_CD,
A.ALT_BRANCH_CD,
LTRIM(RTRIM(ISNULL(B.LINE4, '')))
+ LTRIM(RTRIM(ISNULL(B.LINE5, '')))
+ LTRIM(RTRIM(ISNULL(B.LINE6, ''))) AS 'Old_Address',
LTRIM(RTRIM(ISNULL(A.LINE4, '')))
+ LTRIM(RTRIM(ISNULL(A.LINE5, '')))
+LTRIM(RTRIM(ISNULL(A.LINE6, ''))) AS 'New_Address',
CASE
WHEN (LTRIM(RTRIM(ISNULL(B.LINE4, ''))) <> LTRIM(RTRIM(ISNULL(A.LINE4, ''))))
OR (LTRIM(RTRIM(ISNULL(B.LINE5,''))) <> LTRIM(RTRIM(ISNULL(A.LINE5, ''))))
OR (LTRIM(RTRIM(ISNULL(B.LINE6, ''))) <> LTRIM(RTRIM(ISNULL(A.LINE6, ''))))
THEN 'Address Change'
ELSE ''
END as 'Address_Update',
LTRIM(RTRIM(ISNULL(B.LINE1, '')))
+ LTRIM(RTRIM(ISNULL(B.LINE2, '')))
+ LTRIM(RTRIM(ISNULL(B.LINE3, ''))) AS 'Client_Old_Name',
LTRIM(RTRIM(ISNULL(A.LINE1, '')))
+ LTRIM(RTRIM(ISNULL(A.LINE2, '')))
+ LTRIM(RTRIM(ISNULL(A.LINE3, ''))) AS 'Client_New_Name',
CASE
WHEN (LTRIM(RTRIM(ISNULL(B.LINE1,''))) <> LTRIM(RTRIM(ISNULL(A.LINE1, ''))))
OR (LTRIM(RTRIM(ISNULL(B.LINE2, ''))) <> LTRIM(RTRIM(ISNULL(A.LINE2, ''))))
OR (LTRIM(RTRIM(ISNULL(B.LINE3, ''))) <> LTRIM(RTRIM(ISNULL(A.LINE3, ''))))
THEN 'Title Change'
ELSE ''
END AS 'Client_Name_Update'
FROM #NEW A
INNER JOIN #OLD B
ON A.BRANCH_CD = B.BRANCH_CD
AND A.ACCOUNT_CD = B.ACCOUNT_CD
WHERE (A.[LINE1] <> B.[LINE1])
OR (A.[LINE2] <> B.[LINE2])
OR (A.[LINE3] <> B.[LINE3])
OR (A.[LINE4] <> B.[LINE4])
OR (A.[LINE5] <> B.[LINE5])
OR (A.[LINE6] <> B.[LINE6])
See whether this helps
(Assumed that the requirement is [Old_Address] = [New_Address])
INSERT INTO OPT_INT.mgnNACHG_Results (BRANCH_CD,ACCOUNT_CD,RR_CD,ALT_BRANCH_CD,[OldAddress],[NewAddress],[AddressUpdate],[OldName],[NewName],[NameUpdate])
Select BRANCH_CD, ACCOUNT_CD, ACCOUNT_CD, ALT_BRANCH_CD,
Case When [Old_Address] = [New_Address] then ' blank or 1234 Address as you need ' Else [Old_Address] End as [Old_Address],
Case When [Old_Address] = [New_Address] then ' blank or 1234 Address as you need ' Else [New_Address] End as [New_Address],
[Address_Update], [Client_Old_Name], [Client_New_Name], [Client_Name_Update]
From
(
SELECT A.BRANCH_CD,A.ACCOUNT_CD,A.RR_CD,A.ALT_BRANCH_CD
,LTRIM(RTRIM(ISNULL(B.LINE4,'')))+LTRIM(RTRIM(ISNULL(B.LINE5,'')))+LTRIM(RTRIM(ISNULL(B.LINE6,''))) AS [Old_Address]
,LTRIM(RTRIM(ISNULL(A.LINE4,'')))+LTRIM(RTRIM(ISNULL(A.LINE5,'')))+LTRIM(RTRIM(ISNULL(A.LINE6,''))) AS [New_Address]
,CASE WHEN (LTRIM(RTRIM(ISNULL(B.LINE4,'')))<>LTRIM(RTRIM(ISNULL(A.LINE4,'')))) OR (LTRIM(RTRIM(ISNULL(B.LINE5,'')))<>LTRIM(RTRIM(ISNULL(A.LINE5,'')))) OR (LTRIM(RTRIM(ISNULL(B.LINE6,'')))<>LTRIM(RTRIM(ISNULL(A.LINE6,'')))) THEN 'Address Change' ELSE '' END as [Address_Update]
,LTRIM(RTRIM(ISNULL(B.LINE1,'')))+LTRIM(RTRIM(ISNULL(B.LINE2,'')))+LTRIM(RTRIM(ISNULL(B.LINE3,''))) AS [Client_Old_Name]
,LTRIM(RTRIM(ISNULL(A.LINE1,'')))+LTRIM(RTRIM(ISNULL(A.LINE2,'')))+LTRIM(RTRIM(ISNULL(A.LINE3,''))) AS [Client_New_Name]
,CASE WHEN (LTRIM(RTRIM(ISNULL(B.LINE1,'')))<>LTRIM(RTRIM(ISNULL(A.LINE1,'')))) OR (LTRIM(RTRIM(ISNULL(B.LINE2,'')))<>LTRIM(RTRIM(ISNULL(A.LINE2,'')))) OR (LTRIM(RTRIM(ISNULL(B.LINE3,'')))<>LTRIM(RTRIM(ISNULL(A.LINE3,'')))) THEN 'Title Change' ELSE '' END AS [Client_Name_Update]
FROM #NEW A
INNER JOIN #OLD B ON A.BRANCH_CD=B.BRANCH_CD AND A.ACCOUNT_CD=B.ACCOUNT_CD
WHERE (A.[LINE1]<>B.[LINE1]) OR (A.[LINE2]<>B.[LINE2]) OR (A.[LINE3]<>B.[LINE3]) OR (A.[LINE4]<>B.[LINE4]) OR (A.[LINE5]<>B.[LINE5]) OR (A.[LINE6]<>B.[LINE6])
) AS Q
If in case you need to check whether the 2 data fields containing a particular text, you may use
Case When [Old_Address] = [New_Address] and [Old_Address] = '1234 Address' then ' blank or 1234 Address as you need ' Else [Old_Address] End as [Old_Address],

How to concatenate cols under special rules?

I want to select concatenated string, max 255 char. Original query, without shortenings, was:
SELECT
b.title
||
CASE
WHEN b.subtitle != '' THEN '. ' || b.subtitle
ELSE ''
END
||
CASE
WHEN b.cover = 'paperback' THEN ' P'
WHEN b.cover = 'hardcover' THEN ' K'
WHEN b.cover = 'spiral' THEN ' S'
ELSE ''
END
||
CASE
WHEN b.year > 0 THEN ' ' || b.year
ELSE ''
END
||
CASE
WHEN b.volume > 0 THEN ' ' || b.volume || '. osa'
ELSE ''
END
||
CASE
WHEN p.name != '' THEN ' ' || p.name
ELSE ''
END
AS name
FROM table b
JOIN table_p p
ON b.id = p.foreign_id;
Rule: concatenated string may be up to 255 chars
Prioritized fields:
b.title (text, up to 250 chars),
b.cover (2 chars),
b.year (4 char + space = 5),
b.volume (int<100 + additional string '. osa' = 7-8 chars).
Less important:
b.subtitle (text, up to 250 chars),
p.name (text, up to 150 chars )
All fields besides b.title may absent/be empty.
One approach I can think of: because three short mandatory fields (cover, year, volume) may be max 15 chars, everything other could be max 240 chars. Because p.name is not so important, I could concatenate and cut afterwards, if needed. So main objective with this approach: b.title + b.subtitle must fit into 240 chars. Looks like that:
SELECT
SUBSTR(
SUBSTR( b.title
||
CASE
WHEN b.subtitle != '' THEN '. ' || b.subtitle
ELSE ''
END,
0, 240 )
||
CASE
WHEN b.cover = 'paperback' THEN ' P'
WHEN b.cover = 'hardcover' THEN ' K'
WHEN b.cover = 'spiral' THEN ' S'
ELSE ''
END
||
CASE
WHEN b.year > 0 THEN ' ' || b.year
ELSE ''
END
||
CASE
WHEN b.volume > 0 THEN ' ' || b.volume || '. osa'
ELSE ''
END
||
CASE
WHEN p.name != '' THEN ' ' || p.name
ELSE ''
END,
0, 255 ) AS name
FROM table b
JOIN table_p p
ON b.id = p.foreign_id;
This approach is Ok-ish.
My question: is there better way to control every aspect of such concatenation?
I am using Postgresql 9.6

using xmlagg to find count to concatenate

SELECT DISTINCT
ent.entity_key_id AS query1kid,
CAST(substr(rtrim(XMLAGG(xmlelement(e,alstd.relationship_desc
||
CASE
WHEN(alstd.joint_flag = 'No' AND ent.anonymous_flag = 'No') THEN ''
ELSE('('
||
CASE
WHEN alstd.joint_flag = 'Yes' THEN 'Joint'
ELSE ''
END
||
CASE
WHEN ent.anonymous_flag = 'Yes' THEN ',Anon'
ELSE ''
END
|| ')')
END
|| ': '
|| allocthm.allocation_description
|| '('
|| substr(allocthm.allocation_code,5,6) || ***count(ben.entity_key_ID)***
|| ')',',').extract('//text()')
ORDER BY
ent.entity_key_id
).getclobval(),','),1,4000) AS VARCHAR(4000) ) AS displayfiled1
FROM
er_datamart.allocation_theme allocthm
left JOIN er_datamart.allocation_stewardee alstd ON (allocthm.allocation_code = alstd.allocation_code and alstd.status_code <> 'F')
INNER JOIN er_datamart.entity_d ent ON alstd.entity_key_id = ent.entity_key_id
LEFT OUTER JOIN ER_DATAMART.ALLOCATION_BENEFICIARY ben ON ben.allocation_code = allocthm.allocation_code
GROUP BY
ent.entity_key_id
This gives me an error:
ORA-00937: not a single-group group function
I'm trying to find the count(ben.entity_key_ID) so that I can have it appended to my already functional query. Any help would be appreciated.
The problem seems to be the count() inside the xmlagg() - you have nested group functions that the group-by clause can't handle.
You can move the string concatenation into an inline view with its own group-by to get that count, and then perform the XML aggregation from that; something like:
SELECT
entity_key_id AS query1kid,
CAST(substr(rtrim(
XMLAGG(xmlelement(e, constructed_string, ',').extract('//text()')
ORDER BY entity_key_id
).getclobval(),','),1,4000) AS VARCHAR(4000) ) AS displayfiled1
FROM (
SELECT ent.entity_key_id,
alstd.relationship_desc
||
CASE
WHEN(alstd.joint_flag = 'No' AND ent.anonymous_flag = 'No') THEN ''
ELSE('('
||
CASE
WHEN alstd.joint_flag = 'Yes' THEN 'Joint'
ELSE ''
END
||
CASE
WHEN ent.anonymous_flag = 'Yes' THEN ',Anon'
ELSE ''
END
|| ')')
END
|| ': '
|| allocthm.allocation_description
|| '('
|| substr(allocthm.allocation_code,5,6) || count(ben.entity_key_ID)
|| ')' as constructed_string
FROM
allocation_theme allocthm
LEFT JOIN allocation_stewardee alstd
ON allocthm.allocation_code = alstd.allocation_code and alstd.status_code <> 'F'
INNER JOIN entity_d ent
ON alstd.entity_key_id = ent.entity_key_id
LEFT OUTER JOIN ALLOCATION_BENEFICIARY ben
ON ben.allocation_code = allocthm.allocation_code
GROUP BY
ent.entity_key_id,
alstd.relationship_desc,
alstd.joint_flag,
ent.anonymous_flag,
allocthm.allocation_description,
allocthm.allocation_code
)
GROUP BY
entity_key_id
The ELSE '' parts are redundant as that is the default behaviour but you might prefer to keep them for clarity/explicitness. Your joint/anon part might need a bit more work - if joint is No and anon is Yes you get (,Anon) - I think - which looks a bit odd, but might be what you need.

conditional clause in Claudera

I am trying to get an if, then clause working in cloudera - along the lines:
if (appointment_purpose = '') and
appointment_purpose2 = '') and
appointment_type2 = '') and
appointment_type3 = '') and
appointment_type4 = '') and
appointment_type5 = '') and
discuss_other ' ') then
discuss_other = 0
else discuss_other = 1,
How do I get this to work?
If all appointment types and appointment purposes were empty, then discuss_other should also be empty, that is 0 - otherwise discuss_other should be 1
Try this:
SELECT (CASE WHEN appointment_purpose = ''
AND appointment_purpose2 = ''
AND appointment_type2 = ''
AND appointment_type3 = ''
AND appointment_type4 = ''
AND appointment_type5 = ''
AND discuss_other ' '
THEN 0
ELSE 1
) as discuss_other
FROM tableName
In SQL, "empty" typically means NULL. So, I wonder if you really intend one of these constructs:
select (case when appointment_purpose is null and
appointment_purpose2 is null and
appointment_type2 is null and
appointment_type3 is null and
appointment_type4 is null and
appointment_type5 is null and
discuss_other is null
then 0 else 1
end) as discuss_other
Because one of the values has a space, I wonder if you want to handle both spaces and null values:
select (case when (appointment_purpose is null or replace(appointment_purpose, ' ', '') = '') and
(appointment_purpose2 is null or replace(appointment_purpose2, ' ', '') = '') and
(appointment_purpose3 is null or replace(appointment_purpose3, ' ', '') = '') and
(appointment_purpose4 is null or replace(appointment_purpose4, ' ', '') = '') and
(appointment_purpose5 is null or replace(appointment_purpose5, ' ', '') = '') and
(discuss_other is null or replace(discuss_other, ' ', '') = '')
then 0 else 1
end) as discuss_other
Finally, multiple columns in the same table that are acting like an array is usually a bad sign. Normally, this suggests that you want a junction table.

SQL Server stored proc substitute an empty string if column is empty or null

I need to check to see if a certain coloumn in my stored proc is either empty or null.
This is a snippet of what I have right now:
SELECT * ,
CASE WHEN a.USER IS NULL
THEN b.ROLE
ELSE ISNULL(a.FirstName,'') + ' ' + (ISNULL(a.MiddleName+' ','') + ISNULL(a.LastName,'')
END AS 'CustomerName'
I am checking to see if a.MiddleName is NULL but how do I also check to see if its empty and if its empty to just insert a empty string (no space)?
Thanks
Change to
SELECT
* ,
CASE
WHEN a.USER IS NULL
THEN b.ROLE
ELSE CASE
WHEN ISNULL(a.MiddleName, '') = ''
THEN ISNULL(a.FirstName,'') + ' ' + ISNULL(a.LastName,'')
ELSE ISNULL(a.FirstName,'') + ' ' + a.MiddleName + ' ' + ISNULL(a.LastName,'')
END
END AS 'CustomerName'
Another sollution is:
SELECT * ,
CASE WHEN a.USER IS NULL
THEN b.ROLE
ELSE ISNULL(a.FirstName,'') + replace( ( ' ' + ISNULL(a.MiddleName+' ',' '),' ',' ') + ISNULL(a.LastName,'')
END AS 'CustomerName'