SQL using between syntax to validate alpha numeric values - sql

I have 2 tables, Table1 has address ranges, Table2 has exact addresses. All fields in both tables are char. I need to identify the addresses in Table2 that fall between the house number ranges in Table1. Please see below for example. I have a join on Street, City, State and HSE# between LOW and HIGH. The results bring back both the 1201 - 1214 and 101 - 126 TABLE1 records. Casting the values as integers doesn't work because some addresses contain alpha characters....101B as the house number for example. Can you help determine the best, most accurate way to accomplish this?
Table1
LOW HIGH STREET CITY STATE
101 126 A ST MYCITY MYSTATE
1201 1214 A ST MYCITY MYSTATE
TABLE2
HSE# STREET CITY STATE
1203 A ST MYCITY MYSTATE
SELECT *
FROM TABLE1 A,
TABLE2 B
WHERE B.STATE = A.STATE
AND B.CITY = A.CITY
AND B.STREET = A.STREET
AND B.HSE# BETWEEN A.LOW AND A.HIGH;
This brings back both the TABLE1 records in the example below. The expect result is that I only get the TABLE1 value with a LOW/HIGH range of 1201/1214 as this is truly the range the house number falls in.

Try this as is:
SELECT A.LOW, A.HIGH, B.HSE#
FROM
(
VALUES
('101B', '126')
, ('1201', '1214')
) A (LOW, HIGH)
JOIN (VALUES '1203') B(HSE#)
ON
INT(NULLIF(REGEXP_REPLACE(B.HSE#, '[^0-9]', ''), ''))
BETWEEN
INT(NULLIF(REGEXP_REPLACE(A.LOW, '[^0-9]', ''), ''))
AND
INT(NULLIF(REGEXP_REPLACE(A.HIGH, '[^0-9]', ''), ''));
The result is:
|LOW |HIGH|HSE#|
|----|----|----|
|1201|1214|1203|

Related

Does a Self Join or EXISTS Help here?

I'm Using Advantage Server 12.
I need a result set that finds all the records when the supplied parameter occurs in either of a master or a detail table when the supplied value is not the linking field.
Customer Table
|AccountCode |Tel1|Tel2|Tel3|Accout Name/Address etc....
| ACODE | | | |
N < 2
Contact Table
|AccountCode |cTel1|cTel2|cTel3|ContactName/Address etc....
| ACODE | | | |
N >=0
Require the Row from Customer and All rows with matching ACODE from Contacts.
When Tel1 or Tel2 or Tel3 or cTel1 or cTel2 or cTel3 is supplied
This SQL gives the required result when the supplied value ('thenumber') is found in any of the Customer fields
When the supplied number is found in the contact table it returns the Customer fields and only data from the matching row in Contacts
declare #Tel string;
set #Tel = 'thenumber';
select
#TEL as calling,c.Tel1,c.Tel2,c.contact as "Primary",
c.acnt_nmbr,c.Acnt_name, a.contact,a.cTel1 as telephone1,a.cTel2 as telephone2
from
customer c
full outer join contact a on (c.acnt_nmbr=a.acnt_nmbr)
where
replace(c.Tel1,' ','') = #Tel
or
replace(c.Tel2,' ','') = #Tel
or
replace(c.Tel3,' ','') = #Tel
or
replace(a.cTel1,' ','') = #Tel
or
replace(a.cTel2,' ','') = #Tel
or
replace(a.cTel3,' ','') = #Tel
Result when 'thenumber' is found in Customer - This is what we want
calling Tel1 Tel2 Primary acnt_nmbr Acnt_name contact telephone1 telephone2
thenumber 11111111 thenumber KIERAN 687 theCo Pat 12234560 333444555
thenumber 11111111 thenumber KIERAN 687 theco Mary 45678900 444555666
thenumber 11111111 thenumber KIERAN 687 theco Jon 22233344
thenumber 11111111 thenumber KIERAN 687 theco Paul 22244455 124578111
thenumber 11111111 thenumber KIERAN 687 theco Jane 33225544
Result when 'thenumber' is found in Contacts - We want the same result set as above
calling Tel1 Tel2 Primary acnt_nmbr Acnt_name contact telephone1 telephone2
thenumber 11111111 2222222 KIERAN 687 theco Jane thenumber
I'm thinking either some self join or EXISTS statement is the answer but not sure how to proceed.
It's a bit more tricky than you seem to expect and as far as I just see your latest comment, I hope the solution I will propose you will work (I do not know SAP technical environment at all).
On a SQL point of you, my approach is the following.
For both customer and contact, find matching values. It's quite easy for customer, but for contacts you need to identify single matching values, then keep contacts where all values are matching
Then, build the assembly of customer and contacts.
Finally, union results from both first filters, joined with the basis data.
Here is the final request I propose:
with cust_check as (
SELECT account_code, tel1, tel2, tel3, address,
CASE WHEN tel1 = #Tel THEN 1
ELSE CASE WHEN tel2 = #Tel THEN 1
ELSE CASE WHEN tel3 = #Tel THEN 1
ELSE 0
END
END
END as cust_match
from customer
),
cust_filter as (
SELECT account_code, tel1, tel2, tel3, address AS detail
FROM cust_check
WHERE cust_match = 1
),
contact_check as (
SELECT account_code, ctel1, ctel2, ctel3, cname,
CASE WHEN ctel1 = #Tel THEN 1
ELSE CASE WHEN ctel2 = #Tel THEN 1
ELSE CASE WHEN ctel3 = #Tel THEN 1
ELSE 0
END
END
END as contact_match
FROM contact
),
contact_filter as (
SELECT account_code, count(*) as nb_rows, sum(contact_match) as nb_matched
FROM contact_check
GROUP BY account_code
HAVING count(*) = sum(contact_match)
),
all_contacts as (
SELECT t.account_code, ctel1, ctel2, ctel3, cname, address
FROM contact as t
JOIN customer as c ON c.account_code = t.account_code
),
union_match as
(
SELECT c.account_code, c.ctel1, c.ctel2, c.ctel3, c.cname, c.address
FROM all_contacts c
JOIN cust_filter f ON f.account_code = c.account_code
UNION
SELECT c.account_code, c.ctel1, c.ctel2, c.ctel3, c.cname, c.address
FROM all_contacts c
JOIN contact_filter f ON f.account_code = c.account_code
)
SELECT account_code, ctel1, ctel2, ctel3, cname, address
FROM union_match
This request is based on MS SQL Server and you can easily 'play' with it thanks to DB-Fiddle - I just hope it will be compliant with your constraints!
This should achieve what you want:
select
#TEL as calling,c.Tel1,c.Tel2,c.contact as "Primary",
c.acnt_nmbr,c.Acnt_name, a.contact,a.cTel1 as telephone1,a.cTel2 as telephone2
from
customer c
left join contact a on (c.acnt_nmbr=a.acnt_nmbr)
WHERE c.acnt_nmbr IN
(
SELECT DISTINCT acnt_nmbr FROM
(SELECT acnt_nmbr, Tel1, Tel2
FROM Customer
UNION
SELECT acnt_nmbr, cTel1, cTel2
FROM Contact) x
WHERE
replace(x.Tel1,' ','') = #Tel
or
replace(x.Tel2,' ','') = #Tel
)
(I'll leave you to work out how to add the Tel3 :)
Note that I have changed your full outer join to a simple left join because the code as presented will not work with customerless contacts.
If you really need a full outer join, the code becomes a bit more convoluted:
SELECT z.* FROM
( select #TEL as calling,c.Tel1,c.Tel2,c.contact as "Primary",
COALESCE(c.acnt_nmbr,a.acnt_nmbr) AS AccountNo,
c.Acnt_name, a.contact,a.cTel1 as telephone1,a.cTel2 as telephone2
from
Customer c
full outer join Contact a on (c.acnt_nmbr=a.acnt_nmbr)
) z
WHERE COALESCE(z.AccountNo,0) IN
(...)
I have modified Christophe's DB-fiddle to show this working.
Note that the solution for the full outer join is slightly different in the fiddle, to cope with the different behaviour of Microsoft SQL server compared to the Advantage database server. (With ADS DBF tables, NULL values in an integer field become zeros in the UNION for example).

SQL group by similar address but different longitude and latitude

I Have a large Postgres dataset table,
the table ('tbl') has 4 columns,
and a data similar to this:
ID
address
x,y
1
22 E 4th Ave, Cordele, GA, 11015
x1,y1
2
22 E 4th Ave, Cordele, GA 11015
x2,y2
3
408 E 5th Ave, Cordele, CA 11215
x2,y2
4
408 E 5th Ave, Cordele, CA, 11215
x2,y2
5
408 E 5th Ave, vic, VA, 11215
x2,y2
6
408 E 5th Ave, vic, VA, 11215
x3,y3
My question is , how to find all the addresses that have similar address (similar address means ignoring the comma between the state and zip, that's the only part that should be ignored), But having different 'x,y' value
In the above example , id 1 and 2 should be returned because they have the same address ( with a diff in the comma) But different 'x,y' values.
Id 3 and 4 should not be returned because their 'x,y' values are identical.
Id 5 and 6 should not be returned because their address values are identical.
*I can count on the address format to always have a state and a zip
It might be overkill, but can you just remove all commas and compare?
select array_agg(distinct address)
from t
group by replace(address, ',', '')
having min(x_y) <> max(x_y);
To specifically remove that comma, you could instead use:
select array_agg(distinct address)
from t
group by (case when address like '%, _____'
then left(address, -7) || right(address, 6)
else address
end)
having min(x_y) <> max(x_y);
I am not sure how many variations you have in your data, but I was able to get what you want on the sample data provided. I inserted the data in a table named locdat, you could change columns and table as per your need.
SELECT
id, address, xy
FROM
(
SELECT
l.*,
COUNT(l.address)
OVER(PARTITION BY replace(l.address, ',', '')) AS addr_count,
COUNT(l.xy)
OVER(PARTITION BY replace(l.address, ',', ''), l.xy) AS xy_count
FROM
locdat l
)
WHERE
( addr_count >= 1
AND xy_count < 2 );

Case when based on first few characters

I have Table A that looks like this:
Name Phone
John 1111231234
Joe 1111231235
Jack 2221231234
Jenny 2224321234
Jody 3323214211
and Table B that looks like this:
AreaCode
111
111
222
222
How do I return a result that looks like this? I essentially want to return AreaCode if the first 3 numbers/characters from the column 'Phone' exist in the column 'AreaCode' in table B...
Name Phone AreaCode
John 1111231234 111
Joe 1111231235 111
Jack 2221231234 222
Jenny 2224321234 222
Jody 3323214211 null
Use a left join to table b, joining where the phone starts with the areacode:
select
name,
phone,
areacode
from tableA
left join tableB on phone like concat(areacode, '%')
I used distinct to avoid repeated areacodes that will bring you duplicated results.
If you store areacode an phone as numeric you can omit the cast
select a.*, b.AreaCode
from TableA a left join (select distinct areacode from tableb) b
on left(cast(a.Phone as varchar(20)),3)=cast(b.AreaCode as varchar(20))
In teradata, you may need something like this
select
name,
phone,
areacode
from tableA
left join tableB on SUBSTRING(phone FROM 1 FOR 3) = areacode
As some values in column phone exceeds the max allowed limit for INTEGER i.e. 2147483647, so i assume that BIGINT is the datatype. In this case below is the query that will return your desired result.
SELECT DISTINCT t1.Name,
t1.Phone,
t2.areacode
FROM t1
LEFT JOIN t2 ON substring(t1.Phone
FROM 11
FOR 3) = t2.areacode
ORDER BY 3 DESC;
substring impliticitly cast bigint to 20 space variable character left justified as bigint requires 20 character, so the starting point for substring is 11. Also teradata implicitly compare character with INT, so no need to cast areacode
Other option is to use trim before substring to remove leading spaces as below.
SELECT DISTINCT t1.Name,
t1.Phone,
t2.areacode
FROM t1
LEFT JOIN t2 ON substring(trim(t1.Phone)
FROM 1
FOR 3) = t2.areacode
ORDER BY 3 DESC;
DISTINCT is used to avoid duplicates, as area code has duplicate values, so joining it to the phone record table will create duplicate rows.
Result:
Name Phone areacode
----------------------------------
Jack 2,221,231,234 222
Jenny 2,224,321,234 222
Joe 1,111,231,235 111
John 1,111,231,234 111
Jody 3,323,214,211 ?
P.S. As substring and trim functions are same in Teradata and MySQL, you can check the demo here
Hope this will help :-)

Return Distinct Values Where One Column Is The Same But One Column Different

I am trying to return results in TSQL where it only displays addresses where there are multiple names. The tricky part has been there are multiple duplicates already in this table... so the Having Count variations that I've tried do not work because they all have a count greater than one. So I have not been able to easily distinguish unique names that have the same address. The solution illustrated below is what I would like to produce... and I have but my solution is a sad last ditched effort within Access where I ended up using a query with three sub queries to get the results:
Address Name
101 1st Ave Brian Wood
101 1st Ave Amy Wood
101 1st Ave Adam Wood
555 5th St Sarah Parker
555 5th St Parker Corp.
Sample Data Looks Like this:
Address Name
101 1st Ave Brian Wood
101 1st Ave Brian Wood
101 1st Ave Brian Wood
101 1st Ave Amy Wood
101 1st Ave Adam Wood
555 5th St Sarah Parker
555 5th St Sarah Parker
555 5th St Sarah Parker
555 5th St Parker Corp.
I've been trying to get this for hours... I know their is a much simpler way to do this but as it's been a 16 hour day and it's 2:00 am I just can't get my head around it.
Here is an example of my best TSQL results... it does the trick but it bumps it into two different columns:
SELECT DISTINCT t1.Name, t2.Name, t1.Address
FROM tblLeads t1
JOIN tblLeads t2 ON t1.Address = t2.Address
WHERE t1.Name <> t2.Name
ORDER BY t1.Address
You can do a GROUP with COUNT(Distinct Name) > 1 to get Address with more than 1 unique name, and then do a select distinct with a filter on the above grouped Addresses like this.
SELECT DISTINCT Address,Name
From Table1
WHERE Address IN (
SELECT Address
FROM Table1
GROUP BY Address
HAVING COUNT(distinct Name) > 1
)
You could use multiple CTE's to simplify this task. You first want to clean up your data, so remove all those duplicates, therefore you can use DISTINCT. Then use Count(*)OVER(Partition By Address) to get the count of rows per Address:
WITH CleanedData AS
(
SELECT DISTINCT Address, Name
FROM dbo.tblLeads
),
CTE AS
(
SELECT Address, Name,
cnt = Count(*) OVER (Partition By Address)
FROM CleanedData
)
SELECT Address, Name
FROM CTE
WHERE cnt > 1
Demo
By the way, this works also if Address has null values: Demo (as opposed to this).
Use EXISTS to verify same addresses but other name:
SELECT DISTINCT t1.LastName, t1.Street
FROM tblLeads t1
WHERE EXISTS (select 1 from tblLeads t2
where t1.Street = t2.Street
and t1.LastName <> t2.LastName)
ORDER BY t1.Street
alternative solution to Tim's one without CTE:
select address, name
from (select t.*, count(*) over(partition by address) as cnt
from (select distinct address, name from tblLeads) t
) where cnt > 1

Collapse Multiple Records Into a Single Record With Multiple Columns

In a program I'm maintaining we were given a massive (~500 lines) SQL statement by the customer. It is used for generating flat files with fixed length records for transmitting data to another big business. Since its a massive flat file its not relational and the standard normal forms of data are collapsed. So, if you have a record that can have multiple codes associated, in this case upto 19, they all have be written into single line, but seperate fields, in the flat file.
Note: this example is simplified.
The data might look like this, with three tables:
RECORDS
record_id firstname lastname
--------------------------------
123 Bob Schmidt
324 George Washington
325 Ronald Reagan
290 George Clooney
CODE_TABLE
code_id code_cd code_txt
--------------------------------
5 3 President
2 4 Actor
3 7 Plumber
CODES_FOR_RECORDS
record_id code_cd
-------------------
123 7
325 3
290 4
324 3
325 4
123 4
This needs to produce records like:
firstname lastname code1 code2 code3
Bob Schmidt Actor Plumber NULL
George Washington President NULL NULL
Ronald Reagon Actor President NULL
George Clooney Actor NULL NULL
The portion of the current query we were given looks like this, but with 19 code columns instead of the 5:
select
x.record_id,
max(case when x.rankk = 1 then code_txt end) as CodeColumn1,
max(case when x.rankk = 2 then code_txt end) as CodeColumn2,
max(case when x.rankk = 3 then code_txt end) as CodeColumn3,
max(case when x.rankk = 4 then code_txt end) as CodeColumn4,
max(case when x.rankk = 5 then code_txt end) as CodeColumn5,
from
(
select
r.record_id,
ct.code_txt as ctag ,
dense_rank() over (partition by r.record_id order by cfr.code_id) as rankk
from
records as r
codes_for_records as cfr,
code_table as ct
where
r.record_id = cfr.record_id
and ct.code_cd = cfr.code_cd
and cfr.code_cd is not null
and ct.code_txt not like '%V%'
) as x
where
x.record_id is not null
group by
x.record_id
I trimmed down things for simplicties sake, but the actual statment includes an inner query and a join and more where conditions, but that should get the idea across. My brain is telling me there has to be a better way, but I'm not an SQL expert. We are using DB2 v8 if that helps. And the codes have to be in seperate columns, so no coalescing things into a single string. Is there a cleaner solution than this?
Update:
I ended up just refacorting the original query, it sill uses the ugly MAX() business, but overall the query is much more readable due to reworking other parts.
It sounds like what you are looking for is pivoting.
WITH joined_table(firstname, lastname, code_txt, rankk) AS
(
SELECT
r.firstname,
r.lastname,
ct.code_txt,
dense_rank() over (partition by r.record_id order by cfr.code_id) as rankk
FROM
records r
INNER JOIN
codes_for_records cfr
ON r.record_id = cfr.record_id
INNER JOIN
codes_table ct
ON ct.code_cd = cfr.code_cd
),
decoded_table(firstname, lastname,
CodeColumn1, CodeColumn2, CodeColumn3, CodeColumn4, CodeColumn5) AS
(
SELECT
firstname,
lastname,
DECODE(rankk, 1, code_txt),
DECODE(rankk, 2, code_txt),
DECODE(rankk, 3, code_txt),
DECODE(rankk, 4, code_txt),
DECODE(rankk, 5, code_txt)
FROM
joined_table jt
)
SELECT
firstname,
lastname,
MAX(CodeColumn1),
MAX(CodeColumn2),
MAX(CodeColumn3),
MAX(CodeColumn4),
MAX(CodeColumn5)
FROM
decoded_table dt
GROUP BY
firstname,
lastname;
Note that I've never actually done this myself before. I'm relying on the linked document as a reference.
You might need to include the record_id to account for duplicate names.
Edit: Added the GROUP BY.
One of the possible solutions is using of recursive query:
with recursive_view (record_id, rankk, final) as
(
select
record_id,
rankk,
cast (ctag as varchar (100))
from inner_query t1
union all
select
t1.record_id,
t1.rankk,
/* all formatting here */
cast (t2.final || ',' || t1.ctag as varchar (100))
from
inner_query t1,
recursive_view t2
where
t2.rankk < t1.rankk
and t1.record_id = t2.record_id
and locate(t1.ctag, t2.final) = 0
)
select record_id, final from recursive_view;
Can't guarantee that it works, but hope it will be helpful. Another way is using of custom aggregate function.