utilizzando * tabelle * come parametri con valori di tabella (TVP)


18

MS SQL 2008 supporta TVP: una funzione utile per il caricamento di massa di dati in una procedura memorizzata per l'elaborazione.

Invece di creare un tipo definito dall'utente, è possibile sfruttare una definizione di tabella esistente? Ad esempio, è possibile creare una procedura memorizzata con la seguente firma?

CREATE PROCEDURE usp_InsertProductionLocation
@TVP **LocationTable** READONLY

La documentazione sembra suggerire che ciò non è possibile.

CODICE D'ESEMPIO

/*
Sample code from:
http://msdn.microsoft.com/en-us/library/bb510489.aspx
*/

USE AdventureWorks2008R2;
GO

/* Create a table type. */
CREATE TYPE LocationTableType AS TABLE 
( LocationName VARCHAR(50)
, CostRate INT );
GO

/* Create a procedure to receive data for the table-valued parameter. */
CREATE PROCEDURE usp_InsertProductionLocation
    @TVP LocationTableType READONLY
    AS 
    SET NOCOUNT ON
    INSERT INTO [AdventureWorks2008R2].[Production].[Location]
           ([Name]
           ,[CostRate]
           ,[Availability]
           ,[ModifiedDate])
        SELECT *, 0, GETDATE()
        FROM  @TVP;
        GO

/* Declare a variable that references the type. */
DECLARE @LocationTVP 
AS LocationTableType;

/* Add data to the table variable. */
INSERT INTO @LocationTVP (LocationName, CostRate)
    SELECT [Name], 0.00
    FROM 
    [AdventureWorks2008R2].[Person].[StateProvince];

/* Pass the table variable data to a stored procedure. */
EXEC usp_InsertProductionLocation @LocationTVP;
GO

/*
The following is not part of the original source code:
*/

CREATE TABLE LocationTable(
 LocationName VARCHAR(50)
, CostRate INT );
GO

Risposte:


16

No, non è possibile sfruttare una definizione di tabella esistente, è necessario definire un tipo esplicito. Questo è stato richiesto nel 2007 ed è stato chiuso perché "non risolverà", ma ti incoraggio ancora fortemente a votare e lasciare un commento che descriva il tuo caso d'uso e come ciò contribuirà a rendere la tua azienda più produttiva. Potresti anche puntare a questa domanda per dimostrare quanto possa essere noioso provare ad automatizzare questo.

Item on UserVoice (Nome precedente: Connect # 294130)

Puoi farlo oggi, in modo dinamico, però ... per esempio per la tua semplice definizione:

-- you would pass these two in as parameters of course:
DECLARE
  @TableName SYSNAME = N'LocationTable',
  @TypeName  SYSNAME = N'LocationTypeTable';

DECLARE @sql NVARCHAR(MAX) = N'';

SELECT @sql = @sql + N',' + CHAR(13) + CHAR(10) + CHAR(9) 
    + QUOTENAME(c.name) + ' '
    + s.name + CASE WHEN LOWER(s.name) LIKE '%char' THEN 
        '(' + CONVERT(VARCHAR(12), (c.max_length/
        (CASE LOWER(LEFT(s.name, 1)) WHEN N'n' THEN 2 ELSE 1 END))) + ')' 
        ELSE '' END
        -- need much more conditionals here for other data types
    FROM sys.columns AS c
    INNER JOIN sys.types AS s
    ON c.system_type_id = s.system_type_id
    AND c.user_type_id = s.user_type_id
    WHERE c.[object_id] = OBJECT_ID(@TableName);

SELECT @sql = N'CREATE TYPE ' + @TypeName
    + ' AS TABLE ' + CHAR(13) + CHAR(10) + '(' + STUFF(@sql, 1, 1, '')
    + CHAR(13) + CHAR(10) + ');';

PRINT @sql;
-- EXEC sp_executesql @sql;

risultati:

CREATE TYPE LocationTypeTable AS TABLE 
(
    [LocationName] varchar(50),
    [CostRate] int
);

Dichiarazione di non responsabilità: questo non riguarda tutti i tipi di altre cose come i tipi MAX, la precisione e la scala per i numeri, ecc. La soluzione finale dovrebbe essere più solida per tenere conto di tutte le potenziali definizioni delle colonne, ma questo dovrebbe darvi un inizio.

In SQL Server 2012 ci sono nuovi DMV e procedure memorizzate che semplificheranno molto il derivare i metadati di colonna dalle tabelle esistenti (o dalle procedure memorizzate o anche dalle query ad hoc) senza dover confondere con tutta la logica condizionale contro sys.types e sys. colonne. Ho scritto un breve blog su questi miglioramenti a dicembre . È ancora noioso, ma è a metà strada tra gli orribili spaghetti non mantenibili sopra e la capacità di dire semplicemente "come copia di [tabella x]" ...


4

Ho affrontato questo problema creando la seguente procedura memorizzata per creare un tipo con lo stesso schema che potrebbe avere la tabella esistente.

Create PROCEDURE [dbo].[Sp_DefineTypeOutOfTableSchema]
@TableNames NVARCHAR(500)
AS
BEGIN

DECLARE @TableName NVARCHAR(100)
DECLARE @strSQL NVARCHAR(max)
DECLARE @strSQLCol NVARCHAR(1000)
DECLARE @ColName NVARCHAR(100)
DECLARE @ColDataTaype NVARCHAR(50)
DECLARE @ColDefault NVARCHAR(50)
DECLARE @ColIsNulable NVARCHAR(50)
DECLARE @ColCharMaxlen NVARCHAR(50)
DECLARE @ColNumPrec NVARCHAR(50)
DECLARE @ColNumScal NVARCHAR(50)


IF LEN(@TableNames) > 0 SET @TableNames = @TableNames + ',' 

WHILE LEN(@TableNames) > 0 
    BEGIN

        SELECT @TableName = LTRIM(SUBSTRING(@TableNames, 1, CHARINDEX(',', @TableNames) - 1))

        DECLARE schemaCur CURSOR FOR 
        SELECT COLUMN_NAME,DATA_TYPE,IS_NULLABLE,COLUMN_DEFAULT,CHARACTER_MAXIMUM_LENGTH,NUMERIC_PRECISION,NUMERIC_SCALE  FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME =@TableName

        OPEN schemaCur

        SELECT @strSQL=''

        FETCH NEXT FROM schemaCur
        INTO @ColName,@ColDataTaype,@ColIsNulable,@ColDefault,@ColCharMaxlen,@ColNumPrec,@ColNumScal

        WHILE @@FETCH_STATUS = 0
        BEGIN
            SELECT @strSQLCol=''
            SELECT @strSQLCol= '['+@ColName+'] '+'[' + @ColDataTaype +'] '

            IF @ColDataTaype='nvarchar' or @ColDataTaype='char'  or @ColDataTaype='varchar' or @ColDataTaype='vchar'
                BEGIN
                    SELECT @strSQLCol=@strSQLCol+ '(' + @ColCharMaxlen +') '
                END 
            ELSE IF @ColDataTaype='numeric' or @ColDataTaype='decimal' 
                BEGIN
                    SELECT @strSQLCol=@strSQLCol +'(' + @ColNumPrec +',' +@ColNumScal + ') '
                END 

            IF @ColIsNulable='YES'
                BEGIN
                    SELECT @strSQLCol=@strSQLCol+ 'NULL '
                END 
            ELSE
                BEGIN
                    SELECT @strSQLCol=@strSQLCol+ ' NOT NULL '
                END 
            IF @ColDefault IS NOT NULL
                BEGIN
                    SELECT @strSQLCol=@strSQLCol+ ' DEFAULT(' +@ColDefault + '),'
                END 
            ELSE
                BEGIN
                    SELECT @strSQLCol=@strSQLCol+ ' ,'
                END

            SELECT @strSQL=@strSQL+@strSQLCol
                --print @strSQL
            FETCH NEXT FROM schemaCur
            INTO @ColName,@ColDataTaype,@ColIsNulable,@ColDefault,@ColCharMaxlen,@ColNumPrec,@ColNumScal

        END

        CLOSE schemaCur
        DEALLOCATE schemaCur

        --print @strSQL
        SELECT @strSQL=left( @strSQL, len(@strSQL)-1)
        --print @strSQL

        IF EXISTS (SELECT * FROM sys.types WHERE IS_TABLE_TYPE = 1 AND name = 't_' +@TableName)
        BEGIN           
            EXEC('DROP TYPE t_' +@TableName )
        END

        SELECT @strSQL =  'CREATE TYPE t_' + @TableName + ' AS TABLE (' +  @strSQL + ')'
        --print @strSQL
        EXEC (@strSQL)
        SELECT @TableNames = SUBSTRING(@TableNames, CHARINDEX(',', @TableNames) + 1, LEN(@TableNames))
END
END

puoi usarlo in questo modo

Exec Sp_DefineTypeOutOfTableSchema 'Table1name, Table2name'


Questo codice non ha funzionato per me. "Le funzioni definite dall'utente, le funzioni di partizione e i riferimenti alle colonne non sono consentite nelle espressioni in questo contesto." Anche se ha funzionato, non sembra che ci sia qualcosa per gestire schemi diversi da dbo.
MgSam,

2

Erland Sommarskog ha un ampio articolo che descrive come usare TVP.

Dai un'occhiata, ne vale la pena!

In breve, non è possibile utilizzare un tipo esistente, proprio come la risposta precedente di Aaron Bertrand . Ma almeno è un trasferimento di massa ..


1

Basato sulla risposta di Jiten, semplificando parte della logica in alcuni punti, tenendo conto delle identitycolonne (usando le sys.tabelle anziché SCHEMEA)

CREATE PROCEDURE [dbo].[usp_DefineTypeFromTable]
@TableNames NVARCHAR(500)
AS
BEGIN

DECLARE @TableName NVARCHAR(100)
DECLARE @strSQL NVARCHAR(max)
DECLARE @strSQLCol NVARCHAR(1000)
DECLARE @ColName NVARCHAR(100)
DECLARE @ColDataTaype NVARCHAR(50)
DECLARE @ColDefault NVARCHAR(50)
DECLARE @ColIsNulable NVARCHAR(50)
DECLARE @ColFirst NVARCHAR(50)
DECLARE @ColSecond NVARCHAR(50)
DECLARE @ColID NVARCHAR(50)
DECLARE @ColCompute NVARCHAR(50)

IF LEN(@TableNames) > 0 SET @TableNames = @TableNames + ',' 
WHILE LEN(@TableNames) > 0 
    BEGIN
        SELECT @TableName = TRIM(LEFT(@TableNames, CHARINDEX(',', @TableNames) - 1))
        DECLARE schemaCur CURSOR FOR 
            SELECT  
                c.name as column_name,
                t.name as [type_name],
                c.is_nullable,
                convert(nvarchar(4000), object_definition(ColumnProperty(c.object_id, c.name, 'default'))) as column_default,
                CASE
                    WHEN c.collation_name IS NOT NULL THEN c.max_length 
                    WHEN t.name like 'datetime%' THEN c.scale
                    WHEN c.scale = 0 THEN NULL
                    ELSE c.precision
                END as firstValue,
                CASE
                    WHEN (c.scale = 0 or t.name like 'datetime%') THEN NULL
                    ELSE c.scale
                END as secondValue,
                c.is_identity, -- would be best to know seed,increment
                c.is_computed -- should really look up col definition. `convert(nvarchar(4000), object_definition(ColumnProperty(c.object_id, c.name, 'computed')))` as computed ?

            FROM sys.columns as c join 
                 sys.all_objects as o 
                    on c.object_id=o.object_id join
                 sys.types as t
                    on c.user_type_id=t.user_type_id
            WHERE
                o.type in ('U','V','TF','IF','TT') and --'S' to include built-in tables/types
                o.name = @TableName
            ORDER BY o.name, c.column_id
        OPEN schemaCur
        SELECT @strSQL=''
        FETCH NEXT FROM schemaCur
            INTO @ColName,@ColDataTaype,@ColIsNulable,@ColDefault,@ColFirst,@ColSecond,@ColID,@ColCompute
        WHILE @@FETCH_STATUS = 0 BEGIN
--            SELECT @strSQLCol=''
            SELECT @strSQLCol= '['+@ColName+'] '+'[' + @ColDataTaype +'] '
            IF @ColSecond is NULL
                BEGIN
                    IF @ColFirst is not NULL SELECT @strSQLCol += '(' + @ColFirst + ') '
                END 
            ELSE SELECT @strSQLCol += '(' + @ColFirst +',' +@ColSecond + ') '
            IF @ColID>0 SELECT @strSQLCol += ' IDENTITY(1,1)'
            IF @ColIsNulable>0 SELECT @strSQLCol += 'NULL'
                ELSE SELECT @strSQLCol += ' NOT NULL'
            IF @ColDefault IS NOT NULL SELECT @strSQLCol += ' DEFAULT(' +@ColDefault + '),'
                ELSE SELECT @strSQLCol += ','
            SELECT @strSQL += @strSQLCol
                --print @strSQL
            FETCH NEXT FROM schemaCur
            INTO @ColName,@ColDataTaype,@ColIsNulable,@ColDefault,@ColFirst,@ColSecond,@ColID,@ColCompute
        END

        CLOSE schemaCur
        DEALLOCATE schemaCur

        SELECT @strSQL=left(@strSQL, len(@strSQL)-1)

        IF EXISTS (SELECT * FROM sys.types WHERE IS_TABLE_TYPE = 1 AND name = 'tt_' +@TableName)
        BEGIN           
            EXEC('DROP TYPE tt_' +@TableName )
        END

        SELECT @strSQL = 'CREATE TYPE tt_' + @TableName + ' AS TABLE (' +  @strSQL + ')'
        -- print @strSQL
        EXEC (@strSQL)
        SELECT @TableNames = SUBSTRING(@TableNames, CHARINDEX(',', @TableNames) + 1, LEN(@TableNames))
    END
END
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.