How to convert rows to columns? - mysql

I've the following query:
SELECT first_period, period, sum(num) trans_num
FROM (SELECT (DATEDIFF(created_at, '2022-12-10') DIV 6) period,
user_id,
count(1) num,
MIN(MIN(DATEDIFF(created_at, '2022-12-10') DIV 6)) OVER (PARTITION BY user_id) as first_period
FROM pos_transactions
WHERE DATE(created_at) >= '2022-12-10'
GROUP BY user_id, DATEDIFF(created_at, '2022-12-10') DIV 6
) u
GROUP BY first_period, period
ORDER BY first_period, period
It returns the following result:
But now I need to make it visualize like a Cohort diagram. So I need to restructure the same result as follows:
+--------------+------+------+------+------+
| first_period | 0 | 1 | 2 | 3 |
+--------------+------+------+------+------+
| 0 | 6230 | 2469 | 2846 | 1713 |
| 1 | | 2589 | 742 | 375 |
| 2 | | | 3034 | 397 |
| 3 | | | | 1207 |
+--------------+------+------+------+------+
Any idea how can I do that?

WITH YOUR_TABLE_DATA(FIRST_PERIOD,PERIOD,TRANS_NUM)AS
(
SELECT 0,0,6230 UNION ALL
SELECT 0,1,2469 UNION ALL
SELECT 0,2,2846 UNION ALL
SELECT 0,3,1713 UNION ALL
SELECT 1,1,2589 UNION ALL
SELECT 1,2,742 UNION ALL
SELECT 1,3,375 UNION ALL
SELECT 2,2,3034 UNION ALL
SELECT 2,3,397 UNION ALL
SELECT 3,3,1207
)
SELECT C.FIRST_PERIOD,
MAX(
CASE
WHEN C.PERIOD=0
THEN C.TRANS_NUM
ELSE 0
END)AS ZERO,
MAX(
CASE
WHEN C.PERIOD=1
THEN C.TRANS_NUM
ELSE 0
END)AS ONE,
MAX(
CASE
WHEN C.PERIOD=2
THEN C.TRANS_NUM
ELSE 0
END)AS TWO,
MAX(
CASE
WHEN C.PERIOD=3
THEN C.TRANS_NUM
ELSE 0
END)AS THREE
FROM YOUR_TABLE_DATA AS C
GROUP BY C.FIRST_PERIOD

Related

JOIN and SUM different statement results (Wordpress-Mailster Database)

After the last update of Mailster (email marketing plugin for wordpress), they have changed the way they store the information about opens, clicks, unsubscribes...
Until now, everything was stored in two databases:
bao_posts: Like any other wordpress post, the information of the
email that is sent was there. (When the post_type = 'newsletter')
bao_mailster_actions: This is where the user's actions with the
email were stored. 1 when it was sent to a person, 2 when they
opened it, 3 when they clicked on it and 4 when they unsubscribed.
And with this query, I could get a table with all the emails and the information of their openings, clicks, unsubscribed...
SELECT bao_posts.post_modified,
bao_posts.ID,
bao_posts.post_title,
COUNT(CASE WHEN bao_mailster_actions.type = 1 then 1 ELSE NULL END) AS Number_People_Reached,
COUNT(CASE WHEN bao_mailster_actions.type = 2 then 1 ELSE NULL END) AS Opens,
COUNT(CASE WHEN bao_mailster_actions.type = 3 then 1 ELSE NULL END) AS Clicks,
COUNT(CASE WHEN bao_mailster_actions.type = 4 then 1 ELSE NULL END) AS Unsubs
FROM bao_posts
LEFT JOIN bao_mailster_actions ON bao_mailster_actions.campaign_id = bao_posts.ID
WHERE bao_posts.post_type = 'newsletter'
GROUP BY bao_posts.ID ;
*Expected result of this query at the end of the post.
Now the problem is that this setting is kept for emails before the update, but it has changed for new ones and now bao_mailster_actions is separated into:
bao_mailster_action_sent
bao_mailster_action_opens
bao_mailster_action_clicks
bao_mailster_action_unsubscribes
I know how to get the count of each of these tables like this:
SELECT bao_mailster_action_sent.campaign_id,
COUNT(bao_mailster_action_sent.count) AS Number_People_Reached
FROM bao_mailster_action_sent
GROUP BY bao_mailster_action_sent.campaign_id;
To get:
campaign_id
Number_People_Reached
9785
300
9786
305
(And so on with each of these 4 new tables).
So what I would like to do would be to join these 4 new queries to the original one. I've been trying to combine different JOINs, but I don't quite understand how to do it.
*Bearing in mind that if an email ID matches in both, I would need it to add up their clicks, opens (or whatever).
The expected outcome would be something like this (the same as the first query but with the aggregate data):
post_modified
ID
post_title
Number_People_Reached
Opens
Clicks
Unsubs
2021-04-29 13:13:03
9785
Prueba email
300
102
30
1
2021-04-30 15:12:01
9786
Segundo email
305
97
56
0
Thanks in advance!
I suggest that you use UNION ALL to join all the tables in a CTE.You can then use this in your query. I have modified the name because we cannot have to records with the same name.
> create table if not exists bao_mailster_action_sent
( campaign_id int,count int);
create table if not exists bao_mailster_action_opens
( campaign_id int,count int);
create table if not exists bao_mailster_action_clicks
( campaign_id int,count int);
create table if not exists bao_mailster_action_unsubscribes
( campaign_id int,count int);
CREATE TABLE if not exists bao_posts(
post_modified date,
ID int,
post_title varchar(50) );
insert into bao_mailster_action_sent values
(1,88),(2,4),(4,6);
insert into bao_mailster_action_opens values
(2,4),(3,5),(4,10);
insert into bao_mailster_action_clicks values
(1,3),(2,3),(4,6);
insert into bao_mailster_action_unsubscribes values
(1,4),(3,5),(4,5);
INSERT INTO bao_posts values
( '2021-03-01',1,'first post'),
( '2021-06-01',2,'second opion'),
( '2021-09-01',3,'third way'),
( '2021-12-01',4,'last post');
WITH bao_mailster_actionsent AS
( SELECT campaign_id,count, 1 type FROM
bao_mailster_action_sent
UNION ALL
SELECT campaign_id,count,2 FROM
bao_mailster_action_opens
UNION ALL
SELECT campaign_id,count,3 FROM
bao_mailster_action_clicks
UNION ALL
SELECT campaign_id,count,4 FROM
bao_mailster_action_unsubscribes)
SELECT bao_mailster_actionsent.campaign_id,
COUNT(bao_mailster_actionsent.count) AS TotalCount,
SUM(bao_mailster_actionsent.count) AS TotalNumber,
'type'
FROM bao_mailster_actionsent
GROUP BY bao_mailster_actionsent.campaign_id,'type' ;
WITH baoMailsterAction AS
( SELECT campaign_id,count, 1 type FROM
bao_mailster_action_sent
UNION ALL
SELECT campaign_id,count,2 FROM
bao_mailster_action_opens
UNION ALL
SELECT campaign_id,count,3 FROM
bao_mailster_action_clicks
UNION ALL
SELECT campaign_id,count,4 FROM
bao_mailster_action_unsubscribes)
SELECT bao_posts.post_modified,
bao_posts.ID,
bao_posts.post_title,
COUNT(CASE WHEN bao_mailster_actions.type = 1 then 1 ELSE NULL END) AS Number_People_Reached,
COUNT(CASE WHEN bao_mailster_actions.type = 2 then 1 ELSE NULL END) AS Opens,
COUNT(CASE WHEN bao_mailster_actions.type = 3 then 1 ELSE NULL END) AS Clicks,
COUNT(CASE WHEN bao_mailster_actions.type = 4 then 1 ELSE NULL END) AS Unsubs
FROM bao_posts
campaign_id | TotalCount | TotalNumber | type
----------: | ---------: | ----------: | ---:
1 | 1 | 88 | 1
2 | 1 | 4 | 1
4 | 1 | 6 | 1
2 | 1 | 4 | 2
3 | 1 | 5 | 2
4 | 1 | 10 | 2
1 | 1 | 3 | 3
2 | 1 | 3 | 3
4 | 1 | 6 | 3
1 | 1 | 4 | 4
3 | 1 | 5 | 4
4 | 1 | 5 | 4
post_modified | ID | post_title | Number_People_Reached | Opens | Clicks | Unsubs
:------------ | -: | :----------- | --------------------: | ----: | -----: | -----:
2021-03-01 | 1 | first post | 1 | 0 | 1 | 1
2021-06-01 | 2 | second opion | 1 | 1 | 1 | 0
2021-09-01 | 3 | third way | 0 | 1 | 0 | 1
2021-12-01 | 4 | last post | 1 | 1 | 1 | 1
db<>fiddle here
I finally got it to work using only the new tables that Mailster created (it seems that finally they did move all the info to the new tables with the update) and with 4 LEFT JOINS.
I leave the code in case someone else finds it useful:
SELECT P.post_modified,
P.ID,
P.post_title,
IFNULL(S.count,0) as 'Total',
IFNULL(O.count,0) as 'Aperturas',
IFNULL(C.count,0) as 'Clicks',
IFNULL(U.count,0) as 'Bajas' from bao_posts as P
LEFT JOIN (select campaign_id, count(DISTINCT subscriber_id) as count from bao_mailster_action_clicks group by campaign_id) as C ON C.campaign_id = P.ID
LEFT JOIN (select campaign_id, count(DISTINCT subscriber_id) as count from bao_mailster_action_opens group by campaign_id) as O ON O.campaign_id = P.ID
LEFT JOIN (select campaign_id, count(DISTINCT subscriber_id) as count from bao_mailster_action_sent group by campaign_id) as S ON S.campaign_id = P.ID
LEFT JOIN (select campaign_id, count(DISTINCT subscriber_id) as count from bao_mailster_action_unsubs group by campaign_id) as U ON U.campaign_id = P.ID
WHERE P.post_type = 'newsletter'
ORDER BY P.post_modified ASC ;
P.S: As I expected, Mailster's support has not helped at all :'(

fixing my table using MySQL with null values

I am using MySQL 8.0.
Here's my sample data:
https://www.db-fiddle.com/f/jYQJPV1X1XPbLp72LqA5CZ/1
Here's my code:
SELECT
COALESCE(t1.A, 0) AS CODE,
CASE WHEN t1.A IS NOT NULL THEN COALESCE(t2.DESCRIPTION, 'NOT VALID') ELSE 'TOTAL' END AS SEX,
t1.TOTAL,
ROUND(100.0 * t1.TOTAL / SUM(CASE WHEN t1.A IS NOT NULL THEN t1.TOTAL ELSE 0 END) OVER (), 2) AS PERCENT,
SUM(CASE WHEN t1.A IS NOT NULL THEN t1.TOTAL ELSE 0 END) OVER (ORDER BY t1.A) AS CUMULATIVE,
ROUND(100.0 * SUM(CASE WHEN t1.A IS NOT NULL THEN t1.TOTAL ELSE 0 END) OVER (ORDER BY t1.A) /
SUM(CASE WHEN t1.A IS NOT NULL THEN t1.TOTAL ELSE 0 END) OVER (), 2) AS CUMPERCENT
FROM
(
SELECT
A,
COUNT(*) AS TOTAL
FROM AA
GROUP BY A WITH ROLLUP
) t1
LEFT JOIN BB t2
ON t2.CODE = t1.A
ORDER BY
CODE;
Output:
CODE | SEX | TOTAL | PERCENT | CUMULATIVE | CUMPERCENT
0 TOTAL 1 16.67 0 0.00
0 TOTAL 7 116.67 0 0.00
1 Male 3 50.00 3 50.00
2 Female 2 33.33 5 83.33
3 NOT VALID 1 16.67 6 100.00
Expected Output:
CODE | SEX | TOTAL | PERCENT | CUMULATIVE | CUMPERCENT
0 TOTAL 7 100.00 0 0.00
1 Male 3 42.86 3 42.86
2 Female 2 28.57 5 71.43
3 NOT VALID 1 14.29 6 85.71
4 BLANK 1 14.29 7 100.00
I just want to make the table with NULL sex be included and rename it to 'BLANK'. Can you help me with this?
If you want a solution for this sample data, as you say in your comment, then this will work:
WITH cte AS (
SELECT MAX(CASE
WHEN a.A IS NULL THEN 4
WHEN b.code IS NULL THEN 3
ELSE a.A
END
) CODE,
CASE
WHEN a.A IS NULL THEN 'BLANK'
WHEN b.code IS NULL THEN 'NOT VALID'
ELSE b.description
END SEX,
COUNT(*) TOTAL,
ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 2) PERCENT
FROM AA a LEFT JOIN BB b ON b.code = a.A
GROUP BY SEX
)
SELECT CODE, SEX, TOTAL, PERCENT,
SUM(TOTAL) OVER (ORDER BY CODE) CUMULATIVE,
ROUND(100.0 * SUM(TOTAL) OVER (ORDER BY CODE) / SUM(TOTAL) OVER (), 2) CUMPERCENT
FROM cte
UNION ALL
SELECT 0, 'TOTAL', COUNT(*), 100.00, 0, 0.00
FROM AA
ORDER BY CODE
See the demo.
Results:
> CODE | SEX | TOTAL | PERCENT | CUMULATIVE | CUMPERCENT
> ---: | :-------- | ----: | ------: | ---------: | ---------:
> 0 | TOTAL | 7 | 100.00 | 0 | 0.00
> 1 | Male | 3 | 42.86 | 3 | 42.86
> 2 | Female | 2 | 28.57 | 5 | 71.43
> 3 | NOT VALID | 1 | 14.29 | 6 | 85.71
> 4 | BLANK | 1 | 14.29 | 7 | 100.00
WITH
cte1 AS ( SELECT AA.A AS code,
CASE WHEN AA.A IS NULL
THEN 'BLANK'
WHEN BB.description IS NULL
THEN 'NOT VALID'
ELSE BB.description
END description,
COUNT(*) AS amount
FROM AA
LEFT JOIN BB ON AA.A = BB.code
GROUP BY AA.A, BB.description ),
cte2 AS ( SELECT SUM(amount) amount, MAX(code) + 1 code
FROM cte1 )
SELECT 0 code,
'TOTAL' description,
amount,
100 percent,
0 cumulative,
0 cumulative_percent
FROM cte2
UNION ALL
SELECT COALESCE(cte1.code, cte2.code) code,
cte1.description,
cte1.amount,
100 * cte1.amount / cte2.amount,
SUM(cte1.amount) OVER (ORDER BY COALESCE(cte1.code, cte2.code)),
100 * SUM(cte1.amount) OVER (ORDER BY COALESCE(cte1.code, cte2.code)) / cte2.amount
FROM cte1
CROSS JOIN cte2
fiddle

MySQL: Select multiple max values from one column

Suppose I got this table:
mytable
+------+-------+
| type | count |
+------+-------+
| red | 4 |
| blue | 3 |
| red | 2 |
| blue | 7 |
+------+-------+
Now I want this back:
+--------+---------+
| maxRed | maxBlue |
+--------+---------+
| 4 | 7 |
+--------+---------+
How can I do this?
This is what I tried
SELECT MAX(count) as maxRed, 0 as maxBlue FROM mytable WHERE type='red'
UNION
SELECT 0 as maxRed, MAX(count) as maxBlue FROM mytable WHERE type='blue'
but it does not quite work and only results
+--------+---------+
| maxRed | maxBlue |
+--------+---------+
| 4 | 0 |
| 0 | 7 |
+--------+---------+
For your query to work you only needed to wrap it with another query:
SELECT MAX(maxRed) as maxRed, MAX(maxBlue) as maxBlue
FROM(
SELECT MAX(count) as maxRed, 0 as maxBlue FROM mytable WHERE type='red'
UNION
SELECT 0 as maxRed, MAX(count) as maxBlue FROM mytable WHERE type='blue') t
A more elegant way is conditional aggregation:
SELECT MAX(CASE WHEN t.type = 'red' THEN t.count END) as maxRed,
MAX(CASE WHEN t.type = 'blue' THEN t.count END) as maxBlue
FROM mytable t
WHERE t.type IN('blue','red')
This is how to do it:
SELECT
MAX(CASE WHEN type = 'red' THEN `count` ELSE NULL END) AS maxRed,
MAX(CASE WHEN type = 'blue' THEN `count` ELSE NULL END) AS maxBlue
FROM
mytable
WHERE type IN ('red', 'blue');
SELECT type,MAX(count) FROM mytable group by type
This will return type and maxcount in vertical and not horizontal and is scalable for n number of records.
If horizontal data doesn't matter otherwise you can use above suggested answers to use case but it will require case when then for each distinct value of data.
EDIT
Because of the comments below I tested it on real data, my version is a bit faster for a large table ,when mytable is indexed. For those who argue about it please test yourself and report back.
select
(select max(count) from mytable WHERE type='red') aa,
(select max(count) from mytable WHERE type='blue') bb

select two tables mysql without join

There are two tables, recharge and purchase.
select * from recharge;
+-----+------+--------+---------------------+
| idx | user | amount | created |
+-----+------+--------+---------------------+
| 1 | 3 | 10 | 2016-01-09 20:16:18 |
| 2 | 3 | 5 | 2016-01-09 20:16:45 |
+-----+------+--------+---------------------+
select * from purchase;
+-----+------+----------+---------------------+
| idx | user | resource | created |
+-----+------+----------+---------------------+
| 1 | 3 | 2 | 2016-01-09 20:55:30 |
| 2 | 3 | 1 | 2016-01-09 20:55:30 |
+-----+------+----------+---------------------+
I want to figure out balance of users which is SUM(amount) - COUNT(purchase.idx). (in this case, 13)
So I had tried
SELECT (SUM(`amount`)-COUNT(purchase.idx)) AS balance
FROM `recharge`, `purchase`
WHERE purchase.user = 3 AND recharge.user = 3
but, it returned error.
If you want an accurate count, then aggregate before doing arithmetic. For your particular case:
select ((select sum(r.amount) from recharge where r.user = 3) -
(select count(*) from purchase p where p.user = 3)
)
To do this for multiple users, move the subqueries to the from clause or use union all and aggregation. The second is safer if a user might only be in one table:
select user, coalesce(sum(suma), 0) - coalesce(sum(countp), 0)
from ((select user, sum(amount) as suma, null as countp
from recharge
group by user
) union all
(select user, null, count(*)
from purchase
group by user
)
) rp
group by user
It is possible to using union like this
SELECT SUM(`amount`-aidx) AS balance
FROM(
SELECT SUM(`amount`) as amount, 0 as aidx
from `recharge` where recharge.user = 3
union
select 0 as amount, COUNT(purchase.idx) as aidx
from `purchase`
WHERE purchase.user = 3 )a

MySQL SUM in different ways

I have two tables
user_raters:
| id(int) | to_id(int) | value(int) | created_at(datetime)
|1 | 2 | 1 | 2009-03-01 00:00:00
EDIT: I changed the user_rater_id. history_user_raters.user_rater_id is related to user_raters.id
history_user_raters:
| id(int) | user_rater_id(int) | value(int) | created_at(datetime)
| 1 | 1 | 1 | 2009-03-02 00:00:00
| 2 | 1 | 1 | 2009-03-02 00:00:00
| 3 | 1 | -1 | 2009-03-02 00:00:00
| 4 | 1 | 1 | 2009-03-03 00:00:00
| 5 | 1 | -1 | 2009-03-03 00:00:00
| 6 | 1 | -1 | 2009-03-03 00:00:00
| 7 | 1 | -1 | 2009-03-03 00:00:00
I want to count the sum of the values from history_user_raters as it relates to the to_id from user_raters. The result from the query should be:
| year | month | day | total | down | up
| 2009 | 3 | 2 | 1 | 1 | 2
| 2009 | 3 | 3 | -2 | 3 | 1
I have a query that is close, but it is not counting the up and down correctly. The total is right. Can some one help me write the query or new query that calculates correct up and down?
My current query:
SELECT
YEAR(history.created_at) AS `year`,
MONTH(history.created_at) AS `month`,
DAY(history.created_at) AS `day`,
SUM(history.value) as `total`,
(SELECT
abs(SUM(historydown.value))
FROM `user_raters` as raterdown
INNER JOIN `history_user_raters` AS historydown
WHERE raterdown.to_id = 2
AND historydown.value = -1
AND date(historydown.created_at)
GROUP BY history.created_at) as down,
(SELECT SUM(historyup.value)
FROM `user_raters` as raterup
INNER JOIN `history_user_raters` AS historyup
WHERE raterup.to_id = 2
AND historyup.value = 1
AND date(history.created_at)
GROUP BY raterup.to_id) as up
FROM `user_raters`
INNER JOIN history_user_raters AS history ON user_raters.id = history.user_rater_id
WHERE (user_raters.to_id = 2)
GROUP BY DATE(history.created_at)
I might see it too simply (and sorry I can't test with data at the moment), but I'm guessing the following trick with two CASE statements would do just what is needed
SELECT
YEAR(history.created_at) AS year,
MONTH(history.created_at) AS month,
DAY(history.created_at) AS day,
SUM(history.value) as total,
SUM(CASE WHEN history.value < 0 THEN history.value ELSE 0 END) as down,
SUM(CASE WHEN history.value > 0 THEN history.value ELSE 0 END) as up
FROM `user_raters`
INNER JOIN `history_user_raters` AS history
ON user_raters.id = history.user_rater_id
WHERE (user_raters.to_id = 1) -- or some other condition...
GROUP BY DATE(history.created_at)
EDIT: #OMG Ponies deleted his answer. This response make no sense now, but I am not going to delete my answer, because I think it is silly.
#OMG ponies
Your query runs, but it returns no results. I had to adjust it a bit to add the to_id in the main queries where clause
SELECT
YEAR( t.created_at ) AS `year` ,
MONTH( t.created_at ) AS `month` ,
DAY( t.created_at ) AS `day` ,
SUM( t.value ) AS `total` ,
MAX( COALESCE( x.sum_down, 0 ) ) AS down,
MAX( COALESCE( y.sum_up, 0 ) ) AS up
FROM history_user_raters AS t
JOIN user_raters AS ur ON ur.to_id = t.user_rater_id
LEFT JOIN (
SELECT hur.user_rater_id,
SUM( hur.value ) AS sum_down
FROM history_user_raters AS hur
WHERE hur.value = -1
GROUP BY hur.user_rater_id
) AS x ON x.user_rater_id = t.user_rater_id
LEFT JOIN (
SELECT hur.user_rater_id,
SUM( hur.value ) AS sum_up
FROM history_user_raters AS hur
WHERE hur.value =1
GROUP BY hur.user_rater_id
) AS y ON y.user_rater_id = t.user_rater_id
WHERE ur.to_id =1
GROUP BY YEAR( t.created_at ) , MONTH( t.created_at ) , DAY( t.created_at )