Extract multiple strings from a free text field - sql

Let's say I have a free text field called 'Note' and contains "ABC:5/52 , *back, orders received"
How do I extract '5/52' and 'back' and place them in two separate columns?
Here's what I wanted to achieve
QUERY:-
SELECT *, SUBSTRING(Note, CHARINDEX(':', Note)+1, 4) as ABC,
SUBSTRING(Note, CHARINDEX('*', Note)+1, 4) as Ret_Stat
, CHARINDEX(':', Note) AS [Colon Index]
FROM [AdventureWorks2012].[Sales].[Comments]
RESULT:-
Note ABC Ret_Stat
ABC:3/52, To give more explanation, *back 3/52 back
ABC:3wks, To debrief, *back, r/v 3wks back
ABC:13/09/16, see cm, *back, new referral 13/0 back
My issue is i wanted to extract 3/52, 3wks, and 13/09/16 but my end result's only 13/10.
I'd like to ask how to achieve this? as the condition of extraction may vary from 4 to 8 characters after ABC: and the table contains thousands of rows of data
Need advice. THank you.

Here's an example of what you want to do. You may have to modify this a little as I don't have a lot of sample data to go on.
Test Data
IF OBJECT_ID('tempdb..#TempData') IS NOT NULL DROP TABLE #TempData
GO
CREATE TABLE #TempData (Notes varchar(100))
INSERT INTO #TempData (Notes)
VALUES
('This is the first "ABC:5/52 string *back, orders received')
,('*back, orders receivedThis"ABC:5/52 string is the second one')
,('You guessed it, this *back, orders received is the third "ABC:5/52 string')
Query
SELECT
CHARINDEX('*',Notes) AsteriskLocation
,SUBSTRING(Notes,CHARINDEX('*',Notes)+1,4) AfterAsterisk
,CHARINDEX(':',Notes) ColonLocation
,SUBSTRING(Notes,CHARINDEX(':',Notes)+1,4) AfterColon
FROM #TempData
Result
AsteriskLocation AfterAsterisk ColonLocation AfterColon
36 back 23 5/52
1 back 31 5/52
22 back 62 5/52
I've left the locations separately so that you can see how they're used in the query. You could search for strings too using the same method.

Related

Create a hardcoded "mapping table" in Trino SQL

I have a query (several CTEs) that get data from different sources. The output has a column name, but I would like to map this nameg to a more user-friendly name.
Id
name
1
buy
2
send
3
paid
I would like to hard code somewhere in the query (in another CTE?) a mapping table. Don't want to create a separate table for it, just plain text.
name_map=[('buy', 'Item purchased'),('send', 'Parcel in transit'), ('paid', 'Charge processed')]
So output table would be:
Id
name
1
Item purchased
2
Parcel in transit
3
Charge processed
In Trino I see the function map_from_entries and element_at, but don't know if they could work in this case.
I know "case when" might work, but if possible, a mapping table would be more convenient.
Thanks
As a simpler alternative to the other answer, you don't actually need to create an intermediate map using map_from_entries and look up values using element_at. You can just create an inline mapping table with VALUES and use a regular JOIN to do the lookups:
WITH mapping(name, description) AS (
VALUES
('buy', 'Item purchased'),
('send', 'Parcel in transit'),
('paid', 'Charge processed')
)
SELECT description
FROM t JOIN mapping ON t.name = mapping.name
(The query assumes your data is in a table named t that contains a column named name to use for the lookup)
Super interesting idea, and I think I got it working:
with tmp as (
SELECT *
FROM (VALUES ('1', 'buy'),
('2', 'send'),
('3', 'paid')) as t(id, name)
)
SELECT element_at(name_map, name) as name
FROM tmp
JOIN (VALUES map_from_entries(
ARRAY[('buy', 'Item purchased'),
('send', 'Parcel in transit'),
('paid', 'Charge processed')])) as t(name_map) ON TRUE
Output:
name
Item purchased
Parcel in transit
Charge processed
To see a bit more of what's happening, we can look at:
SELECT *, element_at(name_map, name) as name
id
name
name_map
name
1
buy
{buy=Item purchased, paid=Charge processed, send=Parcel in transit}
Item purchased
2
send
{buy=Item purchased, paid=Charge processed, send=Parcel in transit}
Parcel in transit
3
paid
{buy=Item purchased, paid=Charge processed, send=Parcel in transit}
Charge processed
I'm not sure how efficient this is, but it's certainly an interesting idea.

Completely Unique Rows and Columns in SQL

I want to randomly pick 4 rows which are distinct and do not have any entry that matches with any of the 4 chosen columns.
Here is what I coded:
SELECT DISTINCT en,dialect,fr FROM words ORDER BY RANDOM() LIMIT 4
Here is some data:
**en** **dialect** **fr**
number SFA numero
number TRI numero
hotel CAI hotel
hotel SFA hotel
I want:
**en** **dialect** **fr**
number SFA numero
hotel CAI hotel
Some retrieved rows would have something similar with each other, like having the same en or the same fr, I would like to retrieved rows that do not share anything similar with each other, how do I do that?
I think I’d do this in the front end code rather the dB, here’s a pseudo code (don’t know what your node looks like):
var seenEn = “en not in (''“;
var seenFr = “fr not in (''“;
var rows =[];
while(rows.length < 4)
{
var newrow = sqlquery(“SELECT *
FROM table WHERE “ + seenEn + “) and ”
+ seenFr + “) ORDER BY random() LIMIT 1”);
if(!newrow)
break;
rows.push(newrow);
seenEn += “,‘“+ newrow.en + “‘“;
seenFr += “,‘“+ newrow.fr + “‘“;
}
The loop runs as many times as needed to retrieve 4 rows (or maybe make it a for loop that runs 4 times) unless the query returns null. Each time the query returns the values are added to a list of values we don’t want the query to return again. That list had to start out with some values (null) that are never in the data, to prevent a syntax error when concatenation a comma-value string onto the seenXX variable. Those syntax errors can be avoided in other ways like having a Boolean of “if it’s the first value don’t put the comma” but I chose to put dummy ineffective values into the sql to make the JS simpler. Same goes for the
As noted, it looks like JS to ease your understanding but this should be treated as pseudo code outlining a general algorithm - it’s never been compiled/run/tested and may have syntax errors or not at all work as JS if pasted into your file; take the idea and work it into your solution
Please note this was posted from an iphone and it may have done something stupid with all the apostrophes and quotes (turned them into the curly kind preferred by writers rather than the straight kind used by programmers)
You can use Rank or find first row for each group to achieve your result,
Check below , I hope this code will help you
SELECT 'number' AS Col1, 'SFA' AS Col2, 'numero' AS Col3 INTO #tbl
UNION ALL
SELECT 'number','TRI','numero'
UNION ALL
SELECT 'hotel','CAI' ,'hotel'
UNION ALL
SELECT 'hotel','SFA','hotel'
UNION ALL
SELECT 'Location','LocationA' ,'Location data'
UNION ALL
SELECT 'Location','LocationB','Location data'
;
WITH summary AS (
SELECT Col1,Col2,Col3,
ROW_NUMBER() OVER(PARTITION BY p.Col1 ORDER BY p.Col2 DESC) AS rk
FROM #tbl p)
SELECT s.Col1,s.Col2,s.Col3
FROM summary s
WHERE s.rk = 1
DROP TABLE #tbl

SQL - Get value for a specific element in array

I asked this question few days ago, but it involves a bit deeper answer so it was suggested I create a brand new one, so here it goes...
Disclaimer: I cannot create any custom DB objects (functions, SP's, views etc.), so everything needs to be in-line inside a SQL query.
I'm querying Audit table which for the simplicity of this question has following fields:
AttributeMask
ChangedData
CreatedOn
ObjectId
Each record in a DB may have multiple Audit records associated with it. Every time a change is made to a DB record, it will create a record in the Audit table with specific ObjectID that will point to the source record, CreatedOn that will have a DateTime of the change, AttributeMask with list of AttributeId's that have been changed when SAVE was executed (note, there may be multiple fields changed at once) and ChangedData will actually have the data that's been changed (pre-changed values). One field can of course be changed multiple times and if it's the case, multiple Audit records for this field will exist (different CreatedOn values). I need to find what some (not all) fields from the source record looked like at a specific date.
I can run query below:
select a1.ChangeData as ChangedData1, a1.AttributeMask as AttributeMask2, a2.ChangeData as ChangedData2, a2.AttributeMask as AttributeMask2
from Table1 t
join audit a1 on a1.AuditId =
(select top 1 a.auditid from audit a where a.objecttypecode = 3
and a.objectid = T.ObjectId
and a.AttributeMask like '%,10192,%'
and a.CreatedOn <= '8-16-2018'
order by a1.CreatedOn desc)
join audit a2 on a2.AuditId =
(select top 1 a.auditid from audit a where a.objecttypecode = 3
and a.objectid = T.ObjectId
and a.AttributeMask like '%,10501,%'
and a.CreatedOn <= '8-16-2018'
order by a1.CreatedOn desc)
where t.ObjectID = SomeGuidValue
This query is looking for the latest change to 2 fields (10192 and 10501) which happened before 8-16-2018. It returns the following data (I added 3rd record to illustrate all possible cases):
ChangeData1 AttributeMask1 ChangeData2 AttributeMask2
NULL NULL True~~True~1904~~~15.8700000000~4760~30000~590~12000~0~390~1904~False~200~ ,10499,10604,10501,10436,10491,10490,10459,10099,10319,10253,10433,10031,10091,10020,10265,10008,10509,
~True~5.56~~House~~200000~ ,10030,10432,10435,197,10099,10192,198, False~1170~600~0~Complete~True~1770~ ,10501,10091,10008,10020,10570,10499,10253,10715,
~~~~200001~ ,10432,10435,197,10099,10192,198, True~2~True~~0~~~100.0000000000~1~business,96838c4f-e63c-e011-9a14-78e7d1644f78~~0~~~~0~False~~1~ ,10499,10509,10501,10203,10436,10491,10490,10459,10099,10157,10253,10433,10715,10031,10091,10020,10265,10008,10319,10699,
This means that 1st record has change to field 10501 only, 2nd record has change to 10192 only and 3rd record has changes to both 10192 and 10501 fields.
AttributeMask field has comma delimited list of all FieldID's that have been changed (note that it starts and ends with comma).
ChangedData field has tilde (~) delimited list of data that's been changed. Each entry in AttributeMask corresponds to entry in ChangedData. For example, if I wanted to see what data was in 10501 field in 1st record, I would need to determine what entry # 10501 is in AttributeMask field (it's #3 in the list) and then I would need to find out what data is in entry #3 in ChangedData field (it's TRUE) and if I wanted to see what was in 2nd record for Field 10192 I'd see what index it has in AttributeMask (it's #6) and its corresponding value in ChangedData field is 2000000.
I need to somehow extract this data in the same query. I was helped with some samples on how this could be done, but I failed to ask the right question in the beginning (thought it would be simpler than explaining all this).
What I need this query to return is something like this:
ChangeData1 AttributeMask1 ChangeData2 AttributeMask2
NULL NULL TRUE 10501
200000 10192 FALSE 10501
200001 10192 TRUE 10501
I hope this is clear now.
As told in my comments you are better off to deal with a set, then working with a broader and broader list with name-numbered columns.
Try to provide your initial input set in the format like the following mockup table:
There is a running ID, your ObjectID, the code you are looking for and the both strings. I inserted the data as provided by you, but not side-by-side:
DECLARE #tbl TABLE(ID INT IDENTITY, CodeId INT,ObjectId INT, ChangeData VARCHAR(1000), AttributeMask VARCHAR(1000));
INSERT INTO #tbl VALUES
(10192,1,NULL,NULL)
,(10501,1,'True~~True~1904~~~15.8700000000~4760~30000~590~12000~0~390~1904~False~200~',',10499,10604,10501,10436,10491,10490,10459,10099,10319,10253,10433,10031,10091,10020,10265,10008,10509,')
,(10192,2,'~True~5.56~~House~~200000~',',10030,10432,10435,197,10099,10192,198,')
,(10501,2,'False~1170~600~0~Complete~True~1770~',',10501,10091,10008,10020,10570,10499,10253,10715,')
,(10192,3, '~~~~200001~',',10432,10435,197,10099,10192,198,')
,(10501,3,'True~2~True~~0~~~100.0000000000~1~business,96838c4f-e63c-e011-9a14-78e7d1644f78~~0~~~~0~False~~1~',',10499,10509,10501,10203,10436,10491,10490,10459,10099,10157,10253,10433,10715,10031,10091,10020,10265,10008,10319,10699,');
--The query will cast the strings to XML in order to grab into it by their position index
--Then all codes are taken and numbered as derived list.
--According to the found position the corresponding value is taken
SELECT t.ID
,t.ObjectId
,t.CodeId
,t.ChangeData
,t.AttributeMask
,Casted.ValueXml.value('/x[sql:column("PartIndex")][1]','nvarchar(max)') ValueAtCode
FROM #tbl t
CROSS APPLY
(
SELECT CAST('<x>' + REPLACE(t.AttributeMask,',','</x><x>') + '</x>' AS XML).query('/x[text()]') AS CodeXml
,CAST('<x>' + REPLACE(t.ChangeData,'~','</x><x>') + '</x>' AS XML) AS ValueXml
) Casted
CROSS APPLY(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS PartIndex
,x.value('text()[1]','nvarchar(max)') AS CodePart
FROM Casted.CodeXml.nodes('/x') A(x)
) CodeDerived
WHERE CodeDerived.CodePart=t.CodeId;
the result
ID ObjectId CodeId ValueAtCode
2 1 10501 True
3 2 10192
4 2 10501 False
5 3 10192 200001
6 3 10501 True
But this will be slooooow...
UPDATE
Your whole approach is not set-based. The following is completely untested, I don't have your database, but will point to a set-based solution.
DECLARE #Codes TABLE(CodeID INT);
INSERT INTO #Codes VALUES(10192),(10501);
select t.SomeIdOfYourMainTable
,c.CodeID
,a1.ChangeData
,a1.AttributeMask
from Table1 t
CROSS JOIN #Codes c --will repeat the result for each value in #Codes
CROSS APPLY
(
select top 1 a.ChangeData
,a.AttributeMask
from [audit] a
where a.objecttypecode = 3
and a.objectid = t.ObjectId
and a.AttributeMask like CONCAT('%,',c.CodeID,',%')
and a.CreatedOn <= '20180816' --use culture independant format!!!
order by a.CreatedOn desc
) a1;
This allows you to insert as many codes as you want (no need to repeat any join) and it will return a set similar to my example above.
If you need further help with this: Please close this question and start a new question with a fully working, stand-alone MCVE to reproduce your case.

SQL - INSERT text into field WHERE ___

I'm looking to add a note to each of my accounts whenever I make contact via a mass email. I'm looking at something like this so far:
INSERT INTO Customer_notes (Note_text)
VALUES ('emailed June 1st')
WHERE Customer_Level = 'Alpha'
This obviously doesn't work, and UPDATE/SET will replace all of my previous notes.
Well, you could do something like this:
INSERT INTO Customer_notes (Note_text)
Note_Text = COALESCE(Note_text + '
', '') + 'emailed June 1st')
WHERE Customer_Level = 'Alpha';
However, it seems like you need more than one note per customer. So perhaps:
INSERT INTO Customer_notes (Customer_Level, Note_text)
VALUES ('Alpha', Note_Text);
This is assuming that Customer_Level is some sort of customer id.
If you mean add that text to whatever currently exists in Note_text for existing rows, then try this..
UPDATE Customer_notes
SET Note_text = ISNULL(Note_text, '') + ' emailed June 1st'
WHERE Customer_Level = 'Alpha'
{edit based on the fifth comment in this answer}
Ok, then instead of VALUES we are SELECTing off of the same table. Try this (Holy Gawd make a backup first) if the idea is to create another row with EVERY row WHERE Customer_Level = 'Alpha' in this table.
INSERT INTO Customer_Notes (CustomerID, note_text)
SELECT CustomerID, 'emailed June 1st'
FROM CustomerNotes
WHERE Customer_Level = 'Alpha'
This does beg the question if Customer_Level is a subset of all customers, and if the idea here is to only insert one row for EACH of these customers, as opposed to ONE row for each customber/existing rows. You'll have to describe this further for us if this is the case.

How can I take the first and last item of string in sql query result

I have a log table of staff entrance and exit dates and time like below;
11/12/2007 12:23,11/12/2007 21:22,...,11/12/2007 22:24
12/12/2007 09:11,12/12/2007 11:34,...,12/12/2007 17:15
...continues
Number of items are different and all entries are in daily based. Minimum entry will be 2 because of entrance and exit logs.
I want to take only the start and end date from the logs. Please help me about the T-SQL query...
Another Simple Solution of your problem use Left and Right Function
DECLARE #str NVARCHAR(MAX)='11/12/2007 12:23,11/12/2007 21:22,11/12/2007 22:24'
SELECT LEFT(#str, CHARINDEX(',',#str) -1),
Right(#str, CHARINDEX(',', Reverse(#str)) -1)
If you are using Sqlserver 2005 or above, using REVERSE function many times is detrimental to performance, below code is more efficient.
Assume that column name ise logdata and table name is logTable
SELECT
SUBSTRING(logdata, 0, CHARINDEX(',', logdata)) AS FirstItem,
SUBSTRING(logdata, (LEN(logdata) - CHARINDEX(',',REVERSE(logdata))+2), LEN(logdata)) AS LastItem
FROM logTable
You can check the exact solution in this link http://rextester.com/TRGHL10059
Actually, due to the fact that all of the datetime stamps are a fixed number of characters, you could be able to use something as simple as the LEFT & RIGHT functions... (no real need to over complicate things)...
IF OBJECT_ID('tempdb..#MovementLog', 'U') IS NOT NULL
DROP TABLE #MovementLog;
CREATE TABLE #MovementLog (
LogDate DATE NOT NULL,
ClockTimeString VARCHAR(1000) NOT NULL
);
INSERT #MovementLog (LogDate, ClockTimeString) VALUES
('2007-12-11', '11/12/2007 12:23,11/12/2007 21:22,11/12/2007 22:24'),
('2007-12-12', '12/12/2007 09:11,12/12/2007 11:34,12/12/2007 17:15');
--==============================================================================
SELECT
ml.LogDate,
BegDTStamp = LEFT(ml.ClockTimeString, 16),
EndDTStamp = RIGHT(ml.ClockTimeString, 16)
FROM
#MovementLog ml;
results...
LogDate BegDTStamp EndDTStamp
---------- ---------------- ----------------
2007-12-11 11/12/2007 12:23 11/12/2007 22:24
2007-12-12 12/12/2007 09:11 12/12/2007 17:15