MySQL Crosstab aggregation with 2 conditions - mysql

I have a query which creates a crosstab. The results are a count of the txn_id for branda, and the count of txn_id for brandb.
The txn_id is NOT UNIQUE. This is an example of the transactions table.:
txn_id | nationality_id | sku | sales | units
1 | 1 | 1 | 20 | 2
1 | 1 | 2 | 15 | 1
2 | 4 | 1 | 20 | 2
3 | 2 | 1 | 10 | 1
4 | 3 | 2 | 15 | 1
5 | 4 | 1 | 10 | 1
There are 2 other tables (products) - (sku, brand, product name), and (nationalities) - (nationality_id, nationality).
I would like to add a third column which gets me the count of txn_id where BOTH brands are purchased
The output should be
nationality | branda | brandb | combined
1 | 1 | 1 | 1
2 | 1 | 0 | 0
3 | 0 | 1 | 0
4 | 2 | 0 | 0
Current query.
SELECT
nationalities.nationality,
COUNT((CASE brand WHEN 'branda' THEN txn_id ELSE NULL END)) AS branda,
COUNT((CASE brand WHEN 'brandb' THEN txn_id ELSE NULL END)) AS brandb
<I want my 3rd column here>
FROM
transaction_data
INNER JOIN
products USING (sku)
INNER JOIN
nationalities USING (nationality_id)
GROUP BY nationality
ORDER BY branda DESC
LIMIT 20;
I have tried using:
COUNT((CASE brand WHEN 'brandb' OR 'brandb' THEN txn_id ELSE NULL END)) AS combined - however this obviously returns too many (returns branda or brandb regardless of whether they were purchased together). I know I can't use AND, because obviously no single cell is going to be both branda AND brandb.
I have also tried using:
COUNT((CASE brand WHEN IN('branda', 'brandb') THEN txn_id ELSE NULL END)) AS combined - However this isn't valid syntax.
I feel that I should be using a HAVING clause, but I'm not sure how this would work in the column list.

I think you are going to need two levels of aggregation:
SELECT n.nationality,
sum(branda), sum(brandb), sum(branda * brandb)
FROM (SELECT t.txn_id, n.nationality,
MAX(CASE brand WHEN 'branda' THEN 1 ELSE 0 END) AS branda,
MAX(CASE brand WHEN 'brandb' THEN 1 ELSE 0 END) AS brandb
FROM transaction_data t INNER JOIN
products p
USING (sku) INNER JOIN
nationalities n
USING (nationality_id)
GROUP BY t.txn_id, n.nationality
) tn
GROUP BY n.nationality
ORDER BY max(txn_id) DESC
LIMIT 20;

Related

Join two table and count, avoid zero if record is not available in second table

I have following tables products and tests.
select id,pname from products;
+----+---------+
| id | pname |
+----+---------+
| 1 | prd1 |
| 2 | prd2 |
| 3 | prd3 |
| 4 | prd4 |
+----+---------+
select pname,testrunid,testresult,time from tests;
+--------+-----------+------------+-------------+
| pname | testrunid | testresult | time |
+--------+-----------+------------+-------------+
| prd1 | 800 | PASS | 2017-10-02 |
| prd1 | 801 | FAIL | 2017-10-16 |
| prd1 | 802 | PASS | 2017-10-02 |
| prd1 | 803 | NULL | 2017-10-16 |
| prd1 | 804 | PASS | 2017-10-16 |
| prd1 | 805 | PASS | 2017-10-16 |
| prd1 | 806 | PASS | 2017-10-16 |
+--------+-----------+------------+-------------+
I like to count test results for products and if there is no result available,for a product just show a zero for it. something like following table:
+--------+------------+-----------+----------------+---------------+
| pname | total_pass | total_fail| pass_lastweek | fail_lastweek |
+--------+------------+-----------+----------------+---------------+
| prd1 | 5 | 1 | 3 | 1 |
| prd2 | 0 | 0 | 0 | 0 |
| prd3 | 0 | 0 | 0 | 0 |
| prd4 | 0 | 0 | 0 | 0 |
+--------+------------+-----------+----------------++--------------+
I have tried different queries like following, which is just working for one product and is incomplete:
SELECT pname, count(*) as pass_lastweek FROM tests where testresult = 'PASS' AND time
>= '2017-10-11' and pname in (select pname from products) group by pname;
+-------------+---------------+
| pname | pass_lastweek |
+-------------+---------------+
| prd1 | 3 |
+-------------+---------------+
it looks so basic but still I am unable to write it, any idea?
Use conditional aggregation. The COUNT function count NULL values as zeros automatically, therefore, there is no need to take care of that.
select p.pname,
count(case when testresult = 'PASS' then 1 end) as total_pass,
count(case when testresult = 'FAIL' then 1 end) as total_fail,
count(case when testresult = 'PASS' and time >= curdate() - INTERVAL 6 DAY then 1 end) as pass_lastweek ,
count(case when testresult = 'FAIL' and time >= curdate() - INTERVAL 6 DAY then 1 end) as fail_lastweek ,
from products p
left join tests t on t.pname = p.pname
group p.id, p.pname
Generally, you need to LEFT JOIN the first table with the second one before you group. The join will give you a row for each product (even if there are no test results to join it to; INNER JOIN would exclude products with no associated tests) + an additional row for each test result (beyond the first). Then you can group them.
SELECT products.*, tests.* FROM products
LEFT JOIN tests ON products.pname = tests.pname
GROUP BY products.id
Also, I would strongly recommend using a product_id column in the tests table, rather than using pname (if a products.pname changes, your whole DB breaks unless you also update the pname field in kind for every test result). The general query would then look like this:
SELECT products.*, tests.* FROM products
LEFT JOIN tests ON products.id = tests.product_id
GROUP BY products.id
I used 2 queries , the first with conditional count and the second one is to change all null values into 0 :
select pname,
case when total_pass is null then 0 else total_pass end as total_pass,
case when total_fail is null then 0 else total_fail end as total_fail,
case when pass_lastweek is null then 0 else pass_lastweek end as pass_lastweek,
case when fail_lastweek is null then 0 else fail_lastweek end asfail_lastweek from (
select products.pname,
count(case when testresult = 'PASS' then 1 end) as total_pass,
count(case when testresult = 'FAIL' then 1 end) as total_fail,
count(case when testresult = 'PASS' and time >= current_date -7 DAY then 1 end) as pass_lastweek ,
count(case when testresult = 'FAIL' and time >= current_date -7 DAY then 1 end) as fail_lastweek ,
from products
left join tests on tests.pname = products.pname
group 1 ) t1

SQL- Count number of 'similar' rows and place results in another table

I have a table called of 'orders' that records an order id for a meal, a person id for the person who ordered the meal and the meal (which can be 1, 2, or 3)
orders
oid | pid | meal
________________
1 | 1 | 2
2 | 1 | 3
3 | 3 | 1
4 | 5 | 2
5 | 5 | 2
6 | 5 | 1
I want to generate a result that will allow me to view the number of orders for each meal for each pid:
pid | meal1 | meal2 | meal3
____________________________
1 | 0 | 1 | 1
3 | 1 | 0 | 0
5 | 0 | 2 | 1
I am not an SQL expert, but I was thinking that I would first group the list by pid, then by meal. But how would I do the counting? I am truly lost.
Basically you need to first create multiple columns for the different meals using case when, then you can group by pid and sum it up
Select pid, sum(case when meal =1 then 1 else 0 end) as meal1,
sum(case when meal =2 then 1 else 0 end) as meal2,
sum(case when meal =3 then 1 else 0 end) as meal3
from Table
group by pid

SQL Increment Column

I am trying to show one semesters aggregates in one column, the next semester's aggregates in the second column, and the third semesters aggregates in the third column. Also the real tables, I don't know how many status codes there are...
I have a semester table:
Id Semester
+----+----------+
| 1 Current |
| 2 Next |
| 3 2 Ahead |
+----+----------+
I have a simple project table:
Id Title Status termId
+----+--------+---------+--------+
| 1 A OK 1 |
| 2 B Bad 1 |
| 3 C OK 1 |
| 4 D Bad 2 |
| 5 E OK 2 |
| 6 F Bad 3 |
| 7 G OK 2 |
+----+--------+---------+--------+
This is the desired Output:
Status CurrentCount NextCount 2AheadCount
+---------+--------------+-----------+-------------+
| OK 2 1 0 |
| Bad 1 1 1 |
+---------+--------------+-----------+-------------+
What would you recommend I do to be able to achieve this?
You can use conditional aggregation with group by:
select status,
sum(case when termId = 1 then 1 else 0 end) CurrentCount,
sum(case when termId = 2 then 1 else 0 end) NextCount,
sum(case when termId = 3 then 1 else 0 end) 2AheadCount
from project
group by status

Mysql split column into multiple columns

I have a table like:
id | cat | price
----------------------
0 | 1 | 2
0 | 2 | 1
0 | 1 | 1
1 | 1 | 31
1 | 2 | 5
----------------------
I like to select it like:
id | cat1_price | cat2_price
----------------------------------
0 | 3 | 1
1 | 31 | 5
----------------------------------
My query so far:
SELECT SUM(`price`) as cat1_price FROM price_table WHERE cat = 1 GROUP BY id
which works to get one of the needed columns. How can I have both?
I also tried something like:
SELECT SUM(`price`) as cat1_price
(SELECT SUM(`price`) FROM price_table WHERE cat = 2) as cat2_price
FROM price_table WHERE cat = 1 GROUP BY id
which works too slow. The actual table is pretty big and has some some joins.
I'm not sql guru, so I hope there is a query for this I'm not aware of :)
Just use conditional aggregation:
SELECT id, SUM(case when cat = 1 then `price` else 0 end) as cat1_price,
SUM(case when cat = 2 then `price` else 0 end) as cat1_price
FROM price_table
GROUP BY id;

MYSQL query display quantity of product from multiple tables

I have table like this
product :
id_product | product_name | price
560 | AAA | 1500
561 | BBB | 1750
attr :
id_attr | id_product | size | qty
100 | 560 | S | 11
100 | 560 | M | 9
100 | 560 | L | 7
100 | 560 | XL | 21
How to display
product_name | qty_S | qty_M | qty_L | qty_XL | qty_total | price
AAA | 11 | 9 | 7 | 21 | 48 | 1500
BBB | 0 | 0 | 0 | 0 | 0 | 1750
i try to make query like this :
select p.*, a.*,
sum(a.qty) as qty_total,
[how_to_display_query] as qty_S,
[how_to_display_query] as qty_M,
[how_to_display_query] as qty_L,
[how_to_display_query] as qty_XL,
FROM product p LEFT JOIN attr a
ON p.id_product = a.id_product
group by p.id_product;
===============================================
Please help me, sorry for bad english... Thanks
you want to pivot the results.. but you cant pivot in mysql as it doesn't have that functionality.. so you need to "fake" a pivot by using an aggregate and a conditional statement
the way to do that is like this.. MAX(CASE... ) SUM(CASE... ) etc.. can also be done with if MAX(IF... ) SUM(IF... ).
SELECT p.product_name
SUM(a.qty) as qty_total,
MAX(CASE size WHEN 'S' THEN qty ELSE 0 END) as qty_S,
MAX(CASE size WHEN 'M' THEN qty ELSE 0 END) as qty_M,
MAX(CASE size WHEN 'L' THEN qty ELSE 0 END) as qty_L,
MAX(CASE size WHEN 'XL' THEN qty ELSE 0 END) as qty_XL,
p.price
FROM product p
LEFT JOIN attr a ON p.id_product = a.id_product
GROUP BY by p.id_product;
i used MAX() here because you only seem to be getting one number.. if you were wanting to add multiple quantities then you can change MAX to SUM