2010-12-14 6 views
6

j'ai une requête comme ceci:requête pour trouver la première et la plus grande valeur SECOND d'un groupe

SELECT 
DATEPART(year,some_date), 
DATEPART(month,some_date), 
MAX(some_value) max_value 
FROM 
some_table 
GROUP BY 
    DATEPART(year,some_date), 
    DATEPART(month,some_date) 

Ceci retourne une table avec: années, mois, la plus grande valeur pour le mois.

Je souhaite modifier la requête pour que je puisse obtenir: années, mois, la plus grande valeur pour le mois, la deuxième plus grande valeur pour le mois dans chaque ligne.

Il me semble que les solutions bien connues comme "TOP 2", "NOT IN TOP 1" ou un sous-select ne fonctionneront pas ici.

(Pour être vraiment spécifique - j'utilise SQL Server 2008.)

Toute aide appréciée, thx.

Répondre

3

Il me semble que la question appelle une requête qui retournerait le mieux, et deuxième meilleur de la même ligne pour chaque mois et l'année, comme suit:

month, year, best, second best 
... 
... 

et non deux lignes pour la même mois et année contenant le meilleur et le deuxième meilleur rapport qualité-prix.

C'est la solution que j'ai trouvée, donc si quelqu'un a un moyen plus simple d'y parvenir, j'aimerais savoir.

with ranks as (
    select 
     year(entrydate) as [year], 
     month(entrydate) as [month], 
     views, 
     rank() over (partition by year(entrydate), month(entrydate) order by views desc) as [rank] 
    from product 
) 
select 
    t1.year, 
    t1.month, 
    t1.views as [best], 
    t2.views as [second best] 
from ranks t1 
    inner join ranks t2 
     on t1.year = t2.year 
     and t1.month = t2.month 
     and t1.rank = 1 
     and t2.rank = 2 

EDIT: Par curiosité, je l'ai fait un peu plus de tests et a fini avec une variation plus simple sur la Stephanie Page's answer qui n'utilise pas une sous-requête aditional. Et j'ai changé la fonction rank() à row_number() car cela ne fonctionne pas lorsque deux valeurs max sont les mêmes.

with ranks as (
    select 
     year(entrydate) as [year], 
     month(entrydate) as [month], 
     views, 
     row_number() over (partition by year(entrydate), month(entrydate) order by views desc) as [rank] 
    from product 
) 
select 
    t1.year, 
    t1.month, 
    max(case when t1.rank = 1 then t1.views else 0 end) as [best], 
    max(case when t1.rank = 2 then t1.views else 0 end) as [second best] 
from 
    ranks t1 
where 
    t1.rank in (1,2) 
group by 
    t1.year, t1.month 
+0

Oui, c'est certainement une solution de travail, thx. Je pense qu'il existe plusieurs solutions similaires en utilisant des jointures, mais je me demande toujours (ou mieux dit, juste curieux) s'il y en a un sans eux? – Helena

+0

Je suis tombé sur des problèmes similaires au cours des années et je me demandais également s'il y avait une solution plus simple (sans jointures peut-être), mais ne pouvait pas affiner un. Et comme il s'avère que la solution de jointure, même si elle peut sembler un kludge, fonctionne parfaitement et assez rapidement. – skajfes

+0

@Helena: btw vote et/ou accepter la réponse si vous pensez que c'est utile, et bienvenue à stackoverflow – skajfes

1

Ceci est un peu old-school mais TOP et une sous-requête fonctionneront si vous utilisez ORDER BY. Essayez ceci:

SELECT TOP 2 
DATEPART(year,some_date), 
DATEPART(month,some_date), 
(SELECT MAX(st1.some_value) FROM some_table AS st1 
    WHERE DATEPART(month,some_date) = DATEPART(month,st1.some_date)) AS max_value 
FROM 
some_table 
GROUP BY 
    DATEPART(year,some_date), 
    DATEPART(month,some_date) 
ORDER BY DATEPART(month,some_date) DESC 

Cela vous donnera les deux lignes avec les valeurs de mois « plus » et la sous-sélection ajoutée devrait vous donner le maximum de chaque groupe.

+0

il veut la valeur Max et l'avant-dernière valeur max. –

+0

@Stephanie: S'il vous plaît voir ma mise à jour. Il m'a fallu quelques lectures à travers l'OP pour comprendre cette partie. –

+0

Heureusement, j'ai été en mesure de le changer –

0

Hmmm il est le genre d'une plate-forme, mais vous pouvez le faire avec ... au lieu de sous-requêtes à l'aide que je serais max sélectionnez les quelques-unes qui ont l'année correspondante & mois, row_number() = 1/row_number() = 2 respectivement et ordonné par some_value DESC.

L'impossibilité d'utiliser OFFSET/LIMIT comme vous pouvez dans SQLite est l'un de mes dégoûts à propos de SQL Server.

+0

SQL Server 2011 ** aura ** OFFSET et LIMIT - voir http://msdn.microsoft.com/fr-fr/library/ ms188385% 28v = sql.110% 29.aspx –

+0

@marc_s: enfin, environ dix ans trop tard si vous me demandez – skajfes

1

Vous pouvez utiliser un CTE avec les fonctions de classement dans SQL Server 2005 et plus:

;WITH TopValues AS 
(
    SELECT 
    YEAR(some_date) AS 'Year', 
    MONTH(some_date) AS 'Month', 
    Some_Value, 
    ROW_NUMBER() OVER(PARTITION BY YEAR(some_date),MONTH(some_date) 
         ORDER BY Some_Value DESC) AS 'RowNumber' 
    FROM 
    dbo.some_table 
) 
SELECT 
    Year, Month, Some_Value 
FROM 
    TopValues 
WHERE 
    RowNumber <= 2 

Ce sera « partition » (c.-à-groupe) vos données par mois/année, pour l'intérieur de chaque groupe par Some_Value descendant (le plus grand en premier), puis vous pouvez sélectionner les deux premiers de chaque groupe de ce CTE. Fonctionne aussi bien (j'utilise le plus souvent ROW_NUMBER) - il produit des résultats légèrement différents, cependant - dépend vraiment de ce que sont vos besoins.

+1

vous donne les deux valeurs, mais pas dans la même rangée. –

+0

@Stephanie Page: Je n'ai pas vu cela comme une exigence - mais oui, c'est vrai - il énumérerait les deux premières valeurs séparément, chacune dans une rangée de données séparée –

2

faire sans joint (je vais vous montrer l'Oracle ... vous utilisez simplement CASE au lieu de décodages)

with ranks as (
     select 
      year(entrydate) as [year], 
      month(entrydate) as [month], 
      views, 
      rank() over (partition by year(entrydate), month(entrydate) order by views desc) as [rank] 
     from product 
    ) 
SELECT [year], [month], Max([best]), Max([second best]) 
FROM 
    (select 
     t1.year, 
     t1.month, 
     Decode([rank],1,t1.views,0) as [best], 
     Decode([rank],2,t1.views,0) as [second best] 
    from ranks t1 
    where t1.rank <= 2) x 
GROUP BY [year], [month] 
+0

Nice, j'aime l'utilisation de la fonction de décodage/cas. – skajfes

Questions connexes