Conditional Inner Join using CASE - mysql

I wanted to count and sum all of bits column in my table if the sum of it is greater than 2 the isApprove column in my documents table will be 1.
SELECT document_id,
SUM(CASE WHEN isApprove = 1 THEN 1 ELSE 0 END) AS approveBy
FROM approvals_document
Output:
document_id | approveBy
96 2
I wanted to join the related table for approvals_document which is documents using this.
SELECT document_id, D.isApprove,
SUM(CASE WHEN isApprove = 1 THEN 1 ELSE 0 END) AS approveBy
FROM approvals_document AD
INNER JOIN documents D ON D.id = AD.document_id
But it won't join the isApprove column. Which part am I missing? Any help would appreciated!
approvals_document column
id | document_id | isApprove
documents column
id | isApprove
Draft output:
document_id | approveBy | isApprove(documents table)
96 2 0

Youre missing an equals in the join clause, in the ON, and you need to alias the case:
SELECT document_id, D.isApprove,
SUM(CASE WHEN AD.isApprove = 1 THEN 1 ELSE 0 END) AS approveBy
FROM approvals_document AD
INNER JOIN documents D ON D.id = AD.document_id

Try with the below query using LEFT JOIN AND GROUP BY.
SELECT d.id,
d.isApprove,
SUM(CASE WHEN ad.document_id IS NULL THEN 0 ELSE 1 END) AS approveBy
FROM documents d
LEFT JOIN approvals_document ad
ON ad.isApprove =1 and ad.document_id = d.id
GROUP BY d.id,d.isApprove

You are trying to list all documents and number of approvedBy:
try:
SELECT documents.id,
documents.isApprove,
(select count(*) from approvals_document WHERE approvals_document.isApprove =1 and approvals_document.document_id = documents.id) as approveBy
FROM documents;
will give you
+------------+--------------------+--------------------------+
| documet.id | isapproved or not, | number of times approved |
+------------+--------------------+--------------------------+
| 1 | 1 | 2 |
+------------+--------------------+--------------------------+

Related

Sum hours value, count and display based on hours using SQL

I have 2 tables which are Teacher and Activities.
CREATE TABLE teacher (
TeacherId INT, BranchId VARCHAR(5));
INSERT INTO teacher VALUES
("1121","A"),
("1132","A"),
("1141","A"),
("2120","B"),
("2122","B");
CREATE TABLE activities (
ID INT, TeacherID INT, Hours INT);
INSERT INTO activities VALUES
(1,1121,2),
(2,1121,1),
(3,1132,1),
(4,1141,NULL),
(5,2120,NULL),
(6,2122,NULL);
NULL indicates no activities and will be convert to 0 on output table. I want to produce a query to count total of hours and count how many activities base on teacher hours such as the following table:
+-----------+------------+------------+
| Hours | A | B |
+-----------+------------+------------+
| 0 | 1 | 2 |
| 1 | 1 | 0 |
| 2 | 0 | 0 |
| 3 | 1 | 0 |
+-----------+------------+------------+
Edited: Sorry I don't know how to elaborate accurately, but here is the fiddle i received from other member https://www.db-fiddle.com/f/mmtuZquKyUqdhPvTFN9qaF/1
Edit: Last, modification need, to sum the hours and count the hours base on branch id and teacher id as the output.
Expected output here (red text): https://drive.google.com/file/d/1wyZ_aX5hz_7I1Ncf5sXLpstYk6FT8PMg/view?usp=sharing
We can handle this via the use of a calendar table of hours joined to an aggregation subquery:
SELECT
t1.Hours,
SUM(CASE WHEN t2.BranchId = 'A' THEN t2.cnt ELSE 0 END) AS A,
SUM(CASE WHEN t2.BranchId = 'B' THEN t2.cnt ELSE 0 END) AS B
FROM (SELECT 0 AS Hours UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) t1
LEFT JOIN
(
SELECT t.BranchId, COALESCE(a.Hours, 0) AS Hours, COUNT(*) AS cnt
FROM Teacher t
LEFT JOIN Activities a ON a.TeacherId = t.TeacherId
GROUP BY t.BranchId, COALESCE(a.Hours, 0)
) t2
ON t1.Hours = t2.Hours
GROUP BY
t1.Hours
ORDER BY
t1.Hours
Demo
This is basically a JOIN and aggregation . . . but you need to start with all the hours you want:
SELECT h.Hours,
COALESCE(SUM(t.BranchId = 'A'), 0) AS A,
COALESCE(SUM(t.BranchId = 'B'), 0) AS B
FROM (SELECT 0 AS Hours UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
) h LEFT JOIN
activities a
ON h.hours = COALESCE(a.hours, 0) LEFT JOIN
teacher t
ON t.TeacherId = a.TeacherId
GROUP BY h.Hours
ORDER BY h.Hours;
Here is a db<>fiddle.

Grouping data into ranges

I have a query that returns the counts from a database. Sample output of the query:
23
14
94
42
23
12
The query:
SELECT COUNT(*)
FROM `submissions`
INNER JOIN `events`
ON `submissions`.event_id = `events`.id
WHERE events.user_id IN (
SELECT id
FROM `users`
WHERE users.created_at IS NOT NULL
GROUP BY `events`.id
Is there a way to easily take the output and split it into pre-defined ranges of values (0-100, 101-200, etc), indicating the number of rows that fall into a particular range?
Use a case expression in select clause.
SELECT `events`.id ,
case when COUNT(`events`.id) between 0 and 100 then '0 - 100'
when COUNT(`events`.id) between 100 and 200 then '100 - 200'
end as Range
FROM `submissions`
INNER JOIN `events`
ON `submissions`.event_id = `events`.id
WHERE events.user_id IN (
SELECT id
FROM `users`
WHERE users.created_at IS NOT NULL
GROUP BY `events`.id
Use conditional count by leveraging SUM() aggregate.
If you need your ranges in columns
SELECT SUM(CASE WHEN n BETWEEN( 0 AND 100) THEN 1 ELSE 0 END) '0-100',
SUM(CASE WHEN n BETWEEN(101 AND 200) THEN 1 ELSE 0 END) '101-200'
-- , add other ranges here
FROM (
SELECT COUNT(*) n
FROM submissions s JOIN events e
ON s.event_id = e.id JOIN users u
ON e.user_id = u.id
WHERE u.created_at IS NOT NULL
GROUP BY e.id
) q
Sample output
+-------+---------+
| 0-100 | 101-200 |
+-------+---------+
| 2 | 3 |
+-------+---------+
1 row in set (0.01 sec)
If you'd rather have it as a set you can do
SELECT CONCAT(r.min, '-', r.max) `range`,
SUM(n BETWEEN r.min AND r.max) count
FROM (
SELECT COUNT(*) n
FROM submissions s JOIN events e
ON s.event_id = e.id JOIN users u
ON e.user_id = u.id
WHERE u.created_at IS NOT NULL
GROUP BY e.id
) q CROSS JOIN (
SELECT 0 min, 100 max
UNION ALL
SELECT 101, 200
-- add other ranges here
) r
GROUP BY r.min, r.max
Sample output
+---------+-------+
| range | count |
+---------+-------+
| 0-100 | 2 |
| 101-200 | 3 |
+---------+-------+
2 rows in set (0.01 sec)

SQL Counting rows with columns that satisfy a condition

Suppose I have a table with columns ID and Content populated with data.
ID | Content
1 | a
1 | b
1 | c
2 | b
2 | a
3 | b
I want to find every ID that has at least one of each 'a', 'b', and 'c' so the returned table would be :
ID | Content
1 | a
1 | b
1 | c
Using conditional SUM validate if inside the group id exist 1 or more of each element.
Then select DISTINCT ID, Content to eliminate possible duplicates
SELECT DISTINCT ID, Content
From YourTable
WHERE ID IN (SELECT ID
FROM YourTable
GROUP BY ID
HAVING SUM(case when Content = 'a' then 1 else 0 end) >= 1
AND SUM(case when Content = 'b' then 1 else 0 end) >= 1
AND SUM(case when Content = 'c' then 1 else 0 end) >= 1
)
select * from (
where
Select ID ,SUM(case when Content = 'a' then 1 else 0 end) as sum_a ,UM(case when Content = 'b' then 1 else 0 end) as sum_b,
SUM(case when Content = 'c' then 1 else 0 end) as sum_c
FROM table
Group by ID)x
where x.sum_a >1 or sum_b>1 or sum_c>1
Here is another approach that checks for a certain number of distinct "content" items per ID. Here is a link to an SQL Fiddle for this solution.
select
id,
count(distinct(content))
from tableX
group by id
having count(distinct(content)) = 3;

Count value variation from 0 to 1 in mysql table

I have a column with two columns. one is TIMESTAMP and the other DIGITAL_BIT.
The value digital bit can be either 0 or 1 and changes a few times during the day. Every minute of the day is stored in this table. I would need to read somehow how many times a day this value changed from 0 to 1.
Is it possible to make a query that returns the count of this changes? What I have in mind is something like this:
select * from mytable where digital_bit = 1 and digital_bit (of previous row) = 0 order by timestamp
Can this be done with a query or do i have to process all data in my program?
Thanks
SAMPLE
timestamp | digital_bit
100000 | 0
100001 | 0
100002 | 1
100003 | 1
100004 | 0
100005 | 1
100006 | 0
100007 | 0
100008 | 1
the above should return 3 because for 3 times the value digital passed from 0 to 1. i need to count how often the value digital CHANGES from 0 to 1.
Here you go. This will get you a count of how many times digital_bit switched from 0 to 1 (in your example, this will return 3).
SELECT COUNT(*)
FROM mytable curr
WHERE curr.digital_bit = 1
AND (
SELECT digital_bit
FROM mytable prev
WHERE prev.timestamp < curr.timestamp
ORDER BY timestamp DESC
LIMIT 1
) = 0
SQLFiddle link
(Original answer relied on the timestamps being sequential: e.g. no jumps from 100001 to 100003. Answer has now been updated not to have that restriction.)
IF you have a result once per minte, you can simple join the table with itself, and
use timestamp+1 as well as leftbit != rightbit as join condition.
http://sqlfiddle.com/#!8/791c0/6
ALL Changes:
SELECT
COUNT(*)
FROM
test a
INNER JOIN
test b
ON
a.digital_bit != b.digital_bit
AND b.timestamp = a.timestamp+1;
Changes from 0 to 1
SELECT
COUNT(*)
FROM
test a
INNER JOIN
test b
ON
a.digital_bit = 0 AND
a.digital_bit != b.digital_bit
AND b.timestamp = a.timestamp+1;
Changes from 1 to 0
SELECT
COUNT(*)
FROM
test a
INNER JOIN
test b
ON
a.digital_bit = 1 AND
a.digital_bit != b.digital_bit
AND b.timestamp = a.timestamp+1;
Adapted from: How do I query distinct values within multiple sub record sets
select count(*)
from (select t1.*,
(select digital_bit
from table t2
where t2.timestamp < t1.timestamp
order by timestamp desc LIMIT 1
) as prevvalue
from table t1
) t1
where prevvalue <> digital_bit and digital_bit = 1;
This isn't likely to be efficient with a lot of data, but you can get all the rows and calculate a sequence number for them, then do the same again but with the sequence number offset by 1. Then join the 2 lots together where those calculated sequence numbers match but the first one has a digital bit of 0 and the other a digital bit of 1:-
SELECT COUNT(*)
FROM
(
SELECT mytable.timestamp, mytable.digital_bit, #aCount1:=#aCount1+1 AS SeqCount
FROM mytable
CROSS JOIN (SELECT #aCount1:=1) sub1
ORDER BY timestamp
) a
INNER JOIN
(
SELECT mytable.timestamp, mytable.digital_bit, #aCount2:=#aCount2+1 AS SeqCount
FROM mytable
CROSS JOIN (SELECT #aCount2:=0) sub1
ORDER BY timestamp
) b
ON a.SeqCount = b.SeqCount
AND a.digital_bit = 0
AND b.digital_bit = 1
EDIT - alternative solution and I would be interested to see how this performs. It avoids the need for adding a sequence number and also avoids a correlated sub query:-
SELECT COUNT(*)
FROM
(
SELECT curr.timestamp, MAX(curr2.timestamp) AS MaxTimeStamp
FROM mytable curr
INNER JOIN mytable curr2
ON curr.timestamp > curr2.timestamp
AND curr.digital_bit = 1
GROUP BY curr.timestamp
) Sub1
INNER JOIN mytable curr
ON Sub1.MaxTimeStamp = curr.timestamp
AND curr.digital_bit = 0
As I understood you have one query every minute. So you have no problem with performance.
You can add flag:
timestamp | digital_bit | changed
100000 | 0 | 0
100001 | 0 | 0
100002 | 1 | 1
100003 | 1 | 0
100004 | 0 | 1
100005 | 1 | 1
100006 | 0 | 1
100007 | 0 | 0
100008 | 1 | 1
And make check before insert:
SELECT digital_bit
FROM table
ORDER BY timestamp DESC
LIMIT 1
and if digital_bit is different insert new row with flag.
And then you just can take COUNT of flags:
SELECT COUNT(*)
FROM table
WHERE DATE BETWEEN (start, end)
AND changed = 1
Hope will see in answers better solution.

Return Yes if only one record is found

I have a table that has this structure:
table
id | site_id
-------------------
240 | 1
240 | 2
240 | 3
320 | 1
320 | 2
421 | 1
520 | 2
-------------------
300k records
Now i am trying to write a query to return a yes or a no for each record (id).
For example if the records with id 240 only have a site_id 1 then return 'Yes', if it has 2, 3 and so on return 'No'
I am not sure how to approach it but here is a result sample:
result_table
.-----------------------.
| id | result |
|-----------------------|
| 240 | No | -- has a site_id 1, 2 and 3
| 320 | No | -- has a site_id 1 and 2
| 421 | Yes | -- has a site_id 1 only
| 520 | No | -- has a site_id 2
'-----------------------'
Here is the query i have so far, but it seems to be incorrect
SELECT CASE WHEN count(id) > 1 THEN 'N' ELSE 'Y' END as Result
FROM table sm
WHERE sm.site_id IN (1)
AND sm.site_id NOT IN (2,3,4,5,6,7,8,9)
AND id = 240
UPDATE
SO Here is my full query, i added the answer from #gordon
SELECT
m.merchant_name,
m.merchant_id,
ut.realname,
a.affiliate_name,
(select (case when count(site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
group by merchant_id) as isTjoos, -- y or no
(select (case when count(site_id) = 2 then 'Yes' else 'No' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
group by merchant_id) as isUCOnly
-- isdlkonly -- y or no
FROM merchant m
LEFT JOIN merchant_editor_assignment mea ON mea.merchant_id = m.merchant_id
LEFT JOIN user_table ut ON ut.user_id = mea.user_id
LEFT JOIN affiliate a ON a.affiliate_id = m.affiliate_id_default
I interpreted this question as you want ids that have only one value for site_id. I took the example in the question to be an example, with site_id = 1. To do this:
You want to use count(distinct):
select id, (case when count(distinct site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
A slightly more efficient version is to use min() and max(), assuming that the site_id is never NULL:
select id, (case when min(site_id) = max(site_id) then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
This is because min and max generally require a bit less processing than count(distinct).
If you want to check that the site_id is "1" and never anything else, then add the condition and min(site_id) = 1 to the when clause.
If you want to check that the site_id is 1 and there is exactly one row, then you can do:
select id, (case when count(site_id) = 1 and min(site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
And, if you want to check that there is exactly one row:
select id, (case when count(site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
SELECT
it,
CASE WHEN COUNT(CASE WHEN site_id THEN 1 END)=1
AND COUNT(CASE WHEN site_id!=1 THEN 1 END)=0 THEN 'Yes'
ELSE 'No'
END
FROM sm
GROUP BY it
Please see fiddle here.
SELECT ID, CASE WHEN EXTRA > 1 THEN 'No' ELSE 'Yes' END AS Result
FROM
(SELECT ID, Sum(site_id) AS Extra
from myTable
GROUP BY ID
) AS Test
EDIT: I suppose this should work in MySQL. I haven't worked on it though.
The idea is to SUM up the site_id. For records with only site_id = 1, the sum will be 1.
Your query seems overcomplicated. Just to start, why the IN(1) and NOT IN(2,3...9)? And why limit to a single ID (AND id = 240) when your "result sample" clearly doesn't want that? It does not make any sense. How about this?
SELECT CASE WHEN count(merchant_id) > 1 THEN 'N' ELSE 'Y' END as isTjoos
FROM site_merchant
GROUP BY site_id;
I would use a Having Count Statement. Something like that:
SELECT site_id
FROM site_merchant
HAVING (count(merchant_id) > 1)
GROUP BY site_id;
Here is the solution i found. I used #Gordons query to get started and what was missing was the site_id, and the group by was not needed:
SELECT
m.merchant_name,
m.merchant_id,
ut.realname,
a.affiliate_name,
(select (case when count(*)>0 then 'No' else 'Yes' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
AND site_id != 1) as isTjoos,
(select (case when count(*)> 0 then 'No' else 'Yes' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
AND site_id != 2) as isUCOnly,
(select (case when count(*)> 0 then 'No' else 'Yes' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
AND site_id != 3) as isDLKonly
FROM merchant m
LEFT JOIN merchant_editor_assignment mea ON mea.merchant_id = m.merchant_id
LEFT JOIN user_table ut ON ut.user_id = mea.user_id
LEFT JOIN affiliate a ON a.affiliate_id = m.affiliate_id_default
Thank you for the help.