String functions in a SQL where clause - sql-server

Lets say I have a field. Lets call it Barcode1. Right now all Barcodes1 are 22 characters with each character is an integer.
Suppose there is a second field Barcode2. Both of these are varchar(22)
My condition in plain english terms is:
Barcode1 is identical to barcode2 except in digits 7,8 where for barcode2, digits 7 and 8 are the same thing in barcode1 plus 20
so
001214**54**54545654521523
549462**74**48634842135782
I also would like the negation of the where clause where rows that do NOT match the condition are returned.
Thank you.

I think this is what you want:
Example Data:
DECLARE #table TABLE ( barcode VARCHAR(22) )
INSERT INTO #table
(
barcode
)
SELECT '0012145454545654521523'
UNION ALL
SELECT '0012142454545654521523'
UNION ALL
SELECT '5494627448634842135782'
UNION ALL
SELECT '5494625448634842135782'
First Condition - meets 7,8 + 20
SELECT a.barcode,
b.barcode,
SUBSTRING(a.barcode, 7, 2) a,
SUBSTRING(b.barcode, 7, 2) b
FROM #table a
INNER JOIN #table b
ON SUBSTRING(a.barcode, 7, 2) + 20 = SUBSTRING(b.barcode, 7, 2)
AND a.barcode != b.barcode
returns:
barcode barcode a b
0012145454545654521523 5494627448634842135782 54 74
5494625448634842135782 5494627448634842135782 54 74
Negation where 7,8 + 20 doesn't exist
SELECT *
FROM #table a
WHERE NOT EXISTS ( SELECT TOP 1 1
FROM #table b
WHERE SUBSTRING(a.barcode, 7, 2) + 20 = SUBSTRING(b.barcode, 7, 2) )
returns:
0012142454545654521523
5494627448634842135782

You'll have to break that barcode up using string operations, something like:
WHERE
substring(barcode1, 0, 6) = substring(barcode2, 0, 6) AND
substring(barcode1, 9, 2) = substring(barcode2, 0, 9) AND
etc...
And since you'll be doing these comparisons on function results, indexes aren't going to be used. If this is a frequent operation, you'd be better off splitting up the barcode strings into individual fields so you can compare the individual chunks as fully separate indexable fields.

Related

Count 0's between 1's - SQL

I need a query or function to count the 0's between 1's in a string.
For example:
String1 = '10101101' -> Result=3
String2 = '11111001101' -> Result=1
String3 = '01111111111' -> Result=1
I only need to search for 101 pattern or 01 pattern if its at the beginning of the string.
You may try to decompose the input strings using SUBTRING() and a number table:
SELECT
String, COUNT(*) AS [101Count]
FROM (
SELECT
v.String,
SUBSTRING(v.String, t.No - 1, 1) AS PreviousChar,
SUBSTRING(v.String, t.No, 1) AS CurrentChar,
SUBSTRING(v.String, t.No + 1, 1) AS NextChar
FROM (VALUES
('10101101'),
('11111001101'),
('01111111111')
) v (String)
CROSS APPLY (VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10)) t (No)
) cte
WHERE
CASE WHEN PreviousChar = '' THEN '1' ELSE PreviousChar END = '1' AND
CurrentChar = '0' AND
NextChar = '1'
GROUP BY String
Result:
String 101Count
10101101 3
11111001101 1
01111111111 1
Notes:
The table with alias v is the source table, the table with alias t is the number table. If the input strings have more than 10 characters, use an appropriate number (tally) table.
-- This converts "111101101010111" in "01101010" and "011101000" in "01110"
regexp_replace(field, '^1*(.*)1*0*$', '\1')
-- This converts "01101010" in "0000"
regexp_replace(field, '1', '')
-- This counts the string length, returning 4 for '0000':
LENGTH(field)
-- Put all together:
LENGTH(
regexp_replace(
regexp_replace(field, '^1*(.*)1*0*$', '\1')
, '1', '')
)
Different or more complicated cases require a modification of the regular expression.
Update
For "zeros between 1s" I see now you mean "101" sequences. This is more complicated because of the possibility of having "10101". Suppose you want to count this as 2:
replace 101 with 11011. Now 10101 will become either 1101101 or 1101111011. In either case, you have the "101" sequence well apart and still only have two of them.
replace all 101s with 'X'. You now have 1X11X1
replace [01] with the empty string. You now have XX.
use LENGTH to count the X's.
Any extra special sequence like "01" at the beginning you can convert as first thing with "X1" ("10" at the end would become "1X"), which will then neatly fold back in the above workflow.
By using the LIKE operator with % you can decide how to search a specific string. In this SQL query I am saying that I want every record that starts as 101 or 01.
SELECT ColumnsYouWant FROM TableYouWant
WHERE ColumnYouWant LIKE '101%' OR '01%';
You can simple COUNT the ColumnYouWant, like this:
SELECT COUNT(ColumnYouWant) FROM TableYouWant
WHERE ColumnYouWant LIKE '101%' OR '01%';
Or you can use a method of your backend language to count the results that the first query returns. This count method will depend on the language you are working with.
SQL Documentation for LIKE: https://www.w3schools.com/sql/sql_like.asp
SQL Documentation for COUNT; https://www.w3schools.com/sql/sql_count_avg_sum.asp
The other solutions do not account for all of the characters (max of 11, of the examples shown)
Data
drop table if exists #tTEST;
go
select * INTO #tTEST from (values
(1, '10101101'),
(2, '11111001101'),
(3, '01111111111')) V(id, string);
Query
;with
split_cte as (
select id, n, substring(t.string, v.n, 1) subchar
from #tTEST t
cross apply (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),
(11),(12),(13),(14),(15),(16),(17),(18),(19),(20)) v(n)
where v.n<=len(t.string)),
lead_lag_cte as (
select id, n, lead(subchar, 1, 9) over (partition by id order by n) lead_c, subchar,
lag(subchar, 1, 9) over (partition by id order by n) lag_c
from split_cte)
select id, sum(case when (lead_c=1 and lag_c=9) then 1 else
case when (lead_c=1 and lag_c=1) then 1 else 0 end end) zero_count
from lead_lag_cte
where subchar=0
group by id;
Results
id zero_count
1 3
2 1
3 1
Another way, perhasp quicker:
DECLARE #T TABLE (ID INT, STRING VARCHAR(32));
INSERT INTO #T
VALUES (1, '10101101'),
(2, '11111001101'),
(3, '01111111111');
SELECT *, LEN(STRING) - LEN(REPLACE(STRING, '0', '')) AS NUMBER_OF_ZERO
FROM #T
Result:
ID STRING NUMBER_OF_ZERO
----------- -------------------------------- --------------
1 10101101 3
2 11111001101 3
3 01111111111 1
select (len(replace('1'+x, '101', '11011')) - len(replace(replace('1'+x, '101', '11011'), '101', '')))/3
from
(
values
('10101101'),
('11111001101'),
('01111111111'),
('01010101010101010101')
) v(x);

SQL Server String manipulation column to new column

Need to create new column from the existing column with string manipulation on the fly
Existing stored string in the column is nvarchar
Existing column string example value 0001134564444
Need, the first three positions '000' to become 'AB' (there is a good number of variations)
the following 11 shall be 1 (there are eight variations results (1 to 8))
the following 3456 shall take the last two characters 56
the last 4444 shall not change.
The new string should then be AB5614444
Now need this done in SQL server,...
have tried with, substring, stuff, replace, charindex
Have reached the point...never too late to give up, this is my last chance.
Oracle version,
select column NewColumn, decode(substr(column, 1,3),'000','AB', '010', 'BC' ...)||substr(column,8,2)
||substr(column,5,1)||substr(column,10,5) NewStringColumn from table
getting absolutely nowhere with SQL server for this task, oracle remains no longer an option.
any help is appreciated,
select
case substring(c, 1, 3) when '000' then 'AB' when '010' then 'BC' ... end
+
case substring(c, 4, 2) when '11' then '1' ... end
+
substring(c, 8, 6)
with first_mapping as (
select k, v
from (values
('000', 'AB'),
('010', 'BC'),
...
) v(k, v)
),
second_mapping as (
select k, v
from (values
('11', '1'),
...
) v(k, v)
)
select
(select v from first_mapping where k = substring(c, 1, 3))
+
(select v from second_mapping where k = substring(c, 4, 2))
+
substring(c, 8, 6)

SQL Server: Create rows of a dynamic range using a select statement

Using Ruby you can produce an array with the following code:
some_range = (1..10).to_a
# Returns => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
I'm looking for something similar in SQL Server using a SELECT statement. i.e. I would like to produce this table:
id
1
2
3
4
5
6
7
8
9
10
etc
With a statement that looks something like this:
DECLARE #ids TVP
INSERT #ids
(id)
SELECT RANGE(0..10000)
My use case is that I want a quick way of testing SPs that takes #ids as a TVP. Does SQL Server provide something that achieves this?
I usually use a recursive CTE like this if I want to generate a numbers table:
;WITH NumberGen AS
(
SELECT 1 AS Number
UNION ALL
SELECT Number + 1 AS Number
FROM NumberGen
WHERE Number < $#RowsToBeGenerated$
)
INSERT INTO $Table$($Field$)
(
SELECT Number
FROM NumberGen
WHERE Number BETWEEN 1 AND $#RowsToBeGenerated$
)
OPTION (MAXRECURSION 0);
DECLARE #Range INT = 10
;with cte
as
(
select 1 as value
union all
select value + 1 from cte where value < #Range
)
select * from cte

How to Capture text from DB column in SQL Server 2012?

I have a column notes with a length of more than 80,0000 characters.
As per the transformation rule, I have to write a SQL script which will caption the notes column in the below steps :
First 300 characters in Column_A
Next 300 characters in Column_B
Next 300 characters in Column_C
and so on.
So I am looking for a output as below :
For every client ID with end of the length of the notes column.
Ouch! That's quite a complex requirement. You will need to combine a number of skills to solve this one.
Firstly you need to create additional rows. One way to achieve this is via recursion. In the example below I've calculated how many rows are required for each Client Id. I've then used recursion to create them.
You also need to break each row into 3 300 character blocks. In my example I've used 3 3 character blocks instead, so it's easier to read. But the principle will scale up. Using SUBSTRING and the record number you can calculate the starting point for each column.
I've created some sample records in a CTE called Raw. This allows anyone to follow the example, which is up on Stack Data Exchange (link below).
Example
DECLARE #ColumnWidth INT = 3; -- Use to adjust required length of columns A, B and C.
DECLARE #ColumnCount INT = 3; -- Use to adjust number of output columns.
WITH [Raw] AS
(
/* This CTE creates sample records for us to experiment with.
* The note column contains each letter of the alphabet, repeated
* 3 times. The repeatition will help us validate the result set.
*
* Using ceiling, to round up, the field length (#ColumnWidth) and
* the number of fields (#ColumnCount) and the number of charaters (LEN)
* we can calculate how many rows are required.
*/
SELECT
r.ClientId,
r.Note,
CEILING(CAST(LEN(r.Note) AS DECIMAL(18, 8)) / (#ColumnWidth * #ColumnCount)) AS RecordsRequired
FROM
(
VALUES
(1, 'aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzz'),
(2, 'aaabbbcccdddeeefffggghhhiiijjjkkklll'),
(3, 'aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnno'),
(4, 'aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnoooppp'),
(5, 'aaabbbcccdddeeefffggghhhiiijjj'),
(6, 'aaabbbcccdd')
) AS r(ClientId, Note)
),
MultiRow AS
(
/* This CTE uses recursion to return multiple rows for
* each orginal row.
* The number returned matches the RecordsRequired value
* from the Raw CTE.
*/
SELECT
1 AS RecordNumber,
RecordsRequired,
ClientId,
Note
FROM
[Raw]
UNION ALL
-- Keep repeating each record until the number of required rows has been returned.
SELECT
RecordNumber + 1 AS RecordNumber,
RecordsRequired,
ClientId,
Note
FROM
MultiRow
WHERE
RecordNumber < RecordsRequired
)
/* Each record returned by the MultiRow CTE is numbered: 1, 2, 3 etc.
* Using this we can extract blocks of text from the orginal Note column.
*/
SELECT
ClientId,
SUBSTRING(Note, ((#ColumnWidth * #ColumnCount) * RecordNumber) - ((#ColumnWidth * 3) -1), #ColumnWidth) AS Column_A,
SUBSTRING(Note, ((#ColumnWidth * #ColumnCount) * RecordNumber) - ((#ColumnWidth * 2) -1), #ColumnWidth) AS Column_B,
SUBSTRING(Note, ((#ColumnWidth * #ColumnCount) * RecordNumber) - ((#ColumnWidth * 1) -1), #ColumnWidth) AS Column_C
FROM
MultiRow
ORDER BY
ClientId, RecordNumber
;
Here is how you can do this:
DECLARE #c TABLE(ID INT, Notes VARCHAR(26))
INSERT INTO #c VALUES
(1, 'abcdefghijklmnopqrstuvwxyz'),
(2, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
DECLARE #size INT = 26
DECLARE #chunk INT = 5
;WITH tally AS(SELECT 1 s1, #chunk + 1 s2, 2*#chunk + 1 s3
UNION ALL
SELECT s3 + #chunk, s3 + 2*#chunk, s3 + 3*#chunk FROM tally
WHERE s3 < #size)
SELECT c.ID,
SUBSTRING(Notes, t.s1, #chunk) A,
SUBSTRING(Notes, t.s2, #chunk) B,
SUBSTRING(Notes, t.s3, #chunk) C
FROM #c c
CROSS JOIN tally t
ORDER BY c.ID, t.s1
Output:
ID A B C
1 abcde fghij klmno
1 pqrst uvwxy z
2 ABCDE FGHIJ KLMNO
2 PQRST UVWXY Z
Description:
tally table returns you the starting positions, which you will use in substring function. For the above configuration it returns:
s1 s2 s3
1 6 11
16 21 26
For this you are using recursive cte which spreads starting positions across the rows with 3 starting position. The rest should be easy to understand.
The required result can be obtained using simple looping
/* Declare a temperory table for storing the results */
DECLARE #Result_TABLE AS TABLE
(
CustomerId BIGINT
,ColA VARCHAR(300)
,ColB VARCHAR(300)
,ColC VARCHAR(300)
)
DECLARE #CustomerCount INT --To store customer count
DECLARE #IteratorForCustomers INT = 1 --To iterate for each customers
/* Get Count of cutomers */
SELECT #CustomerCount = COUNT (1) FROM Customers
DECLARE #CustomerId BIGINT --To store customer id in looping
DECLARE #TempNote VARCHAR(MAX) -- To store customer note of each customer in looping
/* Loop for all customers */
WHILE (#IteratorForCustomers <=#CustomerCount)
BEGIN
;WITH CTE AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY CustomerID ) AS RowId
,CustomerId
,Customer_Note
FROM Customers
)
SELECT
#CustomerId = a.CustomerId
,#TempNote = a.Customer_Note
FROM CTE a
WHERE
RowId = #IteratorForCustomers
/* Loop for generating each row with three columns */
WHILE (LEN(#TempNote)>0)
BEGIN
INSERT INTO #Result_TABLE
VALUES
(
#CustomerId,SUBSTRING(#TempNote,1,300),SUBSTRING(#TempNote,301,300),SUBSTRING(#TempNote,601,300)
)
SET #TempNote = CASE WHEN LEN(#TempNote)>900 THEN SUBSTRING(#TempNote,901,LEN(#TempNote)-900)
ELSE NULL END
END
SET #IteratorForCustomers = #IteratorForCustomers + 1
END
SELECT * FROM #Result_TABLE

How to convert messy two letter code into one "two letter" Column TSQL in MS SQL?

I need to transpose (I hope the word transpose is correct) a string of letter into a two letter in one column
the data I have is as follows: it is basically a two letter code mixed in together, very messy
ColumnID
HG34
HG/WK
HG\34
54/OF
AS\MM
AC54
AC54MM
and what I would like to do so to convert above into below:
ColumnID
HG
34
WK
54
OF
AS
MM
AC
is there a query that can do that?
I don't mind if the result has duplicates as I can always clean it up later
Thank you
select substring(ColumnID, 1, 2) from your_table
union
select substring(replace(replace(ColumnID,'\\',''),'/',''), 3, 2) from your_table
union
select substring(replace(replace(ColumnID,'\\',''),'/',''), 5, 2) from your_table
where length(replace(replace(ColumnID,'\\',''),'/','')) = 6
SQLFiddle demo
select distinct substring(col, x*2-1, 2)
from your_table
cross apply (select replace(replace(ColumnID,'\',''),'/',''))c(col)
inner join (values(1),(2),(3))x(x) on len (col) >= x * 2 -- as much pairs as needed

Resources