How to return row of sum()s - mysql

I now find my original table structure was not good, so want to change it.
But I am having a hard time designing queries to obtain totals in rows with the new structure.
current structure:
+----------+-------+-------+-------+-------+
| state | shop | item0 | item1 | item2 |
+----------+-------+-------+-------+-------+
| 5 | 0 | 1 | 2 | 3 |
| 5 | 1 | 1 | 2 | 3 |
| 5 | 2 | 1 | 2 | 3 |
| 4 | 3 | 1 | 2 | 3 |
+----------+-------+-------+-------+-------+
(quantities of items at shop)
I want to change to these 2 tables:
shops table
+---------+--------+
| shop_id | state |
+---------+--------+
| 0 | 5 |
| 1 | 5 |
| 2 | 5 |
| 3 | 4 |
+---------+--------+
items table
+------------+--------------+
| shop | item | quantity |
+------------+--------------+
| 0 | 0 | 1 |
| 0 | 1 | 2 |
| 0 | 2 | 3 |
| 1 | 0 | 1 |
| 1 | 1 | 2 |
| 1 | 2 | 3 |
| 2 | 0 | 1 |
| 2 | 1 | 2 |
| 2 | 2 | 3 |
| 3 | 0 | 1 |
| 3 | 1 | 2 |
| 3 | 2 | 3 |
+------------+--------------+
The old layout allowed simple queries for getting totals by row:
SELECT state,SUM(item0) t0,SUM(item1) t1,SUM(item2) t2
FROM shops
WHERE state=5
+--------+---------+---------+----------+
| state | t0 | t1 | t2 |
+--------+---------+---------+----------+
| 5 | 3 | 6 | 9 |
+--------+---------+---------+----------+
With the new structure,
I can get the totals in column as follows:
SELECT item,SUM(quantity) total
FROM shops
LEFT JOIN items ON shop=shopid
WHERE state=5
GROUP by item
+--------+---------+
| item | total |
+--------+---------+
| 0 | 3 |
+--------+---------+
| 1 | 6 |
+--------+---------+
| 2 | 9 |
+--------+---------+
but how do I get the totals in rows:
+--------+---------+---------+----------+
| state | t0 | t1 | t2 |
+--------+---------+---------+----------+
| 4 | 1 | 2 | 3 |
| 5 | 3 | 6 | 9 |
+--------+---------+---------+----------+

You might try using a few more JOINs:
SELECT S.state,
SUM(T0.quantity) AS "T0",
SUM(T1.quantity) AS "T1",
SUM(T2.quantity) AS "T2"
FROM shops AS S
LEFT JOIN items AS T0 ON S.shop_id = T0.shop_id AND T0.item=0
LEFT JOIN items AS T1 ON S.shop_id = T1.shop_id AND T1.item=1
LEFT JOIN items AS T2 ON S.shop_id = T2.shop_id AND T2.item=2
GROUP BY S.state
There might be an easier way.

Related

sql count() get wrong result

I have a table like this structure:
| event id | item 1 id | item 2 id | set |
| 1 | 1 | 2 | 1 |
| 1 | 1 | 3 | 1 |
| 1 | 2 | 1 | 1 |
| 1 | 2 | 3 | 1 |
| 1 | 3 | 1 | 1 |
| 1 | 3 | 2 | 1 |
| 1 | 2 | 4 | 2 |
| 1 | 4 | 2 | 2 |
| 2 | 1 | 4 | 3 |
| 2 | 1 | 5 | 3 |
| 2 | 4 | 1 | 3 |
| 2 | 4 | 5 | 3 |
| 2 | 5 | 1 | 3 |
| 2 | 5 | 4 | 3 |
now I wish to count the occurrence of item1 as well as item1 combined with item2 separately
I tried following:
with count_item1 AS (
select event_id, item_1_id, count(distinct set) AS c1 from table
group by event_id, item_1_id
), count_item1_and_item2 AS (
select event_id, item_1_id, item_2_id, count(distinct set) AS c2 from table
group by event_id, item_1_id, item_2_id
)
select t1.event_id, t1.item_1_id, t1.item_2_id, t1.c2, t2.c1
from count_item1_and_item2 AS t1
inner join count_item1 AS t2
on t1.event_id=t2.event_id and t1.item_1_id=t2.item_1_id
for example as table above
so the result for this should be:
| event id | item 1 id | item 2 id | c1 | c2 |
| 1 | 1 | 2 | 1 | 1 |
| 1 | 1 | 3 | 1 | 1 |
| 1 | 2 | 1 | 2 | 1 |
| 1 | 2 | 3 | 2 | 1 |
| 1 | 3 | 1 | 1 | 1 |
| 1 | 3 | 2 | 1 | 1 |
| 1 | 2 | 4 | 2 | 1 |
| 1 | 4 | 2 | 1 | 1 |
| 2 | 1 | 4 | 1 | 1 |
| 2 | 1 | 5 | 1 | 1 |
| 2 | 4 | 1 | 1 | 1 |
| 2 | 4 | 5 | 1 | 1 |
| 2 | 5 | 1 | 1 | 1 |
| 2 | 5 | 4 | 1 | 1 |
the meaning of each row is: in event_id, item 1 appeared c1 in distinct set and (item1, item2) appeared c2 in distinct set.
also count of (item1, item2) is equal to (item2, item1)
then I find a strange thing:
the occurrence of item1 itself should not less than the occurrence of item1 combined with item2, but I find that sometimes item1 combined with item2 has more count than count of item1, is there anything I did wrong here? I think my idea is right but I didn't get the result. I have being stacked in here for a weekend.

Mysql Best way to query notifications like on stackoverflow

I have 4 tables:
comments
+----+-----------+--------------+-------+
| id | content | user_id | article_id |
+----+-----------+--------------+-------+
| 1 | Comment 1 | 2 | 5 |
| 2 | Comment 2 | 5 | 3 |
| 3 | Comment 3 | 1 | 6 |
| 4 | Comment 4 | 6 | 8 |
| 5 | Comment 5 | 1 | 6 |
| ...| ... | ... | ... |
+----------------+---------+------------+
votes
+----+----------+--------------+---+
| id | type | user_id | article_id |
+----+----------+--------------+---+
| 1 | 1 | 2 | 5 |
| 2 | 1 | 3 | 3 |
| 3 | 0 | 1 | 6 |
| 4 | 1 | 7 | 4 |
| 5 | 0 | 9 | 4 |
| 6 | 0 | 1 | 6 |
| ...| ... | ... | ... |
+------------+----------+----------+
notifications (object_id is the id of the vote|comment)
+----+----------+--------------+-------------+-------------+--------------+
| id | object_url| object_id |activitytype_id| sender_id | recipient_id |
+----+----------+------------+---------------+-------------+--------------+
| 1 | /../../.. | 1 | 2 | 2 | 6 |
| 2 | /../../.. | 2 | 2 | 3 | 2 |
| 3 | /../../.. | 1 | 1 | 2 | 7 |
| 3 | /../../.. | 2 | 1 | 5 | 2 |
| 3 | /../../.. | 3 | 1 | 1 | 3 |
| 3 | /../../.. | 3 | 3 | 1 | 2 |
| 3 | /../../.. | 4 | 2 | 7 | 8 |
| 3 | /../../.. | 5 | 3 | 9 | 1 |
| 3 | /../../.. | 6 | 3 | 1 | 5 |
| ...| ... | ... | ... | | |
+----+-----------+-----------+---------------+-------------+--------------+
activitytypes
+----+------------+
| id | label |
+----+------------+
| 1 | comment |
| 2 | vote up |
| 3 | vote down |
| ...| ... |
+-----------------+
I would like to get notifications like on stackoverflow.
I want to query the last notification (with comment content if the activity type is a comment or null if not) for every activitytype and object_url combinaison for a specific user.
For example I have 3 artiles A,B and C which all have 3 comments, 4 voteup and 2 votedown. How to get the last comment, voteup and votedown for every article ?
I have tried this query:
SELECT n.id, n.object_url , n.object_id, n.activitytype_id, IF(n.activitytypeId = 1,
(SELECT content FROM comments WHERE id=n.object_id), null) AS activitycontent
FROM notifications n WHERE n.recipient_id =1
GROUP BY n.activitytype_id,n.object_url
ORDER BY n.id DESC
But it doesn't work. Can anyone help ?
EDIT:
This following query in farhadamjady's answer gives me the first comment:
SELECT
n.id,
n.object_url,
n.object_id,
n.activitytype_id,
cm.content AS activitycontent
FROM
notifications n
LEFT OUTER JOIN `COMMENT` AS cm ON cm.id = n.object_id and n.activitytypeId = 1
WHERE
n.recipient_id = 1
GROUP BY
n.activitytype_id,
n.object_url
HAVING MAX(cm.id)
ORDER BY
n.id DESC
How can I change it to get the last ?
you should use left outer join like this :
SELECT
n.id,
n.object_url,
n.object_id,
n.activitytype_id,
cm.content AS activitycontent
FROM
notifications n
LEFT OUTER JOIN `COMMENT` AS cm ON cm.id = n.object_id and n.activitytypeId = 1
WHERE
n.recipient_id = 1
GROUP BY
n.activitytype_id,
n.object_url
HAVING MAX(cm.id)
ORDER BY
n.id DESC

Get rows that doesn't match on another table

I have this working query that generate data the way i like it to happened but now i want it to add another clause statement that will get rows from tbl_loan_master that was not on the tbl_loanledger.
Example Data:
tbl_borrowers
------------------------------------------
| id | first_name | last_name | deleted |
| 1 | Joe | Smith | 0 |
| 2 | Lily | Mag | 0 |
| 3 | Zen | Green | 0 |
| 4 | Kim | Chan | 0 |
| 5 | Bob | Mac | 1 |
| 6 | Ben | Cork | 0 |
------------------------------------------
tbl_loan_master
----------------------------------------------------------------------
| id | borrowers_id | loan | date_created | due_date | deleted |
| 1 | 4 | 300 | 2016/04/28 | 2017/04/28 | 0 |
| 2 | 1 | 100 | 2016/05/05 | 2017/05/05 | 0 |
| 3 | 2 | 500 | 2016/06/08 | 2017/06/08 | 0 |
| 4 | 1 | 200 | 2016/06/13 | 2017/06/13 | 0 |
| 5 | 3 | 150 | 2016/06/15 | 2017/06/15 | 0 |
| 6 | 6 | 50 | 2016/06/16 | 2017/06/16 | 0 |
----------------------------------------------------------------------
tbl_loanledger
------------------------------------------------------------------------------
| id | borrowers_id | loanmaster_id | payment | balance| date_created | deleted
| 1 | 4 | 1 | 50 | 250 | 2016/05/28 | 0
| 2 | 1 | 2 | 20 | 80 | 2016/05/25 | 0
| 3 | 1 | 2 | 30 | 50 | 2016/06/01 | 0
| 4 | 2 | 3 | 100 | 400 | 2016/06/09 | 0
| 5 | 2 | 3 | 50 | 350 | 2016/06/10 | 0
| 6 | 3 | 4 | 50 | 150 | 2016/06/16 | 0
------------------------------------------------------------------------------
Here is the working query:
$query = "SELECT `tbl_borrowers`.* , `tbl_loanledger`.*, `tbl_loan_master`.*
FROM `tbl_borrowers`
LEFT JOIN `tbl_loanledger`
ON `tbl_borrowers`.id = `tbl_loanledger`.borrower_id
LEFT JOIN `tbl_loan_master`
ON `tbl_loan_master`.id = `tbl_loanledger`.loanmaster_id
WHERE `tbl_borrowers`.deleted = 0 AND `tbl_loanledger`.deleted = 0 AND MONTH ( `tbl_loanledger`.date_created) = MONTH(CURRENT_DATE)
GROUP BY `tbl_loanledger`.borrower_id
ORDER BY `tbl_borrowers`.last_name";
The expected result will output the borrowers with their last transaction on the current month (which is June) in loan ledger and also in loans accounts. Like Ben Cork which is not on the loan ledger, he is on the loan accounts but i want to output him on the result set. The deleted column means if it is 0 it indicates it is active if it is 0 it means it has been deleted.
Expected Result:
|First Name | Last Name | Due Date | Balance |
| Ben | Cork | 2017/06/16 | 50 |
| Joe | Smith | 2017/06/13 | 50 |
| Lily | Mag | 2017/06/08 | 350 |
| Zen | Green | 2017/06/15 | 150 |
Try following;)
select
tb.first_name, tb.last_name, coalesce(tlm.Loan, 0) as Loan, coalesce(t.`Amount Paid`, 0) as `Last Amount Paid`
from tbl_borrowers tb
left join tbl_loan_master tlm
on tb.id = tlm.borrowers_id
left join (
select t1.*
from tbl_loanledger t1
inner join (
select max(id) as id
from tbl_loanledger
group by borrowers_id, loanmaster_id
) t2 on t1.id = t2.id
) t
on tb.id = t.borrowers_id
and tlm.id = t.loanmaster_id
SQLFiddle DEMO HERE

select data from one table based on the data status on other table

My first data table is couponsnmaster
+-----------+----------+------------+
|couponsnid | couponid | couponsn |
+-----------+----------+------------+
| 1 | 1 | 1000 |
| 2 | 1 | 1001 |
| 3 | 1 | 1002 |
| 4 | 1 | 1003 |
| 5 | 1 | 1004 |
| 6 | 1 | 1005 |
+-----------+----------+------------+
My second data table is distribute
+-----------+--------------+--------------+--------------+
| distid | couponid | couponsnid | status |
+-----------+--------------+--------------+--------------+
| 1 | 1 | 1 | distribute |
| 2 | 1 | 2 | distribute |
| 3 | 1 | 3 | distribute |
| 4 | 1 | 1 | returned |
+-----------+--------------+--------------+--------------+
I want to fetch all "couponsn" from "couponsnmaster" with respect to "couponid" except status is " distribute" or "sold" or "bonus" in table "distribute"....
Try this query:
SELECT c.couponsn FROM
couponsmaster c INNER JOIN distribute d
ON c.couponsid = d.couponsid
WHERE d.status NOT IN('distribute','sold','bonus')

select sorted tree and subtrees with materialized path

i want select tree from
id | parent_id | path | order
---------------------------------------
1 | 0 | /1/ | 1
2 | 1 | /1/2/ | 1
3 | 2 | /1/2/3/ | 1
4 | 1 | /1/4/ | 2
5 | 4 | /1/4/5/ | 2
6 | 4 | /1/4/6/ | 1
like this
| 1 | | |
| | 2 | |
| | | 3 |
| | 4 | |
| | | 6 |
| | | 5 |
in one query.
Something like
select * from tbl ORDER BY COALESCE(length(path),0), order asc
but this select sorted groups of each tree level
1
-2
-4
--3
--6
--5
thanks