Count 0's between 1's - SQL - sql-server

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);

Related

How can i order by column with data "01","02","03","1","2","3" and so on in sql server

I am having SQL table with data as shown below
01 Buy-1
010 Buy-10
011 Buy-11
02 Buy-2
1 Direct-1
10 Direct-10
11 Direct-11
2 Direct-2
I want to order by the data like this
01 Buy-1
02 Buy-2
010 Buy-10
011 Buy-11
1 Direct-1
2 Direct-2
10 Direct-10
11 Direct-11
This requires a little extra handling of the strings to get the right sorting criteria.
Assuming your data is consistent with your sample data with a hyphen followed by digits you can order by the data to the left of the hyphen and then by casting the first column as an int
select id, data
from t
order by Left(data, nullif(CharIndex('-',data),0)-1),
Try_Cast(id as int)
This was an intriguing problem and I had to come back and find a solution to this issue as it has been seen many times over the years. When you don't have the leading zeros it is a fairly easy solution but this is different.
This works for your sample data and even some edge cases that I tried. The ordering first effectively counts the number of number of leading zeros and moves that with the most leading zeros to the top. Then it orders by converting the value to an int. This allows you to have more than 1 leading zero and it will still sort correctly. Additionally it will work even if you have character data in the first column.
declare #Something table
(
SomeValue varchar(10)
, SomeOtherValue varchar(30)
)
insert #Something
select '01' , 'Buy-1' union all
select '010', 'Buy-10' union all
select '011', 'Buy-11' union all
select '02', 'Buy-2' union all
select '1', 'Direct-1' union all
select '10', 'Direct-10' union all
select '11', 'Direct-11' union all
select '2', 'Direct-2'
select *
from #Something s
order by len(SomeValue) - len(convert(varchar(10), try_convert(int, SomeValue))) desc
, try_convert(int, SomeValue)
I would first order by your code having a leading zero, and secondly by the code value itself.
select code, description
from MyTable
order by case when left(code,1) = '0' then 0 else 1 end,
convert(int, code)
Id: 01
_Value: Buy-1
SELECT Id, _Value from tblName order by left(_Value, CHARINDEX('-', _Value)), cast(SUBSTRING(_Value, PATINDEX('%[0-9]%', _Value),len(_Value)) as int)

How to find max value in a row with changing

I have an dataset which has 12 different values for an ID, and also start and end Values. What I want to initilize is take the start value as my begining argument of loop and end value as the last argument. Search trough values accourding to them, and finding the maximum of them. After finding maximum search through values again wrt start and end value and find the longest consecutive max value occurance.
Below I posted an example dataset:
create table #sample_data(
ID VARCHAR(10), val1 INT, val2 INT, val3 INT, val4 INT, val5 INT,
val6 INT, val7 INT, val8 INT, val9 INT, val10 INT, val11 INT, val12 INT,
startValue INT, endValue INT );
insert into #sample_data values
(1001,3,2,1,0,1,2,3,0,0,0,0,0,1,7),
(1002,1,2,3,4,0,0,0,1,2,3,0,0,1,12),
(1003,0,3,2,1,0,0,0,3,3,0,0,0,1,12),
(1004,0,1,2,4,4,0,0,0,0,0,0,0,3,9),
(1005,1,2,2,1,0,0,2,2,2,1,0,0,1,8);
The result I expect for Id=1001 start=1, end = 7, max value is 3 and, it occurs 2 times but they arent consecutive, therefore final output I'd like to get is 1.
For ID=1002 start=1, end=12, max is 4 and it only occurs 1 time, so final output shoud be 1.
For ID = 1003 start=1, end=12, max is 3, 3 occurs three times but only 2 of them are consecutive therefore I expect to get 2.
For ID = 1004 start=3, end=9, max is 4 it occurs two times consecutively therefore output should be 2.
For ID = 1005 start=1, end=8, max is 2 it totaly occurs 5 times, 2 and 3 times consecutively, I expect to get 3 as my final output since it is longest.
If I understand the question correctly, the result for the row with Id 1005 should be 2 and not 3, because the max value (which is 2) appears consecutively in places 2,3 and then again in places 7,8,9 - but the endValue of that row is 8, and therefor the larger consecutive should not be counted.
Based on that understanding (which might be incorrect, hence the comment I've written to the question), this can be done with a set based approach (meaning, without any loops), with the help of some nice SQL tricks.
So the first thing you want to do is to use cross apply with a table value constructor to convert the val1...val12 columns to rows. I guess this can also be done using Pivot but I never quite got the hang of pivot so I prefer other solutions to get the same thing.
In my code, this step is done in the first common table expression (called CTEValues).
Next, you use a trick from Itzik Ben-Gan to handle gaps and island problems to identify the groups of consecutive values within each row. This step is done in the second cte (CTEGroups).
The third and final cte called CTEConsecutive use a simple group by and count to get the number of consecutive max values within each row of the original table, providing their column location is between the startValue and EndValue.
The last thing to do is get the max value of that count for each id - and that should give you the desired results.
Here's the full code:
WITH CTEValues AS
(
SELECT ID, startValue, EndValue, Val, ValId, IIF(Val = MAX(Val) OVER(PARTITION BY ID), 1, 0) As IsMax
FROM #sample_data
CROSS APPLY
(
SELECT *
FROM (VALUES
(Val1, 1),
(Val2, 2),
(Val3, 3),
(Val4, 4),
(Val5, 5),
(Val6, 6),
(Val7, 7),
(Val8, 8),
(Val9, 9),
(Val10, 10),
(Val11, 11),
(Val12, 12)
)V(Val, ValId)
) vals
), CTEGroups AS
(
SELECT ID, startValue, EndValue, Val, ValId, IsMax,
ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ValId) -
ROW_NUMBER() OVER(PARTITION BY ID, IsMax ORDER BY ValId) As Grp
FROM CTEValues
), CTEConsecutive AS
(
SELECT ID, COUNT(Val) As NumOfConsecutiveMaxValues --*, OVER(PARTITION BY Id, Grp) As NumOfValues
FROM CTEGroups
WHERE IsMax = 1
AND ValId >= startValue
AND ValId <= EndValue
GROUP BY ID, Grp
)
SELECT ID, MAX(NumOfConsecutiveMaxValues)
FROM CTEConsecutive
GROUP BY ID
ORDER BY Id
You can see a live demo on rextester.
If, however, I'm wrong in my initial assumption and the startvalue and endvalue are only relevant to the range of which to search for the max value, (and that would give you the expected results you've posted in the question), you will need another cte.
WITH CTEValues AS
(
SELECT ID, startValue, EndValue, Val, ValId
FROM #sample_data
CROSS APPLY
(
SELECT *
FROM (VALUES
(Val1, 1),
(Val2, 2),
(Val3, 3),
(Val4, 4),
(Val5, 5),
(Val6, 6),
(Val7, 7),
(Val8, 8),
(Val9, 9),
(Val10, 10),
(Val11, 11),
(Val12, 12)
)V(Val, ValId)
) vals
), CTEValuesWithMax AS
(
SELECT ID, startValue, EndValue, Val, ValId,
IIF(Val = (
SELECT MAX(Val)
FROM CTEValues AS T1
WHERE T0.ID = T1.ID
AND T1.ValId >= T1.startValue
AND T1.ValId <= T1.EndValue
), 1, 0) As IsMax
FROM CTEValues AS T0
)
The rest of the code remains the same, except that CTEGroups now selects from CTEValuesWithMax instead of from CTEValues.
You can see a live demo of this as well.

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

SQL Server Script Quick Replace all found strings with incrementing integer

I have large INSERT SQL script that I want to modify it with quick replace. By replacing each found string with interger, where every next integer is previous integer+1.
Before:
INSERT Compartment (CompartmentID) VALUES ('A')
INSERT Compartment (CompartmentID) VALUES ('B')
After:
INSERT Compartment (CompartmentID) VALUES (1)
INSERT Compartment (CompartmentID) VALUES (2)
I know how to find the specific strings, but I can't find anywhere syntax or way have to replace it incrementing integers.
You can replace all you char CompartmentID with ordered numbers like this:
declare #Compartment table(CompartmentID varchar(10), name varchar(10), intID int)
INSERT INTO #Compartment(CompartmentID, name) values
('a', 'a')
, ('b', 'b')
, ('c', 'c')
, ('d', 'd')
, ('e', 'e')
UPDATE c SET CompartmentID = o.ID
FROM #Compartment c
INNER JOIN (
SELECT CompartmentID, ID = ROW_NUMBER() over(ORDER BY CompartmentID)
FROM #Compartment
) o ON c.CompartmentID = o.CompartmentID
SELECT * FROM #Compartment
Output:
CompartmentID name
1 a
2 b
3 c
4 d
5 e
It would be better to create a new column of type int or change the type of CompartmentID once the update is finished.
You should also use an identity column if you want the numbers to be incremented automaticaly.
Not sure how you want to handle empty string. You can select the rows where CompartmentID contains a character that isnt a numeric and update the result set like this:
DECLARE #Compartment table(CompartmentID varchar(20))
INSERT #Compartment(CompartmentID) VALUES ('A'),('A'),('B'),('1'),('A1')
-- EDIT: Changed answer
;WITH CTE as
(
SELECT CompartmentID, DENSE_RANK() over (ORDER BY CompartmentID) rn
FROM #Compartment
--WHERE CompartmentID LIKE '%[^0-9]%' OR CompartmentID = ''
)
UPDATE CTE
SET CompartmentID = rn
FROM CTE
Result:
CompartmentID
2
2
4
1
3
Note: Now all id will CompartmentID changed(also the numeric CompartmentID), identical values for old CompartmentID will get identical numeric values.

sort float numbers as a natural numbers in SQL Server

Well I had asked the same question for jquery on here, Now my question is same with SQL Server Query :) But this time this is not comma separated, this is separate row in Database like
I have separated rows having float numbers.
Name
K1.1
K1.10
K1.2
K3.1
K3.14
K3.5
and I want to sort this float numbers like,
Name
K1.1
K1.2
K1.10
K3.1
K3.5
K3.14
actually in my case, the numbers which are after decimals will consider as a natural numbers, so 1.2 will consider as '2' and 1.10 will consider as '10' thats why 1.2 will come first than 1.10.
You can remove 'K' because it is almost common and suggestion or example would be great for me, thanks.
You can use PARSENAME (which is more of a hack) or String functions like CHARINDEX , STUFF, LEFT etc to achieve this.
Input data
;WITH CTE AS
(
SELECT 'K1.1' Name
UNION ALL SELECT 'K1.10'
UNION ALL SELECT 'K1.2'
UNION ALL SELECT 'K3.1'
UNION ALL SELECT 'K3.14'
UNION ALL SELECT 'K3.5'
)
Using PARSENAME
SELECT Name,PARSENAME(REPLACE(Name,'K',''),2),PARSENAME(REPLACE(Name,'K',''),1)
FROM CTE
ORDER BY CONVERT(INT,PARSENAME(REPLACE(Name,'K',''),2)),
CONVERT(INT,PARSENAME(REPLACE(Name,'K',''),1))
Using String Functions
SELECT Name,LEFT(Name,CHARINDEX('.',Name) - 1), STUFF(Name,1,CHARINDEX('.',Name),'')
FROM CTE
ORDER BY CONVERT(INT,REPLACE((LEFT(Name,CHARINDEX('.',Name) - 1)),'K','')),
CONVERT(INT,STUFF(Name,1,CHARINDEX('.',Name),''))
Output
K1.1 K1 1
K1.2 K1 2
K1.10 K1 10
K3.1 K3 1
K3.5 K3 5
K3.14 K3 14
This works if there is always one char before the first number and the number is not higher than 9:
SELECT name
FROM YourTable
ORDER BY CAST(SUBSTRING(name,2,1) AS INT), --Get the number before dot
CAST(RIGHT(name,LEN(name)-CHARINDEX('.',name)) AS INT) --Get the number after the dot
Perhaps, more verbal, but should do the trick
declare #source as table(num varchar(12));
insert into #source(num) values('K1.1'),('K1.10'),('K1.2'),('K3.1'),('K3.14'),('K3.5');
-- create helper table
with data as
(
select num,
cast(SUBSTRING(replace(num, 'K', ''), 1, CHARINDEX('.', num) - 2) as int) as [first],
cast(SUBSTRING(replace(num, 'K', ''), CHARINDEX('.', num), LEN(num)) as int) as [second]
from #source
)
-- Select and order accordingly
select num
from data
order by [first], [second]
sqlfiddle:
http://sqlfiddle.com/#!6/a9b06/2
The shorter solution is this one :
Select Num
from yourtable
order by cast((Parsename(Num, 1) ) as Int)

Resources