SQL Group by With Case - sql

Name | Amount | Date
_____________________
Jacob | 15 | 1-Oct-16
Jacob | 50 | 2-Oct-16
Ethan | 24 | 3-Oct-16
Jacob | 30 | 3-Oct-16
William | 17 | 1-Oct-16
William | 11 | 2-Oct-16
Ethan | 36 | 3-Oct-16
Ethan | 9 | 1-Oct-16
I have above table, and i want Sum of amount for last 5 days something like
Name | 5-oct-16 | 4-oct-16 | 3-oct-16 | 2-oct-16| 1-oct-16
___________________________________________________________
Jacob | 0 | 0 |30 | 50 | 15
Ethan | 0 | 0 |60 | 0 | 9
William | 0 | 0 |0 | 11 | 17
want to group by on Name with amount sum of last 5 days separated.

I think you want conditional aggregation:
SELECT Name,
SUM(CASE DATE WHEN '2016-10-05' THEN Amount ELSE 0 END) as amount_2016105,
SUM(CASE DATE WHEN '2016-10-04' THEN Amount ELSE 0 END) as amount_2016104,
SUM(CASE DATE WHEN '2016-10-03' THEN Amount ELSE 0 END) as amount_2016103,
SUM(CASE DATE WHEN '2016-10-02' THEN Amount ELSE 0 END) as amount_2016102,
SUM(CASE DATE WHEN '2016-10-01' THEN Amount ELSE 0 END) as amount_2016101
FROM MyTable
GROUP BY Name;

Related

SQL - Need return from single table of SUMs over 3 different date ranges

I have a table of Account Transactions that includes ID, Amount, Date. Basically, I want to create a resulting table that looks at the table and returns what the SUM was for the Account over three different Ending Date Ranges. Then I want to Flag (Combined_Flag) each Account ID, 1 if any of the SUMs for that ID are non-zero, and a 0 if all of the SUMs are 0.
Date Range 1) Min Date to End of Last Month (-1 Month)
Date Range 2) Min Date to End of 2 Months ago (-2 Months)
Date Range 3) Min Date to End of Last Month, Last Year (-13 Months)
The Resulting table should be: ID, SUM_R1, SUM_R2, SUM_R3, Flag_R1, Flag_R2, Flag_R3, Combined_Flag
Example Data
| ID | Amount | Date |
| -------- | -------------- |-------------- |
| 1 | 20 | 09/01/19 |
| 2 | 40 | 09/01/19 |
| 3 | 0 | 09/01/19 |
| 4 | 0 | 09/01/19 |
| 1 | 10 | 10/01/19 |
| 2 | 0 | 10/01/19 |
| 3 | 0 | 10/01/19 |
| 4 | 0 | 10/01/19 |
| 1 | 15 | 11/01/19 |
| 2 | 40 | 11/01/19 |
| 3 | 0 | 11/01/19 |
| 4 | 0 | 11/01/19 |
| 1 | 20 | 09/01/20 |
| 2 | 40 | 09/01/20 |
| 3 | 0 | 09/01/20 |
| 4 | 50 | 09/01/20 |
| 1 | 10 | 10/01/20 |
| 2 | 0 | 10/01/20 |
| 3 | 0 | 10/01/20 |
| 4 | 65 | 10/01/20 |
| 1 | 15 | 11/01/20 |
| 2 | 40 | 11/01/20 |
| 3 | 0 | 11/01/20 |
| 4 | 0 | 11/01/20 |
Expected Result Table (Using Date of 12/21/2020)
| ID | SUM_R1 | SUM_R2 | SUM_R3 | Flag_R1 | Flag_R2 | Flag_R3 | Combined_Flag |
| -------- | -------- | -------- | -------- | --------- | --------- | --------- | --------------- |
| 1 | 90 | 75 | 45 | 1 | 1 | 1 | 1 |
| 2 | 160 | 120 | 80 | 1 | 1 | 1 | 1 |
| 3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 4 | 115 | 115 | 0 | 1 | 1 | 0 | 1 |
The difficulty I'm having is in joining the table basically to itself 2 times. I'm getting results all over the place and not really sure exactly what's going on.
Is this what you want?
select id,
sum(case when date < datefromparts(year(v.dte), month(v.dte), 1)
then amount else 0
end) as sum_r1,
sum(case when date < dateadd(month, -1, datefromparts(year(v.dte), month(v.dte), 1))
then amount else 0
end) as sum_r2,
sum(case when date < dateadd(month, -13, datefromparts(year(v.dte), month(v.dte), 1))
then amount else 0
end) as sum_r3,
max(case when amount > 0 and date < datefromparts(year(v.dte), month(v.dte), 1)
then 1 else 0
end) as flag_r1,
max(case when amount > 0 and date < dateadd(month, -1, datefromparts(year(v.dte), month(v.dte), 1))
then 1 else 0
end) as flag_r2,
max(case when amount > 0 and date < dateadd(month, -13, datefromparts(year(v.dte), month(v.dte), 1))
then 1 else 0
end) as flag_r3
from t cross join
(values (convert(date, '2020-12-21'))
) v(dte)
group by id;
The flag columns assume that the amounts are never negative (which is consistent with the data in your question.
EDIT:
The shorthand in the comment for creating the flag looks like:
abs(sign(sum(case when amount > 0 and date < datefromparts(year(v.dte), month(v.dte), 1)
then amount else 0
end))) as flag_r1,
Here is a db<>fiddle.

How should I combine multiple columns and convert to rows?

I'm working on a table like the one below, and the table name is information:
-------------------------------
|id | gender | country | age |
-------------------------------
| 1 | F | America | 18 |
-------------------------------
| 2 | M | America | 22 |
-------------------------------
| 3 | M | Japan | 30 |
-------------------------------
| 4 | M | Brazil | 32 |
-------------------------------
| 5 | F | Norway | 34 |
-------------------------------
| 7 | M | America | 32 |
-------------------------------
|10 | F | Norway | 22 |
-------------------------------
|13 | F | Japan | 36 |
-------------------------------
|14 | F | Brazil | 19 |
-------------------------------
|25 | F | Japan | 33 |
-------------------------------
And I want the output to be:
------------------------------------------------------
| country | total | Female | Male | 18 - 25 | 26 -35 |
------------------------------------------------------
| America | 3 | 1 | 2 | 2 | 1 |
------------------------------------------------------
| Japan | 3 | 2 | 1 | 0 | 3 |
------------------------------------------------------
| Brazil | 2 | 1 | 1 | 1 | 1 |
------------------------------------------------------
| Norway | 2 | 2 | 0 | 1 | 1 |
------------------------------------------------------
The following is my approach, first count the distinct id group by country and gender:
SELECT country, gender, COUNT(DISTINCT id) FROM information GROUP BY 1,2;
Then try to create the table with age range:
(SELECT '18-25' AS '18-25'
SUM(CASE WHEN (AGE >=18 AND AGE<=25) THEN 1 ELSE 0 END) AS NUM FROM information)
UNION
(SELECT '26-35' AS '18-25'
SUM(CASE WHEN (AGE >=26 AND AGE<=35) THEN 1 ELSE 0 END) AS NUM FROM information)
But I'm not sure how to combine them and covert the columns to rows. Could anyone give me some suggestion?
Use conditional aggregation:
select country, count(*) as total,
sum(case when gender = 'F' then 1 else 0 end) as num_female,
sum(case when gender = 'M' then 1 else 0 end) as num_male,
sum(case when age >= 18 and age <= 25 then 1 else 0 end) as age_18_25,
sum(case when age >= 26 and age <= 35 then 1 else 0 end) as age_26_35
from information
group by country
A slight variation on Gordon's answer which I prefer, because it eliminates the need for an explicit ELSE case:
SELECT
country,
COUNT(*) AS total,
COUNT(CASE WHEN gender = 'F' THEN 1 END) AS Female,
COUNT(CASE WHEN gender = 'M' THEN 1 END) AS Male,
COUNT(CASE WHEN age BETWEEN 18 AND 25 THEN 1 END) AS "18-25",
COUNT(CASE WHEN age BETWEEN 26 AND 35 THEN 1 END) AS "26-35"
FROM information
GROUP BY
country;
Less code = usually a better thing

Query to both group by a name in one column and show total for each grouped element

I have a table that looks something like this generic example.
+-------+--------------+------------+---------------+-----------+----------+
| id | name | amount | payment_type | year | period |
+-------+--------------+------------+---------------+-----------+----------+
| 1 | Bob | 20 | BACS | 2019 | 5 |
+-------+--------------+------------+---------------+-----------+----------+
| 2 | Jim | 40 | BACS | 2019 | 5 |
+-------+--------------+------------+---------------+-----------+----------+
| 3 | Bob | 60 | BACS | 2019 | 5 |
+-------+--------------+------------+---------------+-----------+----------+
| 4 | Jim | 20 | Voucher | 2019 | 5 |
+-------+--------------+------------+---------------+-----------+----------+
| 5 | Bob | 40 | BACS | 2019 | 5 |
+-------+--------------+------------+---------------+-----------+----------+
| 6 | Bob | 20 | Cheque | 2019 | 5 |
+-------+--------------+------------+---------------+-----------+----------+
| 7 | Jim | 20 | BACS | 2019 | 5 |
+-------+--------------+------------+---------------+-----------+----------+
| 8 | Bob | 20 | BACS | 2019 | 5 |
+-------+--------------+------------+---------------+-----------+----------+
| 9 | Bob | 20 | BACS | 2019 | 5 |
+-------+--------------+------------+---------------+-----------+----------+
| 10 | Dan | 20 | BACS | 2019 | 5 |
+-------+--------------+------------+---------------+-----------+----------+
| 11 | Dan | 20 | Voucher | 2019 | 5 |
+-------+--------------+------------+---------------+-----------+----------+
etc...
I would like to total up results of this table (Many more rows) and have a count of the total number of rows for each user, the total of the amount column for each user, and a total of each payment type for each user, narrowed down by year and period each time I run this query. (The year and period are user-select-able on a form.) So that I end up with a result set that looks something like this:
+-------+--------------+------------+---------------+-----------+----------+
| name | Total | BACS | Cheque | Voucher | Cost |
+-------+--------------+------------+---------------+-----------+----------+
| Bob | 6 | 5 | 1 | 0 | 180 |
+-------+--------------+------------+---------------+-----------+----------+
| Jim | 3 | 2 | 0 | 1 | 80 |
+-------+--------------+------------+---------------+-----------+----------+
| Dan | 2 | 1 | 0 | 1 | 40 |
+-------+--------------+------------+---------------+-----------+----------+
So I need a pivoting query of some kind using a sum case something like this:
SELECT
name,
SUM(CASE WHEN YEAR = '$safefy' THEN 1 ELSE 0 END) AS total,
SUM(CASE WHEN payment_type = 'BACs' OR payment_type = 'BACS' THEN 1 ELSE 0 END) AS BACS,
SUM(CASE WHEN payment_type = 'Cheque' THEN 1 ELSE 0 END) AS Cheque,
SUM(CASE WHEN payment_type = 'Voucher' THEN 1 ELSE 0 END) AS Voucher,
<Not sure what goes here.> AS Cost,
FROM
table_name
WHERE
YEAR = '2019' AND period = '5'
GROUP BY
name "
But I'm not sure how to total up the cost from the tables amount column for each user. I'm probably not using the correct terms in my search for answers because I don't know what it's called?
I think you just want sum(amount):
SELECT name,
SUM(CASE WHEN YEAR = '$safefy' THEN 1 ELSE 0 END) AS total,
SUM(CASE WHEN payment_type = 'BACs' OR payment_type = 'BACS' THEN 1 ELSE 0 END) AS BACS,
SUM(CASE WHEN payment_type = 'Cheque' THEN 1 ELSE 0 END) AS Cheque,
SUM(CASE WHEN payment_type = 'Voucher' THEN 1 ELSE 0 END) AS Voucher,
SUM(amount) AS Cost
FROM table_name
WHERE YEAR = '2019' AND period = '5'
GROUP BY name;
You can simplify the first part because the CASE is not needed:
SELECT name,
SUM(YEAR = '$safefy') AS total,
SUM(payment_type IN ('BACs', 'BACS') THEN 1 ELSE 0 END) AS BACS,
SUM(payment_type = 'Cheque') AS Cheque,
SUM(payment_type = 'Voucher') AS Voucher,
SUM(amount) AS Cost
FROM table_name
WHERE YEAR = '2019' AND period = '5'
GROUP BY name

Calculation going wrong due to JOIN issue

Table -
+----+-----------+-----------+---------+---------------------+------------+
| ID | Client_Id | Driver_Id | City_Id | Status | Request_at |
+----+-----------+-----------+---------+---------------------+------------+
| 1 | 1 | 10 | 1 | completed | 2013-10-01 |
| 2 | 2 | 11 | 1 | cancelled_by_driver | 2013-10-01 |
| 3 | 3 | 12 | 6 | completed | 2013-10-01 |
| 4 | 4 | 13 | 6 | cancelled_by_client | 2013-10-01 |
| 5 | 1 | 10 | 1 | completed | 2013-10-02 |
| 6 | 2 | 11 | 6 | completed | 2013-10-02 |
| 7 | 3 | 12 | 6 | completed | 2013-10-02 |
| 8 | 2 | 12 | 12 | completed | 2013-10-03 |
| 9 | 3 | 10 | 12 | completed | 2013-10-03 |
| 10 | 4 | 13 | 12 | cancelled_by_driver | 2013-10-03 |
+----+-----------+-----------+---------+---------------------+------------+
My attempt -
WITH src
AS (SELECT Count(status) AS Denom,
request_at
FROM trips
WHERE status = 'completed'
GROUP BY request_at),
src2
AS (SELECT Count(status) AS Num,
request_at
FROM trips
WHERE status <> 'completed'
GROUP BY request_at)
SELECT Cast(Count(num) AS FLOAT)/Cast(Count(Denom) AS FLOAT) AS cancel_rate,
trips.request_at
FROM src,
src2,
trips
GROUP BY trips.request_at;
I am trying to find the cancellation rate per day but it is clearing wrong (MY OUTPUT)-
+-------------+------------+
| cancel_rate | request_at |
+-------------+------------+
| 24 | 2013-10-01 |
| 18 | 2013-10-02 |
| 18 | 2013-10-03 |
+-------------+------------+
The cancellation rate for 2013-10-01 should be 0.5 and not 24. Similarly for other dates it should be different.
I know the problem lies with this part but I do not know what is the correct way or how to approach it
SELECT Cast(Count(num) AS FLOAT)/Cast(Count(Denom) AS FLOAT) AS cancel_rate,
trips.request_at
FROM src,
src2,
trips
Is there any way to put in more than 1 select statement in With NAME as () clause ? So that I won't use any JOIN or multiple tables.
Use conditional aggregation:
SELECT SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as denom,
SUM(CASE WHEN status <> 'completed' THEN 1 ELSE 0 END) as num,
AVG(CASE WHEN status <> 'completed' THEN 1.0 ELSE 0 END) as cancel_rate
FROM trips
GROUP BY request_at;
Note that calculation for the cancel_rate. This is simpler to do using AVG() rather than dividing the two values. The use of 1.0is because SQL Server does integer arithmetic, so 1 / 2 is 0 rather than 0.5.
OK, a bit late again, but here is another variation (edited):
SELECT SUM(CASE LEFT(status,9) WHEN 'cancelled' THEN 1. ELSE 0 END)
/COUNT(*) cancellation_rate,
request_at
FROM trips GROUP BY request_at ORDER BY request_at

SQL query select with ranges

I have one table like this...
+---------+------------+------+-------+
| City | NamePeople | Age | Money |
+---------+------------+------+-------+
| Paris | John | 10 | 200 |
| London | Marie | 21 | 300 |
| Paris | Kevin | 50 | 250 |
| Paris | Julia | 45 | 150 |
+---------+------------+------+-------+
I need a result with ranges , something like this
+---------------+------------+-----------------------+-------------------------+
| City | Sum(Money) | CountPeopleWithAge<30 | CountPeopleWithAge>30 |
+---------------+------------+-----------------------+-------------------------+
| Paris | 600 | 1 | 2 |
| London | 300 | 1 | 0 |
+---------------+------------+-----------------------+-------------------------+
How do I do this with sql select?
Thanks.
Select
City,
Sum(Money),
Sum(Case When Age < 30 Then 1 Else 0 End),
Sum(Case When Age > 30 Then 1 Else 0 End)
From
table
Group By
City
You can use this Query,
SELECT City,
SUM(Money),
SUM(case when Age < 30 then 1 else 0 end),
SUM(case when Age > 30 then 1 else 0 end)
FROM tableA
GROUP BY City