sql subquery joins - mysql

I am trying to allow for the custom creation of tables and columns using a few tables in the database.
The Data is in a table called CustomData,
ID | CustomEntries_ID | CustomColumn_ID | Value
-------------------------------------------------
1 | 1 | 1 | Pheven
2 | 1 | 2 | Routine
3 | 1 | 3 | Planned
4 | 1 | 4 | 2014
5 | 2 | 1 | John
6 | 2 | 2 | Routine
7 | 2 | 3 | Planned
8 | 2 | 4 | 2017
SELECT * FROM (
(SELECT CustomEntries_ID AS ID, `Value` AS `Name` FROM `CustomData` WHERE CustomColumn_ID = 1) AS T1 NATURAL JOIN
(SELECT CustomEntries_ID AS ID, `Value` AS `Servive` FROM `CustomData` WHERE CustomColumn_ID = 2) AS T2 NATURAL JOIN
(SELECT CustomEntries_ID AS ID, `Value` AS `Type` FROM `CustomData` WHERE CustomColumn_ID = 3) AS T3 NATURAL JOIN
(SELECT CustomEntries_ID AS ID, `Value` AS `Model` FROM `CustomData` WHERE CustomColumn_ID = 4) AS T4 NATURAL JOIN
)
The first problem seems to be if there is an entry missing the CustomData table all data relating to that customEntries_ID are excluded.
The second issue is this seems very inefficient, I'm sure someone has a better suggestion for this query or to allow for Custom table creation without actually creating new tables on the database.

Just use conditional aggregation:
SELECT customernetyid
max(case when CustomColumn_ID = 1 then value end) as name,
max(case when CustomColumn_ID = 2 then value end) as service,
max(case when CustomColumn_ID = 3 then value end) as type,
max(case when CustomColumn_ID = 4 then value end) as model
FROM CustomData
GROUP BY customentryid;

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 :'(

Return rows that have one value but not another

For every same date (this is just one section of the table), I want to return the account numbers that made a purchase of A but DID NOT purchase B, and another query for vice-versa. So running the first query for A but no B should return 2 and 5. Running the vice-versa query for B but no A should give me 4. Thanks for the help. I'm assuming I would have to do a join of some sorts on the table but I'm stuck.
+----+----------------+---------------+----------+--+--+
| ID | Account Number | Purchase Type | Date | | |
+----+----------------+---------------+----------+--+--+
| 1 | 1 | A | 20140301 | | |
| 1 | 1 | A | 20140301 | | |
| 1 | 1 | B | 20140301 | | |
| 2 | 2 | A | 20140301 | | |
| 3 | 3 | A | 20140301 | | |
| 3 | 3 | B | 20140301 | | |
| 4 | 4 | B | 20140301 | | |
| 5 | 5 | A | 20140301 | | |
| 5 | 5 | A | 20140301 | | |
+----+----------------+---------------+----------+--+--+
Not sure if it is necessarily the best approach, but an inner select will work:
select distinct account_number
from purchases p
where purchase_type = "A" and account_number not in
(
select account_number
from purchases
where purchase_date = p.purchase_date and purchase_type = "B"
)
You first collect all ids that have purchase type "B" and then all ids with purchase type "A" that are not in the first collection.
(Assuming your table is purchases, ID is id int, Purchase Date is purchase_date char(1) and Date is purchase_date char(8), but you should be able to adapt the query to your actual columns.
Corresponding fiddle: http://sqlfiddle.com/#!9/edf73f/7/0)
One approach would be to use a full outer join where one or the other side is null; but mySQL doesn't support them. So to simulate: use a left join and then a union (or union all if you want to keep the fact that 1,1,A exists twice.) and we simply switch the criteria between the joins for the second SQL to union to handle both ways.
DEMO using SQL fiddle in comment: http://sqlfiddle.com/#!9/52c893/20/0
SELECT A.*
FROM purch A
LEFT JOIN purch B
on A.`Account Number` = B.`Account Number`
AND B.`Purchase Type` = 'B'
WHERE b.`Account Number` is null
AND A.`Purchase Type` = 'A'
UNION ALL
SELECT A.*
FROM purch A
LEFT JOIN purch B
on A.`Account Number` = B.`Account Number`
AND B.`Purchase Type` = 'A'
WHERE b.`Account Number` is null
AND A.`Purchase Type` = 'B'
You can use Exists on the same table:
select distinct AccountNumber , Date
from table1 outer_table
where PurchaseType = 'A' and not exists
(
select ID
from table1 inner_table
where
PurchaseType = 'B'
and inner_table.Date = outer_table.Date
and inner_table.AccountNumber = outer_table.AccountNumber
)
Fiddle: http://sqlfiddle.com/#!9/b84ecd/9
select id,sum(if(purchase_type='A',1,0)) as sumA,sum(if(purchase_type='B',1,0)) as sumB
from purchases
group by id
having sumA>0 and sumB=0
Fiddle: http://sqlfiddle.com/#!9/edf73f/16
and to get the two request in one:
select id,sum(if(purchase_type='A',1,0)) as sumA,sum(if(purchase_type='B',1,0)) as sumB
from purchases
group by id
having (sumA>0 and sumB=0) OR(sumA=0 and sumB>0 )
Fiddle: http://sqlfiddle.com/#!9/edf73f/18
You get a bit messed up here because MySQL's set arithmetic operations are incomplete. It has UNION and INTERSECT but not EXCEPT. If it had EXCEPT you could do
SELECT DISTINCT `Account Number` FROM purch WHERE `Purchase Type` = 'A'
EXCEPT /* MySQL fail! */
SELECT DISTINCT `Account Number` FROM purch WHERE `Purchase Type` = 'B'
and your problem would be solved.
So you can use the LEFT JOIN ... IS NULL query pattern. It's more verbose but works fine. (http://sqlfiddle.com/#!9/52c893/18/0)
SELECT suba.`Account Number`
FROM (
SELECT DISTINCT `Account Number`
FROM purch
WHERE `Purchase Type` = 'A'
) suba
LEFT JOIN (
SELECT DISTINCT `Account Number`
FROM purch
WHERE `Purchase Type` = 'B'
) subb ON suba.`Account Number` = subb.`Account Number`
WHERE subb.`Account Number` IS NULL
SELECT GROUP_CONCAT( DISTINCT A.account SEPARATOR ', ') AS "accounts"
FROM test A, test B
WHERE A.type='A'
AND A.id=B.id
AND A.date=B.date
AND A.date='2014-03-01'

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

Condition row total as a column in another table using MySQL

Firstly, I apologize for the terrible wording, but I'm not sure how to describe what I'm doing...
I have a table of computer types (id, type, name), called com_types
id | type | name
1 | 1 | Dell
2 | 4 | HP
In a second table, I have each individual computer, with a column 'type_id' to denote what type of computer it is, called com_assets
id | type_id | is_assigned
1 | 4 | 0
2 | 1 | 1
I'd like to create a view that shows each computer type, and how many we have on hand and in use, and a total, so the outcome would be
id | type | name | on_hand | in_use | total |
1 | 1 | Dell | 0 | 1 | 1 |
2 | 4 | HP | 1 | 0 | 1 |
As you can see, the on_hand, in_use, and total columns are dependent on the type_id and is_assigned column in the second table.
So far I have tried this...
CREATE VIEW test AS
SELECT id, type, name,
( SELECT COUNT(*) FROM com_assets WHERE type_id = id AND is_assigned = '0' ) as on_hand,
( SELECT COUNT(*) FROM com_assets WHERE type_id = id AND is_assigned = '1' ) as in_use,
SUM( on_hand + in_use ) AS total
FROM com_types
But all this returns is one column with all correct values, except the total equals ALL of the computers in the other table. Will I need a trigger to do this instead?
on_hand is the count of assigned = 0, and in_use is the count of assigned = 1. You can count them together, without the correlated subqueries, like this:
SELECT
com_types.id,
com_types.type,
com_types.name,
COUNT(CASE WHEN com_assets.is_assigned = 0 THEN 1 END) AS on_hand,
COUNT(CASE WHEN com_assets.is_assigned = 1 THEN 1 END) AS in_use,
COUNT(*) AS total
FROM com_types
JOIN com_assets ON com_types.id = com_assets.id
GROUP BY
com_types.id,
com_types.type,
com_types.name

how to group by with a sql subqueries

I can't think clearly at the moment, I want to return counts by station_id, an example of output would be:
station 1 has 3 fb post, 6 linkedin posts, 5 email posts
station 2 has 3 fb post, 6 linkedin posts, 5 email posts
So I need to group by the station id, my table structure is
CREATE TABLE IF NOT EXISTS `posts` (
`post_id` bigint(11) NOT NULL auto_increment,
`station_id` varchar(25) NOT NULL,
`user_id` varchar(25) NOT NULL,
`dated` datetime NOT NULL,
`type` enum('fb','linkedin','email') NOT NULL,
PRIMARY KEY (`post_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=x ;
The query I have so far is returning station 0 as having 2 linkedin posts when it has one (2 in the db tho)
SELECT Station_id, (select count(*) FROM posts WHERE type = 'linkedin') AS linkedin_count, (select count(*) FROM posts WHERE type = 'fb') AS fb_count, (select count(*) FROM posts WHERE type = 'email') AS email_count FROM `posts` GROUP BY station_id;
Or, the fastest way, avoiding joins and subselects to get it in the exact format you want:
SELECT
station_id,
SUM(CASE WHEN type = 'linkedin' THEN 1 ELSE 0 END) AS 'linkedin',
SUM(CASE WHEN type = 'fb' THEN 1 ELSE 0 END) AS 'fb',
SUM(CASE WHEN type = 'email' THEN 1 ELSE 0 END) AS 'email'
FROM posts
GROUP BY station_id;
Outputs:
+------------+----------+------+-------+
| station_id | linkedin | fb | email |
+------------+----------+------+-------+
| 1 | 3 | 2 | 5 |
| 2 | 2 | 0 | 1 |
+------------+----------+------+-------+
You may also want to put an index on there to speed it up
ALTER TABLE posts ADD INDEX (station_id, type);
Explain output:
+----+-------------+-------+-------+---------------+------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------------+---------+------+------+-------------+
| 1 | SIMPLE | posts | index | NULL | station_id | 28 | NULL | 13 | Using index |
+----+-------------+-------+-------+---------------+------------+---------+------+------+-------------+
As implied by gnif's answer, having three correlated sub_queries has a performance over-head. Depending on the DBMS you're using, it could perform similarly to having a self join three times.
gnif's methodology ensures that the table is only parsed once, without the need for joins, correlated sub_queries, etc.
The immediately obvious down-side of gnif's answer is that you don't ever get records for 0's. If there are no fb types, you just don't get a record. If that is not an issue, I'd go with his answer. If it is an issue, however, here is a version with similar methodology to gnif, but matching your output format...
SELECT
station_id,
SUM(CASE WHEN type = 'linkedin' THEN 1 ELSE 0 END) AS linkedin_count,
SUM(CASE WHEN type = 'fb' THEN 1 ELSE 0 END) AS fb_count,
SUM(CASE WHEN type = 'email' THEN 1 ELSE 0 END) AS email_count
FROM
posts
GROUP BY
station_id
Give this a go:
SELECT station_id, type, count(*) FROM posts GROUP BY station_id, type
The output format will be a little different to what your attempting to get, but it should provide the statistics your trying to retrieve. Also since its a single query it is much faster.
-- Edit, added example result set
+------------+----------+----------+
| station_id | type | count(*) |
+------------+----------+----------+
| 1 | fb | 2 |
| 1 | linkedin | 3 |
| 1 | email | 5 |
| 2 | linkedin | 2 |
| 2 | email | 1 |
+------------+----------+----------+
try this:
SELECT p.Station_id,
(select count(*) FROM posts WHERE type = 'linkedin' and station_id=p.station_id) AS linkedin_count,
(select count(*) FROM posts WHERE type = 'fb' and station_id=p.station_id) AS fb_count,
(select count(*) FROM posts WHERE type = 'email' and station_id=p.station_id) AS email_count
FROM `posts` p GROUP BY station_id