mySQL Select where results from column "b" have column "a" in common - mysql

This one is kinda hard to explain, I'll give it a shot.
I have this table where one of the columns is the type column. The salesperson will insert records that will contain a b_id and also an action_id.
with the following code I retrieve some info,
SELECT entry_type, COUNT(DISTINCT(b_name)) AS '# of prospects',
SUM(case when entries.out_id = '1' then 1 else 0 end) 'No Interest',
SUM(case when entries.out_id = '2' then 1 else 0 end) 'Needs Follow Up',
SUM(case when entries.out_id = '3' then 1 else 0 end) 'Appointment Booked'
FROM entries
LEFT JOIN outcomes on outcomes.out_id = entries.out_id
LEFT JOIN type on type.type_id = entries.type_id
LEFT JOIN business on entries.b_id = business.b_id
LEFT JOIN users on users.user_id = entries.user_id
WHERE b_name LIKE 'July%' AND (entries.type_id = 1 OR entries.type_id = 2 OR entries.type_id = 14)
GROUP BY entry_type;
The result is the following
ACTION # OF PROSPECTS NO INTEREST NEEDS FOLLOW UP APP. BOOKED
Call 4 1 2 1
Follow Up Contact 2 0 0 2
Walk In 1 1 0 0
The thing is, There are 2 possible initial actions, "Call" or "Walk In". "Follow Up Contact" is used if necessary after a initial call or walk in. As you can see, I have 2 appointments booked originated from this follow up. Here is the question. How do I know if this follow up contact is related to an initial call or an initial walk in?
I need to be able to generate a report specifying how many appointments were originated from each type of approach ( call or walk in ).
Thanks in advance

Use a self-join:
SELECT e1.type AS original_type, COUNT(e2.b_id) AS count
FROM entries AS e1
LEFT JOIN entries AS e2 ON e2.b_id = e1.b_id AND e2.entry_type = 'Follow Up Contact'
WHERE e1.entry_type IN ('Call', 'Walk In')
GROUP BY original_type

Related

Two tables with all rows including allocated based on id

Im trying to make this generic as it might help others in the future.
For an example i have two tables one with books and the other is the user with which book they have read, So ide like to display all the books and include a temporary column value as a (yes / no or 0/1), i have tried a join but the ( WHERE user_id = 3) clause only then return the one row and not all the other rows.
book.book_id book.book_name
10 Book 1
11 Book 2
12 Book 3
-------------
user.user_id user.book_id
1 10
1 12
2 11
3 12
Desired output:
user_id book_id temp_col_read
3 10 0 // yes, on or null
3 12 1 // or yes
3 13 0
This is actually quite simple. In the event that a user could read a book multiple times, I would go with exists in the select:
select b.*,
(case when exists (select 1
from reads r
where r.book_id = b.book_id and r.user_id = 3
)
then 1 else 0
end) as user_read_book
from book b;
In MySQL, the case is not strictly necessary because a boolean expression is treated as 0/1 in many contexts:
select b.*,
(exists (select 1
from reads r
where r.book_id = b.book_id and r.user_id = 3
) as user_read_book
from book b;
You can use a left join and where the join is unresolved then is not read
select
user.user_id
, book.book_id
, case
when book.book_id is null
then 'NO' else 'YES'
end as temp_col_read
from book
left join user on user.book_id = book.book_id

Calculating acceptance_ratio with LEFT JOIN and SELF JOIN and aggregate function

Trying to calculate daily acceptance ratios from the 'connecting' table which has 4 fields with sample values:
date action sender_id recipient_id
'2017-01-05', 'request_link', 'frank', 'joe'
'2017-01-06', 'request_link', 'sally', 'ann'
'2017-01-07', 'request_link', 'bill', 'ted'
'2017-01-07', 'accept_link', 'joe', 'frank'
'2017-01-06', 'accept_link', 'ann', 'sally'
'2017-01-06', 'accept_link', 'ted', 'bill'
Because there are 0 accepts and 1 request on 01-05, its daily acceptance ratio should be 0/1 = 0. Similarly, the ratio for 01-06 should be 2/1, and it should be 1/1 for 01-07.
It is important however that each accept_link has a corresponding request_link where the sender_id of the request_link = the recipient_id of the accept_link (and vice versa). So here a self-join is required I believe to ensure that Joe accepts Frank's request, regardless of the date.
How can the below query be corrected so that the aggregation works correctly while retaining the required join conditions? Will the query calculate correctly as is if the two WHERE conditions are removed, or are they necessary?
SELECT f1.date,
SUM(CASE WHEN f2.action = 'accept_link' THEN 1 ELSE 0 END) /
SUM(CASE WHEN f2.action = 'request_link' THEN 1 ELSE 0 END) AS acceptance_ratio
FROM connecting f1
LEFT JOIN connecting f2
ON f1.sender_id = f2.recipient_id
LEFT JOIN connecting f2
ON f1.recipient_id = f2.sender_id
WHERE f1.action = 'request_link'
AND f2.action = 'accept_link'
GROUP BY f1.date
ORDER BY f1.date ASC
Expected output should look something like:
date acceptance_ratio
'2017-01-05' 0.0000
'2017-01-06' 2.0000
'2017-01-07' 1.0000
Thanks in advance.
Once again, I don't think you need to be using a self join here. Instead, just use conditional aggregation over the entire table, and count the number of requests and accepts which happened on each day:
SELECT t.date,
CASE WHEN t.num_requests = 0
THEN 'No requests available'
ELSE CAST(t.num_accepts / t.num_requests AS CHAR(50))
END AS acceptance_ratio
FROM
(
SELECT c1.date,
SUM(CASE WHEN c1.action = 'accept_link' AND c2.action IS NOT NULL
THEN 1 ELSE 0 END) AS num_accepts,
SUM(CASE WHEN c1.action = 'request_link' THEN 1 ELSE 0 END) AS num_requests
FROM connecting c1
LEFT JOIN connecting c2
ON c1.action = 'accept_link' AND
c2.action = 'request_link' AND
c1.sender_id = c2.recipient_id AND
c2.recipient_id = c1.sender_id
GROUP BY c1.date
) t
ORDER BY t.date
Note here that I use a CASE expression to handle divide by zero, which could occur should a certain day no requests. I also assume here that the same invitation will not be sent out more than once.

Joining two tables and creating PIVOT for varchar with boolean value in SQL

Working with SQL Server 2008 tables where A user can have one or multiple roles
UserDetails:
username level country role
=============================================
john A USA SystemAdmin
john B Brazil Co-ordinator
Smith G Spain Doctor
Anne D USA Nurse
.... ... .... ....
RoleDetails:
role function
============================
SystemAdmin full time
Doctor part time
Co-ordinator consultant
Nurse On call
.... ...
I am trying to create a VIEW where data would look like
username level country SystemAdmin Co-ordinator Doctor Nurse
=============================================================================
john A USA 1 0 0 0
john B Brazil 0 1 0 0
Smith G Spain 0 0 1 0
Anne D USA 0 0 0 1
.... ... .... .... .... .... ...
What I am trying to do is join two tables and generate columns from the rows of the second table where both of them are joined on the basis of UserDetails.role = RoleDetails.role. And most of the columns are varchar in the database. I am trying to generate the Columns from RoleDetails rows with boolean value dynamically. Since RoleDetails table will continue growing, I could not select the individual row like PIVOT ( MAX(role) FOR role IN (Doctor, Nurse...))
Not sure if this is feasible or how to do it. Any direction would be appreciated.
This is a very common question and here is the typical way to do this. If you need to handle the list of roles dynamically then you'll find many solutions out there using dynamic SQL along with XML features to accomplish string concatenation when building the column list.
select
u.username,
min(u.level) as level,
min(u.country) as country,
min(case when role = 'SystemAdmin' then 1 else 0 end) as SystemAdmin,
min(case when role = 'Co-ordinator' then 1 else 0 end) as "Co-ordinator",
min(case when role = 'Doctor' then 1 else 0 end) as Doctor,
min(case when role = 'Nurse' then 1 else 0 end) as Nurse
from UserDetails u left outer join RoleDetails r
on r.role = u.role
group by u.username
Your application may be able to get away with something like this if you can part a hard limit on the number of roles. Depending on how you intend to use this it may be preferable to have static column names anyway.
with NumberedRoles as (
select rolename, row_number() over (order by role) as rn
from RoleDetails
)
select
u.username,
min(u.level) as level,
min(u.country) as country,
min(case when r.rn = 1 then 1 else 0 end) as RoleIsMember01,
min(case when r.rn = 1 then r."role" else null end) as RoleName01,
min(case when r.rn = 1 then 1 else 0 end) as RoleIsMember02,
min(case when r.rn = 1 then r."role" else null end) as RoleName02,
min(case when r.rn = 1 then 1 else 0 end) as RoleIsMember03,
min(case when r.rn = 1 then r."role" else null end) as RoleName03,
...
min(case when r.rn = 1 then 1 else 0 end) as RoleIsMember32,
min(case when r.rn = 1 then r."role" else null end) as RoleName32
from
UserDetails u inner join
NumberedRoles r
on r."role" = u."role"
group by u.username

MySQL - loop through rows

I have the following code
select count(*)
from (select Annotations.user_id
from Annotations, Users
where Users.gender = 'Female'
and Users.user_id = Annotations.user_id
and image_id = 1
group by Annotations.user_id
having sum(case when stem = 'taxi' then 1 else 0 end) > 0 and
sum(case when stem = 'zebra crossing' then 1 else 0 end) > 0
) Annotations
It produces a count of how many females who have given the stem 'taxi' and 'zebra crossing' for image 1.
Sample data
user id, image id, stem
1 1 image
1 1 taxi
1 1 zebra crossing
2 1 person
2 1 zebra crossing
2 1 taxi
3 1 person
3 1 zebra crossing
Expected result (or similar)
stem1, stem2, count
taxi , zebra crossing 2
person, zebra crossing 2
However, as there are over 2000 stems, I cannot specify them all.
How would I go around looping through the stem rows with the image_id = 1 and gender = female as opposed to specifying the stem string?
Thank you
As per my understanding, you need to fetch female users that have 2 or more stems
Update: It seems you need to display the user's that have a stem that is used by another user too, I have updated the query for the same
SELECT
distinct a.user_id,
group_concat(DISTINCT a.stem ORDER BY a.stem)
FROM
Annotations a
JOIN Users u ON ( a.user_id = u.user_id AND u.gender = 'Female' )
JOIN
(
SELECT
b.user_id,
b.stem
FROM
Annotations b
) AS b ON ( a.user_id <> b.user_id AND b.stem = a.stem )
WHERE
a.image_id = 1
GROUP BY
a.user_id
UPDATE: As I understand it, you want to select all combinations of 2 stems, and get a count of how many users have that combination of stems. Here is my solution:
SELECT stem1, stem2, count(*) as count FROM
(
SELECT a.user_id,a.image_id,a.stem as stem1,b.stem as stem2
FROM Annotations a JOIN Annotations b
ON a.user_id=b.user_id && b.image_id=a.image_id && a.stem!=b.stem
JOIN Users ON Users.user_id = a.user_id
WHERE Users.gender = "Female"
) as stems GROUP BY stem1, stem2 having count > 1 WHERE image_id=1;
The caveat here is that it will return 2 rows for each combinations of stems. (The second occurrence will have the stems in reverse order).
Here's my attempt to solve your problem:
SELECT COUNT(*) AS Count, a1.stem AS Stem1, a2.Stem AS Stem2
FROM Annotations AS a1
INNER JOIN Annotations AS a2 ON a1.user_id = a2.user_id AND a1.image_id = a2.image_id
AND a1.stem < a2.stem
WHERE a1.image_id = 1
GROUP BY a1.stem, a2.Stem
HAVING COUNT(*) > 1;
I did not include image_id logic.
Please see my SQL Fiddle here: http://sqlfiddle.com/#!2/4ee69/33
Based on the following data (copied from yours) I get the result posted underneath it.
CREATE TABLE Annotations
(`user_id` int, `image_id` int, `stem` varchar(14))
;
INSERT INTO Annotations
(`user_id`, `image_id`, `stem`)
VALUES
(1, 1, 'image'),
(1, 1, 'taxi'),
(1, 1, 'zebra crossing'),
(2, 1, 'person'),
(2, 1, 'zebra crossing'),
(2, 1, 'taxi'),
(3, 1, 'person'),
(3, 1, 'zebra crossing')
;
COUNT STEM1 STEM2
2 person zebra crossing
2 taxi zebra crossing

mysql conditional field update

I have 3 columns in CATEGORY TABLE for storing pre-calculated counts of records for it in another table PRODUCTS.
CATEGORY(c_id,name,c30,c31,c32)
c30=count for New Products (value 30)
c31 count for used products (value 31)
c32 count for Damaged products (value 32)
PRODUCT(p_id,c_id,name,condition)
condition can be 30,31 or 32.
I am thinking to write a single UPDATE statement so, it will update respective category count.
Althogh below statement is syntactically wrong, but i am looking for similar type of solution.
select case product.condition
when 30 then update category set category.c30=category.c30+1 where category.c_id=product.category3
when 31 then update category set category.c31=category.c31+1 where category.c_id=product.category3
when 32 then update category set category.c32=category.c32+1 where category.c_id=product.category3
end case
from product
where product.c_id=12
Any suggestion!
You can do this:
UPDATE CATEGORY c
INNER JOIN
(
SELECT
c_id,
SUM(CASE WHEN `condition` = 30 THEN 1 ELSE 0 END) c30,
SUM(CASE WHEN `condition` = 31 THEN 1 ELSE 0 END) c31,
SUM(CASE WHEN `condition` = 32 THEN 1 ELSE 0 END) c32
FROM product
GROUP BY c_id
) p ON c.c_id = p.c_id
SET c.c30 = p.c30,
c.c31 = p.c31,
c.c32 = p.c32;
SQL Fiddle Demo
You can join both the tables and then update the value in same join query.