TSQL query to extract a value between to char where a specific set of characters is there - tsql

I have a problem I can't seem to figure out. I am trying to extract capacity from a product description. It is always between two values, "," and "oz." however there could be other commas included in the description that are not part of what I'm trying to extract. Example value is , 15 oz., or , 2 oz.,
I'm trying to find values that have the oz in them and are between two commas and I have been completely unsuccessfully. I've tried many things, but here is the latest that I have tried today and I'm just getting an error.
SELECT SUBSTRING(
FullDescription,
CHARINDEX(',', FullDescription),
CHARINDEX('oz.',FullDescription)
- CHARINDEX(',', FullDescription)
+ Len('oz.')
)
from CatalogManagement.Product

Since the backwards pattern ,.zo is more recognisable, I'd go with the REVERSE function
Sample values:
"something, something more, 18oz., complete"
"shorter, 12oz., remainder"
"there is no capacity, in this, value"
"a bit more, 14oz, and some followups, maybe"
SELECT REVERSE(
SUBSTRING (
REVERSE(FullDescription),
CHARINDEX(',.zo', REVERSE(FullDescription)) + 1,
CHARINDEX(',', REVERSE(FullDescription), CHARINDEX(',.zo', REVERSE(FullDescription)) + 1) - CHARINDEX(',.zo', REVERSE(FullDescription)) - 1
)
)
FROM CatalogManagement.Product
WHERE FullDescription LIKE '%oz.,%'

You might use XML-splitting together with a XQuery predicate:
DECLARE #tbl TABLE(ID INT IDENTITY, YourString VARCHAR(MAX));
INSERT INTO #tbl VALUES('Here is one with an amount, 1 oz., some more text')
,('Here is one with no amount, some more text')
,('a, 10 oz.')
,('b, 20oz., no blank between oz and the number')
,('30oz., starts with the pattern, no leading comma');
SELECT t.*
,A.oz.value('.','nvarchar(max)') oz
FROM #tbl t
CROSS APPLY(SELECT CAST('<x>' + REPLACE((SELECT t.YourString AS [*] FOR XML PATH('')),',','</x><x>') + '</x>' AS XML)
.query('/x[contains(text()[1],"oz.")]')) A(oz);
The idea in short:
We use some string methods to replace commas with XML tags and to cast your string to XML. each fragment is placed within a decent <x> element.
We use a predicate to return just the fragments containing "oz.".
You can filter easily with
WHERE LEN(A.oz.value('.','nvarchar(max)'))>0

Related

Trying to extract text using CHARINDEX ()- 1 but getting an error

I have a column with Names, and I am trying to split the column into First and Last Name using Text functions such as LEFT/SUBSTRING/CHARINDEX.
Data in the column:
Name
Yang, Jon
Huang, Eugene
Torres, Ruben
Zhu, Christy
Johnson, Elizabeth
Everything works fine as long as I use this code:
SELECT
[Name]
--,LEFT([Name], CHARINDEX(' ', [Name])) AS FirstName
,SUBSTRING([Name], 1, CHARINDEX(' ', [Name] )) AS FirstName
FROM
DataModeling.Customer
But the problem arises when I try to subtract 1 from CHARINDEX to exclude the Comma from the result and it throws this error:
I have done this operation many times in Excel so trying to replicate it with TSQL. Any suggestion on what I am doing wrong is helpful.
You get that error when CHARINDEX(' ', [Name] ) return 0. So minus 1 will make it negative and it is invalid value for substring()
You can use CASE expression to check the return value from CHARINDEX() and return the correct value to substring()
Or, you can "cheat" by using
CHARINDEX( ' ', [Name] + ' ' )
So CHARINDEX() will always return a value that is more than 0

How to find/replace weird whitespace in string

I find in my sql database string whit weird whitespace which cannot be replace like REPLACE(string, ' ', '') RTRIM and cant it even find with string = '% %'. This space is even transfered to new table when using SELECT string INTO
If i select this string in managment studio and copy that is seems is normal space and when everything is works but cant do nothing directly from database. What else can i do? Its some kind of error or can i try some special character for this?
First, you must identify the character.
You can do that by using a tally table (or a cte) and the Unicode function:
The following script will return a table with two columns: one contains a char and the other it's unicode value:
DECLARE #Str nvarchar(100) = N'This is a string containing 1 number and some words.';
with Tally(n) as
(
SELECT TOP(LEN(#str)) ROW_NUMBER() OVER(ORDER BY ##SPID)
FROM sys.objects a
--CROSS JOIN sys.objects b -- (unremark if there are not enough rows in the tally cte)
)
SELECT SUBSTRING(#str, n, 1) As TheChar,
UNICODE(SUBSTRING(#str, n, 1)) As TheCode
FROM Tally
WHERE n <= LEN(#str)
You can also add a condition to the where clause to only include "special" chars:
AND SUBSTRING(#str, n, 1) NOT LIKE '[a-zA-Z0-9]'
Then you can replace it using it's unicode value using nchar (I've used 32 in this example since it's unicode "regular" space:
SELECT REPLACE(#str, NCHAR(32), '|')
Result:
This|is|a|string|containing|1|number|and|some|words.

SQL RIGHT function not working as expected

I'm trying to extract the month number from a date as a left padded string with 0's.
So, for example, from '2018-01-31' I want the string '01'.
Currently I have this:
SELECT RIGHT('0' + CAST(MONTH('2018-01-31') AS CHAR(2)), 2)
Which is returning '1' but I would have expected it to return '01' because I've provided the second argument to RIGHT as 2.
Could someone explain why this isn't working as I think it should?
You need to change CHAR to VARCHAR:
SELECT RIGHT('0' + CAST(MONTH('2018-01-31') AS VARCHAR(2)), 2)
db<>fiddle demo
CHAR(2) is blank padded so you get RIGHT('01 ',2) which is '1 '.
You could use FORMAT instead, when you first cast the string to a DATE type.
SELECT FORMAT(CAST('2018-01-31' as DATE),'MM')
As for why that SQL with the right didn't work?
Try this SQL and notice the difference (the extra space):
SELECT quotename('0' + CAST(1 AS CHAR(2))), quotename('0' + CAST(1 AS VARCHAR(2)))

Count previous occurences of a value split by date ranges

Here's a simple query we do for ad hoc requests from our Marketing department on the leads we received in the last 90 days.
SELECT ID
,FIRST_NAME
,LAST_NAME
,ADDRESS_1
,ADDRESS_2
,CITY
,STATE
,ZIP
,HOME_PHONE
,MOBILE_PHONE
,EMAIL_ADDRESS
,ROW_ADDED_DTM
FROM WEB_LEADS
WHERE ROW_ADDED_DTM BETWEEN #START AND #END
They are asking for more derived columns to be added that show the number of previous occurences of ADDRESS_1 where the EMAIL_ADDRESS matches. But they want is for different date ranges.
So the derived columns would look like this:
,COUNT_ADDRESS_1_LAST_1_DAYS,
,COUNT_ADDRESS_1_LAST_7_DAYS
,COUNT_ADDRESS_1_LAST_14_DAYS
etc.
I've manually filled these derived columns using update statements when there was just a few. The above query is really just a sample of a much larger query with many more columns. The actual request has blossomed into 6 date ranges for 13 columns. I'm asking if there's a better way then using 78 additional update statements.
I think you will have a hard time writing a query that includes all of these 78 metrics per e-mail address without actually creating a query that hard-codes the different choices. However you can generate such a pivot query with dynamic SQL, which will save you some keystrokes and will adjust dynamically as you add more columns to the table.
The result you want to end up with will look something like this (but of course you won't want to type it):
;WITH y AS
(
SELECT
EMAIL_ADDRESS,
/* aggregation portion */
[ADDRESS_1] = COUNT(DISTINCT [ADDRESS_1]),
[ADDRESS_2] = COUNT(DISTINCT [ADDRESS_2]),
... other columns
/* end agg portion */
FROM dbo.WEB_LEADS AS wl
WHERE ROW_ADDED_DTM >= /* one of 6 past dates */
GROUP BY wl.EMAIL_ADDRESS
)
SELECT EMAIL_ADDRESS,
/* pivot portion */
COUNT_ADDRESS_1_LAST_1_DAYS = *count address 1 from 1 day ago*,
COUNT_ADDRESS_1_LAST_7_DAYS = *count address 1 from 7 days ago*,
... other date ranges ...
COUNT_ADDRESS_2_LAST_1_DAYS = *count address 2 from 1 day ago*,
COUNT_ADDRESS_2_LAST_7_DAYS = *count address 2 from 7 days ago*,
... other date ranges ...
... repeat for 11 more columns ...
/* end pivot portion */
FROM y
GROUP BY EMAIL_ADDRESS
ORDER BY EMAIL_ADDRESS;
This is a little involved, and it should all be run as one script, but I'm going to break it up into chunks to intersperse comments on how the above portions are populated without typing them. (And before long #Bluefeet will probably come along with a much better PIVOT alternative.) I'll enclose my interspersed comments in /* */ so that you can still copy the bulk of this answer into Management Studio and run it with the comments intact.
Code/comments to copy follows:
/*
First, let's build a table of dates that can be used both to derive labels for pivoting and to assist with aggregation. I've added the three ranges you've mentioned and guessed at a fourth, but hopefully it is clear how to add more:
*/
DECLARE #d DATE = SYSDATETIME();
CREATE TABLE #L(label NVARCHAR(15), d DATE);
INSERT #L(label, d) VALUES
(N'LAST_1_DAYS', DATEADD(DAY, -1, #d)),
(N'LAST_7_DAYS', DATEADD(DAY, -8, #d)),
(N'LAST_14_DAYS', DATEADD(DAY, -15, #d)),
(N'LAST_MONTH', DATEADD(MONTH, -1, #d));
/*
Next, let's build the portions of the query that are repeated per column name. First, the aggregation portion is just in the format col = COUNT(DISTINCT col). We're going to go to the catalog views to dynamically derive the list of column names (except ID, EMAIL_ADDRESS and ROW_ADDED_DTM) and stuff them into a #temp table for re-use.
*/
SELECT name INTO #N FROM sys.columns
WHERE [object_id] = OBJECT_ID(N'dbo.WEB_LEADS')
AND name NOT IN (N'ID', N'EMAIL_ADDRESS', N'ROW_ADDED_DTM');
DECLARE #agg NVARCHAR(MAX) = N'', #piv NVARCHAR(MAX) = N'';
SELECT #agg += ',
' + QUOTENAME(name) + ' = COUNT(DISTINCT '
+ QUOTENAME(name) + ')' FROM #N;
PRINT #agg;
/*
Next we'll build the "pivot" portion (even though I am angling for the poor man's pivot - a bunch of CASE expressions). For each column name we need a conditional against each range, so we can accomplish this by cross joining the list of column names against our labels table. (And we'll use this exact technique again in the query later to make the /* one of past 6 dates */ portion work.
*/
SELECT #piv += ',
COUNT_' + n.name + '_' + l.label
+ ' = MAX(CASE WHEN label = N''' + l.label
+ ''' THEN ' + QUOTENAME(n.name) + ' END)'
FROM #N as n CROSS JOIN #L AS l;
PRINT #piv;
/*
Now, with those two portions populated as we'd like them, we can build a dynamic SQL statement that fills out the rest:
*/
DECLARE #sql NVARCHAR(MAX) = N';WITH y AS
(
SELECT
EMAIL_ADDRESS, l.label' + #agg + '
FROM dbo.WEB_LEADS AS wl
CROSS JOIN #L AS l
WHERE wl.ROW_ADDED_DTM >= l.d
GROUP BY wl.EMAIL_ADDRESS, l.label
)
SELECT EMAIL_ADDRESS' + #piv + '
FROM y
GROUP BY EMAIL_ADDRESS
ORDER BY EMAIL_ADDRESS;';
PRINT #sql;
EXEC sp_executesql #sql;
GO
DROP TABLE #N, #L;
/*
Now again, this is a pretty complex piece of code, and perhaps it can be made easier with PIVOT. But I think even #Bluefeet will write a version of PIVOT that uses dynamic SQL because there is just way too much to hard-code here IMHO.
*/

Dynamic pivot - how to obtain column titles parametrically?

I wish to write a Query for SAP B1 (t-sql) that will list all Income and Expenses Items by total and month by month.
I have successfully written a Query using PIVOT, but I do not want the column headings to be hardcoded like: Jan-11, Feb-11, Mar-11 ... Dec-11.
Rather I want the column headings to be parametrically generated, so that if I input:
--------------------------------------
Query - Selection Criteria
--------------------------------------
Posting Date greater or equal 01.09.10
Posting Date smaller or equal 31.08.11
[OK] [Cancel]
the Query will generate the following columns:
Sep-10, Oct-10, Nov-10, ..... Aug-11
I guess DYNAMIC PIVOT can do the trick.
So, I modified one SQL obtained from another forum to suit my purpose, but it does not work. The error message I get is Incorrect Syntax near 20100901.
Could anybody help me locate my error?
Note: In SAP B1, '[%1]' is an input variable
Here's my query:
/*Section 1*/
DECLARE #listCol VARCHAR(2000)
DECLARE #query VARCHAR(4000)
-------------------------------------
/*Section 2*/
SELECT #listCol =
STUFF(
( SELECT DISTINCT '],[' + CONVERT(VARCHAR, MONTH(T0.RefDate), 102)
FROM JDT1
FOR XML PATH(''))
, 1, 2, '') + ']'
------------------------------------
/*Section 3*/
SET #query = '
SELECT * FROM
(
SELECT
T0.Account,
T1.GroupMask,
T1.AcctName,
MONTH(T0.RefDate) as [Month],
(T0.Debit - T0.Credit) as [Amount]
FROM dbo.JDT1 T0
JOIN dbo.OACT T1 ON T0.Account = T1.AcctCode
WHERE
T1.GroupMask IN (4,5,6,7) AND
T0.[Refdate] >= '[%1]' AND
T0.[Refdate] <= '[%2]'
) S
PIVOT
(
Sum(Amount)
FOR [Month] IN ('+#listCol+')
) AS pvt
'
--------------------------------------------
/*Section 4*/
EXECUTE (#query)
I don't know SAP, but a couple of things spring to mind:
It looks like you want #listCol to contain a collection of numbers within square brackets, for example [07],[08],[09].... However, your code appears not to put a [ at the start of this string.
Try replacing the lines
T0.[Refdate] >= '[%1]' AND
T0.[Refdate] <= '[%2]'
with
T0.[Refdate] >= ''[%1]'' AND
T0.[Refdate] <= ''[%2]''
(I also added a space before the AND in the first of these two lines while I was editing your question.)