Extract multiple numeric values from string with T-SQL - sql-server

I have a column with data in a special “format”. Example:
L100000
L50
L5
S10
S15L10
S20
S90
S10
S10L5
S10L40
S10L5
The value consists of an “S” and/or an “L”, each with a number following the letter.
I need to write a query, which will return two columns, “S” and “L”, which will have only the coresponding numeric value following the letter.
The above example should look like this:
S L
======== ==========
0 100000
0 50
0 5
10 0
15 10
20 0
90 0
10 0
10 5
10 40
10 5
If no "S" or "L" is found, the default value is zero.

try this:
DECLARE #YourTable table (RowValue varchar(30))
INSERT INTO #YourTable VALUES ('L100000')
INSERT INTO #YourTable VALUES ('L50')
INSERT INTO #YourTable VALUES ('L5')
INSERT INTO #YourTable VALUES ('S10')
INSERT INTO #YourTable VALUES ('S15L10')
INSERT INTO #YourTable VALUES ('S20')
INSERT INTO #YourTable VALUES ('S90')
INSERT INTO #YourTable VALUES ('S10')
INSERT INTO #YourTable VALUES ('S10L5')
INSERT INTO #YourTable VALUES ('S10L40')
INSERT INTO #YourTable VALUES ('S10L5')
SELECT
CASE
WHEN LocationS>0 AND LocationL=0 THEN RIGHT(RowValue,Length-1)
WHEN LocationS>0 THEN SUBSTRING(RowValue,2,LocationL-2)
ELSE NULL
END AS S
,CASE
WHEN LocationS=0 AND LocationL>0 THEN RIGHT(RowValue,Length-1)
WHEN LocationS>0 AND LocationL>0 THEN RIGHT(RowValue,Length-LocationL)
ELSE NULL
END AS L
,RowValue
FROM (SELECT
RowValue
,CHARINDEX('S',RowValue) AS LocationS
,CHARINDEX('L',RowValue) AS LocationL
,LEN(RowValue) AS Length
FROM #YourTable
) dt
OUTPUT
S L RowValue
------------------------------ ------------------------------ --------------
NULL 100000 L100000
NULL 50 L50
NULL 5 L5
10 NULL S10
15 10 S15L10
20 NULL S20
90 NULL S90
10 NULL S10
10 5 S10L5
10 40 S10L40
10 5 S10L5
(11 row(s) affected)
if you have loads of data give this a try, it may be faster (has same output, basically removed the derived table and made everything use inline functions):
SELECT
CASE
WHEN CHARINDEX('S',RowValue)>0 AND CHARINDEX('L',RowValue)=0 THEN RIGHT(RowValue,LEN(RowValue)-1)
WHEN CHARINDEX('S',RowValue)>0 THEN SUBSTRING(RowValue,2,CHARINDEX('L',RowValue)-2)
ELSE NULL
END AS S
,CASE
WHEN CHARINDEX('S',RowValue)=0 AND CHARINDEX('L',RowValue)>0 THEN RIGHT(RowValue,LEN(RowValue)-1)
WHEN CHARINDEX('S',RowValue)>0 AND CHARINDEX('L',RowValue)>0 THEN RIGHT(RowValue,LEN(RowValue)-CHARINDEX('L',RowValue))
ELSE NULL
END AS L
,RowValue
FROM #YourTable

SELECT
SVal = CASE
WHEN PATINDEX('S%L%', TextVal) > 0 THEN REPLACE(LEFT(TextVal, CHARINDEX('L', TextVal) - 1), 'S', '')
WHEN PATINDEX('S%', TextVal) > 0 THEN REPLACE(TextVal, 'S', '')
ELSE '0'
END,
LVal = CASE
WHEN PATINDEX('S%L%', TextVal) > 0 THEN REPLACE(RIGHT(TextVal, LEN(TextVal) - CHARINDEX('L', TextVal)), 'L', '')
WHEN PATINDEX('L%', TextVal) > 0 THEN REPLACE(TextVal, 'L', '')
ELSE '0'
END
FROM StringList
Assumes S always comes before L. Also you might want to cast the results as numbers (they are strings now) depending on what you need for the output.

I would suggest a clr-based function which would use a regex to extract the S or L value from the string. You could use it like this:
insert new_table (s_value, l_value)
select getValue('S', original_value), getValue('L', original_value)
from original_table

not tested but should be close assuming s always appears before l:
select
case when charindex(data, 's') <> 0 then
substr(data, charindex(data, 's'), charindex(data ,'l'))
else 0 end
, case when charindex(data, 'l') <> 0 then
substr(data, charindex(data, 'l'))
else 0 end
from some_table

Related

REPLACE string with SUBSTRING function

Given input data:
Col1
---------------------------------------
'-'::"varchar" COLLATE "default"
'-1'::integer
'0'::smallint
'1'::"varchar" COLLATE "default"
(get_val())::"timestamp"
0
0.0
10
210
90000
getdate()
I'm trying to replace the part of the string(column Col1) to empty string ''.
Want to replace anything after :: with empty string as shown below in the expected result.
Expected Result:
Col1 Col2
------------------------------------------------------------------------
'-'::"varchar" COLLATE "default" '-'
'-1'::integer '-1'
'0'::smallint '0'
'1'::"varchar" COLLATE "default" '1'
(get_val())::"timestamp" (get_val())
0 0
0.0 0.0
10 10
210 210
90000 90000
8 8
getdate() getdate()
My try:
SELECT Col1 REPLACE(REPLACE(Col1,SUBSTRING(Col1,CHARINDEX('::',Col1),LENGTH(Col1)),''),'(''','''')
FROM tbl_string_pattern;
But getting output like:
Col1 Col2
------------------------------------------------------------------------
'-'::"varchar" COLLATE "default" '-'
'-1'::integer '-1'
'0'::smallint '0'
'1'::"varchar" COLLATE "default" '1'
(get_val())::"timestamp" (get_val())
0 0
0.0 0
10 0
210 0
90000 0
8 8
getdate() )
Try this:
DECLARE #DataSource TABLE
(
[value] VARCHAR(128)
);
INSERT INTO #DataSource ([value])
VALUES ('''-''::"varchar" COLLATE "default"')
,('''-1''::integer')
,('''0''::smallint')
,('''1''::"varchar" COLLATE "default"')
,('(get_val())::"timestamp" ')
,('0')
,('0.0')
,('10 ')
,('210')
,('90000')
,('8')
,(null)
,('')
,('getdate()');
SELECT [value]
,REPLACE(REPLACE([value],SUBSTRING([value],CHARINDEX('::',[value]),LEN([value])),''),'(''','''')
,IIF(CHARINDEX('::', [value]) > 0, SUBSTRING([value], 0, CHARINDEX('::', [value])), [value])
-- John Cappellletti's idea
,left([value],charindex('::',[value]+'::')-1)
FROM #DataSource;

Return non-numeric characters

I have 1 table.
Result
I want to show columns Names and Digits.
How do I return all rows that has a non-numeric characters in the Digits.
Thanks.
You can use ISNUMERIC function
ISNUMERIC returns 1 when the input expression evaluates to a valid numeric data type; otherwise it returns 0.
SELECT names, digit
FROM yourTable
WHERE ISNUMERIC(digit) = 0
You could use PATINDEX
Declare #SampleData as Table
(
Id int,
Digit varchar(10)
)
insert into #SampleData
values
( 1, '100'),(2,'2ab'),
(3,'200'), (4, ''), (5, 'a11')
select * from #SampleData
where PATINDEX('%[^0-9]%',Digit) > 0
Returns
Id Digit
----------
2 2ab
5 a11

Get columns only if it is not null is sql server 2012?

I have the following rows.I dont want to select those columns whose entire value is null
col1 col2 col3
1 2 NULL
2 3 NULL
3 4 NULL
. . NULL
. . NULL
. . NULL
100 101 NULL
For example,
I want to select only col1 and col2 since all the values of col3 is null ie if col3 contains any value other than null then col1 col2 and col3 should be selected.Other wise col1 and col2 should only be selected.
How to acheive the above scenaria in sqlserver
There could be a better/efficient way present but you can try the below which is mixed o bit TSQL script and dynamic SQL. teasted and it works fine. A sample code below.
create table test1(col1 int,col2 int);
insert into test1(col1) values(2),(3),(4)
declare #col1sum int, #col2sum int, #countrow int;
-- This query will get you NULL count for all columns
select #col1sum = sum(case when col1 is null then 1 else 0 end),
#col2sum = sum(case when col2 is null then 1 else 0 end),
#countrow = count(1)
from test1;
select #col1sum, #col2sum, #countrow; --for display
declare #sql varchar(100);
-- check for column null count < total count of rows
if(#col1sum < #countrow)
set #sql = 'select col1 from test1';
else if(#col2sum < #countrow)
set #sql = 'select col2 from test1';
else
set #sql = 'select col1,col2 from test1';
exec(#sql); -- Finally execute the dynamic sql
If you want to go the way that David suggests in the comments and do the hiding in the UI, but have the database do the work to help you out, you can use aggregate functions with windows to add some extra columns:
declare #t table (col1 int,col2 int,col3 int)
insert into #t(col1,col2,col3) values
(1 ,2 ,NULL),
(2 ,3 ,NULL),
(3 ,4 ,NULL),
(9 ,NULL ,NULL),
(100 ,101 ,NULL)
select
col1,CASE WHEN MAX(col1) OVER () IS NULL THEN 0 ELSE 1 END as col1_HasValues,
col2,CASE WHEN MAX(col2) OVER () IS NULL THEN 0 ELSE 1 END as col2_HasValues,
col3,CASE WHEN MAX(col3) OVER () IS NULL THEN 0 ELSE 1 END as col3_HasValues
from #t
Which produces the result set:
col1 col1_HasValues col2 col2_HasValues col3 col3_HasValues
----------- -------------- ----------- -------------- ----------- --------------
1 1 2 1 NULL 0
2 1 3 1 NULL 0
3 1 4 1 NULL 0
9 1 NULL 1 NULL 0
100 1 101 1 NULL 0
The _HasValues columns will be identical across all rows and tells you if any row has a non-NULL value in the preceding column. If it's 0, you should hide the column in the UI.

Reporting on data when data is missing (ie. how to report zero activities for a customer on a given week)

I want to create a report which aggregates the number of activities per customer per week.
If there has been no activites on that customer for a given week, 0 should be displayed (i.e week 3 and 4 in the sample below)
CUSTOMER | #ACTIVITIES | WEEKNUMBER
A | 4 | 1
A | 2 | 2
A | 0 | 3
A | 0 | 4
A | 1 | 5
B ...
C ...
The problem is that if there are no activities there is no data to report on and therefor week 3 and 4 in the sample below is not in the report.
What is the "best" way to solve this?
Try this:
DECLARE #YourTable table (CUSTOMER char(1), ACTIVITIES int, WEEKNUMBER int)
INSERT #YourTable VALUES ('A' , 4 , 1)
INSERT #YourTable VALUES ('A' , 2 , 2)
INSERT #YourTable VALUES ('A' , 0 , 3)
INSERT #YourTable VALUES ('A' , 0 , 4)
INSERT #YourTable VALUES ('A' , 1 , 5)
INSERT #YourTable VALUES ('B' , 5 , 3)
INSERT #YourTable VALUES ('C' , 2 , 4)
DECLARE #StartNumber int
,#EndNumber int
SELECT #StartNumber=1
,#EndNumber=5
;WITH AllNumbers AS
(
SELECT #StartNumber AS Number
UNION ALL
SELECT Number+1
FROM AllNumbers
WHERE Number<#EndNumber
)
, AllCustomers AS
(
SELECT DISTINCT CUSTOMER FROM #YourTable
)
SELECT
n.Number AS WEEKNUMBER, c.CUSTOMER, CASE WHEN y.Customer IS NULL THEN 0 ELSE y.ACTIVITIES END AS ACTIVITIES
FROM AllNumbers n
CROSS JOIN AllCustomers c
LEFT OUTER JOIN #YourTable y ON n.Number=y.WEEKNUMBER AND c.CUSTOMER=y.CUSTOMER
--OPTION (MAXRECURSION 500)
OUTPUT:
WEEKNUMBER CUSTOMER ACTIVITIES
----------- -------- -----------
1 A 4
1 B 0
1 C 0
2 A 2
2 B 0
2 C 0
3 A 0
3 B 5
3 C 0
4 A 0
4 B 0
4 C 2
5 A 1
5 B 0
5 C 0
(15 row(s) affected)
I use a CTE to build a Numbers table, but you could build a permanent one look at this question: What is the best way to create and populate a numbers table?. You could Write the Query without a CTE (same results as above):
SELECT
n.Number AS WEEKNUMBER, c.CUSTOMER, CASE WHEN y.Customer IS NULL THEN 0 ELSE y.ACTIVITIES END AS ACTIVITIES
FROM Numbers n
CROSS JOIN (SELECT DISTINCT
CUSTOMER
FROM #YourTable
) c
LEFT OUTER JOIN #YourTable y ON n.Number=y.WEEKNUMBER AND c.CUSTOMER=y.CUSTOMER
WHERE n.Number>=1 AND n.Number<=5
ORDER BY n.Number,c.CUSTOMER
Keep a table of time periods separately, and then outer left join the activities to it.
Like:
select *
from ReportingPeriod as p
left join Activities as a on a.ReportingPeriodId = p.ReportingPeriodId;

How to get the true/false count from a bit field into two separate columns

I need to create a query that will sum the number of True(1) and False(0) into two separate columns from one bit field.
I'm joining 3 tables and need it to be something like:
Attribute | Class | Pass | Fail
I will be grouping on Attribute and Class.
Something like this:
SUM(CASE WHEN ColumnName = 1 THEN 1 ELSE 0 END) AS Pass,
SUM(CASE WHEN ColumnName = 0 THEN 1 ELSE 0 END) AS Fail
This works (at least in SQL 2008)
SELECT SUM(Passed + 0) PASS , SUM(1 - Passed) FAIL
I am adding 0 to Passed in the first sum as a short hand way of converting from bit to int since you can't sum bits directly.
try:
declare #table table (columnName bit)
insert into #table values (1)
insert into #table values (1)
insert into #table values (1)
insert into #table values (1)
insert into #table values (1)
insert into #table values (0)
insert into #table values (0)
insert into #table values (0)
insert into #table values (0)
SELECT
SUM(CASE WHEN ColumnName = 1 THEN 1 ELSE 0 END) AS True1
, SUM(CASE WHEN ColumnName = 0 THEN 1 ELSE 0 END ) AS False0
from #Table
OUTPUT:
True1 False0
----------- -----------
5 4
(1 row(s) affected)
SELECT
Attribute,
Class,
SUM(CASE BitField WHEN 1 THEN 1 ELSE 0 END) AS [Pass],
SUM(CASE BitField WHEN 0 THEN 1 ELSE 0 END) AS [Fail]
FROM
Table
GROUP BY
Attribute,
Class
Another option would be
SELECT Attribute, Class
COUNT(CASE WHEN ColumnName = 1 THEN 1 END) Pass,
COUNT(CASE WHEN ColumnName = 0 THEN 1 END) Fail FROM YourTable
GROUP BY Attribute, Class
there is even one more option:
SELECT
Attribute,
Class,
COUNT(BoolColumnName = 1 or NULL) Pass,
COUNT(BoolColumnName = 0 or NULL) Fail
FROM Table
GROUP BY Attribute, Class

Resources