简体   繁体   中英

SELECT CASE to return accumulative results

I'm trying to create a query using SELECT CASE that would return the accumulative results.

Here is my query, it works but doesnt return the correct result.

  SELECT total, count(*) as count FROM
                       (
                         SELECT case
                         WHEN ( X ) < 1 THEN '1 km'
                         WHEN ( X ) < 3 THEN '3 km'
                         WHEN ( X ) < 5 THEN '5 km'
                         WHEN ( X ) < 10 THEN '10 km'
                         WHEN ( X ) < 15 THEN '15 km'
                         WHEN ( X ) < 25 THEN '25 km'
                         WHEN ( X ) < 50 THEN '50 km'
                         WHEN ( X ) < 100 THEN '100 km'
                         WHEN ( X ) > 0 THEN '-1'
                         else '-2'
                         end AS `total` 
                         FROM `store` AS d WHERE d.pending!='1' 
                    ) AS someRandomAliasHere
                    GROUP BY `total`

X is a formula i'm using to calculate radius from a lat and lang. total is NOT a column in my database table, just a result to calculations of X

The query above gives me this..

 1 km (4)
 3 km (19)
 5 km (103)
 25 km  (540)
 50 km (61)
 ....

4,19,103,540,62 are the total matches found.

The total count for 3 should be 19+4=23 .
5 should be 103+19+4=122 etc. And WHEN ( X ) > 0 THEN '-1' should show the total count. of matches

I tried using BETWEEN 0 AND 1, BETWEEN 0 AND 3 etc but it still didn't give me the correct results

Any ideas?

Another approach is to calculate the results independently then union them:

SELECT 1 AS total, COUNT(*) AS cnt FROM `store` WHERE store.pending != 1 AND ( X ) < 1
UNION ALL
SELECT 3 AS total, COUNT(*) AS cnt FROM `store` WHERE store.pending != 1 AND ( X ) < 3
UNION ALL
SELECT 5 AS total, COUNT(*) AS cnt FROM `store` WHERE store.pending != 1 AND ( X ) < 5
UNION ALL
/** ... **/
SELECT 100 AS total, COUNT(*) AS cnt FROM `store` WHERE store.pending != 1 AND ( X ) < 100

In addition to the accumulation, you also want a total value at the end with -1 . This is a bit of a pain, but it can be accomplished.

The simplest way to do cumulative sums in MySQL is using variables. The basic idea is this:

 SELECT total, cnt, (@cnt := @cnt + cnt) as count  
 FROM (SELECT (case  WHEN ( X ) < 1 THEN '1'
                     WHEN ( X ) < 3 THEN '3'
                     WHEN ( X ) < 5 THEN '5'
                     WHEN ( X ) < 10 THEN '10'
                     WHEN ( X ) < 15 THEN '15'
                     WHEN ( X ) < 25 THEN '25'
                     WHEN ( X ) < 50 THEN '50'
                     WHEN ( X ) < 100 THEN '100'
                     WHEN ( X ) > 0 THEN '-1'
                     else '-2'
               end) AS total, COUNT(*) as cnt
        FROM store s
        WHERE s.pending <> '1' 
        GROUP BY total
       ) t CROSS JOIN
       (SELECT @cnt := 0) vars
  ORDER BY total;

The issue with this is that you will not get an overall total of the non-negative values. Let me assume that you have no negative values. This requires adding another row into the total line:

 SELECT total, cnt, (@cnt := @cnt + cnt) as count
 FROM (SELECT (case  WHEN ( X ) < 1 THEN '1'
                     WHEN ( X ) < 3 THEN '3'
                     WHEN ( X ) < 5 THEN '5'
                     WHEN ( X ) < 10 THEN '10'
                     WHEN ( X ) < 15 THEN '15'
                     WHEN ( X ) < 25 THEN '25'
                     WHEN ( X ) < 50 THEN '50'
                     WHEN ( X ) < 100 THEN '100'
                     WHEN ( X ) > 0 THEN '-1'
                     else '-2'
               end) AS total, COUNT(*) as cnt
        FROM store s
        WHERE s.pending <> '1' 
        GROUP BY `total`
        UNION ALL
        SELECT -1, 0
       ) t CROSS JOIN
       (SELECT @cnt := 0) vars
  ORDER BY (total >= 0) desc, total;

I've changed the order by as well. Note that the value -2 is probably meaningless, because X < 1 and X > 0 cover all possible values of X (except for NULL ). If you actually have values 100 or greater, there are some small changes to refine the query. You do not describe what to do with those values, so clarification on the question would be helpful.

Not sure if this works since I don't have a database to test this on. Also not exactly in the format you want.

select sum(if(X<1,1,0)) as C1,
       sum(if(X<3,1,0)) as C3,
       sum(if(X<5,1,0)) as C5,
       sum(if(X<10,1,0)) as C10,
       sum(if(X<15,1,0)) as C15,
       sum(if(X<25,1,0)) as C25,
       sum(if(X<50,1,0)) as C50,
       sum(if(X<100,1,0)) as C100,
       sum(if(X>=100,1,0)) as C100P
from store
where store.pending != '1'

Unfortunatelly MySQL do not have analytical functions and windowed functions, but in this case, you can achieve your goal using a variable and a nested subquery:

SELECT
  total,
  cnt,
  @rollupCount:=@rollupCount+cnt AS rollupCount
FROM
  (
    SELECT
      total,
      count(*) AS cnt
    FROM
    (
         SELECT
            CASE
             WHEN ( X ) < 1 THEN '1'
             WHEN ( X ) < 3 THEN '3'
             WHEN ( X ) < 5 THEN '5'
             WHEN ( X ) < 10 THEN '10'
             WHEN ( X ) < 15 THEN '15'
             WHEN ( X ) < 25 THEN '25'
             WHEN ( X ) < 50 THEN '50'
             WHEN ( X ) < 100 THEN '100'
             WHEN ( X ) > 0 THEN '-1'
             ELSE '-2'
           END AS `total` 
         FROM
           `store` AS d
         WHERE
           d.pending != '1' 
    ) AS someRandomAliasHere
    GROUP BY
      `total`
  ) AS anotherRandomAliasHere
  , (SELECT @rollupCount:=0) AS RC
ORDER BY
  total ASC

This is the same as when you want to calculate the row number for each record:

SELECT
  @rowNumber:=@rowNumber+1 AS rowNumber,
  sourceColumns
FROM
  sourceTable, (SELECT @rowNumber:=0) AS t
ORDER BY
  orderColumn;

A column to give cumulative totals from an existing query can be created relatively simply:

SELECT X,
       total,
       (SELECT SUM(total)
        FROM (<<<your_current_query>>>) ycq2
        WHERE ycq2.X <= ycq1.X) `cumulative_total`
FROM (<<<your_current_query>>>) ycq1

Of course this will expand quite a lot when pasting your current query in the two marked places.

See SQL fiddle demo .

try this:


/sql server version/
DECLARE @GROUPS TABLE (TOTAL INT)
INSERT INTO @GROUPS
VALUES (1),
       (3),
       (5),
       (10),
       (15),
       (25),
       (50),
       (100),
       (-1)

SELECT a.TOTAL, z.[COUNT] FROM @GROUPS a CROSS APPLY (SELECT COUNT(*) as [COUNT] FROM store x WHERE CASE WHEN a.TOTAL = -1 THEN -x.X ELSE x.X END < REPLACE(a.TOTAL,-1,0) AND pending != 1) z

/mysql version/

CREATE TEMPORARY TABLE GROUPS TABLE (TOTAL INT) INSERT INTO GROUPS VALUES (1), (3), (5), (10), (15), (25), (50), (100), (-1)

SELECT a.TOTAL, z.[COUNT] FROM GROUPS a CROSS APPLY (SELECT COUNT(*) as [COUNT] FROM store x WHERE CASE WHEN a.TOTAL = -1 THEN -x.X ELSE x.X END < REPLACE(a.TOTAL,-1,0) AND pending != 1) z

Your question is a bit hard to understand. Is this what you want?

CREATE TABLE Totals (X INT, SUM INT);

INSERT INTO Totals VALUES
(1, 4),
(3, 19),
(5,  103),
(25, 540),
(50, 61)

SELECT first.X
, Sum(second.SUM)
FROM Totals first
JOIN Totals second
ON first.x >= second.x
GROUP BY first.X
UNION
SELECT 0, SUM(sum) * 2
FROM Totals

http://sqlfiddle.com/#!3/65665/12

try this one :

1.) value after "THEN" must be numeric

2.) make temporary table with running id (example for SQL SERVER)

SELECT  identity(int, 1, 1) as id,
        case
        WHEN ( X ) < 1 THEN 1
        WHEN ( X ) < 3 THEN 3
        WHEN ( X ) < 5 THEN 5
        WHEN ( X ) < 10 THEN 10
        WHEN ( X ) < 15 THEN 15
        WHEN ( X ) < 25 THEN 25
        WHEN ( X ) < 50 THEN 50
        WHEN ( X ) < 100 THEN 100
        WHEN ( X ) > 0 THEN -1
        else '-2' end AS total
Into storeID
FROM store AS d 
WHERE d.pending!='1' 
Order BY total

3.) join table with same table, with criteria

Select a.*, sum(b.total) as NewTotal
From storeID a
Left Join storeID b
    On b.id <= a.id
Group By a.id, a.total

4.) i think, "NewTotal" is what you are looking for

I suppose that you X function returns a floating point number. If I understand your logic correctly, you want to group together values where X is >=0 and <1 , where X >=0 and <3 , >=0 and <5 and so on, and you want to return -1 when the value is >=0 , and -2 when the value is a negative <0 number.

I would use an intervals table, defined like this:

CREATE TABLE intervals (
  i_begin INT,
  i_end INT,
  i_value INT
);

INSERT INTO intervals VALUES
(0,    1,     1),
(0,    3,     3),
(0,    5,     5),
(0,    10,   10),
(0,    15,   15),
(0,    25,   25),
(0,    50,   50),
(0,    100, 100),
(0,    null, -1),
(null, 0,    -2);

or you can play with values in this table to make it suite your needs.

then you can just use an INNER JOIN and a GROUP BY query:

SELECT
  i_value, COUNT(*)
FROM
  store INNER JOIN intervals
  ON ((i_begin IS NULL OR X>=i_begin) AND (i_end IS NULL OR X<i_end))
WHERE
  store.pending<>1
GROUP BY
  i_value

Please see an example here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM