C'est une sorte de tâche triviale dans mon monde natal C #, mais je ne le fais pas encore en SQL et préférerais le résoudre en fonction d'un ensemble (sans curseurs). Un jeu de résultats doit provenir d'une requête comme celle-ci.
SELECT SomeId, MyDate,
dbo.udfLastHitRecursive(param1, param2, MyDate) as 'Qualifying'
FROM T
Comment ça devrait fonctionner
J'envoie ces trois params dans un UDF.
L'UDF utilise en interne des paramètres pour récupérer les lignes associées <= 90 jours plus anciennes, à partir d'une vue.
L'UDF traverse «MyDate» et renvoie 1 s'il doit être inclus dans un calcul total.
Si ce n'est pas le cas, il renvoie 0. Nommé ici comme "qualificatif".
Que fera l'UDF
Répertoriez les lignes dans l'ordre des dates. Calculez les jours entre les rangées. La première ligne du jeu de résultats par défaut est Hit = 1. Si la différence est jusqu'à 90, - passez ensuite à la ligne suivante jusqu'à ce que la somme des intervalles soit de 90 jours (le 90e jour doit passer). Il serait également préférable d'omettre la ligne du résultat.
|(column by udf, which not work yet)
Date Calc_date MaxDiff | Qualifying
2014-01-01 11:00 2014-01-01 0 | 1
2014-01-03 10:00 2014-01-01 2 | 0
2014-01-04 09:30 2014-01-03 1 | 0
2014-04-01 10:00 2014-01-04 87 | 0
2014-05-01 11:00 2014-04-01 30 | 1
Dans le tableau ci-dessus, la colonne MaxDiff est l'écart par rapport à la date de la ligne précédente. Le problème avec mes tentatives jusqu'à présent est que je ne peux pas ignorer l'avant-dernière ligne de l'exemple ci-dessus.
[EDIT]
Selon le commentaire, j'ajoute une balise et je colle également le udf que j'ai compilé tout à l'heure. Cependant, c'est juste un espace réservé et ne donnera pas de résultat utile.
;WITH cte (someid, otherkey, mydate, cost) AS
(
SELECT someid, otherkey, mydate, cost
FROM dbo.vGetVisits
WHERE someid = @someid AND VisitCode = 3 AND otherkey = @otherkey
AND CONVERT(Date,mydate) = @VisitDate
UNION ALL
SELECT top 1 e.someid, e.otherkey, e.mydate, e.cost
FROM dbo.vGetVisits AS E
WHERE CONVERT(date, e.mydate)
BETWEEN DateAdd(dd,-90,CONVERT(Date,@VisitDate)) AND CONVERT(Date,@VisitDate)
AND e.someid = @someid AND e.VisitCode = 3 AND e.otherkey = @otherkey
AND CONVERT(Date,e.mydate) = @VisitDate
order by e.mydate
)
J'ai une autre requête que je définis séparément qui est plus proche de ce dont j'ai besoin, mais bloquée par le fait que je ne peux pas calculer sur des colonnes fenêtrées. J'ai également essayé un similaire qui donne plus ou moins la même sortie juste avec un LAG () sur MyDate, entouré d'un datiff.
SELECT
t.Mydate, t.VisitCode, t.Cost, t.SomeId, t.otherkey, t.MaxDiff, t.DateDiff
FROM
(
SELECT *,
MaxDiff = LAST_VALUE(Diff.Diff) OVER (
ORDER BY Diff.Mydate ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
FROM
(
SELECT *,
Diff = ISNULL(DATEDIFF(DAY, LAST_VALUE(r.Mydate) OVER (
ORDER BY r.Mydate ASC
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING),
r.Mydate),0),
DateDiff = ISNULL(LAST_VALUE(r.Mydate) OVER (
ORDER BY r.Mydate ASC
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING),
r.Mydate)
FROM dbo.vGetVisits AS r
WHERE r.VisitCode = 3 AND r.SomeId = @SomeID AND r.otherkey = @otherkey
) AS Diff
) AS t
WHERE t.VisitCode = 3 AND t.SomeId = @SomeId AND t.otherkey = @otherkey
AND t.Diff <= 90
ORDER BY
t.Mydate ASC;
la source
Réponses:
En lisant la question, l'algorithme récursif de base requis est:
Ceci est relativement facile à implémenter avec une expression de table commune récursive.
Par exemple, en utilisant les exemples de données suivants (basés sur la question):
Le code récursif est:
Les résultats sont:
Avec un index ayant
TheDate
comme clé principale, le plan d'exécution est très efficace:Vous pouvez choisir d'envelopper cela dans une fonction et de l'exécuter directement contre la vue mentionnée dans la question, mais mes instincts sont contre. Habituellement, les performances sont meilleures lorsque vous sélectionnez des lignes d'une vue dans une table temporaire, fournissez l'index approprié sur la table temporaire, puis appliquez la logique ci-dessus. Les détails dépendent des détails de la vue, mais c'est mon expérience générale.
Pour être complet (et invité par la réponse de ypercube), je dois mentionner que mon autre solution de référence pour ce type de problème (jusqu'à ce que T-SQL obtienne les fonctions d'ensemble ordonnées appropriées) est un curseur SQLCLR ( voir ma réponse ici pour un exemple de la technique ). Cela fonctionne beaucoup mieux qu'un curseur T-SQL et est pratique pour ceux qui ont des compétences en langages .NET et la capacité d'exécuter SQLCLR dans leur environnement de production. Il ne peut pas offrir beaucoup dans ce scénario sur la solution récursive parce que la majorité du coût est le genre, mais il convient de le mentionner.
la source
Comme il s'agit d' une question sur SQL Server 2014, je pourrais aussi bien ajouter une version de procédure stockée compilée en mode natif d'un "curseur".
Tableau source avec quelques données:
Un type de table qui est le paramètre de la procédure stockée. Réglez le de
bucket_count
manière appropriée .Et une procédure stockée qui parcourt le paramètre de valeur de table et collecte les lignes
@R
.Code pour remplir une variable de table optimisée en mémoire qui est utilisée comme paramètre de la procédure stockée compilée en mode natif et appeler la procédure.
Résultat:
Mise à jour:
Si pour une raison quelconque, vous n'avez pas besoin de visiter chaque ligne du tableau, vous pouvez faire l'équivalent de la version "passer à la date suivante" qui est implémentée dans le CTE récursif par Paul White.
Le type de données n'a pas besoin de la colonne ID et vous ne devez pas utiliser d'index de hachage.
Et la procédure stockée utilise un
select top(1) ..
pour rechercher la valeur suivante.la source
T.TheDate >= dateadd(day, 91, @CurDate)
tout, ça irait, non?TheDate
dansTType
laDate
.Une solution qui utilise un curseur.
(d'abord, quelques tableaux et variables nécessaires) :
Le curseur réel:
Et obtenir les résultats:
Testé chez SQLFiddle
la source
INSERT @cd
que quand@Qualify=1
(et donc en n'insérant pas 13 millions de lignes si vous n'en avez pas besoin dans la sortie). Et la solution dépend de la recherche d'un indiceTheDate
. S'il n'y en a pas, ce ne sera pas efficace.Résultat
Consultez également Comment calculer le total cumulé dans SQL Server
mise à jour: veuillez voir ci-dessous les résultats des tests de performances.
En raison de la logique différente utilisée pour trouver un "écart de 90 jours", les ypercubes et mes solutions s'ils sont laissés intacts peuvent renvoyer des résultats différents à la solution de Paul White. Cela est dû à l'utilisation des fonctions DATEDIFF et DATEADD respectivement.
Par exemple:
renvoie «2014-04-01 00: 00: 00.000», ce qui signifie que «2014-04-01 01: 00: 00.000» est au-delà d'un intervalle de 90 jours
mais
Renvoie «90», ce qui signifie qu'il est toujours dans l'écart.
Prenons un exemple de détaillant. Dans ce cas, la vente d'un produit périssable qui s'est vendu à la date «2014-01-01» à «2014-01-01 23: 59: 59: 999» est acceptable. La valeur DATEDIFF (DAY, ...) dans ce cas est donc OK.
Un autre exemple est un patient qui attend d'être vu. Pour quelqu'un qui vient le '2014-01-01 00: 00: 00: 000' et qui part le '2014-01-01 23: 59: 59: 999', il est 0 (zéro) jour si DATEDIFF est utilisé même si le l'attente réelle était de près de 24 heures. Encore une fois, le patient qui vient au '2014-01-01 23:59:59' et repart au '2014-01-02 00:00:01' a attendu un jour si DATEDIFF est utilisé.
Mais je m'égare.
J'ai laissé les solutions DATEDIFF et même les performances testées, mais elles devraient vraiment être dans leur propre ligue.
Il a également été noté que pour les grands ensembles de données, il est impossible d'éviter les valeurs du même jour. Donc, si nous disons 13 millions d'enregistrements couvrant 2 ans de données, nous finirons par avoir plus d'un enregistrement pendant quelques jours. Ces enregistrements sont filtrés dès que possible dans mes solutions DATEDIFF et ypercube. J'espère que ypercube ne s'en soucie pas.
Les solutions ont été testées sur le tableau suivant
avec deux index clusterisés différents (mydate dans ce cas):
Le tableau a été rempli de la manière suivante
Pour un cas de plusieurs millions de lignes, INSERT a été modifié de telle manière que des entrées de 0 à 20 minutes ont été ajoutées au hasard.
Toutes les solutions ont été soigneusement emballées dans le code suivant
Codes réels testés (sans ordre particulier):
La solution DATEDIFF d' Ypercube ( YPC, DATEDIFF )
La solution DATEADD d'Ypercube ( YPC, DATEADD )
La solution de Paul White ( PW )
Ma solution DATEADD ( PN, DATEADD )
Ma solution DATEDIFF ( PN, DATEDIFF )
J'utilise SQL Server 2012, donc je m'excuse auprès de Mikael Eriksson, mais son code ne sera pas testé ici. Je m'attendrais toujours à ce que ses solutions avec DATADIFF et DATEADD retournent des valeurs différentes sur certains ensembles de données.
Et les résultats réels sont:
la source
Ok, ai-je raté quelque chose ou pourquoi ne sauteriez-vous pas simplement la récursivité et vous rejoindriez-vous? Si la date est la clé primaire, elle doit être unique et dans l'ordre chronologique si vous prévoyez de calculer le décalage à la ligne suivante
Rendements
Sauf si j'ai raté quelque chose d'important ...
la source
WHERE [TheDate] > [T1].[TheDate]
pour tenir compte du seuil de différence de 90 jours. Mais encore, votre sortie n'est pas celle souhaitée.