Possible to Search Partial Matched Strings from same table? - sql

I have a table and lets say the table has items with the item numbers:
12345
12345_DDM
345653
2345664
45567
45567_DDM
I am having trouble creating a query that will get all of the _DDM and the corresponding item that has the same prefix digits.
So in this case I'd want both 12345 and 12345_DDM etc to be returned

Use like to find rows with _DDM.
Use EXISTS to find rows with numbers also having a _DDM row.
working demo
select *
from tablename t1
where columnname LIKE '%_DDM'
or exists (select 1 from tablename t2
where t1.columnname + '_DDM' = t2.columnname)

Try this query:
--sample data
;with tbl as (
select col from (values ('12345'),('12345_DDM'),('345653'),('2345664'), ('45567'),('45567_DDM')) A(col)
)
--select query
select col from (
select col,
prefix,
max(case when charindex('_DDM', col) > 0 then 1 else 0 end) over (partition by prefix) [prefixGroupWith_DDM]
from (
select col,
case when charindex('_DDM', col) - 1 > 0 then substring(col, 1, charindex('_DDM', col) - 1) else col end [prefix]
from tbl
) a
) a where [prefixGroupWith_DDM] = 1

Related

SQL determine if more than one column on a given row has the same value

I have a SQL Server table that includes 9 columns used to indicated what things to include or exclude in part of the UI. Each column can have the value 'A', 'X', or blank. Each row should have at most 1 'A' in any of the columns.
Due to an error many columns have multiple 'A' values. How can I write a query that returns every row that breaks this constraint?
All I have is something like:
SELECT PrimaryKey
FROM Criteria C
WHERE (C.First = 'A' AND C.Second = 'A')
OR (C.First = 'A' AND C.Third = 'A')
OR (C.First = 'A' AND C.Fourth = 'A')
...
OR (C.Eighth = 'A' AND C.Ninth = 'A')
Is there any cleaner or more elegant way to write this code?
You can use APPLY:
SELECT C.*
FROM Criteria C CROSS APPLY
(SELECT COUNT(*) as num_a_s
FROM (VALUES (First), (Second), . . . -- list all the columns here
) V(x)
WHERE v.x = 'A'
) v
WHERE v.num_a_s >= 2;
Note: Something is probably wrong with your data model if you are storing these values in columns rather than in separate rows.
Here is one way to do this
create table dbo.t1(x int, col1 varchar(10), col2 varchar(10))
insert into dbo.t1 values(1,'A','A')
insert into dbo.t1 values(2,'A','')
insert into dbo.t1 values(3,'','')
insert into dbo.t1 values(4,'','X')
select x
,count(case when n='A' then 1 end) as cnt
from dbo.t1
cross apply(values ('col1',col1)
,('col2',col2)
---repeat this for columns up to col9...
)x(m,n)
group by x
having count(case when n='A' then 1 end)>1
try the following:
select
* from YourTable
where REPLACE(TRIM('X' FROM col1),'A',1)
+ REPLACE(TRIM('X' FROM col2),'A',1)
+ REPLACE(TRIM('X' FROM col3),'A',1)
+ REPLACE(TRIM('X' FROM col4),'A',1)
+ REPLACE(TRIM('X' FROM col5),'A',1)
+ REPLACE(TRIM('X' FROM col6),'A',1)
+ REPLACE(TRIM('X' FROM col7),'A',1)
+ REPLACE(TRIM('X' FROM col8),'A',1)
+ REPLACE(TRIM('X' FROM col9),'A',1)> 1
I have used TRIM also which requires SQL Server 2017 or above.
or the following:
select *
from YourTable
where replace(replace(concat(col1,col2,col3,col4,col5,col6,col7,col8,col9), 'X', ''), 'A', 1) > 1
Please see the demo here.
SELECT *
FROM MyTable
WHERE LEN(CONCATENATE(C.First, C.... , C.Eighth)) - LEN(REPLACE(CONCATENATE(C.First, C.... , C.Eighth), 'A', '')) = 1
As an example

Find way for gathering data and replace with values from another table

I am looking for an Oracle SQL query to find a specific pattern and replace them with values from another table.
Scenario:
Table 1:
No column1
-----------------------------------------
12345 user:12345;group:56789;group:6785;...
Note: field 1 may be has one or more pattern
Table2 :
Id name type
----------------------
12345 admin user
56789 testgroup group
Result must be the same
No column1
-----------------------------------
12345 user: admin;group:testgroup
Logic:
First split the concatenated string to individual rows using connect
by clause and regex.
Join the newly created table(split_tab) with Table2(tab2).
Use listagg function to concatenate data in the columns.
Query:
WITH tab1 AS
( SELECT '12345' NO
,'user:12345;group:56789;group:6785;' column1
FROM DUAL )
,tab2 AS
( SELECT 12345 id
,'admin' name
,'user' TYPE
FROM DUAL
UNION
SELECT 56789 id
,'testgroup' name
,'group' TYPE
FROM DUAL )
SELECT no
,listagg(category||':'||name,';') WITHIN GROUP (ORDER BY tab2.id) column1
FROM ( SELECT NO
,REGEXP_SUBSTR( column1, '(\d+)', 1, LEVEL ) id
,REGEXP_SUBSTR( column1, '([a-z]+)', 1, LEVEL ) CATEGORY
FROM tab1
CONNECT BY LEVEL <= regexp_count( column1, '\d+' ) ) split_tab
,tab2
WHERE split_tab.id = tab2.id
GROUP BY no
Output:
No Column1
12345 user:admin;group:testgroup
with t1 (no, col) as
(
-- start of test data
select 1, 'user:12345;group:56789;group:6785;' from dual union all
select 2, 'user:12345;group:56789;group:6785;' from dual
-- end of test data
)
-- the lookup table which has the substitute strings
-- nid : concatenation of name and id as in table t1 which requires the lookup
-- tname : required substitute for each nid
, t2 (id, name, type, nid, tname) as
(
select t.*, type || ':' || id, type || ':' || name from
(
select 12345 id, 'admin' name, 'user' type from dual union all
select 56789, 'testgroup', 'group' from dual
) t
)
--select * from t2;
-- cte table calculates the indexes for the substrings (eg, user:12345)
-- no : sequence no in t1
-- col : the input string in t1
-- si : starting index of each substring in the 'col' input string that needs attention later
-- ei : ending index of each substring in the 'col' input string
-- idx : the order of substring to put them together later
,cte (no, col, si, ei, idx) as
(
select no, col, 1, case when instr(col,';') = 0 then length(col)+1 else instr(col,';') end, 1 from t1 union all
select no, col, ei+1, case when instr(col,';', ei+1) = 0 then length(col)+1 else instr(col,';', ei+1) end, idx+1 from cte where ei + 1 <= length(col)
)
,coll(no, col, sstr, idx, newstr) as
(
select
a.no, a.col, a.sstr, a.idx,
-- when a substitute is not found in t2, use the same input substring (eg. group:6785)
case when t2.tname is null then a.sstr else t2.tname end
from
(select cte.*, substr(col, si, ei-si) as sstr from cte) a
-- we don't want to miss if there is no substitute available in t2 for a substring
left outer join
t2
on (a.sstr = t2.nid)
)
select no, col, listagg(newstr, ';') within group (order by no, col, idx) from coll
group by no, col;

SQL Server 2008 select with regular expression

I have an SQL Server 2008 database with a table called "page_data" that contains a NVARCHAR column called "path"
The path column will contain data like the follwoing:
/aaa/bbb
/aaa/bbb/zzz
/aaa/ccc
/aaa/ccc/xxx
/aaa/ddd
/aaa/ddd/yyy
I want to select rows where the path data only contains two slashes. So I should get the following data returned:
/aaa/bbb
/aaa/ccc
/aaa/ddd
I can't think of how to do this. Can anyone help?
Please try:
;with T as (
select 0 as row, CHARINDEX('/', Col) pos, Col from page_data
union all
select row + 1, CHARINDEX('/', Col, pos + 1), Col
from T
where pos > 0
)
select distinct MIN(Col1) Col
from(
select
row,
Col,
(case when row=2 then SUBSTRING(Col, 1, pos-1) else Col end) Col1
from T
where pos > 0 and row<3
)x
group by Col
SQL Fiddle demo

SQL: Modify query result rows that have a unique value

I have a sqlite query that returns something like the following with columns [letter, number]:
("a", 1)
("a", 2)
("b", 3)
("c", 3)
I want to retrieve the number column as 0 if the letter is distinct. How is it done?
Expected output:
("a", 1)
("a", 2)
("b", 0)
("c", 0)
SELECT tba.mychar
-- if tbu.mychar is null, then the letter is not unique
-- when it happens, the letter is not "unique" thus use the number column
-- else use zero for "unique" letters
, CASE WHEN tbu.mychar IS NULL THEN tba.mynum ELSE 0 END AS newnum
FROM mytab tba
LEFT JOIN (
-- this subquery only returns the letters that don't repeat
SELECT mychar
FROM mytab
GROUP BY mychar
HAVING COUNT(*) = 1
) AS tbu ON tba.mychar=tbu.mychar
You can use a sub-query:
select t1.col1,
case when t2.cnt > 1 then t1.col2 else 0 end col2
from table1 t1
left join
(
select count(*) as cnt, col1
from table1
group by col1
) t2
on t1.col1 = t2.col1
See SQL Fiddle with Demo
How about (SQL Fiddle):
SELECT Q.letter,
CASE WHEN (SELECT COUNT(*) FROM (query) QQ WHERE QQ.letter = Q.letter) = 1 THEN 0
ELSE Q.number
END AS number
FROM (query) Q
Note, replace "query" with the query that generates your first result.
It's possible to do this using a UNION ALL of 2 separate statements (one for repeated letters and one for letters that only occur once):
SELECT letter, number
FROM tableName
WHERE letter IN (
SELECT letter
FROM tableName
GROUP BY letter
HAVING COUNT(1) > 1
)
UNION ALL
SELECT letter, 0
FROM tableName
WHERE letter IN (
SELECT letter
FROM tableName
GROUP BY letter
HAVING COUNT(1) = 1
)

SQL query for finding first missing sequence string (prefix+no)

T-SQL query for finding first missing sequence string (prefix+no)
Sequence can have a prefix + a continuing no.
ex sequence will be
ID
-------
AUTO_500
AUTO_501
AUTO_502
AUTO_504
AUTO_505
AUTO_506
AUTO_507
AUTO_508
So above the missing sequence is AUTO_503 or if there is no missing sequence then it must return next sequence.
Also starting no is to specified ex. 500 in this case and prefix can be null i.e. no prefix only numbers as sequence.
You could LEFT JOIN the id numbers on shifted(+1) values to find gaps in sequential order:
SELECT
MIN(a.offsetnum) AS first_missing_num
FROM
(
SELECT 500 AS offsetnum
UNION
SELECT CAST(REPLACE(id, 'AUTO_', '') AS INT) + 1
FROM tbl
) a
LEFT JOIN
(SELECT CAST(REPLACE(id, 'AUTO_', '') AS INT) AS idnum FROM tbl) b ON a.offsetnum = b.idnum
WHERE
a.offsetnum >= 500 AND b.idnum IS NULL
SQLFiddle Demo
Using a recursive CTE to dynamically generate the sequence between the min and max of the ID Numbers maybe over complicated things a bit but it seems to work -
LIVE ON FIDDLE
CREATE TABLE tbl (
id VARCHAR(55)
);
INSERT INTO tbl VALUES
('AUTO_500'),
('AUTO_501'),
('AUTO_502'),
('AUTO_504'),
('AUTO_505'),
('AUTO_506'),
('AUTO_507'),
('AUTO_508'),
('509');
;WITH
data_cte(id)AS
(SELECT [id] = CAST(REPLACE(id, 'AUTO_', '') AS INT) FROM tbl)
,maxmin_cte(minId, maxId)AS
(SELECT [minId] = min(id),[maxId] = max(id) FROM data_cte)
,recursive_cte(n) AS
(
SELECT [minId] n from maxmin_cte
UNION ALL
SELECT (1 + n) n FROM recursive_cte WHERE n < (SELECT [maxId] from maxmin_cte)
)
SELECT x.n
FROM
recursive_cte x
LEFT OUTER JOIN data_cte y ON
x.n = y.id
WHERE y.id IS NULL
Check this solution.Here you just need to add identity column.
CREATE TABLE tbl (
id VARCHAR(55),
idn int identity(0,1)
);
INSERT INTO tbl VALUES
('AUTO_500'),
('AUTO_501'),
('AUTO_502'),
('AUTO_504'),
('AUTO_505'),
('AUTO_506'),
('AUTO_507'),
('AUTO_508'),
('509');
SELECT min(idn+500) FROM tbl where 'AUTO_'+cast((idn+500) as varchar)<>id
try this:
with cte as(
select cast(REPLACE(id,'AUTO_','') as int)-500+1 [diff],ROW_NUMBER()
over(order by cast(REPLACE(id,'AUTO_','') as int)) [rnk] from tbl)
select top 1 'AUTO_'+cast(500+rnk as varchar(50)) [ID] from cte
where [diff]=[rnk]
order by rnk desc
SQL FIddle Demo
Had a similar situation, where we have R_Cds that were like this R01005
;with Active_R_CD (R_CD)
As
(
Select Distinct Cast(Replace(R_CD,'R', ' ') as Int)
from table
where stat = 1)
select Arc.R_CD + 1 as 'Gaps in R Code'
from Active_R_CD as Arc
left outer join Active_R_CD as r on ARC.R_CD + 1 = R.R_CD
where R.R_CD is null
order by 1