MySQL count 2 columns in a row and join with another query - mysql

I have a table visits like this:
+--------------+-------------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| vis_id | int(11) | NO | | NULL | |
| unit | int(11) | NO | | NULL | |
| time_in | timestamp | NO | | CURRENT_TIMESTAMP | |
| time_out | timestamp | NO | | 0000-00-00 00:00:00 | |
| in_username | varchar(16) | NO | | NULL | |
| out_username | varchar(16) | NO | | NULL | |
+--------------+-------------+------+-----+---------------------+----------------+
and a table users like this:
+------------+-------------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| fname | varchar(32) | NO | | NULL | |
| lname | varchar(32) | NO | | NULL | |
| date_added | timestamp | NO | | CURRENT_TIMESTAMP | |
| username | varchar(16) | NO | | NULL | |
| password | varchar(40) | NO | | NULL | |
| auth_level | int(1) | NO | | 1 | |
| last_login | timestamp | NO | | 0000-00-00 00:00:00 | |
+------------+-------------+------+-----+---------------------+----------------+
I want to be able to count how many times each user is in in_username AND out_username... The query I was using before looks like this:
select count(*) as "count", u.fname as "fname"
from visits v
inner join users as u on u.username = v.in_username
group by u.username order by u.fname
But that only returns how many in_username's there are... I'd like to have both in the same query if possible, so I could get results like this:
+----------+-----------+----------+
| count_in | count_out | fname |
+----------+-----------+----------+
| 118 | 224 | Bo |
| 27 | 64 | James |
| 147 | 138 | Jeremy |
| 23 | 37 | Jim |
| 182 | 172 | Robert |
| 120 | 158 | Tom |
+----------+-----------+----------+
Where count_in is how many times their username appears in visits.in_username, and count_out is how many times their username appears in visits.out_username
Everything I've tried with UNION seems to add the counts together, or removes rows for some reason. Any ideas?

Do a subquery to get each total, combine them with UNION, and then merge them with SUM().
SELECT SUM(count_in) count_in, SUM(count_out) count_out, fname
FROM (SELECT COUNT(*) count_in, 0 count_out, in_username fname
FROM visits v
GROUP BY fname
UNION
SELECT 0 count_in, COUNT(*) count_out, out_username fname
FROM visits v
GROUP BY fname) combined

Related

MySQL query not producing the correct result

Given this:
MariaDB [master]> describe history;
+--------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+----------------+
| historyid | int(10) unsigned | NO | PRI | NULL | auto_increment |
| amount | float | NO | | NULL | |
| subsidy | char(1) | NO | | NULL | |
| last_payment | date | NO | | NULL | |
| amount_paid | float | NO | | NULL | |
| balance | float | NO | | NULL | |
| attend | char(1) | NO | | N | |
| attend_date | date | NO | | NULL | |
| groupid | int(11) unsigned | NO | | NULL | |
| clientid | int(10) unsigned | NO | MUL | NULL | |
| memberid | int(10) unsigned | NO | MUL | NULL | |
+--------------+------------------+------+-----+---------+----------------+
MariaDB [master]> describe participation;
+-----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+------------------+------+-----+---------+----------------+
| partid | int(11) | NO | PRI | NULL | auto_increment |
| notes | varchar(255) | NO | | NULL | |
| groupdate | date | NO | | NULL | |
| clientid | int(10) unsigned | NO | MUL | NULL | |
| memberid | int(10) unsigned | NO | MUL | NULL | |
+-----------+------------------+------+-----+---------+----------------+
MariaDB [master]> select * from participation;
+--------+-----------+------------+----------+----------+
| partid | notes | groupdate | clientid | memberid |
+--------+-----------+------------+----------+----------+
| 194 | test test | 2016-01-26 | 3 | 1 |
+--------+-----------+------------+----------+----------+
How do I write the following query
MariaDB [master]> SELECT attend_date, groupdate, h.clientid, h.memberid
-> FROM history AS h
-> LEFT JOIN participation AS p ON groupdate = attend_date
-> WHERE h.memberid = "1"
-> AND MONTH(attend_date) = "1"
-> AND YEAR(attend_date) = "2016"
-> AND attend_date <> "0000-00-00"
-> ORDER BY attend_date ASC;
+-------------+------------+----------+----------+
| attend_date | groupdate | clientid | memberid |
+-------------+------------+----------+----------+
| 2016-01-26 | 2016-01-26 | 3 | 1 |
| 2016-01-26 | 2016-01-26 | 4 | 1 |
| 2016-01-26 | 2016-01-26 | 1 | 1 |
| 2016-01-26 | 2016-01-26 | 2 | 1 |
| 2016-01-28 | NULL | 3 | 1 |
| 2016-01-28 | NULL | 4 | 1 |
| 2016-01-28 | NULL | 1 | 1 |
| 2016-01-28 | NULL | 2 | 1 |
+-------------+------------+----------+----------+
so that my return is like this
+-------------+------------+----------+----------+
| attend_date | groupdate | clientid | memberid |
+-------------+------------+----------+----------+
| 2016-01-26 | 2016-01-26 | 3 | 1 |
| 2016-01-26 | NULL | 4 | 1 |
| 2016-01-26 | NULL | 1 | 1 |
| 2016-01-26 | NULL | 2 | 1 |
| 2016-01-28 | NULL | 3 | 1 |
| 2016-01-28 | NULL | 4 | 1 |
| 2016-01-28 | NULL | 1 | 1 |
| 2016-01-28 | NULL | 2 | 1 |
+-------------+------------+----------+----------+
The whole idea here is to be able to pull all the clients who don't yet have notes applied to a groupdate matching history table's attend_date. And only when a note is posted will the groupdate be set to the attend_date. Yet, the query, as I have it , returns a groupdate of 2016-01-26 for all four clients instead of just the one. Thanks in advance for your help.
Looking at your table structure, I see the relationship between your attend_date and groupdate on which you attempted to join, but I also see that those tables have clientid and memberid in common.
Since you only joined on the date, you're getting back a cartesian product of all clients and all dates for that condition. You can make the join's ON clause more specific by adding additional conditions with a logical AND.
LEFT JOIN participation AS p
ON groupdate = attend_date
AND h.clientid = p.clientid
It isn't fully obvious to me if your desired result set also requires a condition to match memberid between those tables, but if it does, the solution is as simple as another AND.
LEFT JOIN participation AS p
ON groupdate = attend_date
AND h.memberid = p.memberid
It helps not to think of a join's ON clause as merely a match between common columns, though that's the most common way it is used. Instead the ON clause just needs to present some condition that when true results in a joined row being returned (just like you're used to with a WHERE clause). That means you can place just about anything there which can be evaluated as true or false, allowing for complex joining logic. And the same can be said for ORDER BY and GROUP BY.

Subquery as another field

I have trying to do a SELECT query....not an UPDATE or INSERT or DELETE.
I have three tables.
The customers table
The invoices table
The invoice_items table
I want to run a query that will show me every invoice. Each invoice can have only ONE customer and MANY items...hence the existence of invoice_items
My current query looks like this
SELECT i.order_date, c.name, thedata.info from invoices i inner join customers c ON (i.customer = c.id) right join ( select x.order, group_concat( concat(x.itemname,' ', x.itemdesc) separator "\n" ) as info from invoice_items x ) thedata on (i.id = thedata.order)
When I run this query, I receive one row that contains, one customer, one invoice, and a list of any an every item regardless of invoice id or customer...???
+---------------------+--------------+---------------------------------------------------------------------------------------------------------------------------------+
| order_date | name | info |
+---------------------+--------------+---------------------------------------------------------------------------------------------------------------------------------+
| 2014-01-23 20:39:20 | Joe Customer | Boxes for boxing
Shoes for shining
2" Hermosa Plank for bobblin
Boxes for boxing
bobbles for bobblin
Lot 297 Woodale Carmel Oak |
+---------------------+--------------+---------------------------------------------------------------------------------------------------------------------------------+
My goal is to receive this same list but show all customers along with THEIR items.
What am I doing wrong?
Here are the schemas, for those that need them.
Customers
+---------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | text | NO | | NULL | |
| ship_address | text | NO | | NULL | |
| ship_address2 | text | NO | | NULL | |
| ship_city | text | NO | | NULL | |
| ship_state | text | NO | | NULL | |
| ship_zip | int(6) | NO | | NULL | |
| bill_address | text | NO | | NULL | |
| bill_address2 | text | NO | | NULL | |
| bill_city | text | NO | | NULL | |
| bill_state | text | NO | | NULL | |
| bill_zip | text | NO | | NULL | |
| phone | bigint(20) | NO | | NULL | |
| email | text | NO | | NULL | |
+---------------+------------+------+-----+---------+----------------+
Invoices
+-------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+----------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| customer | int(11) | NO | | NULL | |
| order_date | datetime | NO | | NULL | |
| status | text | NO | | NULL | |
| freightcost | double | NO | | NULL | |
+-------------+----------+------+-----+---------+----------------+
Invoice_items
+-----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| order | int(11) | NO | | NULL | |
| qty | int(11) | NO | | NULL | |
| itemname | text | NO | | NULL | |
| itemdesc | text | NO | | NULL | |
| itemprice | double | NO | | NULL | |
+-----------+---------+------+-----+---------+----------------+
try the below query, you need to use GROUP BY if you use GROUP_CONCAT().
SELECT i.order_date,
c.name,
group_concat( concat(x.itemname,' ', x.itemdesc) separator "\n" ) as info
FROM invoices i
INNER JOIN customers c ON i.customer = c.id
LEFT JOIN invoice_items x ON i.id = x.order
GROUP BY i.order_date,c.name

COUNT, Minimum Value per course without subquery

Can anyone help me find the minimum student action per course? Listed like this:
+-------------+--------------------------+
| Course | Lowest Action |
+-------------+--------------------------+
| Maths Y1 | |
| English C | |
| Science Y1 | |
for all users, even if they are not in the log table, without a subquery? My thanks to #luckylwk for assistance with my initial query. I have a solution with a subquery but want to put this into a variable for a much large query.
SELECT
COUNT(tbl_log.action)
lastname,
c.fullname,
FROM tbl_log
JOIN tbl_user ON tbl_log.userid = tbl_user.id
JOIN tbl_course ON tbl_log.course = tbl_course.id
GROUP BY tbl_log.userid, tbl_log.course
LOG TABLE
+-------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+----------------+
| id | | NO | PRI | NULL | auto_increment |
| time | | NO | | NULL | |
| userid | | NO | | NULL | |
| course | | NO | | NULL | |
| action | | NO | | NULL | |
+-------------+---------------------+------+-----+---------+----------------+
USER Table
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| id | | NO | PRI | NULL | auto_increment |
| username | | NO | | NULL | |
| userpassword | | NO | | NULL | |
| lastname | | NO | | NULL | |
| firstname | | NO | | NULL | |
+--------------+---------------------+------+-----+---------+----------------+
COURSE table
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| id | | NO | PRI | NULL | auto_increment |
| category | | NO | | NULL | |
| fullname | | NO | | NULL | |
| shortname | | NO | | NULL | |
+--------------+---------------------+------+-----+---------+----------------+
I link the users together via the enrolment and context tables.
Try:
SELECT
tbl_course.course_name,
MIN(tbl_log.action) as Lowest_Action
FROM tbl_log
JOIN tbl_user ON tbl_log.userid = tbl_user.id
JOIN tbl_course ON tbl_log.course = tbl_course.id
GROUP BY tbl_course.course_name
See Fiddle Demo

MySQL JOIN and COUNT not coherent

I have these tables :
mysql> desc mod_asterisk_booking;
+---------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| uid | int(10) unsigned | NO | | NULL | |
| server_id | int(10) unsigned | NO | | NULL | |
| date_call | datetime | NO | | NULL | |
| participants | int(10) unsigned | NO | | NULL | |
| ... |
+---------------+------------------+------+-----+---------+----------------+
mysql> desc mod_asterisk_servers;
+-------------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(32) | NO | | NULL | |
| channels_capacity | int(10) unsigned | NO | | NULL | |
| ... |
+-------------------+------------------+------+-----+---------+----------------+
mysql> desc mod_asterisk_server_phones;
+------------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| server_id | int(10) unsigned | NO | | NULL | |
| phone_number | varchar(15) | NO | | NULL | |
| phone_alias | varchar(15) | NO | | NULL | |
| extension | int(10) unsigned | NO | | NULL | |
| is_toll_free | tinyint(1) | NO | | 0 | |
| is_allow_foreign | tinyint(1) | NO | | 0 | |
+------------------+------------------+------+-----+---------+----------------+
The goal is to fetch a server (from mod_asterisk_servers) that has the enough channels available for a given date interval. This query
SELECT s.*,
s.`channels_capacity` - IFNULL(SUM(b.`participants`), 0) as 'channels_available'
FROM `mod_asterisk_servers` as s
LEFT JOIN `mod_asterisk_booking` as b ON (b.server_id=s.id AND (b.date_call BETWEEN '2011-07-30 15:15:00' AND '2011-07-30 17:15:00'))
GROUP BY s.id
ORDER BY 'channels_available' DESC;
could return something like :
+----+-------------+-----+------------------+--------------------+
| id | name | ... |channels_capacity | channels_available |
+----+-------------+-----+------------------+--------------------+
| 1 | Test server | ... | 150 | 140 |
+----+-------------+-----+------------------+--------------------+
Now, I'd like to add some columns to this query; notably the phone numbers associated with each server found. A phone number may have these combination :
local phone number (is_toll_free=0 AND is_allow_foreign=0)
toll free number, limited to a given region (is_toll_free=1 AND is_allow_foreign=0)
toll free number, allowing an "extended" region (is_toll_free=1 AND is_allow_foreign=1)
I tried this query
SELECT s.*,
s.`channels_capacity` - IFNULL(SUM(b.`participants`), 0) as 'channels_available',
count(p1.phone_number) as 'local_phones',
count(p2.phone_number) as 'toll_free_phones',
count(p3.phone_number) as 'allow_foreign_phones'
FROM `mod_asterisk_servers` as s
LEFT JOIN `mod_asterisk_booking` as b ON (b.server_id=s.id AND (b.date_call BETWEEN '2011-07-30 15:15:00' AND '2011-07-30 17:15:00'))
LEFT JOIN `mod_asterisk_server_phones` as p1 ON (p1.server_id=s.id AND p1.is_toll_free=0 AND p1.is_allow_foreign=0)
LEFT JOIN `mod_asterisk_server_phones` as p2 ON (p2.server_id=s.id AND p2.is_toll_free=1 AND p2.is_allow_foreign=0)
LEFT JOIN `mod_asterisk_server_phones` as p3 ON (p3.server_id=s.id AND p3.is_toll_free=1 AND p3.is_allow_foreign=1)
ORDER BY 'channels_available' DESC;
but it returns
+----+-------------+-----+-------------------+--------------------+--------------+------------------+----------------------+
| id | name | ... | channels_capacity | channels_available | local_phones | toll_free_phones | allow_foreign_phones |
+----+-------------+-----+-------------------+--------------------+--------------+------------------+----------------------+
| 1 | Test server | ... | 150 | 140 | 2 | 2 | 2 |
+----+-------------+-----+-------------------+--------------------+--------------+------------------+----------------------+
even though there are only three numbers for that server :
mysql> select * from mod_asterisk_server_phones where server_id = 1;
+----+-----------+----------------+-------------+-----------+--------------+------------------+
| id | server_id | phone_number | phone_alias | extension | is_toll_free | is_allow_foreign |
+----+-----------+----------------+-------------+-----------+--------------+------------------+
| 1 | 1 | XXX-XXX-XXXX | | XXXX | 0 | 0 |
| 2 | 1 | 1-800-XXX-XXXX | | XXXX | 1 | 0 |
| 3 | 1 | 1-800-XXX-XXXX | | XXXX | 1 | 1 |
+----+-----------+----------------+-------------+-----------+--------------+------------------+
Maybe someone with better understanding of SQL can help me figure out this one?
Thanks!
Try count(DISTINCT p1.phone_number) instead of count(p1.phone_number) (and the same for p2,p3). And don't forget the proper GROUP BY

Help with MySQL join 3 Tables

I am working on a registration system that uses the following tables:
Person (name etc.), Course (course date), Registration (association table pid, cid)
Person
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | text | YES | | NULL | |
+-------+---------+------+-----+---------+-------+
Example Data:
select * from person;
+-----+--------+
| id | name |
+-----+--------+
| 101 | Graham |
| 102 | Lisa |
| 103 | John |
+-----+--------+
Course
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | text | YES | | NULL | |
| date | date | YES | | NULL | |
+-------+---------+------+-----+---------+-------+
select * from course;
+----+---------+------------+
| id | name | date |
+----+---------+------------+
| 1 | Hip Hop | 2011-06-08 |
| 2 | Dancing | 2006-06-23 |
| 3 | Running | 2007-07-08 |
+----+---------+------------+
Registration
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| pid | int(11) | YES | | NULL | |
| cid | int(11) | YES | | NULL | |
+-------+---------+------+-----+---------+-------+
select * from registration;
+----+------+------+
| id | pid | cid |
+----+------+------+
| 1 | 101 | 1 |
| 2 | 101 | 2 |
| 3 | 103 | 2 |
+----+------+------+
I would like to find person(s) that have no registration records within the past two years. I am attempting to join the tables based on date calculation but it does not seem to work this way. Is this possible with mysql or is my approach of trying this with one query wrong?
query I have come up with:
select * from
(person left join registration on person.id = registration.pid)
left join course on course.id = registration.cid
AND DATE_FORMAT(`course`.`date`, "%m.%Y") > DATE_FORMAT( DATE_SUB(NOW(), INTERVAL 2 YEAR),"%m.%Y")
WHERE
registration.id IS NULL;
+-----+------+------+------+------+------+------+------+
| id | name | id | pid | cid | id | name | date |
+-----+------+------+------+------+------+------+------+
| 102 | Lisa | NULL | NULL | NULL | NULL | NULL | NULL |
+-----+------+------+------+------+------+------+------+
It should list person 102 and 103 since both registrations are older than 2 years and no other records of newer course dates can be found...
Give this a shot, using a NOT EXISTS clause:
select p.* from person p
where not exists (select 1 from person px
join registration rx on px.id = rx.pid
join course cx on rx.cid = cx.id
where px.id = p.id
and cx.date > DATE_SUB(NOW(), INTERVAL 2 YEAR))