L'esecuzione di una query su dati CSV archiviati in una colonna ntext
-
23-10-2019 - |
Domanda
Dire che il testo grezzo delle esportazioni CSV e un timestamp associati vengono memorizzati in un database, in cui un record equivale a un export.
Qualcuno ha un modo per eseguire una query sul file CSV memorizzato in quel campo , senza creare una seconda connessione al database o esportare i dati in un file e poi riaprire usando il testo csv autista?
Si supponga che:
1) non è possibile scrivere un file fisico sul server nella soluzione
2) non è possibile una seconda connessione al server w / OPENROWSET (server, i nomi utente e le password cambia)
3) che deve essere una soluzione SQL 100% - deve essere in grado di essere eseguito come un SP
4) che avete solo bisogno di lavorare con un record alla volta -. La soluzione non c'è bisogno di conto per la selezione da più file csv memorizzati nel DB
Soluzione
La mia soluzione sarebbe quella di creare una funzione definita dall'utente che analizzare i dati CSV in una variabile di tabella. Poi, nel SP, recuperare il CSV, passarlo al UDF, quindi eseguire la query sulla variabile di tabella.
In primo luogo, creare un'UDF per restituire un tavolo dal valore CSV (usi CHAR (13) per determinare nuove linee, potrebbe essere necessario essere modificato per operare con i dati):
CREATE FUNCTION [dbo].[fnParseCSV] (@InputString NVARCHAR(MAX), @Delimiter NCHAR(1) = ',')
RETURNS @tbl TABLE (ID int, Val NVARCHAR(64)) AS
BEGIN
declare @singleLine nvarchar(max)
declare @id int
declare @val varchar(64)
WHILE LEN(@InputString) > 0 BEGIN
IF CHARINDEX(char(13), @InputString) > 0 BEGIN
SELECT @singleLine = SUBSTRING(@InputString, 1, CHARINDEX(char(13), @InputString) - 1)
IF CHARINDEX(@Delimiter, @singleline) > 0 BEGIN
SELECT @id = convert(int, SUBSTRING(@singleline, 1, CHARINDEX(@Delimiter, @singleline) - 1))
SELECT @val = RIGHT(@singleline, LEN(@singleline) - CHARINDEX(@Delimiter, @singleline) )
INSERT INTO @tbl (id, val) values (@id, @val)
END
SELECT @InputString = RIGHT(@InputString, LEN(@InputString) - CHARINDEX(char(13), @InputString) )
END
ELSE
BEGIN
IF CHARINDEX(@Delimiter, @inputString) > 0
BEGIN
SELECT @id = convert(int, SUBSTRING(@inputString, 1, CHARINDEX(@Delimiter, @inputString) - 1))
SELECT @val = RIGHT(@inputString, LEN(@inputString) - CHARINDEX(@Delimiter, @inputString) )
INSERT INTO @tbl (id, val) values (@id, @val)
END
set @inputString = ''
END
END
RETURN
END
Quindi eseguire la query che in uscita:
select * from dbo.fnParseCsv('123,val1' + char(13) + '456,val2' + CHAR(13) + '789,val3', ',')
Altri suggerimenti
È possibile impostare una serie di funzioni definite dall'utente, che in grado di analizzare attraverso la colonna. Sarebbe probabilmente essere lento e non sarebbe affidabile a tutti.
Per fare un esempio se (con nessun controllo vero e proprio errore, ecc e solo in minima parte testato):
IF OBJECT_ID('dbo.Test_CSV_Search') IS NOT NULL
DROP TABLE dbo.Test_CSV_Search
GO
CREATE TABLE dbo.Test_CSV_Search
(
my_id INT IDENTITY NOT NULL,
txt VARCHAR(MAX) NOT NULL,
CONSTRAINT PK_Test_CSV_Search PRIMARY KEY CLUSTERED (my_id)
)
GO
INSERT INTO dbo.Test_CSV_Search (txt) VALUES ('11, 12, 13, 14,15,16
21,22, 23,24, 25,26
31,22,33,34,35,36')
GO
IF OBJECT_ID('dbo.Get_CSV_Row') IS NOT NULL
DROP FUNCTION dbo.Get_CSV_Row
GO
CREATE FUNCTION dbo.Get_CSV_Row
(@my_id INT, @col_num SMALLINT, @search_value VARCHAR(100))
RETURNS @results TABLE (row_num INT, row_txt VARCHAR(MAX))
AS
BEGIN
DECLARE
@csv_txt VARCHAR(MAX),
@full_row VARCHAR(MAX),
@start_pos INT,
@end_pos INT,
@col_txt VARCHAR(100),
@cur_col SMALLINT,
@line_start INT,
@line_end INT,
@row_num INT
SELECT @csv_txt = txt + CHAR(10) FROM dbo.Test_CSV_Search WHERE my_id = @my_id
SELECT
@line_start = 1,
@cur_col = 1,
@start_pos = 1,
@row_num = 1
WHILE (CHARINDEX(CHAR(10), @csv_txt, @line_start) > 0)
BEGIN
SELECT
@line_end = CHARINDEX(CHAR(10), @csv_txt, @line_start),
@end_pos = CHARINDEX(',', @csv_txt, @start_pos)
WHILE (@cur_col < @col_num)
BEGIN
SET @start_pos = @end_pos + 1
SET @end_pos = CHARINDEX(',', @csv_txt, @start_pos)
SET @cur_col = @cur_col + 1
END
IF (RTRIM(LTRIM(SUBSTRING(@csv_txt, @start_pos, @end_pos - @start_pos))) = @search_value)
BEGIN
INSERT INTO @results (row_num, row_txt) VALUES (@row_num, RTRIM(LTRIM(SUBSTRING(@csv_txt, @line_start, @line_end - @line_start))))
END
SELECT
@line_start = @line_end + 1,
@start_pos = @line_end + 1,
@cur_col = 1,
@row_num = @row_num + 1
END
RETURN
END
GO
SELECT * FROM dbo.Get_CSV_Row(1, 1, '11')