Mysql, left join and count conditions - mysql

I have fallowing SQL query (pseudo query):
SELECT
some columns [...]
COUNT(clicks.id) AS clicks,
COUNT(transactions.id) AS transactions
FROM
campaign
LEFT JOIN
clicks ON clicks.key = campaign.key
LEFT JOIN (
SELECT
id, key
FROM
transactions
GROUP BY
userkey
) transactions ON clicks.key = transactions.key;
GROUP BY
campaign.id
Query return good results. On example:
column | columns [..] | 34 | 10
column | columns [..] | 22 | 1
column | columns [..] | 34 | 17
So, records in table clicks they have transactions or a few transactions or they do not.
Haw Can I retun COUNT() clicks who have COUNT(transactions.id) = 0 and COUNT(transactions.id) > 0 ? On example:
column | columns [..] | 34 | 10 | 4 (count data from clicks table which have related transactions) | 30 (count data from clicks table which not have related transactions)
column | columns [..] | 22 | 1 | 6 | 16
column | columns [..] | 34 | 17 | 10 | 24
Tahnks for help.
#UPDATE:
I solved my problem adding second table. Now my SQL query looks like:
SELECT
some columns [...]
COUNT(clicks.id) AS clicks,
COUNT(transactions.id) AS transactions,
COUNT(clicks_count.id) as witchout_transactions,
(COUNT(clicks.id) - COUNT(clicks_count.id)) as witch_transactions
FROM
campaign
LEFT JOIN
clicks ON clicks.key = campaign.key
LEFT JOIN (
SELECT
id, key
FROM
transactions
GROUP BY
userkey
) transactions ON clicks.key = transactions.key
LEFT JOIN (
SELECT
clicks.id,
COUNT(transactions.id) AS transactions
FROM
clicks
LEFT JOIN transactions ON clicks.key = transactions.key
GROUP BY clicks.id
HAVING transactions = 0
) clicks_count ON clicks_count.id = clicks.id
GROUP BY
campaign.id

If I understand correctly, you can try to use CASE WHEN expression and COUNT
Because you didn' provide any sample data and expected result, so I can only provide pseudo-query.
SELECT...,
COUNT(CASE WHEN [have transactions condition] then 1 end),
COUNT(CASE WHEN [not have related transactions condition] then 1 end)
If that didn't help you, you can provide some data and expect result, I will edit my answer.

What about adding a second join with the clicks table
JOIN (
SELECT id
FROM clicks
LEFT JOIN transactions ON clicks.key = transactions.key AND transactions.id != 0) clicks2 ON clicks.id = clicks2.id
And in the select clause use this to for the two columns
SELECT
some columns [...]
COUNT(clicks.id) AS clicks,
COUNT(transactions.id) AS transactions
COUNT(clicks2.id) as clicks_with
clicks - clicks_with AS clicks_without

Related

Query: I have 4 rows, need to add the results from 3 rows into one, and leave the last row untouched

I have a kind of tricky question for this query. First the code:
SELECT user_type.user_type_description,COUNT(incident.user_id) as Quantity
FROM incident
INNER JOIN user ON incident.user_id=user.user_id
INNER JOIN user_type ON user.user_type=user_type.user_type
WHERE incident.code=2
GROUP BY user.user_type
What Am I doing?
For example, I am counting police reports of robbery, made from different kind of users. In my example, "admin" users reported 6 incidents of code "2" (robbery) and so on, as is showed in 'where' clause (incident must be robbery, also code 2).
this brings the following result:
+-----------------------+----------+
| user_type_description | Quantity |
+-----------------------+----------+
| Admin | 6 |
| Moderator | 8 |
| Fully_registered_user | 8 |
| anonymous_user | 9 |
+-----------------------+----------+
Basically Admin,Moderator and Fully_registered_user are appropriately registered users. I need to add them in a result where it shows like:
+--------------+------------+
| Proper_users | Anonymous |
+--------------+------------+
| 22 | 9 |
+--------------+------------+
I am not good with sql. Any help is appreciated. Thanks.
You can try to use condition aggregate function base on your current result set.
SUM with CASE WHEN expression.
SELECT SUM(CASE WHEN user_type_description IN ('Admin','Moderator','Fully_registered_user') THEN Quantity END) Proper_users,
SUM(CASE WHEN user_type_description = 'anonymous_user' THEN Quantity END) Anonymous
FROM (
SELECT user_type.user_type_description,COUNT(incident.user_id) as Quantity
FROM incident
INNER JOIN user ON incident.user_id=user.user_id
INNER JOIN user_type ON user.user_type=user_type.user_type
WHERE incident.code=2
GROUP BY user.user_type
) t1
You just need conditional aggregation:
SELECT SUM( ut.user_type_description IN ('Admin', 'Moderator', 'Fully_registered_user') ) as Proper_users,
SUM( ut.user_type_description IN ('anonymous_user') as anonymous
FROM incident i INNER JOIN
user u
ON i.user_id = u.user_id INNER JOIN
user_type ut
ON u.user_type = ut.user_type
WHERE i.code = 2;
Notes:
Table aliases make the query easier to write and to read.
This uses a MySQL shortcut for adding values -- just just adding the booelean expressions.
I would solve it with a CTE, but it would be better to have this association in a table.
WITH
user_type_categories
AS
(
SELECT 'Admin' AS [user_type_description] , 'Proper_users' AS [user_type_category]
UNION SELECT 'Moderator' AS [user_type_description] , 'Proper_users' AS [user_type_category]
UNION SELECT 'Fully_registered_user' AS [user_type_description] , 'Proper_users' AS [user_type_category]
UNION SELECT 'anonymous_user' AS [user_type_description] , 'Anonymous' AS [user_type_category]
)
SELECT
CASE WHEN utc.[user_type_category] = 'Proper_users' THEN
SUM(incident.user_id)
END AS [Proper_Users_Quantity]
, CASE WHEN utc.[user_type_category] = 'Anonymous' THEN
SUM(incident.user_id)
END AS [Anonymous_Quantity]
FROM
[incident]
INNER JOIN [user] ON [incident].[user_id] = [user].[user_id]
INNER JOIN [user_type] ON [user].[user_type] = [user_type].[user_type]
LEFT JOIN user_type_categories AS utc ON utc.[user_type_description] = [user_type].[user_type_description]
WHERE
[incident].[code] = 2

Select inner join with n_m table

I have three tables as following:
USERS TABLE
id_user| name |
---------------
1 | ...
2 | ...
SERVICES TABLE
id_service | name |
-------------------
1 | ...
2 | ...
3 | ...
USER_SERVICES TABLE (n-m)
id_user | id_service
--------------------
1 | 1
1 | 2
2 | 1
And I need to do a SELECT starting from "SELECT * FROM users" and then, getting the users by services. Ex. I need to get every user with services = 1 and services = 2 (and maybe he has other more services, but 1 and 2 for sure).
I did the following:
SELECT *
FROM `users`
INNER JOIN user_services ON users.id_user = user_services.id_user
WHERE id_service=1 AND id_service=2
But this, of course dont works since there is not a single record matching service = 1 and service = 2.
What can I do?
Add an extra join for the other service you want to check:-
SELECT *
FROM `users`
INNER JOIN user_services us1 ON users.id_user = us1.id_user AND us1.id_service=1
INNER JOIN user_services us2 ON users.id_user = us2.id_user AND us2.id_service=2
select t.*,
(select count(*) from user_services where id_user = t.id_user) how_much
from users t;
Is this what you want???
It shows the data of the users and how much services are in the services table. Other possibility is this:
select t.*,
(case when (select count(*)
from user_services where id_user = 1) > 0
then 'service1'
else 'null'
end) has_service_1
from users t;
The problem with this select is that you have to repeat this case...end as much times as id_services you have, so it doesn't make sense if the number of services is increasing over time. On the contrary, if it is a somewhat fixed number, and it is not a big number, this could be a solution.

MySQL query to get first unique values of one column on multiple tables

I have the following SQL query which queries my tickets, ticketThreads, users and threadStatus tables:
SELECT tickets.threadId, ticketThreads.threadSubject, tickets.ticketCreatedDate, ticketThreads.threadCreatedDate, threadStatus.threadStatus, users.name
FROM
tickets
INNER JOIN
ticketThreads
ON
tickets.threadId = ticketThreads.threadId
INNER JOIN
threadStatus
ON
ticketThreads.threadStatus = threadStatus.id
INNER JOIN
users
ON
users.id = ticketThreads.threadUserId
WHERE
tickets.ticketId = ticketThreads.lastMessage
AND
ticketThreads.threadStatus != 3
ORDER BY
tickets.ticketCreatedDate
DESC
The abridged version of what this returns is:
threadId |
----------
1 |
2 |
This works fine, and is what I expect, however to clean up the code and database slightly I need to remove the ticketThreads.lastMessage column.
If I remove the line WHERE tickets.ticketId = ticketThreads.lastMessage then this is an abridged version of what is returned:
threadId |
----------
1 |
2 |
1 |
What I need to do then is edit the query above to enable me to select the highest unique value for each threadId value in the tickets database.
I know about MAX() and GROUP BY but can't figure how to get them into my query above.
The relevant parts of the tables are shown below:
tickets
ticketId | ticketUserId | threadId
-------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 1 | 1
ticketThreads
threadId | lastMessage | threadStatus
-------------------------------
1 | 3 | 4
2 | 2 | 1
I hope all the above is clear and makes sense
So you need the ticket with the highest id per each thread? Your problem is actually very easy variant of greatest record per group problem. No need for any subqueries. Basicaly you have two options, which both should perform much better than your query, the second be faster (please post the actual durations in your db!):
1. Standard compliant query, but slower:
SELECT t1.threadId, ticketThreads.threadSubject, t1.ticketCreatedDate,
ticketThreads.threadCreatedDate, threadStatus.threadStatus, users.name
FROM tickets as t1
LEFT JOIN tickets as t2
ON t1.threadId = t2.threadId AND t1.ticketId < t2.ticketId
JOIN ticketThreads ON t1.threadId = ticketThreads.threadId
JOIN threadStatus ON ticketThreads.threadStatus = threadStatus.id
JOIN users ON users.id = ticketThreads.threadUserId
WHERE t2.threadId is NULL
AND ticketThreads.threadStatus != 3
ORDER BY t1.ticketCreatedDate DESC
This one joins the tickets table two times, which can make it a bit slower for big tables.
2. Faster, but uses MySQL extension to standard SQL:
set #prev_thread := NULL;
SELECT t.threadId, ticketThreads.threadSubject, t.ticketCreatedDate,
ticketThreads.threadCreatedDate, threadStatus.threadStatus, users.name
FROM tickets as t
JOIN ticketThreads ON t.threadId = ticketThreads.threadId
JOIN threadStatus ON ticketThreads.threadStatus = threadStatus.id
JOIN users ON users.id = ticketThreads.threadUserId
WHERE ticketThreads.threadStatus != 3
AND IF(IFNULL(#prev_thread, -1) = #prev_thread := t.threadId, 0, 1)
ORDER BY t.threadId, t.ticketId DESC,
t.ticketCreatedDate DESC
Here, we perform one pass scan on ordered joined data, using auxiliary mysql variable #prev_thread to filter only the first (in the given order) ticket for each thread (the one with highest ticketId).

Combine multiple SQL select statements into columns

I'm having a hard time wrapping my mind around this, any assistance is most appreciated.
I have two select statements with joins to 1 or more tables.
SELECT repinfo.repName, SUM(callstatssummary.CallsIn)
FROM repinfo
LEFT JOIN callstatssummary
ON repinfo.isaacID = callstatssummary.IsaacID AND callstatssummary.ShiftDate >= '2013-02-10' AND callstatssummary.ShiftDate <= '2013-02-16'
GROUP BY repinfo.repName;
The output of the first statement is a list of everyone in the repinfo table, with the sum of the total calls they took during the week. I used a left join to include people who didn't take calls in the result.
SELECT repinfo.repName, SUM(`1036`.afterRgu) - SUM(`1036`.priorRgu)
FROM repinfo
JOIN reporders
ON repinfo.repID = reporders.oRep
JOIN `1036`
ON reporders.workOrder = `1036`.workOrder AND `1036`.entryDate >= '2013-02-10' AND `1036`.entryDate <= '2013-02-16' AND `1036`.afterRgu >= `1036`.priorRgu
GROUP BY repinfo.repName;
The second statement outputs the number of products that each person sold during the week. The repinfo table has the information about the representative, which joins with the reporders table to match the work order. The 1036 table has detailed information about the orders.
I am looking to output something like this - essentially combine the output of the two select statements:
| repName | SUM(callstatssummary.CallsIn) | SUM(`1036`.afterRgu) - SUM(`1036`.priorRgu) |
______________________________________________________________________________________________
| Bruce W | 41 | 13 |
| Cathy M | 84 | 17 |
| Jonah S | NULL | 29 |
Any suggestions?
One way to combine those statements is to make each of them a derived-table / inline-view and join on repName.
Please note: Obviously you would want to join on a rep ID number (or whatever you call the primary key of the repinfo table) if two reps can have the same name.
select
r.repName, c.sumCallsIn, o.sumProdSold
from
repinfo r
left join (
SELECT repinfo.repName,
SUM(callstatssummary.CallsIn) sumCallsIn
FROM repinfo
LEFT JOIN callstatssummary
ON repinfo.isaacID = callstatssummary.IsaacID
AND callstatssummary.ShiftDate >= '2013-02-10'
AND callstatssummary.ShiftDate <= '2013-02-16'
GROUP BY repinfo.repName
) c
on c.repName = r.repName
left join (
SELECT repinfo.repName,
SUM(`1036`.afterRgu) - SUM(`1036`.priorRgu) sumProdSold
FROM repinfo
JOIN reporders
ON repinfo.repID = reporders.oRep
JOIN `1036`
ON reporders.workOrder = `1036`.workOrder
AND `1036`.entryDate >= '2013-02-10'
AND `1036`.entryDate <= '2013-02-16'
AND `1036`.afterRgu >= `1036`.priorRgu
GROUP BY repinfo.repName
) o
on r.repName = o.repName
order by r.repName;

MySQL SELECT combining 3 SELECTs INTO 1

Consider following tables in MySQL database:
entries:
creator_id INT
entry TEXT
is_expired BOOL
other:
creator_id INT
entry TEXT
userdata:
creator_id INT
name VARCHAR
etc...
In entries and other, there can be multiple entries by 1 creator. userdata table is read only for me (placed in other database).
I'd like to achieve a following SELECT result:
+------------+---------+---------+-------+
| creator_id | entries | expired | other |
+------------+---------+---------+-------+
| 10951 | 59 | 55 | 39 |
| 70887 | 41 | 34 | 108 |
| 88309 | 38 | 20 | 102 |
| 94732 | 0 | 0 | 86 |
... where entries is equal to SELECT COUNT(entry) FROM entries GROUP BY creator_id,
expired is equal to SELECT COUNT(entry) FROM entries WHERE is_expired = 0 GROUP BY creator_id and
other is equal to SELECT COUNT(entry) FROM other GROUP BY creator_id.
I need this structure because after doing this SELECT, I need to look for user data in the "userdata" table, which I planned to do with INNER JOIN and select desired columns.
I solved this problem with selecting "NULL" into column which does not apply for given SELECT:
SELECT
creator_id,
COUNT(any_entry) as entries,
COUNT(expired_entry) as expired,
COUNT(other_entry) as other
FROM (
SELECT
creator_id,
entry AS any_entry,
NULL AS expired_entry,
NULL AS other_enry
FROM entries
UNION
SELECT
creator_id,
NULL AS any_entry,
entry AS expired_entry,
NULL AS other_enry
FROM entries
WHERE is_expired = 1
UNION
SELECT
creator_id,
NULL AS any_entry,
NULL AS expired_entry,
entry AS other_enry
FROM other
) AS tTemp
GROUP BY creator_id
ORDER BY
entries DESC,
expired DESC,
other DESC
;
I've left out the INNER JOIN and selecting other columns from userdata table on purpose (my question being about combining 3 SELECTs into 1).
Is my idea valid? = Am I trying to use the right "construction" for this?
Are these kind of SELECTs possible without creating an "empty" column? (some kind of JOIN)
Should I do it "outside the DB": make 3 SELECTs, make some order in it (let's say python lists/dicts) and then do the additional SELECTs for userdata?
Solution for a similar question does not return rows where entries and expired are 0.
Thank you for your time.
This should work (assuming all creator_ids appear in the userdata table.
SELECT userdata.creator_id, COALESCE(entries_count_,0) AS entries_count, COALESCE(expired_count_,0) AS expired_count, COALESCE(other_count_,0) AS other_count
FROM userdata
LEFT OUTER JOIN
(SELECT creator_id, COUNT(entry) AS entries_count_
FROM entries
GROUP BY creator_id) AS entries_q
ON userdata.creator_id=entries_q.creator_id
LEFT OUTER JOIN
(SELECT creator_id, COUNT(entry) AS expired_count_
FROM entries
WHERE is_expired=0
GROUP BY creator_id) AS expired_q
ON userdata.creator_id=expired_q.creator_id
LEFT OUTER JOIN
(SELECT creator_id, COUNT(entry) AS other_count_
FROM other
GROUP BY creator_id) AS other_q
ON userdata.creator_id=other_q.creator_id;
Basicly, what you are doing looks correct to me.
I would rewrite it as follows though
SELECT entries.creator_id
, any_entry
, expired_entry
, other_entry
FROM (
SELECT creator_id, COUNT(entry) AS any_entry,
FROM entries
GROUP BY creator_id
) entries
LEFT OUTER JOIN (
SELECT creator_id, COUNT(entry) AS expired_entry,
FROM entries
WHERE is_expired = 1
GROUP BY creator_id
) expired ON expired.creator_id = entries.creator_id
LEFT OUTER JOIN (
SELECT creator_id, COUNT(entry) AS other_entry
FROM other
GROUP BY creator_id
) other ON other.creator_id = entries.creator_id
How about
SELECT creator_id,
(SELECT COUNT(*)
FROM entries e
WHERE e.creator_id = main.creator_id AND
e.is_expired = 0) AS entries,
(SELECT COUNT(*)
FROM entries e
WHERE e.creator_id = main.creator_id AND
e.is_expired = 1) as expired,
(SELECT COUNT(*)
FROM other
WHERE other.creator_id = main.creator_id) AS other,
FROM entries main
GROUP BY main.creator_id;