2009-04-06 9 views
2

Je sens que j'ai déjà vu cette question, mais ni la recherche SO ni google ne m'aide ... peut-être que je ne sais pas comment formuler la question. J'ai besoin de compter le nombre d'événements (dans ce cas, les connexions) par jour sur une période donnée afin que je puisse faire un graphique de l'utilisation du site. La requête que j'ai à ce jour est le suivant:SQL pour compter les événements par date

select 
    count(userid) as numlogins, 
    count(distinct userid) as numusers, 
    convert(varchar, entryts, 101) as date 
from 
    usagelog 
group by 
    convert(varchar, entryts, 101) 

Cela fait plus de ce que je dois (je reçois une ligne par date que la sortie contenant le nombre total de connexions et le nombre d'utilisateurs uniques à cette date). Le problème est que si personne ne se connecte à une date donnée, il n'y aura pas de ligne dans l'ensemble de données pour cette date. Je veux qu'il ajoute des lignes indiquant les connexions zéro pour ces dates. Il y a deux approches auxquelles je peux penser pour résoudre ceci, et aucune ne me semble très élégante.

  1. Ajoutez une colonne au jeu de résultats qui répertorie le nombre de jours entre le début de la période et la date de la ligne en cours. Lorsque je construis ma sortie graphique, je garde une trace de cette valeur et si la ligne suivante n'est pas égale à la ligne courante plus une, insère des zéros dans le tableau pour chacun des jours manquants.
  2. Créez une table "date" contenant toutes les dates de la période d'intérêt et la jointure externe. Malheureusement, le système sur lequel je travaille a déjà une table à cet effet qui contient une rangée pour chaque date dans le futur ... Je n'aime pas ça, et je préfère éviter de l'utiliser, d'autant plus que La table est destinée à un autre module du système et introduirait ainsi une dépendance à ce que je développe actuellement.

De meilleures solutions ou astuces pour de meilleurs termes de recherche pour google? Merci.

Répondre

3

Franchement, je le ferais par programme lors de la construction de la sortie finale. Essentiellement, vous essayez de lire quelque chose de la base de données qui n'est pas là (données pour les jours sans données). SQL n'est pas vraiment destiné à ce genre de chose.

Si vous voulez vraiment faire cela, cependant, une table "date" semble votre meilleure option. Pour le rendre un peu plus agréable, vous pouvez le générer à la volée, en utilisant les fonctions de date de votre base de données et une table dérivée.

+0

C'est aussi une bonne idée :) –

+0

J'ai fini par utiliser la table de date existante dans mon application et je tolère juste la dépendance qu'elle a introduite. C'était la solution la plus rapide. – rmeador

0
WITH q(n) AS 
      (
      SELECT 0 
      UNION ALL 
      SELECT n + 1 
      FROM q 
      WHERE n < 99 
     ), 
    qq(n) AS 
      (
      SELECT 0 
      UNION ALL 
      SELECT n + 1 
      FROM q 
      WHERE n < 99 
     ), 
    dates AS 
      (
      SELECT q.n * 100 + qq.n AS ndate 
      FROM q, qq 
     ) 
SELECT COUNT(userid) as numlogins, 
      COUNT(DISTINCT userid) as numusers, 
      CAST('2000-01-01' + ndate AS DATETIME) as date 
FROM  dates 
LEFT JOIN 
      usagelog 
ON  entryts >= CAST('2000-01-01' AS DATETIME) + ndate 
      AND entryts < CAST('2000-01-01' AS DATETIME) + ndate + 1 
GROUP BY 
      ndate 

Cela sélectionnera jusqu'à 10,000 dates construites à la volée, qui devrait être suffisant pour 30 ans.

SQL Server a une limitation de 100 récurrences par CTE, c'est pourquoi les requêtes internes peuvent revenir jusqu'à 100 lignes chacun.

Si vous avez besoin de plus de 10,000, ajoutez simplement un troisième CTEqqq(n) et effectuez une jointure croisée avec celui-ci dans dates.

+0

SQL Server ne dispose pas d'une limitation de 100 lignes par CTE. Je pense qu'il a une limite de 100 récursions dans un CTE, mais c'est très différent. –

+0

Juste vérifié, et en fait la limite par défaut est de 100 récursions. Vous pouvez définir cela avec MAXRECURSION jusqu'à 32 767 –

+0

Bien sûr, vous avez raison – Quassnoi

1

Créez une table de mémoire (une variable de table) dans laquelle vous insérez vos plages de dates, puis rejoignez la table de connexion externe par rapport à celle-ci. Groupez par date de début, vous pouvez ensuite effectuer vos agrégations et calculs.

1

La stratégie que j'utilise normalement est UNION avec l'opposé de la requête, généralement une requête qui récupère des données pour les lignes qui n'existent pas.

Si je voulais obtenir la note moyenne pour un cours, mais certains cours ont pas été pris par des étudiants, je dois union avec ceux qui ne pris par quiconque pour afficher une ligne pour chaque classe:

SELECT AVG(mark), course FROM `marks` 
    UNION 
SELECT NULL, course FROM courses WHERE course NOT IN 
    (SELECT course FROM marks) 

Votre requête sera plus complexe mais le même principe devrait s'appliquer. Vous pouvez en effet besoin d'un tableau de dates pour votre deuxième requête

1

Option 1 Vous pouvez créer une table temporaire et horodater avec la plage et faire une jointure externe gauche avec le usagelog Option 2 Vous pouvez insérer programmetically les dates manquantes tout en évaluant l'ensemble des résultats pour produire la sortie finale

2

J'ai dû faire exactement la même chose récemment. Voilà comment je l'ai fait dans T-SQL ( YMMV sur la vitesse, mais je l'ai trouvé assez performant sur une des lignes coupla millions de données d'événement):

DECLARE @DaysTable TABLE ([Year] INT, [Day] INT) 

DECLARE @StartDate DATETIME 
SET @StartDate = whatever 

WHILE (@StartDate <= GETDATE()) 
BEGIN 

    INSERT INTO @DaysTable ([Year], [Day]) 
    SELECT DATEPART(YEAR, @StartDate), DATEPART(DAYOFYEAR, @StartDate) 

    SELECT @StartDate = DATEADD(DAY, 1, @StartDate) 
END 

-- This gives me a table of all days since whenever 
-- you could select @StartDate as the minimum date of your usage log) 

SELECT days.Year, days.Day, events.NumEvents 
FROM @DaysTable AS days 
LEFT JOIN (
    SELECT 
    COUNT(*) AS NumEvents 
    DATEPART(YEAR, LogDate) AS [Year], 
    DATEPART(DAYOFYEAR, LogDate) AS [Day] 
    FROM LogData 
    GROUP BY 
    DATEPART(YEAR, LogDate), 
    DATEPART(DAYOFYEAR, LogDate) 
) AS events ON days.Year = events.Year AND days.Day = events.Day 
Questions connexes