2010-10-22 42 views
1

Je voudrais résoudre ce problème en évitant d'utiliser des curseurs (FETCH).Traiter des périodes et des dates sans utiliser de curseurs

vient ici le problème ...

1st Table/quantity 
------------------ 
periodid periodstart periodend quantity 

1  2010/10/01 2010/10/15 5 


2st Table/sold items 
----------------------- 
periodid periodstart periodend solditems 

14343 2010/10/05 2010/10/06 2 

Maintenant, je voudrais obtenir la vue suivante ou juste résultats d'une requête

Table Table/stock 
----------------------- 
periodstart periodend itemsinstock 

2010/10/01 2010/10/04  5 

2010/10/05 2010/10/06  3 

2010/10/07 2010/10/15  5 

Il semble impossible de résoudre ce problème sans l'aide de curseurs, ou sans utiliser de dates uniques au lieu de périodes.

J'apprécierais toute aide.

Merci

Répondre

0

John, ce que vous pourriez faire est une boucle while. Déclarez et initialisez 2 variables avant votre boucle, l'une étant la date de début et l'autre étant la date de fin. Votre boucle serait alors ressembler à ceci:

WHILE(@StartEnd <= @EndDate) 
BEGIN 
    --processing goes here 
    SET @StartEnd = @StartEnd + 1 
END 

Vous devez stocker vos définitions de période dans une autre table, vous pouvez donc récupérer ceux-ci et les lignes de sortie en cas de besoin à une table temporaire. Faites-moi savoir si vous avez besoin d'exemples plus détaillés, ou si j'ai la mauvaise extrémité du bâton!

+0

Voir cet article MSDN pour l'utilisation détaillée des boucles While dans T-SQL http://msdn.microsoft.com/en-us/library/ms178642.aspx (en supposant que vous utilisez SQL Server!) – jules

2
DECLARE @t1 TABLE (periodid INT,periodstart DATE,periodend DATE,quantity INT) 
DECLARE @t2 TABLE (periodid INT,periodstart DATE,periodend DATE,solditems INT) 

INSERT INTO @t1 VALUES(1,'2010-10-01T00:00:00.000','2010-10-15T00:00:00.000',5) 
INSERT INTO @t2 VALUES(14343,'2010-10-05T00:00:00.000','2010-10-06T00:00:00.000',2) 

DECLARE @D1 DATE 

SELECT @D1 = MIN(P) FROM (SELECT MIN(periodstart) P FROM @t1 
          UNION ALL 
          SELECT MIN(periodstart) FROM @t2) D 

DECLARE @D2 DATE 

SELECT @D2 = MAX(P) FROM (SELECT MAX(periodend) P FROM @t1 
          UNION ALL 
          SELECT MAX(periodend) FROM @t2) D 

;WITH 
L0 AS (SELECT 1 AS c UNION ALL SELECT 1), 
L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B), 
L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B), 
L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B), 
L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B), 
Nums AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS i FROM L4), 
Dates AS(SELECT DATEADD(DAY,i-1,@D1) AS D FROM Nums where i <= 1+DATEDIFF(DAY,@D1,@D2)) , 
Stock As (
SELECT D ,t1.quantity - ISNULL(t2.solditems,0) AS itemsinstock 
FROM Dates 
LEFT OUTER JOIN @t1 t1 ON t1.periodend >= D and t1.periodstart <= D 
LEFT OUTER JOIN @t2 t2 ON t2.periodend >= D and t2.periodstart <= D), 
NStock As (
select D,itemsinstock, ROW_NUMBER() over (order by D) - ROW_NUMBER() over (partition by itemsinstock order by D) AS G 
from Stock) 
SELECT MIN(D) AS periodstart, MAX(D) AS periodend, itemsinstock 
FROM NStock 
GROUP BY G, itemsinstock 
ORDER BY periodstart 
+0

+1, fonctionne pour moi (après avoir changé tous les types de données 'DATE' en' DATETIME' parce que je ne suis pas sur SQL Server 2008) –

1

Espérons que c'est un peu plus facile à lire que celui de Martin. J'ai utilisé différents tableaux et exemples de données, extrapolant, espérons l'info droite:

CREATE TABLE [dbo].[Quantity](
    [PeriodStart] [date] NOT NULL, 
    [PeriodEnd] [date] NOT NULL, 
    [Quantity] [int] NOT NULL 
) ON [PRIMARY] 

CREATE TABLE [dbo].[SoldItems](
    [PeriodStart] [date] NOT NULL, 
    [PeriodEnd] [date] NOT NULL, 
    [SoldItems] [int] NOT NULL 
) ON [PRIMARY] 

INSERT INTO Quantity (PeriodStart,PeriodEnd,Quantity) 
SELECT '20100101','20100115',5 

INSERT INTO SoldItems (PeriodStart,PeriodEnd,SoldItems) 
SELECT '20100105','20100107',2 union all 
SELECT '20100106','20100108',1 

La requête réelle est maintenant:

;WITH Dates as (
    select PeriodStart as DateVal from SoldItems union select PeriodEnd from SoldItems union select PeriodStart from Quantity union select PeriodEnd from Quantity 
), Periods as (
    select d1.DateVal as StartDate, d2.DateVal as EndDate 
    from Dates d1 inner join Dates d2 on d1.DateVal < d2.DateVal left join Dates d3 on d1.DateVal < d3.DateVal and d3.DateVal < d2.DateVal where d3.DateVal is null 
), QuantitiesSold as (
    select StartDate,EndDate,COALESCE(SUM(si.SoldItems),0) as Quantity 
    from Periods p left join SoldItems si on p.StartDate < si.PeriodEnd and si.PeriodStart < p.EndDate 
    group by StartDate,EndDate 
) 
select StartDate,EndDate,q.Quantity - qs.Quantity 
from QuantitiesSold qs inner join Quantity q on qs.StartDate < q.PeriodEnd and q.PeriodStart < qs.EndDate 

Et le résultat est:

StartDate EndDate (No column name) 
2010-01-01 2010-01-05 5 
2010-01-05 2010-01-06 3 
2010-01-06 2010-01-07 2 
2010-01-07 2010-01-08 4 
2010-01-08 2010-01-15 5 

Explication: Je m en utilisant trois expressions de table communes. Le premier (Dates) rassemble toutes les dates dont nous parlons, à partir des deux tables impliquées. La seconde (Périodes) sélectionne des valeurs consécutives à partir des dates CTE. Et le troisième (QuantitiesSold) trouve ensuite les éléments de la table SoldItems qui chevauchent ces périodes, et ajoute leurs totaux ensemble. Tout ce qui reste dans la partie extérieure sélectionner est de soustraire ces quantités de la quantité totale stockée dans la quantité Tableau

+0

+1 - Ceci est plus clair. –

+0

Oui - ce qui nous manque pour le moment (parce que les données de l'échantillon ne sont pas très expansives) est a) si nous avons plusieurs lignes dans le tableau des quantités (changement simple au troisième CTE pour accommoder), et s'il y a une relation réelle –

+0

@Martin - le mien ne correspond pas exactement aux exigences OP, puisque mes périodes couvrent entièrement les plages (ie ma première période se termine le 5, pas le 4ème). C'est parce que je suis habitué à modéliser des périodes comme [StartDate, EndDate] (un intervalle semi-ouvert) –

0

Damien,

Je suis en train de comprendre votre solution et tester sur une grande échelle de données, mais Je reçois les erreurs suivantes pour votre code.

Msg 102, niveau 15, état 1, ligne 20

syntaxe incorrecte près de 'dates'.

Msg 102, niveau 15, état 1, ligne 22

syntaxe incorrecte près ''.

Msg 102, niveau 15, état 1, ligne 25

syntaxe incorrecte près ''.

+0

vous auriez mieux valu l'ajouter comme commentaire à ma réponse, ou l'éditer dans votre question (commentaire sur ma réponse aurait m'a alerté de votre mise à jour). –

+0

fonctionne uniquement avec SQL 2005 ou version ultérieure (vous ne savez pas quelle version vous utilisez) et si la requête démarre plus tard dans le lot, vous avez besoin d'un ";" avant le mot-clé WITH. –

+0

J'utilise SQL 2005 et déjà essayé avec ";" avant le kwd AVEC mais toujours obtenir le même ensemble d'erreurs. Puisque je n'utilise pas souvent avec cela, j'ai essayé d'exécuter des exemples de base pour l'instruction WITH, et ils ont tous retourné la même erreur. ?? – mko

0

Damien,

Sur la base de votre solution Je voulais aussi obtenir un affichage net pour stockpièces sans dates qui se chevauchent. Que diriez-vous de cette solution?

CREATE TABLE [dbo].[SoldItems](
    [PeriodStart] [datetime] NOT NULL, 
    [PeriodEnd] [datetime] NOT NULL, 
    [SoldItems] [int] NOT NULL 
) ON [PRIMARY] 


INSERT INTO SoldItems (PeriodStart,PeriodEnd,SoldItems) 
SELECT '20100105','20100106',2 union all 
SELECT '20100105','20100108',3 union all 
SELECT '20100115','20100116',1 union all 
SELECT '20100101','20100120',10 


;WITH Dates as (
    select PeriodStart as DateVal from SoldItems 
    union 
    select PeriodEnd from SoldItems 
    union 
    select PeriodStart from Quantity 
    union 
    select PeriodEnd from Quantity 

), Periods as (
    select d1.DateVal as StartDate, d2.DateVal as EndDate 
    from Dates d1 
    inner join Dates d2 on d1.DateVal < d2.DateVal 
    left join Dates d3 on d1.DateVal < d3.DateVal and 
    d3.DateVal < d2.DateVal where d3.DateVal is null 

), QuantitiesSold as (
    select StartDate,EndDate,SUM(si.SoldItems) as Quantity 
    from Periods p left join SoldItems si on p.StartDate < si.PeriodEnd and si.PeriodStart < p.EndDate 
    group by StartDate,EndDate 
) 

select StartDate,EndDate, qs.Quantity 
from QuantitiesSold qs 
where qs.quantity is not null