Using CASE to align values with user IDs - mysql

I have a table which is a custom data table where name value pairs exist. Here's an example of some custom data in this table for an individual:
table custom_data
user_id | custom_data_name | custom_data_value
123 | initial contact date | 2014-01-01
123 | subscription start date | 2014-02-02
123 | favorite fruit | pears
I would like to create a query that parses this out at the individual level so I can see the data side by side like this:
user_id | initial_contact_date | subscription_start_date
123 | 2014-01-01 | 2014-02-02
I tried this:
SELECT
user_id,
CASE WHEN base1.custom_data_name = 'initial contact date' THEN custom_data.data END AS initial_contact_date,
CASE WHEN base1.custom_data_name = 'subscription start date' THEN custom_data.data END AS subscription_start_date
FROM
(
SELECT * FROM custom_data
WHERE custom_data_name = 'initial contact date' OR custom_data_name = 'subscription start date') base1
GROUP BY user_id
But I'm having a hard time interpreting the results. The results show, for each user, a value for EITHER initial_contact_date or subscription_start_date but not both together in one record.
How would I do that?

Try this:
SELECT
user_id,
MAX(CASE WHEN custom_data_name = 'initial contact date' THEN custom_data_value END) AS initial_contact_date,
MAX(CASE WHEN custom_data_name = 'subscription start date' THEN custom_data_value END) AS subscription_start_date
--MAX(CASE WHEN custom_data_name = 'favorite fruit' THEN custom_data_value END) AS favorite_fruit
FROM custom_data
GROUP BY user_id
Sample SQL Fiddle

You need to aggregate the data
select user_id
, min(case
when custom_data_name = 'initial contact date' then custom_data_value
end) initialContactDate
, min(case
when custom_data_name = 'subscription start date' then custom_data_value
end) subscriptionStartDate
from (
select '123' user_id, 'initial contact date' custom_data_name, '2014-01-01' custom_data_value union all
select '123' user_id, 'subscription start date' custom_data_name, '2014-01-02' custom_data_value union all
select '123' user_id, 'favorite fruit' custom_data_name, 'pears' custom_data_value
) tt
group by user_id
USER_ID INITIALCONTACTDATE SUBSCRIPTIONSTARTDATE
-----------------------------------------------------
123 2014-01-01 2014-01-02
SQLFiddle

Related

How to show pivot data without directly using pivot statement?

I am trying to pivoting the data without directly using the pivot function.
I have a simple table t1 which has:
ID Employee Name
100 Amit
100 Rohan
101 Rohit
102 Pradnya
My expected output is:
100 101 103
2 1 1
I want to achieve this without using pivot. I tried using:
SELECT *
FROM (SELECT CASE
WHEN id = '101' THEN '101'
END,
CASE
WHEN id = '102' THEN '102'
END,
CASE
WHEN id = '103' THEN '103'
END,
Count(*) cnt
FROM t1
GROUP BY CASE
WHEN id = '101' THEN '101'
END,
CASE
WHEN id = '102' THEN '102'
END,
CASE
WHEN id = '102' THEN '102'
END);
How can I achieve the output without pivot?
You are sort of there, try the following:
with s as (
select id, Count(*) cnt
from t
group by id
)
select
max(case when id=100 then cnt end) as '100',
max(case when id=101 then cnt end) as '101',
max(case when id=102 then cnt end) as '102'
from s
See Example Fiddle

Join not summing CASE WHEN

What I'm looking to do is show my current performance for this month, compared with expected scheduled wins to come in and then display the total expected amount, by product type.
For clarity, I have two sub-products that I'm grouping under the same name.
My issue is that for my 'Charged' amount, it's keeping the two sub-products separate, where as the 'Scheduled' amount is working fine.
The table should look like:
Type | Charged | Scheduled | Expected
A 3 2 5
B 1 1 2
What's actually showing is:
Type | Charged | Scheduled | Expected
A 2 1 3
A 1 1 2
B 1 1 2
The code is as follows:
select
t2.product,
t1.Charged,
t2.Scheduled,
t1.charged + t2.scheduled as 'expected'
from(
select
case
when user_type = 'a1' then 'a'
when user_type = 'a2' then 'a'
else 'b'
end as 'Type',
SUM(charged) as 'Scheduled'
from
table
where
month(date) = month(now())
and
year(date) = year(now())
and status like 'scheduled'
group by 1
order by 2 desc) t2 join
(select
case
when user_type = 'a1' then 'a'
when user_type = 'a2' then 'a'
else 'b'
end as 'Type',
sum(charged) as 'Charged'
FROM table
WHERE (status = 'Complete'
AND str_to_date(concat(date_format(date, '%Y-%m'), '-01'), '%Y-%m-%d') = str_to_date(concat(date_format(now(), '%Y-%m'), '-01'), '%Y-%m-%d'))
GROUP BY user_type
ORDER BY user_type ASC) as t1 on t1.type = t2.type
I appreciate I might not be explaining this incredibly well (and that my code is probably quite clunky - I'm still fairly new!) so any help/direction would be appreciated.
Thanks!
Just some suggestion
you have a column product in main select but you have type in subquery and not product
you should not use sigle quote around column name
ad you have group by user_type but you need group by type for charged
select
t2.type,
t1.Charged,
t2.Scheduled,
t1.charged + t2.scheduled as 'expected'
from(
select
case
when user_type = 'a1' then 'a'
when user_type = 'a2' then 'a'
else 'b'
end as Type,
SUM(charged) as Scheduled
from
table
where
month(date) = month(now())
and
year(date) = year(now())
and status like 'scheduled'
group by 1
order by 2 desc) t2 join
(select
case
when user_type = 'a1' then 'a'
when user_type = 'a2' then 'a'
else 'b'
end as Type,
sum(charged) as Charged
FROM table
WHERE (status = 'Complete'
AND str_to_date(concat(date_format(date, '%Y-%m'), '-01'), '%Y-%m-%d') = str_to_date(concat(date_format(now(), '%Y-%m'), '-01'), '%Y-%m-%d'))
GROUP BY Type
ORDER BY Type ASC) as t1 on t1.type = t2.type

MySQL - users' monthly spend in the first 6 months of initial subscription purchase

I am trying to create report that shows subscription price (AmountPerMonth) month by month for 6 months for all users - where Month1 is the date the user has purchased the 1st subscription date (NOT the registration date) , and Month2 etc are the subsequent months from that date, varying for each account.
The format of the table for this report
I have managed to pull the first 2 months table, but can't figure out how to continue up to the 6th month. Thank you in advance!
SELECT F1.Id, F1.Month1, F2.Month2
FROM
(SELECT Id, AmountPerMonth AS Month1, ActionDate
FROM MONTLYSPEND
GROUP BY ID
HAVING MIN(ActionDate)) AS F1,
(SELECT t1.Id R, t2.AmountPerMonth AS Month2, MIN(t2.ActionDate)
FROM MONTLYSPEND t1
INNER JOIN MONTLYSPEND t2
ON t1.Id = t2.Id
AND t1.ActionDate < t2.ActionDate
GROUP BY t1.Id) AS F2
WHERE F1.id = F2.R
;
Turns out there were two ways - Stored Procedure, which takes too much memory, or using CASE WHEN, this pivots the table as well. Hope it's useful to people who have to generate reports showing various activity per user day by day or month by month on the x axis. The main difficulty I had was the fact that Month_1 (first purchased subscription) was a different date for every user. This report can be used to analyse your users behavior in the first 6 months of their subscription.
The report generated by this query looks like this:
+--------+----------+------------------+---------+---------+--------+
| UserId | Currency | FirstSubscrPurch | Month_1 | Month_2 | etc... |
+--------+----------+------------------+---------+---------+--------+
| 123 | GBP | 2010-05-27 | 34.00 | 27.00 | 0.00 |
+--------+----------+------------------+---------+---------+--------+
SELECT F6.USERID, F6.Currency, DATE_FORMAT(F6.FirstSubscrPurch, "%Y-%m-%d") AS FirstSubscrPurch, F6.MONTH_1, F6.MONTH_2,F6.MONTH_3, F6.MONTH_4, F6.MONTH_5, F6.MONTH_6, ROUND(((F6.MONTH_1+F6.MONTH_2+F6.MONTH_3+F6.MONTH_4+F6.MONTH_5+F6.MONTH_6)/6),2) AVERAGE, F6.CURRENCY
FROM (
SELECT
UserId, Currency, FirstSubscrPurch,
SUM(CASE WHEN YEAR_AND_MONTH_INDEX = 0 THEN TOTAL_AMOUNT_PAID ELSE 0 END) MONTH_1,
SUM(CASE WHEN YEAR_AND_MONTH_INDEX = 1 THEN TOTAL_AMOUNT_PAID ELSE 0 END) MONTH_2,
SUM(CASE WHEN YEAR_AND_MONTH_INDEX = 2 THEN TOTAL_AMOUNT_PAID ELSE 0 END) MONTH_3,
SUM(CASE WHEN YEAR_AND_MONTH_INDEX = 3 THEN TOTAL_AMOUNT_PAID ELSE 0 END) MONTH_4,
SUM(CASE WHEN YEAR_AND_MONTH_INDEX = 4 THEN TOTAL_AMOUNT_PAID ELSE 0 END) MONTH_5,
SUM(CASE WHEN YEAR_AND_MONTH_INDEX = 5 THEN TOTAL_AMOUNT_PAID ELSE 0 END) MONTH_6
FROM (
SELECT
hp.UserId, hp.Currency, MIN(hp.Date) AS FirstSubscrPurch,
CONCAT(YEAR(Date),'-',MONTH(Date)) AS YEAR_AND_MONTH,
TIMESTAMPDIFF( MONTH, CONCAT(YEAR(FIRST_PAYMENT_DATE),'-',MONTH(FIRST_PAYMENT_DATE),'-1'), CONCAT(YEAR(Date),'-',MONTH(Date),'-1')) AS YEAR_AND_MONTH_INDEX, -- generates string in format YYYY-M-D
MIN(Date) FIRST_PAYMENT_OF_MONTH,
MAX(Date) LAST_PAYMENT_OF_MONTH,
COUNT(*) NUM_PAYMENTS,
SUM(hp.Amount) TOTAL_AMOUNT_PAID,
SUM(hp.Credits) Credits
FROM payments hp
JOIN (
SELECT UserId, MIN(Date) FIRST_PAYMENT_DATE, ADDDATE(MIN(Date), INTERVAL 6 MONTH) SIX_MONTHS_AFTER_FIRST_PAYMENT
FROM payments hp
GROUP BY UserId
) USER_MIN_ID ON USER_MIN_ID.UserId = hp.UserId
AND hp.Date BETWEEN FIRST_PAYMENT_DATE AND CONCAT(YEAR(SIX_MONTHS_AFTER_FIRST_PAYMENT),'-',MONTH(SIX_MONTHS_AFTER_FIRST_PAYMENT),'-1')
GROUP BY UserId, Currency, YEAR_AND_MONTH
ORDER BY hp.UserId, hp.Date
) F
GROUP BY UserId, Currency
ORDER BY UserId DESC) F6;

need absent and present count with month name

I need a month name with absent and present count. This is my database query:
SELECT sid,COUNT(CASE WHEN STATUS ='A' THEN 1 END) AS absent_count,COUNT(CASE WHEN STATUS ='P' THEN 1 END) AS present_count,
MONTHNAME(attendance_date) AS `Month_Name`
FROM attendance
WHERE SID = '2'
AND campus_id = 2
GROUP BY sid;
There's no point in group by sid - it will always be '2', as per your where clause. Instead, since you want to count per month name, that should appear in the group by clause:
SELECT MONTHNAME(attendance_date) AS `Month_Name`,
COUNT(CASE WHEN STATUS ='A' THEN 1 END) AS absent_count,
COUNT(CASE WHEN STATUS ='P' THEN 1 END) AS present_count,
FROM attendance
WHERE sid = '2' AND campus_id = 2
GROUP BY MONTHNAME(attendance_date);

SQL query - print the min and max from one table in the same row

I need a little help with an sql query.
I have a table with a format and data that looks like this:
id | applicant_id | application_status | status_time
1 | 1234 | received | 2013-05-06 15:00:00
1 | 1234 | pending | 2013-05-06 15:30:00
1 | 1234 | approved | 2013-05-06 16:00:00
The problem that I need to solve will have to print the following:
applicant_id | initial_status | initial_time | current_status | current_status_time
1234 | received | 2013-05-06 15:00:00 | approved | 2013-05-06 16:00:00
How could I go about accomplishing something like this, preferably only using joins and no nested selects?
The best way to approach this, in general, is to use the row_number() function. However, this requires a nested select:
select t.applicant_id,
max(case when seqnum_asc = 1 then status end) as initial_status,
max(case when seqnum_asc = 1 then status_time end) as initial_time,
max(case when seqnum_desc = 1 then status end) as current_status,
max(case when seqnum_desc = 1 then status_time end) as current_time
from (select t.*,
row_number() over (partition by applicant_id order by status_time) as seqnum_asc,
row_number() over (partition by applicant_id order by status_time desc) as seqnum_desc
from t
) t
group by t.applicant_id;
If your database did not support row_number(), I would recommend correlated subqueries, for readability. But those are also nested. Here is a solution in MySQL that meets your requirements:
select t.applicant_id,
substring_index(group_concat(status) separator ',' order by status_time), ',', 1) as initial_status,
min(status_time) as initial_time,
substring_index(group_concat(status) separator ',' order by status_time desc), ',', 1) as current_status,
max(status_time) as current_time
from t
group by t.applicant_id;
You did not state your database product, but you could use something like this on any database:
select t1.id,
t1.applicant_id,
max(case when t1.status_time = t2.mintime then t1.application_status end) initial_status,
max(case when t1.status_time = t2.mintime then t1.status_time end)initial_time,
max(case when t1.status_time = t2.maxTime then t1.application_status end) current_status,
max(case when t1.status_time = t2.maxTime then t1.status_time end) `current_time`
from yourtable t1
inner join
(
select id, applicant_id,
max(status_time) maxTime,
min(status_time) mintime
from yourtable
group by id, applicant_id
) t2
on t1.id = t2.id
and t1.applicant_id = t2.applicant_id
and
(
t1.status_time = t2.mintime
or t1.status_time = t2.maxtime
)
group by t1.id, t1.applicant_id;
See SQL Fiddle with Demo
SELECT a.application_id
, a.application_status as initial_status
, a.status_time as initial_time
, b.application_status as current_status
, b.status_time as current_status_time
FROM sample1 A
CROSS JOIN sample1 B
WHERE A.application_status = 'received'
and b. application_status = 'approved'
Assuming MS SQL (Transact-SQL), and that your source table is aptly named [SourceTable]. =)
SELECT DISTINCT
[Probe].applicant_id,
[LogMin].application_status [initial_status],
[LogMin].status_time [initial_time],
[LogMax].application_status [current_status],
[LogMax].status_time [current_status_time]
FROM (
SELECT MAX(status_time) [MaxDate],
MIN(status_time) [MinDate],
[applicant_id]
FROM [SourceTable]
GROUP BY [applicant_id]
) [Probe]
INNER JOIN [SourceTable] [LogMax]
ON [Probe].[applicant_id] = [LogMax].[applicant_id]
AND [Probe].[MaxDate] = [LogMax].[status_time]
INNER JOIN [SourceTable] [LogMin]
ON [Probe].[applicant_id] = [LogMin].[applicant_id]
AND [Probe].[MinDate] = [LogMin].[status_time]
Link to the SQLFiddle test is here.
Assuming that for one applicant_id you have one line for 'received' status and also one line for 'approved' status (as you listed in the question) you can use inline views to solve your issue:
select section1.applicant_id AS applicant_id, 'received' AS initial_status,
section1.status_time AS initial_time, 'approved' AS current_status,
section2.status_time AS current_status_time from
(select applicant_id, status_time from yourtable where application_status = 'received') section1,
(select applicant_id, status_time from yourtable where application_status = 'approved') section2
where section1.applicant_id = section2.applicant_id;
Try something like this.
select
t1.applicant_id,
t2.application_status initial_status,
t1.initial_time,
t3.application_status current_status,
t1.current_status_time
from
(select
applicant_id,
min(status_time) initial_time,
max(status_time) current_status_time
from
your_table
group by
applicant_id) t1
inner join your_table t2
on (t1.applicant_id = t2.applicant_id and t1.initial_time = t2.status_time)
inner join your_table t3
on (t1.applicant_id = t3.applicant_id and t1.current_status_time = t3.status_time)