how can i achieve this mysql group query - mysql

I have two tables A and B.
My goal is to list every row from A, while attach the SUM of 'amount' from B.
like this:
SELECT a.name,
SUM(b.amount) as amount
FROM a
LEFT JOIN b ON a.id = b.a_id
GROUP BY a.id
No probleam until this, but i also need to check whether the B table's 'shop_id' matches a value, and if does, i want that single row's 'amount', and not the SUM of all groupped rows. I hope it is understandable.
Table A
id name
----------------
1 john
2 doe
3 smith
Table B
a_id amount shop
-----------------------
1 4 1
1 3 2
2 2 2
2 7 3
3 3 3
3 1 2
Desired result with 'shop'=1:
name amount shop
---------------------
john 4 1 //no SUM, only the value of amount where shop=1
doe 9 0 //sum(7,2) because shop is not 1
smith 4 0 //sum(3,1)
I was thinking of an if statement at the SUM() selection something similar to this, but the below statement returns not the desired groupped row value
SELECT a.name,
( CASE
WHEN b.shop <> 1 THEN Sum(b.amount)
ELSE b.amount
end ) AS amount,
( CASE
WHEN b.shop <> 1 THEN 0
ELSE b.shop
end ) AS shop
FROM a
LEFT JOIN b
ON a.id = b.a_id
GROUP BY a.id
Any ideas ? Is there a way to put this condition to the GROUP like: case shop<>1 THEN .... ELSE GROUP BY a.id ??

You will need to Left Join twice with the table b.
First Join will enable computation of Sum.
Second Join will join only when shop = 1. So if we get some non-null value due to second join, we will consider that, else the Sum
Try the following:
SELECT
a.id,
a.name,
COALESCE(MAX(b2.amount), SUM(b1.amount)) AS amount,
COALESCE(b2.shop, 0) AS shop
FROM a
LEFT JOIN b AS b1 ON a.id = b1.a_id
LEFT JOIN b AS b2 ON a.id = b2.a_id AND b2.shop = 1
GROUP BY a.id, a.name
Result
| id | name | amount | shop |
| --- | ----- | ------ | ---- |
| 1 | john | 4 | 1 |
| 2 | doe | 9 | 0 |
| 3 | smith | 4 | 0 |
View on DB Fiddle

You were on the right track to use what is called conditional aggregation. Here is a version which should work:
SELECT
a.name,
CASE WHEN MAX(CASE WHEN b.shop = 1 THEN 1 ELSE 0 END) > 0
THEN SUM(CASE WHEN b.shop = 1 THEN b.amount ELSE 0 END)
ELSE SUM(b.amount) END AS amount,
MAX(CASE WHEN b.shop = 1 THEN 1 ELSE 0 END) AS shop
FROM a
LEFT JOIN b
ON a.id = b.a_id
GROUP BY
a.name;
Demo

Related

count amount of transactions per type per ID in mysql

Having three tables
Users u
ID email
1 ..
2 ..
3 ..
4 ..
Transactions t
userID transactionID productID
1 1111 999
1 1112 897
2 1222 989
3 1333 989
4 1444 897
4 1114 897
Products p
productID type
999 Sports
989 Fashion
789 Fashion
897 Sports
Want to create a table in which I can find the total count of type per UserID. So ultimately I want to create the following
UserID Sports Fashion
1 1 1
2 0 1
3 0 1
4 2 0
Tried using the following code
SELECT u.email,
p.productID,
COUNT(CASE WHEN type = 'Sports' THEN 1 ELSE 0 END),
COUNT(CASE WHEN type = 'Fashion' THEN 1 ELSE 0 END)
from transactions t
LEFT JOIN users u ON u.ID = t.userID
LEFT JOIN products p ON t.productID = p.productID
group by u.email
Hence it does not work as hoped.
You can join and do conditional aggregation:
select
u.id,
sum(p.type = 'Sports') sports,
sum(p.type = 'Fashion') fashion
from users u
inner join transactions t on t.userid = u.id
inner join products p on p.id = t.productid
group by u.id
The problem with your original query is the use of count(): this aggregates function takes in account all non-null values: so both 1 and 0 are counted in. So you can either use a sum() (as shown above), or change the case expression to return null when there is no match, like so:
COUNT(CASE WHEN type = 'Sports' THEN 1 END),
COUNT(CASE WHEN type = 'Fashion' THEN 1 END)
Side note: presumably, you want inner join instead of left join. It does not look like your data has orphan records - and inner join is more efficient than left join.
Demo on DB Fiddle:
id | sports | fashion
-: | -----: | ------:
1 | 2 | 0
2 | 0 | 1
3 | 0 | 1
4 | 2 | 0

MySql query to find difference between same column with condition

I have this table register:
id quantity type
1 | 10 | in
2 | 5 | in
1 | 3 | out
1 | 2 | out
2 | 5 | out
3 | 2 | in
3 | 1 | out
I want the balance of each stock *sum of type='in' - sum of type= 'out'*.
Desired output would be:
1 | 5
2 | 0
3 | 1
I also have another table item:
id | name
1 | A
2 | B
3 | C
Is it possible to view the output with the item name instead of the id?
So the final result is like:
A | 5
B | 0
C | 1
The basic idea is conditional aggregation --case inside of sum(). You also need a join to get the name:
select i.name,
sum(case when r.type = 'in' then quantity
when r.type = 'out' then - quantity
else 0
end) as balance
from register r join
item i
on r.id = i.id
group by i.name;
Acccording to description as mentioned in above question,as a solution to it please try executing following SQL query
SELECT i.name,
#in_total:= (select sum(quantity) from register where type = 'in'
and id = r.id group by id),
#out_total:= (select sum(quantity) from register where type = 'out'
and id = r.id group by id),
#balance:= (#in_total - #out_total) as balance
FROM `register`
as r join item i on r.id = i.id group by r.id
CROSS JOIN (SELECT #in_total := 0,
#out_total := 0,
#balance := 0) AS user_init_vars

MySQL: Multiple Running Totals from Different Subqueries

When I run a single query using the following formula to have the first column give back the month/year, the second give back the number of people signing per month, and the third give back the running total of signers, it works great:
SET #runtot1:=0;
SELECT
1rt.MONTH,
1rt.1signed,
(#runtot1 := #runtot1 + 1rt.1signed) AS 1rt
FROM
(SELECT
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 791796 THEN s.id ELSE NULL END),0) AS 1signed
FROM table1 s
JOIN table2 m ON s.id = m.id AND m.current = "Yes"
WHERE STR_TO_DATE(s.datecontacted,'%m/%d/%Y') > '2015-03-01'
GROUP BY MONTH
ORDER BY MONTH) AS 1rt
With the query above, I get the following results table, which would be exactly what I want if I only needed to count one thing:
MONTH 1signed 1rt
2015-03 0 0
2015-04 1 1
2015-05 0 1
2015-08 1 2
2015-10 1 3
2015-11 1 4
2016-01 0 4
2016-02 0 4
But I can't figure out how to do that with multiple subqueries since I need this to happen for multiple columns at the same time. For example, I was attempting things like this (which doesn't work):
SET #runtot1:=0;
SET #runtot2:=0;
select
DATE_FORMAT(STR_TO_DATE(s1.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
t1.1signed,
(#runtot1 := #runtot1 + t1.1signed) AS 1rt,
t2.2signed,
(#runtot2 := #runtot2 + t2.2signed) AS 2rt
from
(select
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 791796 THEN s.id ELSE NULL END),0) AS 1signed
from table1 s
left join table2 m ON m.id = s.id
where m.current = "Yes"
GROUP BY MONTH
ORDER BY MONTH) as T1,
(select
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 846346 THEN s.id ELSE NULL END),0) AS 2signed
from table1 s
left join table2 m ON m.id = s.id
where m.current = "Yes"
GROUP BY MONTH
ORDER BY MONTH) as T2,
table1 s1
LEFT JOIN table2 m1 ON m1.id = s1.id AND m1.current = "Yes"
WHERE STR_TO_DATE(s1.datecontacted,'%m/%d/%Y') > '2015-03-01'
GROUP BY DATE_FORMAT(STR_TO_DATE(s1.datecontacted,'%m/%d/%Y'),'%Y-%m')
ORDER BY DATE_FORMAT(STR_TO_DATE(s1.datecontacted,'%m/%d/%Y'),'%Y-%m')
That blew up my results badly -- I also tried LEFT JOINs to get those two next each other, but that didn't work either.
Here's a SQL Fiddle with a few values with the query at the top that works, but not the query needed to look like the idea below.
If the multiple subquery version of the code worked, below would be the ideal end-result:
MONTH 1signed 1rt 2signed 2rt
2015-03 0 0 1 1
2015-04 1 1 0 1
2015-05 0 1 1 2
2015-08 1 2 0 2
2015-10 1 3 0 2
2015-11 1 4 0 2
2016-01 0 4 0 2
2016-02 0 4 1 3
Just trying to figure out a way to get counts by month and rolling totals since March 2015 for two different survey questions using the same query. Any help would be greatly appreciated!
Your attempt was actually pretty close. I just got rid of S1 and joined the two subqueries together on their MONTH columns:
SET #runtot1:=0;
SET #runtot2:=0;
select
T1.MONTH,
t1.1signed,
(#runtot1 := #runtot1 + t1.1signed) AS 1rt,
t2.2signed,
(#runtot2 := #runtot2 + t2.2signed) AS 2rt
from
(select
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 791796 THEN s.id ELSE NULL END),0) AS 1signed
from table1 s
left join table2 m ON m.id = s.id
where m.current = "Yes" and STR_TO_DATE(s.datecontacted,'%m/%d/%Y') > '2015-03-01'
GROUP BY MONTH
ORDER BY MONTH) as T1,
(select
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 846346 THEN s.id ELSE NULL END),0) AS 2signed
from table1 s
left join table2 m ON m.id = s.id
where m.current = "Yes" and STR_TO_DATE(s.datecontacted,'%m/%d/%Y') > '2015-03-01'
GROUP BY MONTH
ORDER BY MONTH) as T2
WHERE
T1.MONTH=T2.MONTH
GROUP BY T1.MONTH
ORDER BY T1.MONTH
I haven't tested Strawberry's solution, which looks more elegant. But I thought you'd like to know that your approach (solving the running totals individually, then joining the results together) would have worked too.
It seems that you're after something like this...
The data set:
DROP TABLE IF EXISTS table1;
CREATE TABLE table1
( id INT NOT NULL
, date_contacted DATE NOT NULL
, survey_id INT NOT NULL
, PRIMARY KEY(id,survey_id)
);
DROP TABLE IF EXISTS table2;
CREATE TABLE table2
(id INT NOT NULL PRIMARY KEY
,is_current TINYINT NOT NULL DEFAULT 0
);
INSERT INTO table1 VALUES
(1,"2015-03-05",846346),
(2,"2015-04-15",791796),
(2,"2015-05-04",846346),
(3,"2015-06-07",791796),
(3,"2015-06-08",846346),
(4,"2015-08-02",791796),
(5,"2015-10-15",791796),
(6,"2015-11-25",791796),
(6,"2016-01-02", 11235),
(6,"2016-02-06",846346);
INSERT INTO table2 (id,is_current) VALUES
(1,1),
(2,1),
(3,0),
(4,1),
(5,1),
(6,1);
The query:
SELECT x.*
, #a:=#a+a rt_a
, #b:=#b+b rt_b
FROM
( SELECT DATE_FORMAT(date_contacted,'%Y-%m') month
, SUM(survey_id = 791796) a
, SUM(survey_id = 846346) b
FROM table1 x
JOIN table2 y
ON y.id = x.id
WHERE y.is_current = 1
GROUP
BY month
) x
JOIN (SELECT #a:=0,#b:=0) vars
ORDER
BY month;
+---------+------+------+------+------+
| month | a | b | rt_a | rt_b |
+---------+------+------+------+------+
| 2015-03 | 0 | 1 | 0 | 1 |
| 2015-04 | 1 | 0 | 1 | 1 |
| 2015-05 | 0 | 1 | 1 | 2 |
| 2015-08 | 1 | 0 | 2 | 2 |
| 2015-10 | 1 | 0 | 3 | 2 |
| 2015-11 | 1 | 0 | 4 | 2 |
| 2016-01 | 0 | 0 | 4 | 2 |
| 2016-02 | 0 | 1 | 4 | 3 |
+---------+------+------+------+------+

sql query to show row data in columns

Table Structure
Table 1
-------------
code | name
-------------
1 | abc
2 | xyz
Table 2
-------------------------------
code | table1_code | data
-------------------------------
1 | 1 | a
2 | 1 | b
3 | 1 | c
4 | 1 | d
5 | 1 | e
6 | 2 | f
7 | 2 | g
Now Expected Result:
Result
table1_code | name | data_col1 | data_col2 | data_col3
------------------------------------------------------------------
1 abc a b c
2 xyz f g -
What i have tried so far
SELECT a.code AS table1_code,
a.NAME,
b.data_col
FROM table1 AS a
LEFT OUTER JOIN table2 AS b
ON a.code = b.table1_code
kindly help me to alter the query to get above output,
i want data upto 3 columns only.
To do this you need a way to determine what the first three items in each group should be. The way to do this is to add a row number that restarts for each new group. This would be easy in any database that supports window function, but MySQL doesn't so it gets a bit more complicated as you have to use user defined variables to do the ranking.
In the query below ranking of data is done in the derived table used as source and then a conditional aggregation is performed so that you get one row per group:
SELECT
code,
name,
MAX(CASE WHEN rank = 1 THEN data END) data_col1,
MAX(CASE WHEN rank = 2 THEN data END) data_col2,
MAX(CASE WHEN rank = 3 THEN data END) data_col3
FROM (
SELECT
a.code,
a.name,
b.data,
(
CASE a.name
WHEN #grp THEN #row := #row + 1
ELSE #row := 1 AND #grp := a.name
END
) + 1 AS rank
FROM table1 AS a
LEFT OUTER JOIN table2 AS b ON a.code = b.table1_code
, (SELECT #row := 0, #grp := '') r
ORDER BY a.code, a.name asc
) src
GROUP BY code, name;
Sample SQL Fiddle

mysql join show all records in left table with a flag for matching records in right table

I need a help from mySQL Join tables which should list all users in 'Table A' and also should show flag by matching records in 'Table B'
Table A (users)
=====================
id | name
=====================
1 | aaa
2 | bbb
3 | ccc
4 | ddd
5 | eee
Table B (users_likes)
=====================
like_by | like_to
=====================
1 | 2
1 | 3
2 | 3
4 | 1
5 | 1
if user 'aaa'(id:1) login to the system and performs a search to list all users except his details so the results will be
2 | bbb
3 | ccc
4 | ddd
5 | eee
also he needs to see a flag when listing which shows his 'like_by'
eg:
2 | bbb | (flag:TRUE)
3 | ccc | (flag:TRUE)
4 | ddd | flag:false)
5 | eee | (flag:false)
an easy solution for your problem is to use UNION and few SubQuery(although other solution may exist other than this)
SQLFiddle Demo
SELECT DISTINCT b.id, b.name,'TRUE' AS FLAG
FROM users_likes a
INNER JOIN users b
on a.like_to = b.id
WHERE like_by = 1
UNION
SELECT DISTINCT id, name,'FALSE' AS FLAG
FROM users
WHERE ID NOT IN
(
SELECT DISTINCT b.id
FROM users_likes a
INNER JOIN users b
on a.like_to = b.id
WHERE like_by = 1
)
AND id <> 1
or without using UNION and much simplier solution is the one below.
SQLFiddle Demo
SELECT a.id,
a.name,
IF(COALESCE(b.like_by, -1) = 1, 'TRUE', 'FALSE') AS `Flag`
FROM users a
LEFT JOIN users_Likes b
ON a.id = b.like_to
WHERE a.ID <> 1
GROUP BY a.id
Try this query
select
B.like_to,
BNames.name,
case when B.like_by = 1 then True else False end
from Table_A A
left outer join Table_B B on A.id = B.like_by
left outer join Table_A BNames on B.like_to = BNames.id
where B.like_to <> 1
hope it helps.
I think this is what you're after:
SELECT a.id,a.name,b.like_by IS NOT NULL
FROM users a
LEFT JOIN users_likes b ON (a.id = b.like_to)
GROUP BY a.id
This will give all items of 'a' with 1 where there is a like_to to that a and 0 where there isn't.
(You can add WHERE a.id != 1 to exclude user 1).
Edit:
Changing the third column to be true false based on user 1's likes. I originally thought it was any like.
SELECT a.id,a.username,b.like_by IS NOT NULL
FROM users a
LEFT JOIN user_likes b ON (a.id = b.like_to AND b.like_by = 1)
WHERE a.id != 1
GROUP BY a.id
http://sqlfiddle.com/#!2/ea34d/10