2009-08-25 10 views
2

Nous extrayons les informations de réservation de nos courts de tennis de notre base de données SQL dans un tableau de résultats simple pour nous aider à construire une image de l'utilisation des tribunaux. C'est assez simple sauf quand il s'agit de réservations qui durent plus d'une heure.INSERTs simples ou multiples basés sur des valeurs SELECTed

À l'heure actuelle, chaque réservation entraîne une ligne dans notre tableau des résultats. Chaque rangée contient une heure de début, une durée et un numéro de cour. Nous aimerions faire correspondre cette table directement à une feuille de calcul ou à un tableau croisé dynamique afin que nous puissions voir combien d'heures nos tribunaux sont réservés et quelles heures de la journée.

Actuellement, notre requête SQL ressemble à quelque chose comme:

INSERT INTO Results (year, month, day, hour, duration, court) 
SELECT DATEPART (yy, b.StartDateTime), 
     DATEPART (mm, b.StartDateTime), 
     DATEPART (dd, b.StartDateTime), 
     DATEPART (hh, b.StartDateTime), 
     a.Duration, 
     a.Court 
FROM Bookings b 
INNER JOIN Activities a 
ON b.ActivityID = a.ID 

Notre problème est que les réservations pour 2, 3 durée heures ou plus ont seulement une ligne dans le tableau des résultats, par exemple. pour la première heure de la réservation. C'est parce que la durée de la réservation est capturée dans les données de durée. Nous pourrions faire un peu de post-traitement sur les données pour atteindre nos objectifs, mais ce serait plus facile si cela était possible dans notre requête SQL.

Cette requête peut-elle être modifiée d'une manière ou d'une autre, de sorte que selon la durée (qui peut être 1, 2, 3, ... heures) le nombre de lignes approprié est inséré dans la table de résultats Ainsi, une réservation de 3 heures à partir de 9h donnera trois rangées dans le tableau des résultats, une à 9h, une à 10h et une à 11h, chacune d'une durée d'une heure.

Ainsi, au lieu de la ligne suivante dans le tableau des résultats:

Year, Month, Day, Hour, Duration, Court 
2009, 08, 25, 09,  3,  1 

nous obtenons les lignes suivantes:

Year, Month, Day, Hour, Duration, Court 
2009, 08, 25, 09,  1,  1 
2009, 08, 25, 10,  1,  1 
2009, 08, 25, 11,  1,  1 

Ceci ferait cartographier le tableau des résultats dans une feuille de calcul beaucoup plus facile.

MISE À JOUR 2009-08-25: Bien sûr, comme le montrent les deux premières réponses, il ne doit pas nécessairement y avoir une seule requête. Un ensemble est bon.

MISE À JOUR 2009-08-26: Ont été suivis et n'ont pas encore eu l'occasion d'essayer les solutions proposées. Espérons le faire bientôt et sélectionnera une réponse basée sur les résultats.

MISE À JOUR 2009-08-27: Enfin eu l'occasion d'essayer les solutions. La table des nombres entiers et l'union pour produire une solution était une révélation. Surtout l'utilisation de jointures croisées pour créer une telle table. C'est probablement le plus propre, la façon SQL de faire les choses.

Cependant, à la fin, je suis allé avec la solution d'Aaron impliquant le drapeau et l'algorithme simple. Je l'ai amélioré en enveloppant son algorithme dans une boucle while pour continuer à itérer jusqu'à ce qu'il ne reste plus que des durées> 1. C'était rapide et facile à mettre en œuvre. Il a également souligné que nous avions des réservations de 10 heures, donc je n'ai pas eu besoin de coder une limite ici.

Je devrais noter que j'ai incorporé l'idée de Jeff de la durée maximum dans le compteur de boucle while, plutôt que mon idée originale de compter les articles avec la durée> 1. Un peu moins de code.

+0

Quelle version de SQL Server? –

+0

SQL Server 2000. Cela fera-t-il une différence? – dave

+0

Certaines fonctions de SQL 2005 et ultérieures facilitent ce type de requête. –

Répondre

0

Ce n'est pas trivial.Tout d'abord, vous avez besoin d'une autre colonne « Drapeau » qui est 0:

INSERT INTO Results (year, month, day, hour, duration, court, Flag) 
SELECT DATEPART (yy, b.StartDateTime), 
     DATEPART (mm, b.StartDateTime), 
     DATEPART (dd, b.StartDateTime), 
     DATEPART (hh, b.StartDateTime), 
     a.Duration, 
     a.Court, 
     0 
FROM Bookings b 
INNER JOIN Activities a 
ON b.ActivityID = a.ID 

Vous devez exécuter ces requêtes à plusieurs reprises:

-- Copy all rows with duration > 1 and set the flag to 1 
insert into results(year, month, day, hour, duration, court, Flag) 
select year, month, day, hour+1, duration-1, court, 1 
from result 
where duration > 1 
; 
-- Set the duration of all copied rows to 1 
update result 
set duration = 1 
where flag = 0 and duration > 1 
; 
-- Prepare the copies for the next round 
update result 
set flag = 0 
where flag = 1 

Cela va créer une entrée supplémentaire pour chaque duration > 1. Je suppose que vous ne pouvez pas affecter un tribunal pendant plus de 8 heures, il vous suffit donc de les exécuter 8 fois pour les réparer tous.

+0

Je pense que je suis ceci. Mais votre seconde requête n'a-t-elle pas besoin de réduire la durée de 1, ne la placez pas exactement à 1? – dave

+0

@dave: Non, il prend toutes les requêtes de processus hors de l'image en définissant la durée à 1. Tous les résultats avec "flag = 1" contiendront la nouvelle "duration-1". C'est pourquoi j'ai besoin du drapeau (pour savoir quelle durée il faut "effacer") –

+0

@Aaron, je l'ai maintenant. Une fois que j'ai commencé un exemple dans ma tête c'était évident. J'avais supposé que "l'itération" serait basée sur la rangée "parent", ie. la ligne d'origine avec la durée> 1. Mais votre solution crée une ligne enfant pour l'itérer. Ce qui est assez intelligent. Peut-être que je vais envelopper le tout dans une boucle while, qui vérifie s'il y a des lignes avec une durée> 1 à gauche. De cette façon, je n'ai pas à m'inquiéter du nombre maximum d'heures pouvant être réservées ou d'itérations inutiles. – dave

1

modifié pour corriger le calcul de l'heure manquante

Créer une table temporaire à une seule colonne avec n lignes pour entier n - (je l'ai supposé que le moment de la réservation maximale est de 8 heures).

create table #t 
(id int 
,addHour int 
) 

insert #t 
select 1,0 
union all select 2,0 
union all select 2,1 
union all select 3,0 
union all select 3,1 
union all select 3,2 
union all select 4,0 
union all select 4,1 
union all select 4,2 
union all select 4,3 
union all select 5,0 
union all select 5,1 
union all select 5,2 
union all select 5,3 
union all select 5,4 
union all select 6,0 
union all select 6,1 
union all select 6,2 
union all select 6,3 
union all select 6,4 
union all select 6,5 
union all select 7,0 
union all select 7,1 
union all select 7,2 
union all select 7,3 
union all select 7,4 
union all select 7,5 
union all select 7,6 
union all select 8,0 
union all select 8,1 
union all select 8,2 
union all select 8,3 
union all select 8,4 
union all select 8,5 
union all select 8,6 
union all select 8,7 

Vous pouvez vérifier que la table temporaire a le nombre correct de lignes avec la requête suivante:

select id, count(1) 
from #t 
group by id 
order by id 

Modifier votre requête pour inclure une jointure à la table temporaire:

INSERT INTO Results (year, month, day, hour, duration, court) 
SELECT DATEPART (yy, b.StartDateTime), 
     DATEPART (mm, b.StartDateTime), 
     DATEPART (dd, b.StartDateTime), 
     DATEPART (hh, b.StartDateTime) + addHour, 
     1 AS Duration, 
     a.Court 
FROM Bookings b 
INNER JOIN Activities a 
ON b.ActivityID = a.ID 
INNER JOIN #t AS t 
ON t.id = a.Duration 

EDIT - une clarification sur comment cela fonctionne

Lorsque vous joignez des tables, une ligne est produite dans la sortie pour chaque combinaison de lignes jointes dans les tables source qui répondent aux critères de jointure. J'utilise la table temporaire pour "multiplier" le jeu de résultats d'origine des Réservations et des Activités par le nombre d'heures que la réservation dure en rejoignant la Durée. Cela ne fonctionne que si les réservations sont faites en nombres entiers d'heures.

Si vous voulez voir plus clair, ajouter une deuxième colonne à #t qui identifie de manière unique chaque ligne et l'inclure dans le résultat de sortie ensemble:

create table #t 
(id int 
,unique_id int identity 
) 

INSERT #t (id) 
select 1 
union all select 2 
... etc 

SELECT DATEPART (yy, b.StartDateTime), 
     DATEPART (mm, b.StartDateTime), 
     DATEPART (dd, b.StartDateTime), 
     DATEPART (hh, b.StartDateTime) + addHour, 
     1 AS Duration, 
     a.Court, 
     t.unique_id 
FROM Bookings b 
INNER JOIN Activities a 
ON b.ActivityID = a.ID 
INNER JOIN #t AS t 
ON t.id = a.Duration 

Cela devrait préciser que chaque ligne du résultat L'ensemble est produit à partir d'une combinaison unique valide de Réservations, Activités et #t.

+0

Pouvez-vous expliquer un peu comment cela fonctionne? Je comprends la création de la table temporaire, mais pas comment la jointure de #t produit le résultat désiré. – dave

+0

Cela ne donne pas l'heure correcte dans les résultats. –

+0

@Aaron - bon point. J'ai modifié la requête pour gérer cela. –

0

Vous pourriez envisager de placer un déclencheur INSTEAD OF INSERT sur la table "Results" qui insérerait plusieurs lignes pour chaque réservation de plus d'une heure. Cela ajoute de la complexité, mais cela pourrait être une approche raisonnable, car cela ne ressemble pas à un système OLTP à grand volume.

1

Une légère modification à votre suffixes d'origine, si vous introduisez une table integers (ou VIEW) pour servir de générateur de série:

INSERT INTO Results (year, month, day, hour, duration, court) 
SELECT DATEPART (yy, b.StartDateTime), 
     DATEPART (mm, b.StartDateTime), 
     DATEPART (dd, b.StartDateTime), 
     DATEPART (hh, b.StartDateTime) + (a.Duration - i.iii - 1) 
     1, 
     a.Court 
FROM Bookings b 
INNER JOIN Activities a 
    ON b.ActivityID = a.ID 
INNER JOIN Integers999 i  -- N.B.: Integers999 (iii INT), all numbers 0 .. 999 
    ON a.Duration > i.iii;  -- So, a true Duration of 1 appears once, of 2 twice ... 
+0

Vous avez oublié de fixer l'heure de début. –

+0

@Aaron, donc je l'ai fait, merci. Fixé. – pilcrow

0

Je n'ai pas eu la chance de déboguer, mais quelque chose comme cela devrait le faire pour vous:

DECLARE @maxDuration INTEGER 
DECLARE @curDuration INTEGER 

SELECT @MaxDuration = SELECT MAX(Duration) FROM Activities 
SET @curDuration = 1 

WHILE @curDuration <= @MaxDuration 
BEGIN 
    INSERT INTO Results (year, month, day, hour, duration, court) 
    SELECT DATEPART (yy, b.StartDateTime), 
      DATEPART (mm, b.StartDateTime), 
      DATEPART (dd, b.StartDateTime), 
      DATEPART (hh, b.StartDateTime) + @curDuration - 1, 
      a.Duration, 
      a.Court 
    FROM Bookings b 
    INNER JOIN Activities a 
    ON b.ActivityID = a.ID 
    WHERE a.Duration <= @MaxDuration 

    SET @curDuration = @curDuration + 1 
END 
Questions connexes