Fundo
Estou tentando estabelecer uma sequência de "visitas", em que se um animal for detectado essencialmente no mesmo local ( General_Location
) conta como uma única visita, mas se for para outro lugar e depois voltar, é uma visita adicional ao mesmo local. Portanto, se o animal for detectado em uma Location
sequência
A1, A2, A3, A3, A3, A1, B2, D4, A2
em que, por exemplo, todos os locais A(n) pertencem a General_Location
"A", isso terá as primeiras 6 detecções como visita 1 (@A), a seguir como visita 2 (@B), a seguir como visita 3 (@D), a seguir como visita 4 (voltar @A).
Como LAG
e LEAD
não estão disponíveis no SQL Server 2008R2 (nem está UNBOUNDED PRECEDING
na PARTITION
cláusula ing), estou tentando uma solução alternativa, conforme descrito nesta entrada de blog do SQL Authority .
Estou com problemas de memória (para não mencionar o tempo de computação) com o seguinte:
WITH s AS (
SELECT
RANK() OVER (PARTITION BY det.Technology, det.XmitID ORDER BY DetectDate ASC, ReceiverID ASC) as DetID,
COALESCE(TA.AnimalID, det.Technology+'-'+cast(da.XmitID AS nvarchar), 'BSVALUE999') as AnimalID,
det.Technology, det.XmitID, DetectDate, det.location as Location, RL.General_Location as GLocation, ReceiverID
FROM
Detections_with_Location as det JOIN
Receiver_Locations as RL
ON det.Location=RL.Location LEFT OUTER JOIN
Tagged_Animal as TA
ON det.AnimalID=TA.AnimalID
)
INSERT INTO ##ttOrder_det (AnimalID, Technology, XmitID, DD, Location, GLocation, ReceiverID, DetID, PrevDD, BinInc)
SELECT
s1.AnimalID, --was a coalesce
s1.Technology, s1.XmitID, s1.DetectDate, s1.Location, s1.GLocation, s1.ReceiverID,
s1.DetID,
sLAG.DetectDate,
CASE WHEN sLAG.DetectDate IS NULL
THEN 1
ELSE CASE WHEN sLAG.GLocation = s1.GLocation
THEN 0
ELSE 1
END
END AS BinInc
FROM s as s1
LEFT OUTER JOIN s AS sLAG ON
s1.DetID = sLAG.DetID + 1 AND
s1.AnimalID= sLAG.AnimalID --and s.Technology=sLAG.Technology and s.XmitID=sLAG.XmitID;
Como vários usuários (@MartinSmith, @Frisbee) mencionaram ou aludiram, o uso de AnimalID
não é a chave primária completa de Tagged_Animal
, nem é definido em uma restrição UNIQUE. No entanto, a contagem de linhas na tabela onde A.AnimalID=B.AnimalID AND A.TagSN<B.TagSN
é (atualmente) zero. Para tornar essa consulta robusta, eu teria que fazer com que fosse único (ou apenas retirar TagSN do PK).
Definições de tabela e índice
##ttOrder_det (tabela temporária)
Os índices são criados atualmente antes de preencher a tabela; Estou passando por testes em que mudo a criação NONCLUSTERED
não UNIQUE
indexada para uma posição depois que a tabela temporária é preenchida.
CREATE TABLE ##ttOrder_det (
AnimalID nvarchar(50) not null,
Technology varchar(25) not null,
XmitID int not null,
DD DateTime not null,
[Location] [nvarchar](255) NULL,
[GLocation] nvarchar(255) NULL,
PrevDD DateTime NULL,
ReceiverID int not null,
DetID int NOT NULL,
BinInc int NULL,
BinNum int NULL,
CONSTRAINT [PK_ttRDA] PRIMARY KEY CLUSTERED
([AnimalID] ASC, [DD] ASC, ReceiverID ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY];
GO
CREATE NONCLUSTERED INDEX NIX_F on ##ttOrder_det (AnimalID ASC);
CREATE NONCLUSTERED INDEX NIX_VTC ON ##ttOrder_det (ReceiverID ASC, AnimalID ASC);
CREATE NONCLUSTERED INDEX NIX_TCD ON ##ttOrder_det (AnimalID ASC, DD ASC);
CREATE NONCLUSTERED INDEX NIX_R ON ##ttOrder_det (DetID ASC);
CREATE NONCLUSTERED INDEX NIX_A ON ##ttOrder_det (GLocation ASC);
CREATE NONCLUSTERED INDEX NIX_DD ON ##ttOrder_det (DD, PrevDD);
CREATE UNIQUE INDEX UX_CTR ON ##ttOrder_det (AnimalID ASC, DetID ASC);
CREATE NONCLUSTERED INDEX NIX_Bi ON ##ttOrder_det (BinInc ASC);
CREATE NONCLUSTERED INDEX NIX_CT ON ##ttOrder_det (XmitID ASC, Technology ASC);
Tagged_Animal
CREATE TABLE [dbo].[Tagged_Animal](
[DateTagged] [datetime] NULL,
[AnimalID] [nvarchar](50) NOT NULL,
[TagSN] [nvarchar](50) NOT NULL,
[XmitID] [int] NULL,
[Technology] [varchar](25) NULL,
[Animal_SubType] [nvarchar](50) NULL,
[Species] [nvarchar](30) NULL,
[StudyID] [nvarchar](50) NULL,
[Weight] [float] NULL,
[Length] [int] NULL,
[Length_Type] [nvarchar](50) NULL,
[Date_Released] [datetime] NULL,
[Release_Location] [nvarchar](50) NULL,
[Lat] [float] NULL,
[Lon] [float] NULL,
[Course_Dist_km] [float] NULL,
[Sex] [nvarchar](255) NULL,
[Life_Stage] [nvarchar](255) NULL,
[Marking_Method] [nvarchar](255) NULL,
[Tag_Type] [varchar](30) NULL,
[Notes] [nvarchar](255) NULL,
CONSTRAINT [PK_tbl_Tagged_Animal] PRIMARY KEY CLUSTERED
(
[AnimalID] ASC,
[TagSN] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [I_TF_TagCode] ON [dbo].[Tagged_Animal]
(
[XmitID] ASC,
[Technology] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
Detecções_com_Localização
CREATE TABLE [dbo].[Detections_with_Location](
[AnimalID] [nvarchar](50) NOT NULL,
[XmitID] [int] NOT NULL,
[Technology] [varchar](25) NOT NULL,
[DetectDate] [datetime] NOT NULL,
[ReceiverID] [int] NOT NULL,
[Data] [float] NULL,
[Units] [varchar](50) NULL,
[Location] [nvarchar](255) NULL,
[StartD] [datetime] NULL,
[StopD] [datetime] NULL,
[fname] [nvarchar](255) NULL,
[notes] [nvarchar](max) NULL,
CONSTRAINT [PK_dlwk] PRIMARY KEY CLUSTERED
(
[ReceiverID] ASC,
[Technology] ASC,
[XmitID] ASC,
[DetectDate] ASC,
[AnimalID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [NIX_VTC] ON [dbo].[Detections_with_Location]
(
[ReceiverID] ASC,
[AnimalID] ASC,
[XmitID] ASC,
[Technology] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [NIX_TCpi] ON [dbo].[Detections_with_Location]
(
[XmitID] ASC,
[Technology] ASC
)
INCLUDE ( [DetectDate],
[ReceiverID],
[Data],
[Units],
[Location]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [NIX_TCD] ON [dbo].[Detections_with_Location]
(
[AnimalID] ASC,
[XmitID] ASC,
[Technology] ASC,
[DetectDate] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [NIX_F] ON [dbo].[Detections_with_Location]
(
[AnimalID] ASC
)
INCLUDE ( [XmitID],
[Technology],
[DetectDate],
[ReceiverID],
[Data],
[Units],
[Location]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [NIX_DSS] ON [dbo].[Detections_with_Location]
(
[ReceiverID] ASC,
[Location] ASC,
[StartD] ASC,
[StopD] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
Receptor_Locais
CREATE TABLE [dbo].[Receiver_Locations](
[Region] [nvarchar](50) NULL,
[Location_Long] [nvarchar](50) NULL,
[Location] [nvarchar](50) NOT NULL,
[Lat] [float] NULL,
[Lon] [float] NULL,
[Altitude] [float] NULL,
[Elevation] [float] NULL,
[RiverKm] [float] NULL,
[LocationType] [nvarchar](50) NULL,
[General_Location] [nvarchar](50) NULL,
[Nearest_Access] [nvarchar](50) NULL,
[Responsible_Agent] [nvarchar](50) NULL,
[Agent_Phone] [nvarchar](255) NULL,
[Agent_Email] [nvarchar](255) NULL,
CONSTRAINT [PK_tbl_Receiver_Locations] PRIMARY KEY CLUSTERED
(
[Location] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Tamanhos de tabela
Tagged_Animals: 10 mil
Detections_with_Location: mais de 46 milhões de entradas
Receiver_Locations: 800
Erros Específicos Recebidos
Não foi possível alocar espaço para o objeto 'dbo.SORT armazenamento de execução temporária: 140737631617024' no banco de dados 'tempdb' porque o grupo de arquivos 'PRIMARY' está cheio. Crie espaço em disco excluindo arquivos desnecessários, descartando objetos no grupo de arquivos, adicionando arquivos adicionais ao grupo de arquivos ou definindo o crescimento automático para arquivos existentes no grupo de arquivos.
O log de transações do banco de dados 'tempdb' está cheio. Para descobrir por que o espaço no log não pode ser reutilizado, consulte a coluna log_reuse_wait_desc em sys.databases (tempdb ACTIVE_TRANSACTION)
Ocorreu um erro ao executar o lote. A mensagem de erro é: Exceção do tipo 'System.OutOfMemoryException' foi lançada --(se estiver fazendo
s
uma seleção direta após cerca de 33 milhões de registros).
(Estimado) Plano de Execução Resumo do código inicial
INSERT
custo 0%
SEQUENCE
custo 0%, mas com base em 9 subetapas diferentes. Essas subetapas (com custos típicos) são um Index Insert
(2%), seguido por Sort
(8%), seguido por Table Spool
(2%). NIX_A
tem um custo de 9% para Index Insert
e NIX_TCD
nem NIX_F
tem Sort
degrau; O Table Spool
para NIX_F
é grátis.
O Clustered Index Insert
custo é de 10%.
Há também um Sort
custo de 2% e um Parallelism
custo de 1% para distribuição de streams.
Para o SEQUENCE
custo, parece aumentar para 95%, com outras etapas custando 13%, então obviamente há alguns "erros" de arredondamento em algum lugar, provavelmente principalmente nos 14% da sequência Index Insert
- Sort
- .Table Spool
Notas/Ref.
Implementação LAG/LEAD baseada na entrada do blog SQL Authority
Veja também este Stackexchange Thread
Minhas perguntas
Alguma sugestão para melhorar?
Posso particionar também quando juntar as cópias de
s
?As coisas melhorariam se eu
s
criasse uma tabela temporária discreta e a indexasse apropriadamente?Seria mais eficiente criar os não-
UNIQUE
índices nas tabelas temporárias depois que todas as inserções forem realizadas? Presumo que os índicesUNIQUE
(e, portanto,PRIMARY KEY
) devem ser criados antecipadamente para evitar violações de restrição chave.
Para responder a uma das minhas próprias perguntas
- Sim, sim, seria. Depois de mais otimizações
- 21 minutos para preencher a tabela temporária com dados
- 1 minuto para indexar
Anteriormente, esse processo durava pelo menos 1,5 horas, apresentava erros e não produzia uma tabela de resultados. Antes de começar a mexer na lógica da consulta, na verdade, levaria mais de 4 horas antes de ocorrer um erro.
Especificações do servidor:
Processador: Xeon E3-1240 V2 a 3,4 GHz (4 núcleos/8 threads)
Memória: 16 GB
Arquivo de paginação: 16 GB em SSD de 111 GB (52 GB grátis)
tempdb + meu banco de dados em SSD de 223 GB (119 GB grátis)
Status atual
Veja minha solução/resposta postada.
Estou enviando isso como uma resposta, pois atualmente estou evitando erros do tipo falta de memória, bem como reduzi significativamente o tempo de execução (eram mais de 4 horas, terminando em falha; agora é 1,25 horas, terminando em sucesso ). No entanto, tenho certeza de que, após cerca de 10 milhões de registros, esse problema pode ocorrer novamente, portanto, gostaria de receber comentários ou respostas adicionais com o objetivo de tornar isso mais eficiente em termos de memória para o futuro.
A "solução" para este ponto foi eliminar campos desnecessários e especialmente índices do design das tabelas temporárias. Além disso, a criação de índice para chaves sem restrição foi adiada até que a tabela fosse preenchida.
Para resolver o problema (apontado primeiro por @MartinSmith) de particionamento que não corresponde ao posterior
JOIN
, criei um campo em uma subconsulta que usei paraPARTITION
ing e paraJOIN
ing.Código para o Q
Comentários rápidos
Eu mudei para usar
ROW_NUMBER
em vez deRANK
. Isso não é determinístico, mas pelo menos não resultará em "empates", o que resultaria na quebra daLAG
implementação nesse ponto. Laços não deveriam existir, mas isso é apenas uma proteção futura contraGeneral_Location
s que estão muito próximos e co-detectam a mesma transmissão.Again, as two users pointed out in the comments above, I'm not using the full PK of the
Tagged_Animal
table, so there's a possibility that there be aJOIN
with an ambiguousAnimalID
. However, at present, bothAnimalID
andTagSN
areUNIQUE
, albeit unconstrained.I was planning on dropping the
ReceiverID
field in favor of using theLocation
, but I had a period in which there were two Receivers deployed at the same location (one receiver was presumed lost but later found) which did indeed detect the same animal at the same timeFollow-up code to complete the task