Mysql split column into multiple columns - mysql

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;

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

Multiple SQL rows merge into single row if the id is same

What is the mysql I need to achieve the result below given this table:
table:
+----+-------+--------------
| name| id | items | vol
+----+-------+---------------
| A | 1111 | 4 | 170
| A | 1111 | 5 | 100
| B | 2222 | 6 | 200
| B | 2222 | 7 | 120
+----+-------+-----------------
Above table is the result of union query
SELECT * FROM imports
union all
SELECT * FROM exports
ORDER BY name;
I want to create a temporary view that looks like this
desired result:
+----+---------+---------+-------------------
| name| id | items | vol | items1 | vol2
+-----+--------+-------+--------------------
| A | 1111 | 4 | 170 | 5 | 100
| B | 2222 | 6 | 200 | 7 | 120
+----+---------+---------+-------------------
any help would be greatly appreciated! -Thanks
Use PIVOT:
SELECT name,id,
SUM( CASE WHEN typ = 'imports' THEN items ELSE 0 END) as imports_items,
SUM( CASE WHEN typ = 'imports' THEN vol ELSE 0 END) as imports_vol,
SUM( CASE WHEN typ = 'exports' THEN items ELSE 0 END) as exports_items,
SUM( CASE WHEN typ = 'exports' THEN vol ELSE 0 END) as exports_vol
FROM (
SELECT 'imports' as typ, t.* FROM imports t
union all
SELECT 'exports' as typ, t.* FROM exports t
) x
GROUP BY name,id
ORDER BY name;
This should give you the table you are looking for:
SELECT
a.name,
a.id,
a.items,
a.vol,
b.items as items2,
b.vol as vol2
FROM imports a
INNER JOIN exports b on b.id = a.id;

MySQL Crosstab aggregation with 2 conditions

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;

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

Count rows with specific value over multiple rows

Its very hard for to set a proper title, because I dont know how I describe my problem.
I have a table like this:
dlID | dl_seID | dlEpisode | dlFlag
___________________________________
1 | 1 | 1 | 0
2 | 1 | 2 | 1
3 | 1 | 3 | 1
4 | 2 | 1 | 1
5 | 2 | 2 | 0
6 | 3 | 1 | 0
What i want is a select query where I get something like this:
dlID | dl_seID | dlEpisode | dlFlag | dlFlagCount
_________________________________________________
1 | 1 | 1 | 0 | 2
2 | 1 | 2 | 1 | 2
3 | 1 | 3 | 1 | 2
4 | 2 | 1 | 1 | 1
5 | 2 | 2 | 0 | 1
6 | 3 | 1 | 0 | 0
dlFlagCount shoud be a counter of dlFlag = 1 where dl_seID = dl_seID.
Second try:
I need a value where I see how many Flags have the value 1 with the same dl_seID.
Is that possible?
I hope you guys know what I want^^
Regards
Try this:
select
a.*,
ifnull(b.ctflags,0)
from
tablea a left join
( select dl_seID, count(dlFlag) ctflags
from tablea
where dlFlag=1
group by dl_seID ) b on (a.dl_seID = b.dl_seID)
The left join is just to get the registry with 0 flags
See the fiddle: http://sqlfiddle.com/#!2/ef9b0/5
EDIT:
As op requested some explanation, here it goes:
What you asked is to count the amount of flags by the dl_seID and to do that you need to do this you separeta your problems, first you get the count for the dl_seID by flags, this is this subquery:
select dl_seID, count(dlFlag) ctflags
from tablea
where dlFlag=1
group by dl_seID
This became a 'separe table' or a new group of data, whatever you wanna call it. Then you have to join this with your original data (from your table) like the query for answer.
The left join part is because maybe there are some data that wont complain with where dlFlag=1 therefore if you want to get then as 0 you have to bring all values from table that exists or not on our created subgroup. And this ifnull(b.ctflags,0) is for theese data data exists on your table but has no flags (for your problem). If you use just b.ctflags it will bring null.
SELECT x.*
, COALESCE(y.flagcount,0) flagcount
FROM my_table x
LEFT
JOIN
( SELECT seID
, COUNT(*) flagcount
FROM my_table
WHERE flag = 1
GROUP
BY seid
) y
ON y.seid = x.seid;