У вас есть несколько проблем с дизайном базы данных, которые вы, возможно, уже поняли.
- У вас не должно быть нескольких записей (заметок) в одной записи.
- Вы не должны иметь несколько полей (Дата, Имя пользователя, Комментарий) в одном поле.
Я предполагаю, что вы используете SQL Сервер. Если нет, то это решение должно быть в состоянии адаптироваться к вашей платформе. Я позволил себе добавить столбец ID с именем SomeID .
IF OBJECT_ID('tempdb.dbo.#Notes', 'U') IS NOT NULL
DROP TABLE #Notes;
CREATE TABLE #Notes
(
SomeID INT
, Note VARCHAR(4000)
);
INSERT INTO #Notes
VALUES
(1, '3/4/2020 1:06:30 PM by username
Notes #1
3/4/2020 1:06:41 PM by username
Notes #2')
,
(2
, '3/4/2020 1:16:30 PM by username
Notes #1
3/4/2020 1:23:41 PM by username
Notes #2
3/4/2020 1:32:51 PM by username
Notes #3');
SELECT
*
FROM (
SELECT
y.SomeID
, CONVERT(DATETIME2, y.NoteDateText) AS NoteDate
, REPLACE(y.Line1, y.NoteDateText + ' by ', '') AS UserName
, y.Comment
, ROW_NUMBER() OVER (PARTITION BY y.SomeID
ORDER BY CONVERT(DATETIME2, y.NoteDateText) DESC
) AS RowNumber
FROM (
SELECT
x.SomeID
, LEFT(x.Line1, PATINDEX('%by%', x.Line1) - 2) AS NoteDateText
, x.Line1
, x.Line2 AS Comment
FROM (
SELECT
SomeID
, LEFT(value, PATINDEX('%' + CHAR(13) + CHAR(10) + '%', value) - 1) AS Line1
, RIGHT(value, LEN(value) - PATINDEX('%' + CHAR(13) + CHAR(10) + '%', value) - 1) AS Line2
, value
FROM #Notes
CROSS APPLY STRING_SPLIT(REPLACE(Note, CHAR(13) + CHAR(10) + CHAR(13) + CHAR(10), '~'), '~')
) x
) y
) z
WHERE z.RowNumber = 1
Основная идея c состоит в том, чтобы разделить заметки на отдельные записи с помощью STRING_SPLIT () табличная функция. Поскольку STRING_SPLIT () работает только с разделителем единственного символа, а записи заметок, кажется, разделены двумя наборами возврата каретки (CHAR (13)) и перевода строки (CHAR (10)), я заменил их на '~' , Вы должны использовать некоторый символ, который не будет в тексте ваших записей заметок. Теперь, когда у меня есть разделенные заметки, я могу выделить каждую заметку в соответствующие поля NoteDate, UserName и Comment, используя функции манипуляции со строками.
Наконец, я использовал функцию ROW_NUMBER () , чтобы Сортируйте заметки в порядке убывания по NoteDate для каждого значения SomeID, чтобы я мог затем выбрать только первую строку для каждого SomeID.
Я, конечно, мог бы сократить это далее и не использовать столько уровней подзапросов, но я хочу показать прогрессию и, надеюсь, облегчить следить. Кроме того, я попытался поместить это в SQL Fiddle или dbfiddle, но я продолжал получать следующую ошибку:
Недопустимый параметр длины, переданный в функцию LEFT или SUBSTRING.
Этот код работает в SQL Server Management Studio на SQL Server 2016 или выше.
Вот альтернативный подход, который я получил для работы на SQL Server 2012 server на основе этого блога post .
IF OBJECT_ID('tempdb.dbo.#Notes', 'U') IS NOT NULL
DROP TABLE #Notes;
CREATE TABLE #Notes
(
SomeID INT
, Note VARCHAR(4000)
);
INSERT INTO #Notes
VALUES
(1, '3/4/2020 1:06:30 PM by username
Notes #1
3/4/2020 1:06:41 PM by username
Notes #2')
, (2
, '3/4/2020 1:16:30 PM by username
Notes #1
3/4/2020 1:23:41 PM by username
Notes #2
3/4/2020 1:32:51 PM by username
Notes #3');
SELECT
x6.SomeID
, x6.NoteDate
, x6.UserName
, x6.Comment
FROM (
SELECT
x5.SomeID
, x5.NoteDate
, x5.UserName
, x5.Comment
, ROW_NUMBER() OVER (PARTITION BY x5.SomeID
ORDER BY CONVERT(DATETIME2, x5.NoteDate) DESC
) AS RowNumber
FROM (
SELECT
x4.SomeID
, CONVERT(DATETIME, LEFT(x4.SingleNote, PATINDEX('%by%', x4.SingleNote) - 2)) AS NoteDate
, SUBSTRING(
x4.SingleNote
, PATINDEX('%by%', x4.SingleNote) + 3
, PATINDEX('%' + CHAR(10) + '%', x4.SingleNote)
- (PATINDEX('%by%', x4.SingleNote) + 3)
) AS UserName
, RIGHT(x4.SingleNote, LEN(x4.SingleNote) - PATINDEX('%' + CHAR(10) + '%', x4.SingleNote)) AS Comment
FROM (
SELECT
x2.SomeID
, x2.Note
, LTRIM(RTRIM(m.n.value('.[1]', 'varchar(8000)'))) AS [SingleNote]
FROM (
SELECT
SomeID
, Note
, CAST('<XMLRoot><RowData>'
+ REPLACE(
Note
, CHAR(13) + CHAR(10) + CHAR(13) + CHAR(10)
, '</RowData><RowData>'
) + '</RowData></XMLRoot>' AS XML) AS x1
FROM #Notes
) x2
CROSS APPLY x1.nodes('/XMLRoot/RowData') m(n)
) x4
) x5
) x6
WHERE x6.RowNumber = 1;
Это ужасно, я знаю. И я не уверен, что могу полностью объяснить каждую строчку. Я просто пытаюсь предложить решение как метод обучения самостоятельно. Этот запрос является прекрасным примером того, почему хороший дизайн базы данных имеет значение.