Parsing a string in SQL with If statement - sql

I have a table with a string in some columns values that tells me if I should delete the row....however this string needs some parsing to understand whether to delete or not.
What is the string: it tells me the recurrence of meetings eg everyday starting 21st march for 10 meetings.
My table is a single column called recurrence:
Recurrence
-------------------------------
daily;1;21/03/2015;times;10
daily;1;01/02/2016;times;8
monthly;1;01/01/2016;times;2
weekly;1;21/01/2016;times;4
What to do: if the meetings are finished then remove the row.
The string is of the following format
<frequency tag>;<frequency number>;<start date>;times;<no of times>
For example
daily;1;21/03/2016;times;10
everyday starting 21st march, for 10 times
Does anybody know how I would calculate if the string indicates all meetings are in past? I want a select statement that tells me if the recurrence values are in past - true or false

I added one string ('weekly;1;21/05/2016;times;4') that definitely must not be deleted to show some output. At first try to add to temp table `#table1' all data from your table and check if all is deleted well.
DECLARE #table1 TABLE (
Recurrence nvarchar(max)
)
DECLARE #xml xml
INSERT INTO #table1 VALUES
('daily;1;21/03/2016;times;10'),
('daily;1;21/03/2015;times;10'),
('daily;1;01/02/2016;times;8'),
('monthly;1;01/01/2016;times;2'),
('weekly;1;21/01/2016;times;4'),
('weekly;1;21/05/2016;times;4')
SELECT #xml= (
SELECT CAST('<s><r>' + REPLACE(Recurrence,';','</r><r>') + '</r><r>'+ Recurrence+'</r></s>' as xml)
FROM #table1
FOR XML PATH ('')
)
;WITH cte as (
SELECT t.v.value('r[1]','nvarchar(10)') as how,
t.v.value('r[2]','nvarchar(10)') as every,
CONVERT(date,t.v.value('r[3]','nvarchar(10)'),103) as since,
t.v.value('r[4]','nvarchar(10)') as what,
t.v.value('r[5]','int') as howmany,
t.v.value('r[6]','nvarchar(max)') as Recurrence
FROM #xml.nodes('/s') as t(v)
)
DELETE t
FROM #table1 t
LEFT JOIN cte c ON c.Recurrence=t.Recurrence
WHERE
CASE WHEN how = 'daily' THEN DATEADD(day,howmany,since)
WHEN how = 'weekly' THEN DATEADD(week,howmany,since)
WHEN how = 'monthly' THEN DATEADD(month,howmany,since)
ELSE NULL END < GETDATE()
SELECT * FROM #table1
Output:
Recurrence
-----------------------------
weekly;1;21/05/2016;times;4
(1 row(s) affected)

Related

SQL Insert Set of Values Optimized

The goal is to create a table with sample data in Teradata for a year. A way to do it, is to copy the first 6 entries of similar data and just alter the timestamp 365 times (for my usecase).
I didn't know better and wrote a procedure
REPLACE PROCEDURE stackoverflow()
BEGIN
DECLARE i INTEGER DEFAULT 0;
DELETE FROM TestTable;
WHILE i < 365 DO
INSERT INTO TestTable
SELECT
TestName
,Name_DT + i
FROM
(SELECT TOP 6
*
FROM TestTable2
WHERE Name_DT = '2021-12-15') AS sampledata;
SET i = i + 1;
END WHILE;
END;
This works, but is awfully slow. Also the IT department doesn't want us to use procedures. Is there a better way to achieve the same result without a procedure?
The generic way to get repeated data is a CROSS JOIN:
SELECT
TestName
,calendar_date
FROM
( SELECT TOP 6 *
FROM TestTable2
WHERE Name_DT = DATE '2015-12-15'
) AS sampledata
CROSS JOIN
( SELECT calendar_date
FROM sys_calendar.CALENDAR
WHERE calendar_date BETWEEN DATE '2011-12-15'
AND DATE '2011-12-15' + 364
) AS cal
;
In your case there's Teradata's proprietary EXPAND ON syntax to create time series:
SELECT TestName, Begin(pd)
FROM
( SELECT TOP 6 *
FROM TestTable2
WHERE Name_DT = DATE '2015-12-15'
) AS sampledata
-- create one row per day in the range
EXPAND ON PERIOD(Name_DT, Name_DT +365) AS pd

SQL query to match entire sentence against keywords

I am trying to implement faq section on the website, where after will ask a question, the whole sentence will be matched again list of keywords and if any matched will be found, this will be returned back to user.
The database is running on MS SQL 2014,
+----+----------------------------------+----------------------------------------------------------+
| ID | Keywords | Answer |
+----+----------------------------------+----------------------------------------------------------+
| 1 | opening, open, hour, hours, time | We are open from 9AM to 6PM every day, Monday to Sunday. |
+----+----------------------------------+----------------------------------------------------------+
In this case, let's assume user will ask following question:
What time are you open?
This will be matched against the Keywords, as the 'time' is used in question and is among keywords, the first answer will be returned.
I would prefer to avoid using like for every single word in sentence if possible.
I tried using contains, but this doesn't work well with whole sentence:
SELECT * FROM FAQ
WHERE CONTAINS(Keywords,'"What time are you open?"');
http://sqlfiddle.com/#!6/895e5/1
Any help would be appreciated.
I suggest normalize your table and try this one
--create temporary table and populate data
Declare #keywordTable as TAble (ID INT, Keyword varchar(100))
declare #AnswerTable as table (ID int, Answer nvarchar(max))
declare #question nvarchar(max) = 'What time are you open?'
SET #question = REPLACE(#question,'?','')
INSERT #keywordTable
values
(1,'opening'),
(1,'open'),
(1,'hours'),
(1,'hour'),
(1,'time'),
(2,'keyword2'),
(2,'second')
insert #AnswerTable
values (1, 'We are open from 9AM to 6PM every day, Monday to Sunday.'),
(2, 'second question')
display data table
SELECT * FROM #keywordTable
ID Keyword 1 opening 1 open 1 hours 1 hour
1 time 2 keyword2 2 second
SELECT * FROM #AnswerTable
ID Answer 1 We are open from 9AM to 6PM every day, Monday to
Sunday. 2 second question
use function to split the question by words
ALTER FUNCTION [MDM].[fn_SplitString]
(
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #output TABLE(splitdata NVARCHAR(MAX)
)
BEGIN
DECLARE #start INT, #end INT
SELECT #start = 1, #end = CHARINDEX(#delimiter, #string)
WHILE #start < LEN(#string) + 1 BEGIN
IF #end = 0
SET #end = LEN(#string) + 1
INSERT INTO #output (splitdata)
VALUES(SUBSTRING(#string, #start, #end - #start))
SET #start = #end + 1
SET #end = CHARINDEX(#delimiter, #string, #start)
END
RETURN
END
This is the result of split function
SELECT * FROM [MDM].[fn_SplitString](#question,' ')
splitdata What time are you open
Final Query
SELECT Answer from #AnswerTable where ID in (select ID FROM #keywordTable where keyword in (SELECT * FROM [MDM].[fn_SplitString](#question,' ')))
Final Result
Answer We are open from 9AM to 6PM every day, Monday to Sunday.
Well one way could be break down the comma separated keyword into different rows and match them with the sentence.
And you can use any standard splitter. See this link here. I've used XML based because most likely your input should not contain any valid character.
I've also added multiple matching to SQL. This way if more than one answer has same keyword, the code will pick up the one with most keywords.
See working demo
declare #question varchar(max)
set #question='What time are you open?'
create table t ( ID int,Keywords varchar(max), Answer varchar(max));
insert into t
values
( 1,'opening, open, hour, hours, time','We are open from 9AM to 6PM every day, Monday to Sunday.'),
(2,'open,weekends', 'No we don''t!')
SELECT TOP 1
Max(Answer) as Answer
FROM t outer apply (
SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
FROM
(
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(Keywords, ',', '</i><i>')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
)T1
WHERE #question like '%'+Item+'%'
group by Id
order by count(1) desc

How to get name of column for use in SELECT clause

I have a table in an Access DB that has columns for each of the 12 months of the year. The columns have names of "1" to "12". That is to say, the names of the columns are just the number of the month it represents. The columns contain numbers as data, and I need to sum the columns for the months remaining in the year. For example, right now we're in September, so I'd need the SELECT clause to sum the values in columns (months) 9 through 12. This must be able to dynamically sum up the relevant months, so that next month (Oct) Sep will be excluded and only 10 through 12 are summed.
How can I reference the name of the column in the SELECT clause to be able to perform a test on it. I need something like the following:
IIf(Table1.[1].ColumnName >= Month(Now), Table1.[1], 0)
+ IIf(Table1.[2].ColumnName >= Month(Now), Table1.[2], 0)
...
+ IIf(Table1.[12].ColumnName >= Month(Now), Table1.[12], 0)
This would be one approach, but, in passing, if there's a better way to do this, please let me know as well.
I've seen other posts on SO that discuss returning all column names for a table. That is not what I need here. I need to return the column name and perform tests on it within a SELECT clause. Thanks.
EDIT
I understand that this structure (having the data across 12 different columns) is not the greatest. This is not a new DB that I'm setting up. It is an old DB that I've inherited, so I can't make changes to the structure.
Not sure I understood your question, but it would seem the major issue is the fact that you store values in different columns instead of something like .
One solution could be to use union in a subquery to make the columns available to the outer select and use that to filter, sum and group by.
If you have a table looking like:
id (number, primary key), 1..12 (number, month values)
Then a query like this should work (this is for month 1-5 only):
SELECT id, SUM(MonthValue) AS SumFutureMonths
FROM (
SELECT 1 AS MonthNo, id, YourTable.[1] AS MonthValue FROM YourTable
union all
SELECT 2 AS MonthNo, id, YourTable.[2] AS MonthValue FROM YourTable
union all
SELECT 3 AS MonthNo, id, YourTable.[3] AS MonthValue FROM YourTable
union all
SELECT 4 AS MonthNo, id, YourTable.[4] AS MonthValue FROM YourTable
union all
SELECT 5 AS MonthNo, id, YourTable.[5] AS MonthValue FROM YourTable
)
WHERE MonthNo > MONTH(NOW())
GROUP BY id
Whether it would perform well I can't say - it depends on your data, but the retrieving all data in the table for a union can be a costly operation. Anyway, please try it out to see if it works.
I don't know if this works in Access; but in MS SQL you can write a string and execute it..
For example
#sqlStr = "Select 9, 10, 11, 12 from table"
Exec(#sqlStr)
And a string would be fairly easy to manipulate to contain relevant info..
But another question - why would you have all your data in each column instead of different rows? That way you could really easily get the data you want and show it the way you would want to..
UPDATE:
DECLARE #i int
SET #i = MONTH(getdate())
DECLARE #str nvarchar(max)
SET #str = 'SELECT '
WHILE #i <= 12
BEGIN
SET #str = #str + cast(#i as nvarchar(max))+ ','
SET #i = #i + 1
END
SET #str = LEFT(#str, len(#str) - 1) + ' FROM TABLE'
Exec(#str)

Remove a sentence from a paragraph that has a specific pattern with T-SQL

I have a large number of descriptions that can be anywhere from 5 to 20 sentences each. I am trying to put a script together that will locate and remove a sentence that contains a word with numbers before or after it.
before example: Hello world. Todays department has 345 employees. Have a good day.
after example: Hello world. Have a good day.
My main problem right now is identifying the violation.
Here "345 employees" is what causes the sentence to be removed. However, each description will have a different number and possibly a different variation of the word employee.
I would like to avoid having to create a table of all the different variations of employee.
JTB
This would make a good SQL Puzzle.
Disclaimer: there are probably TONS of edge cases that would blow this up
This would take a string, split it out into a table with a row for each sentence, then remove the rows that matched a condition, and then finally join them all back into a string.
CREATE FUNCTION dbo.fn_SplitRemoveJoin(#Val VARCHAR(2000), #FilterCond VARCHAR(100))
RETURNS VARCHAR(2000)
AS
BEGIN
DECLARE #tbl TABLE (rid INT IDENTITY(1,1), val VARCHAR(2000))
DECLARE #t VARCHAR(2000)
-- Split into table #tbl
WHILE CHARINDEX('.',#Val) > 0
BEGIN
SET #t = LEFT(#Val, CHARINDEX('.', #Val))
INSERT #tbl (val) VALUES (#t)
SET #Val = RIGHT(#Val, LEN(#Val) - LEN(#t))
END
IF (LEN(#Val) > 0)
INSERT #tbl VALUES (#Val)
-- Filter out condition
DELETE FROM #tbl WHERE val LIKE #FilterCond
-- Join back into 1 string
DECLARE #i INT, #rv VARCHAR(2000)
SET #i = 1
WHILE #i <= (SELECT MAX(rid) FROM #tbl)
BEGIN
SELECT #rv = IsNull(#rv,'') + IsNull(val,'') FROM #tbl WHERE rid = #i
SET #i = #i + 1
END
RETURN #rv
END
go
CREATE TABLE #TMP (rid INT IDENTITY(1,1), sentence VARCHAR(2000))
INSERT #tmp (sentence) VALUES ('Hello world. Todays department has 345 employees. Have a good day.')
INSERT #tmp (sentence) VALUES ('Hello world. Todays department has 15 emps. Have a good day. Oh and by the way there are 12 employees somewhere else')
SELECT
rid, sentence, dbo.fn_SplitRemoveJoin(sentence, '%[0-9] Emp%')
FROM #tmp t
returns
rid | sentence | |
1 | Hello world. Todays department has 345 employees. Have a good day. | Hello world. Have a good day.|
2 | Hello world. Todays department has 15 emps. Have a good day. Oh and by the way there are 12 employees somewhere else | Hello world. Have a good day. |
I've used the split/remove/join technique as well.
The main points are:
This uses a pair of recursive CTEs, rather than a UDF.
This will work with all English sentence endings: . or ! or ?
This removes whitespace to make the comparison for "digit then employee" so you don't have to worry about multiple spaces and such.
Here's the SqlFiddle demo, and the code:
-- Split descriptions into sentences (could use period, exclamation point, or question mark)
-- Delete any sentences that, without whitespace, are like '%[0-9]employ%'
-- Join sentences back into descriptions
;with Splitter as (
select ID
, ltrim(rtrim(Data)) as Data
, cast(null as varchar(max)) as Sentence
, 0 as SentenceNumber
from Descriptions -- Your table here
union all
select ID
, case when Data like '%[.!?]%' then right(Data, len(Data) - patindex('%[.!?]%', Data)) else null end
, case when Data like '%[.!?]%' then left(Data, patindex('%[.!?]%', Data)) else Data end
, SentenceNumber + 1
from Splitter
where Data is not null
), Joiner as (
select ID
, cast('' as varchar(max)) as Data
, 0 as SentenceNumber
from Splitter
group by ID
union all
select j.ID
, j.Data +
-- Don't want "digit+employ" sentences, remove whitespace to search
case when replace(replace(replace(replace(s.Sentence, char(9), ''), char(10), ''), char(13), ''), char(32), '') like '%[0-9]employ%' then '' else s.Sentence end
, s.SentenceNumber
from Joiner j
join Splitter s on j.ID = s.ID and s.SentenceNumber = j.SentenceNumber + 1
)
-- Final Select
select a.ID, a.Data
from Joiner a
join (
-- Only get max SentenceNumber
select ID, max(SentenceNumber) as SentenceNumber
from Joiner
group by ID
) b on a.ID = b.ID and a.SentenceNumber = b.SentenceNumber
order by a.ID, a.SentenceNumber
One way to do this. Please note that it only works if you have one number in all sentences.
declare #d VARCHAR(1000) = 'Hello world. Todays department has 345 employees. Have a good day.'
declare #dr VARCHAR(1000)
set #dr = REVERSE(#d)
SELECT REVERSE(RIGHT(#dr,LEN(#dr) - CHARINDEX('.',#dr,PATINDEX('%[0-9]%',#dr))))
+ RIGHT(#d,LEN(#d) - CHARINDEX('.',#d,PATINDEX('%[0-9]%',#d)) + 1)

selecting max date in range, excluding multiple other date ranges

my first time posting.
I have a tricky task of finding the latest date within a range, but excluding multiple other date ranges. I have code that does work, but it seems awfully taxing.
I am selecting the MAX(Date) within a range. However, I have a table, bfShow, where each show has its own date-range (stored as DateStart and DateEnd). So I need the MAX(Date) within the range which does NOT have a show on that date (there may be 0 to 99 shows overlapping my date-range).
Note: I have dbo.fnSeqDates which works great (found via Google) and returns all dates within a range - makes for very fast filling in 6/1/12, 6/2/12, 6/3/12...6/30/12, etc.
What I'm doing (below) is creating a table with all the dates (within range) in it, then find all the Shows within that range (#ShowIDs) and iterate through those shows, one at a time, deleting all those dates (from #DateRange). Ultimately, #DateRange is left with only "empty" dates. Thus, the MAX(Date) remaining in #DateRange is my last date in the month without a show.
Again, my code below does work, but there's got to be a better way. Thoughts?
Thank you,
Todd
CREATE procedure spLastEmptyDate
#DateStart date
, #DateEnd date
as
begin
-- VARS...
declare #ShowID int
declare #EmptyDate date
-- TEMP TABLE...
create table #DateRange(dDate date)
create table #ShowIDs(ShowID int)
-- LOAD ALL DATES IN RANGE (THIS MONTH-ISH)...
insert into #DateRange(dDate)
select SeqDate
from dbo.fnSeqDates(#DateStart, #DateEnd)
-- LOAD ALL SHOW IDs IN RANGE (THIS MONTH-IS)...
insert into #ShowIDs(ShowID)
select s.ShowID
from bfShow s
where s.DateStart = #DateStart
-- PRIME SHOW ID...
set #ShowID = 0
select #ShowID = min(ShowID)
from #ShowIDs
-- RUN THRU ALL, REMOVING DATES AS WE GO...
while (#ShowID > 0)
begin
-- REMOVE FROM TEMP...
delete DR
from #DateRange DR
, bfShow s
where DR.dDate between s.DateStart and s.DateEnd
and s.ShowID = #ShowID
-- DROP THAT ONE FROM TEMP...
delete from #ShowIDs
where ShowID = #ShowID
-- GET NEXT ID...
set #ShowID = 0
select #ShowID = min(ShowID)
from #ShowIDs
end
-- GET LAST EMPTY SPOT...
select #EmptyDate = max(dDate)
from #DateRange
-- CLEAN UP...
drop table #DateRange
drop table #ShowIDs
-- RETURN DATA...
select #EmptyDate as LastEmptyDateInRange
end
Let us know what version of SQL Server you're on because that will help determine your options, but you should be able to use the BETWEEN operator in a JOIN between the fnSeqDates function (it's a table-valued function, so you can join to it directly rather than inserting them into a temp table) and the bfShow tables:
SELECT TOP 1 tDate.SeqDate
FROM dbo.fnSeqDates('6/1/2012', '6/30/2012') tDate
LEFT JOIN bfShow tShow
ON tDate.SeqDate BETWEEN tShow.DateStart AND tShow.DateEnd
WHERE tShow.ShowID IS NULL -- no matches found
ORDER BY tDate.SeqDate DESC -- to pull the most recent date
Okay, I thought I'd re-phrase the question, and try to expose some edge cases. I'm not using your function at all. If this isn't right, can you give an example where it fails?
create table bfShow (
DateStart date,
DateEnd date
)
go
CREATE procedure spLastEmptyDate
#DateStart date
, #DateEnd date
as
--Return #DateEnd, or, if that is within a show, find the contiguous
--region of shows covering it, and select the day before that
;with ShowsCovering as (
select DateStart,DateEnd from bfShow where DateStart <= #DateEnd and DateEnd >= #DateEnd
union all
select s1.DateStart,s2.DateEnd
from
bfShow s1
inner join
ShowsCovering s2
on
s1.DateStart < s2.DateStart and
(
--This join would be helped by an indexed computed column on bfShow, either Start-1 or End+1
s1.DateEnd >= s2.DateStart or
s1.DateEnd = DATEADD(day,-1,s2.DateStart)
)
where
s2.DateStart > #DateStart
), Earliest as (
select MIN(DateStart) as MinDate from ShowsCovering
)
--1) If there are no rows, the answer is #DateEnd
--2) If there are rows, and the MIN(DateStart) = #DateStart, then no day exists
--3) Otherwise, the answer is MIN(DateStart)-1
, Answer as (
select #DateEnd as Result where exists(select * from Earliest where MinDate is null)
union all
select DATEADD(day,-1,MinDate) from Earliest where MinDate > #DateStart
)
select Result from Answer
go
insert into bfShow(DateStart,DateEnd)
values ('20120601','20120612'),
('20120619','20120630')
go
exec spLastEmptyDate '20120601','20120625'
--Result = 2012-06-18
go
exec spLastEmptyDate '20120525','20120625'
--Result = 2012-06-18
go
exec spLastEmptyDate '20120601','20120705'
--Result = 2012-07-05
go
insert into bfShow(DateStart,DateEnd)
values ('20120613','20120618')
go
exec spLastEmptyDate '20120601','20120625'
--Result - no rows
By the way, in your current solution, these lines:
drop table #DateRange
drop table #ShowIDs
Are unnecessary. Temp tables created within a stored procedure are automatically dropped when the stored procedure exits. So you can avoid the little dance at the end and make the last line just select max(dDate) as LastEmptyDateInRange from #DateRange, if you want to continue using your solution.