MySql Combine two queries (subselect or join or union) - mysql

I have two working queries that I would like to combine to provide one lot of output for a wall display. I am NOT a DB person by any means but have managed to create these queries from scratch, albeit with a lot of info from here!
I have four tables with relevant columns:
Hosts[host, hostid]
Items[hostid, itemid, itemname]
History_unit[itemid, value]
History_log[itemid, value]
hostid and itemid are our identifiers
The history_xxx tables are just that, and entry for every record of that data.
After trying to combine these for too many hours I just don't understand enough to make it work.
Query 1
SELECT hosts.host,
max((case when items.name='RP_Dayend_OK' then history_uint.value end) *1000) as 'Day End',
max((case when items.name='RP_Sync_OK' then history_uint.value end) *1000) as 'Sync',
max((case when items.name='RP_Monthend_OK' then history_uint.value end) *1000) as 'Month End',
max(case when items.name='RP_Version' then history_uint.value end) as 'Version'
from hosts, items, history_uint
where hosts.hostid=items.hostid and items.itemid=history_uint.itemid and items.name like '%RP\_%'
group by hosts.host
Output:
Host | Day End | Sync | Month End | Version
Host 1 | date | date | date | 7xx
Host 2 | date | date | date | 7xx
Query 2
SELECT hosts.host,
max(case when items.name='RP_Cron' then history_log.value end) as 'cron'
from hosts, items, history_log
where hosts.hostid=items.hostid and items.itemid=history_log.itemid and items.name like '%RP\_%'
group by hosts.host
Output:
Host | Cron
Host 1 | string
Host 2 | string
What I would like is:
Host | Day End | Sync | Month End | Version | Cron
Host 1 | date | date | date | 7xx | string
Host 2 | date | date | date | 7xx | string
I did manage a sub select but I ended up with a different row for each host for each item, and no data for 'cron'. I also tried joins to no avail. It is simply my lack of understanding.
Thanks for any help!

You should abandon the use of implicit (comma separated) joins in favour of explicit joins. In your case LEFT (outer) joins are appropriate.
DROP TABLE IF EXISTS Hosts;
DROP TABLE IF EXISTS Items;
DROP TABLE IF EXISTS History_unit;
DROP TABLE IF EXISTS History_uint;
DROP TABLE IF EXISTS History_log;
CREATE TABLE Hosts(host VARCHAR(20), hostid INT);
CREATE TABLE Items(hostid INT, itemid INT, name VARCHAR(20));
CREATE TABLE History_uint(itemid INT, value INT);
CREATE TABLE History_log(itemid INT, value INT);
INSERT INTO HOSTS VALUES ('HOST1',1),('HOST2',2);
INSERT INTO ITEMS VALUES
(1,1,'RP_Dayend_OK'),
(1,2,'RP_Sync_OK'),
(1,3,'RP_Monthend_OK'),
(1,4,'RP_Version' ),
(2,1,'RP_Dayend_OK'),
(2,2,'RP_Sync_OK'),
(2,2,'RP_cron')
;
INSERT INTO HISTORY_uint VALUES
(1,10),(2,10),(3,10),(4,10),
(1,50),(3,60);
INSERT INTO HISTORY_log VALUES
(1,10),(2,10),(3,10),(4,10)
;
SELECT hosts.host,
max((case when items.name='RP_Dayend_OK' then history_uint.value end) *1000) as 'Day End',
max((case when items.name='RP_Sync_OK' then history_uint.value end) *1000) as 'Sync',
max((case when items.name='RP_Monthend_OK' then history_uint.value end) *1000) as 'Month End',
max(case when items.name='RP_Version' then history_uint.value end) as 'Version',
max(case when items.name='RP_Cron' then history_log.value end) as 'cron'
from hosts
left join items on items.hostid = hosts.hostid
left join history_uint on history_uint.itemid = items.itemid
left join history_log on history_log.itemid = items.itemid
where items.name like '%RP\_%'
group by hosts.host;
+-------+---------+-------+-----------+---------+------+
| host | Day End | Sync | Month End | Version | cron |
+-------+---------+-------+-----------+---------+------+
| HOST1 | 50000 | 10000 | 60000 | 10 | NULL |
| HOST2 | 50000 | 10000 | NULL | NULL | 10 |
+-------+---------+-------+-----------+---------+------+
2 rows in set (0.00 sec)
Note it's usually best if the OP provides the data.

Related

table creation logic

I am looking to take two tables I have a perform a data transformation to create a single table. I have an events table and user table:
Events: {id, user_id, start_date, end_date, cost...}
Users: {id, name, ...}
I am trying to create a table at that shows user spend at a daily level, assuming the user start with a starting cost of zero and it goes up after every event.
The intended output would be:
{date, userid, beginning_balance, sum(cost), num_of_events}
i need some direction on how to tackle this one as I am not very familiar with data transformation within SQL
Your requirement is a bit unclear but you may be after something like this
drop table if exists event;
create table event(id int auto_increment primary key, user_id int,start_date date, end_date date, cost int);
insert into event (user_id,start_date , end_date, cost) values
(1,'2017-01-01','2017-01-01',10),(1,'2017-01-01','2017-01-01',10),
(1,'2017-02-01','2017-01-01',10),
(2,'2017-01-01','2017-01-01',10);
select e.user_id,start_date,
ifnull(
(select sum(cost)
from event e1
where e1.user_id = e.user_id and e1.start_date <e.start_date
), 0 )beginning_balance,
sum(cost),count(*)
as num_of_events
from users u
join event e on e.user_id = u.userid
group by e.user_id,start_date
+---------+------------+-------------------+-----------+---------------+
| user_id | start_date | beginning_balance | sum(cost) | num_of_events |
+---------+------------+-------------------+-----------+---------------+
| 1 | 2017-01-01 | 0 | 20 | 2 |
| 1 | 2017-02-01 | 20 | 10 | 1 |
| 2 | 2017-01-01 | 0 | 10 | 1 |
+---------+------------+-------------------+-----------+---------------+
3 rows in set (0.03 sec)
Can you try this query
SELECT
data,
User.id AS userid,
0 AS beginning_balance,
SUM(cost) AS cost,
COUNT(0) AS num_of_events
FROM
Users
LEFT JOIN Events ON (user_id = Users.id)
GROUP BY
Users.id

Transposing rows into columns (MySQL)

So, lets say I have a table called "imports" that looks like this:
| id | importer_id | total_m | total_f |
|====|=============|=========|=========|
| 1 | 1 | 100 | 200 |
| 1 | 1 | 0 | 200 |
And I need the query to return it pivoted or transposed (rows to columns) in this way:
| total_m | sum(total_m) |
| total_f | sum(total_f) |
I can't think on a way to do this without using another table (maybe a temporary table?) and using unions, but there should be a better way to this anyway (maybe with CASE or IF?).
Thanks in advance.
select 'total_m', sum(total_m) from imports
union
select 'total_f', sum(total_f) from imports
http://sqlfiddle.com/#!9/fc1c0/2/0
You can "unpivot" by first expanding the number of rows, which is done below by cross joining a 2 row subquery. Then on each of those rows use relevant case expression conditions to align the former columns to the new rows ("conditional aggregates").
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE imports
(`id` int, `importer_id` int, `total_m` int, `total_f` int)
;
INSERT INTO imports
(`id`, `importer_id`, `total_m`, `total_f`)
VALUES
(1, 1, 100, 200),
(1, 1, 0, 200)
;
Query 1:
select
*
from (
select
i.importer_id
, concat('total_',cj.unpiv) total_type
, sum(case when cj.unpiv = 'm' then total_m
when cj.unpiv = 'f' then total_f else 0 end) as total
from imports i
cross join (select 'm' as unpiv union all select 'f') cj
group by
i.importer_id
, cj.unpiv
) d
Results:
| importer_id | total_type | total |
|-------------|------------|-------|
| 1 | total_f | 400 |
| 1 | total_m | 100 |

SQL select and take data as column name

I have a database which store records like:
+----+---------------------+-------------+-----------------+
| id | user_name | status| date |
+----+---------------------+-------------+-----------------+
| 1 | A | Paid| 2016-10-11|
| 2 | B | Not Paid| 2016-10-12|
| 3 | C | Paid| 2016-10-12|
| 4 | A | Not Paid| 2016-10-13|
+----+---------------------+-------------+-----------------+
I wish to obtain the results like:
+----+---------------------+-------------+-----------------+-----------------+
| id | user_name | 2016-10-11| 2016-10-12 | 2016-10-13 |
+----+---------------------+-------------+-----------------+-----------------+
| 1 | A | Paid| NA| Not Paid|
| 2 | B | NA| Not Paid| NA|
| 3 | C | NA| Paid| Na|
+----+---------------------+-------------+-----------------+-----------------+
How can I query it to obtain the results like this?
PS: Poor English
FYI: I'm using mySQL as as DBMS and here is the create script:
CREATE TABLE `moneygame`.`pay_table`(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_name` VARCHAR(50),
`status` VARCHAR(50),
`p_date` DATE,
PRIMARY KEY (`id`)
);
If don't have a fixed number of dates then I wouldn't recommend doing what you are trying to do. Anyways, here's the solution to the problem.
create table p as select * from
(select 1 id, 'A' user_name, 'Paid' status, '2016-10-11' date union
select 2 id, 'B' user_name, 'Not Paid' status, '2016-10-12' date union
select 3 id, 'C' user_name, 'Paid' status, '2016-10-12' date union
select 4 id, 'A' user_name, 'Not Paid' status, '2016-10-13' date) t;
When dates are represented as columns
select user_name, ifnull(max(a),'NA') `2016-10-11`,
ifnull(max(b),'NA') `2016-10-12`, ifnull(max(c),'NA') `2016-10-13`
from (select user_name,
case when date = '2016-10-11' then status else null end a,
case when date = '2016-10-12' then status else null end b,
case when date = '2016-10-13' then status else null end c
from p group by user_name, date) s group by user_name;
When user names are represented as columns
Doing it this way should be optimal if you have a fixed number of users and an moving range of dates.
select date, ifnull(max(a),'NA') A,
ifnull(max(b),'NA') B, ifnull(max(c),'NA') C from
(select date,
case when user_name = 'A' then status else null end a,
case when user_name = 'B' then status else null end b,
case when user_name = 'C' then status else null end c
from p group by date, user_name) x group by date;
By the way if you still need to do dynamic columns, you should read this:
How to automate pivot tables in MySQL
MySQL pivot into dynamic number of columns
You can query like this
select user_name, max([2016-10-11]) as [2016-10-11], max([2016-10-12]) [2016-10-12],max([2016-10-13]) [2016-10-13] from #yourtable
pivot
(max(status) for date in ([2016-10-11],[2016-10-12],[2016-10-13])) p
group by user_name

How to pivot a table in MySQL using case statements?

I am trying to pivot a table in MySQL using case statements. This question has been asked many times here, and I have studied all of those answers, but I am looking for a solution that:
1. Uses case statements. Not self joins, subqueries, or unions.
2. Uses just SQL. Not Excel or shell scripts.
3. Works on MySQL.
Here is the table:
create table client (
name varchar(10),
revenue int(11),
expense int(11)
);
insert into client (name, revenue, expense) values ("Joe", 100, 200);
insert into client (name, revenue, expense) values ("Bill", 300, 400);
insert into client (name, revenue, expense) values ("Tim", 500, 600);
mysql> select * from client;
+------+---------+---------+
| name | revenue | expense |
+------+---------+---------+
| Joe | 100 | 200 |
| Bill | 300 | 400 |
| Tim | 500 | 600 |
+------+---------+---------+
I would like to pivot the table to this:
+-----+------+-----+
| Joe | Bill | Tim |
| 100 | 300 | 500 |
| 200 | 400 | 600 |
+-----+------+-----+
How can I accomplish this?
I have already seen the solutions at artfulsoftware dot com and buysql dot com, but those solutions are not working for my table.
see fiddle demo here
select
sum(case when name='Joe' then revenue else 0 end) as JOE,
sum(case when name='Bill' then revenue else 0 end) as Bill,
sum(case when name='Tim' then revenue else 0 end) as TIM
from client
union
select
sum(case when name='Joe' then expense else 0 end) as JOE,
sum(case when name='Bill' then expense else 0 end) as Bill,
sum(case when name='Tim' then expense else 0 end) as TIM
from client

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