Retrieve SUM(count) by ENUM value - mysql

This is an example of my table:
drug_id | route (enum) | count
------------------------------
1 | PO | 2
1 | IV | 4
1 | IV | 6
2 | PO | 1
2 | PO | 5
2 | IV | 2
This is how I need the information. Basically its SUM(count) but in a column for each "route":
drug_id | PO | IV
-----------------
1 | 2 | 10
2 | 6 | 2
I assume I need a pivot, which I was trying to learn about, but I cannot for the life of me piece together a succinct query which will work.
I've tried things like:
SELECT drug_id, PO, IV
FROM
(
SELECT drug_id, SUM(count) as PO, '0' as IV FROM `core_reports_antiinfectives` WHERE route="PO"
UNION
SELECT drug_id, SUM(count) as IV, '0' as PO FROM `core_reports_antiinfectives` WHERE route="IV"
) aa
However this gives me 0 for ALL IV columns, and I'm not convinced that its appropriate anyway - it'll need to "group" by drug_id and put the columns together, which I am also stuck on.
Is there something I am missing? Or is there a better way to go about it?
Thanks!

Try this
SELECT drug_id,
SUM(case route when 'po' then `count` else 0 end) totalPO,
SUM(case route when 'iv' then `count` else 0 end) totalIV
FROM core_reports_antiinfectives
GROUP BY drug_id
SQLFiddle Demo

SELECT drug_id,
SUM(IF(route='PO',count,NULL)) AS PO,
SUM(IF(route='IV',count,NULL)) AS IV
FROM core_reports_antiinfectives
GROUP BY drug_id
See it on sqlfiddle.

Related

Mariadb 'unknown column in where clause' and get wrong result using 'count()' with 'group by' in subquery

I'm having problem to get the right result from the following tables
table_buyer
pkey | buyer_id | buyer_name
1 | 1 | john
2 | 2 | mike
table_fruit
pkey | buyer_id | fruit_name
1 | 1 | banana
2 | 1 | banana
3 | 1 | apple
4 | 2 | grape
5 | 2 | grape
6 | 2 | grape
then i'm trying to run the following query
select buyer_name, (select count(*) from (select fruit_name from table_fruit where buyer_id = table_buyer.buyer_id group by fruit_name) as table_group_of_fruit) as group_of_fruit from table_buyer;
expected result
buyer_name | group_of_fruit
john | 2
mike | 1
error message : Unknown column 'table_buyer.buyer_id' in 'where clause'
and count(*) does not count grouped fruit as a group but individually.
It seems simple and easy but i've been thinking about this for 2 days and still don't have a clue to get the right result as expected, any help will be greatly appreciated
I think you want to aggregate by buyer and then select the distinct count of fruit:
SELECT b.buyer_name, COUNT(DISTINCT f.fruit_name) AS group_of_fruit
FROM table_buyer b
LEFT JOIN table_fruit f
ON f.buyer_id = b.buyer_id
GROUP BY b.buyer_name;
Note that if two or more buyers could share the same name, you might want to also add the buyer_id to the select and group by clauses:
SELECT b.buyer_id, b.buyer_name, COUNT(DISTINCT f.fruit_name) AS group_of_fruit
FROM table_buyer b
LEFT JOIN table_fruit f
ON f.buyer_id = b.buyer_id
GROUP BY b.buyer_id, b.buyer_name;

Dividing new created columns

orders_table:
orders_id_column | user_id_column | final_status_column
----------------------------------------------------
1 | 4455 | DeliveredStatus
2 | 4455 | DeliveredStatus
3 | 4455 | CanceledStatus
4 | 8888 | CanceledStatus
I want to calculate the total number of orders, and the number of Canceled orders by user_id, and then the cocient between these two, to arrive to something like is:
user_id | total_orders | canceled_orders | cocient
---------------------------------------------------
4455 | 3 | 1 | 0.33
8888 | 1 | 1 | 1.00
I managed to create the first two columns, but not the last one:
SELECT
COUNT(order_id) AS total_orders,
SUM(if(orders.final_status = 'DeliveredStatus', 1, 0)) AS canceled_orders
FROM users
GROUP BY user_id;
You can use an easy approach :
SELECT
user_id,
COUNT(order_id) AS total_orders,
SUM(CASE WHEN final_status = 'CanceledStatus' THEN 1 ELSE 0 END ) AS
canceled_orders,
SUM(CASE WHEN final_status = 'CanceledStatus' THEN 1 ELSE 0 END ) /COUNT(order_id)
as cocient
FROM users
GROUP BY user_id;
Demo: https://www.db-fiddle.com/f/7yUJcuMJPncBBnrExKbzYz/136
You could just use a sub-query.
Then you can refer to the newly created columns, as the outer query exists in a different scope (one where the new columns now exist).
(Thus avoids repeating any logic, and maintaining DRY code.)
SELECT
user_id,
total_orders,
cancelled_orders,
cancelled_orders / total_orders
FROM
(
SELECT
user_id,
COUNT(order_id) AS total_orders,
SUM(if(orders.final_status = 'DeliveredStatus', 1, 0)) AS canceled_orders
FROM
users
GROUP BY
user_id
)
AS per_user
Note, selecting from the users table appears to be a typo in your example. It would appear that you should select from the orders table...

How to replace a UNION in a query for a view?

I have created the following query to use in a view
SELECT
*
FROM
customers c
JOIN
customer_business cb
ON
c.customer_id = cb.customer_id
union
SELECT
*
FROM
customers c
LEFT JOIN
customer_business
ON
business_id=NULL;
It makes his work perfectly. It shows all customers with the business associated, and at the end, shows all customers with the info of the business in null.
customer_id | business_id
--------------------------------
1 | 1
2 | 1
2 | 2
1 | NULL
2 | NULL
3 | NULL
But the problem es that the UNION makes the view has very poor performace.
I tryed to do it with LEFT JOIN but doesnt shows al the customers with business in null, just the ones without any businesses associated
I know that the solution to speed up my view is to remove that UNION, but i cant figure out how.
Can anyone help me?
Thanks
EDIT
Here's an example
Customer Table
customer_id | name
--------------------------------
1 | test1
2 | test2
3 | test3
Customer_business Table
customer_business_id | customer_id | business_id
----------------------------------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 1
5 | 2 | 2
Expected query result:
name | customer_id | business_id
----------------------------------------------------------
test1 | 1 | 1
test1 | 1 | 2
test1 | 1 | 3
test2 | 2 | 1
test2 | 2 | 2
test1 | 1 | NULL
test2 | 2 | NULL
test3 | 3 | NULL
Updating it based on the comments below and the output you want.
Note that I have used UNION ALL which is faster than UNION as UNION uses DISTINCT to get unique records which in your case doesn't apply. Also, make sure customer_id is PK in Customer table and try adding non-unique index on customer_id in Customer_Business table and it should help with performance.
SELECT name,
C.customer_id,
business_id
FROM Customer C
INNER JOIN Customer_Business CB
ON C.customer_id = CB.customer_id
UNION ALL
SELECT name,
C.customer_id,
NULL
FROM Customer C
Excluding the union which we know that is not performant the other thing that slows down you query is the statement in the second query ON idbusiness = NULL.
I propose to edit you query like this and see the performance as a view:
SELECT c.customer_id, idbusiness
FROM customers c
JOIN customer_business cb ON c.customer_id = cb.customer_id
UNION
SELECT customer_id, NULL
FROM customers c
EDIT:
Looking for an alternative you could try this, it should return the same output (i've changed null values with 0) but i don't think it's faster:
SELECT c.customer_id, idbusiness
FROM customers c
INNER JOIN (
SELECT customer_id, idbusiness
FROM customer_business
UNION
SELECT 0 , 0
)b ON ( c.customer_id = b.customer_id )
OR (
b.idbusiness =0
)
Eventually you could try to put into a view only the subquery b or delete the union by putting the values 0,0 as a record in table customer_business.

How to query number of changes in a column in MySQL

I have a table that stores items with two properties. So the table has three columns:
item_id | property_1 | property_2 | insert_time
1 | 10 | 100 | 2012-08-24 00:00:01
1 | 11 | 100 | 2012-08-24 00:00:02
1 | 11 | 101 | 2012-08-24 00:00:03
2 | 20 | 200 | 2012-08-24 00:00:04
2 | 20 | 201 | 2012-08-24 00:00:05
2 | 20 | 200 | 2012-08-24 00:00:06
That is, each time either property of any item changes, a new row is inserted. There is also a column storing the insertion time. Now I want to get the number of changes in property_2. For the table above, I should get
item_id | changes_in_property_2
1 | 2
2 | 3
How can I get this?
This will tell you how many distinct values were entered. If it was changed back to a previous value, it will not be counted as a new change, though. Without a chronology to your data, hard to do much more.
select item_id, count(distinct property_2)
from Table1
group by item_id
Here is the closest that I could get to your desired result. I should note however, that you are asking for the number of changes to property_2 based on item_id. If you are analyzing strictly those two columns, then there is only 1 change for item_id 1 and 2 changes for item_id 2. You would need to expand your result to aggregate by property_1. Hopefully, this fiddle will show you why.
SELECT a.item_id,
SUM(
CASE
WHEN a.property_2 <>
(SELECT property_2 FROM tbl b
WHERE b.item_id = a.item_id AND b.insert_time > a.insert_time LIMIT 1) THEN 1
ELSE 0
END) AS changes_in_property_2
FROM tbl a
GROUP BY a.item_id
My take :
SELECT
i.item_id,
SUM(CASE WHEN i.property_1 != p.property_1 THEN 1 ELSE 0 END) + 1
AS changes_1,
SUM(CASE WHEN i.property_2 != p.property_2 THEN 1 ELSE 0 END) + 1
AS changes_2
FROM items i
LEFT JOIN items p
ON p.time =
(SELECT MAX(q.insert_time) FROM items q
WHERE q.insert_time < i.insert_time AND i.item_id = q.item_id)
GROUP BY i.item_id;
There is one entry for each item that is not selected in i, the one that has no predecessor. It counts for a change though, that's why the sums are incremented.
I would do it this way, with user-defined variables to keep track of the previous row's value.
SELECT item_id, MAX(c) AS changes_in_property_2
FROM (
SELECT IF(#i = item_id, IF(#p = property_2, #c, #c:=#c+1), #c:=1) AS c,
(#i:=item_id) AS item_id,
(#p:=property_2)
FROM `no_one_names_their_table_in_sql_questions` AS t,
(SELECT #i:=0, #p:=0) AS _init
ORDER BY insert_time
) AS sub
GROUP BY item_id;

Select every other row as male/female from mysql table

I've got a table containing persons gender-coded as 0 and 1. I need to select every other row as male/female. I thought I could manage this somehow by using modulo and the gender-codes 0 and 1, but I haven't managed to figure it out yet...
The result I'm looking for would look like this:
+-----+--------+-------+
| row | gender | name |
+-----+--------+-------+
| 1 | female | Lisa |
| 2 | male | Greg |
| 3 | female | Mary |
| 4 | male | John |
| 5 | female | Jenny |
+-----+--------+-------+
etc.
The alternative is to do it in PHP by merging 2 separate arrays, but I would really like it as a SQL query...
Any suggestions are appreciated!
Do two subqueries to select male and female. Use ranking function to have them enumerated.
Males:
1 | Peter
2 | John
3 | Chris
Females:
1 | Marry
2 | Christina
3 | Kate
Then multiplay ranking result by x10 and add 5 for females. So you have this:
Males:
10 | Peter
20 | John
30 | Chris
Females:
15 | Marry
25 | Christina
35 | Kate
Then do the UNION ALL and sort by new sort order/new ID.
Together it should like this (pseudo code)
SELECT
Name
FROM
(subquery for Males: RANK() AS sortOrd, Name)
UNION ALL
(subquery for Females: RANK()+1 AS SortOrd, Name)
ORDER BY SortOrd
Result should be like this:
Males and Females:
10 | Peter
15 | Marry
20 | John
25 | Christina
30 | Chris
35 | Kate
Found Emulate Row_Number() and modified a bit for your case.
set #rownum := 0;
set #pg := -1;
select p.name,
p.gender
from
(
select name,
gender,
#rownum := if(#pg = gender, #rownum+1, 1) as rn,
#pg := gender as pg
from persons
order by gender
) as p
order by p.rn, p.gender
Try on SQL Fiddle
Note: From 9.4. User-Defined Variables
As a general rule, you should never assign a value to a user variable
and read the value within the same statement. You might get the
results you expect, but this is not guaranteed.
I will leave it up to you do decide if you can use this. I don't use MySQL so I can't really tell you if you should be concerned or not.
Similar to Mikael's solution but without the need to order the resultset multiple times -
SELECT *
FROM (
SELECT people.*,
IF(gender=0, #mr:=#mr+1, #fr:=#fr+1) AS rank
FROM people, (SELECT #mr:=0, #fr:=0) initvars
) tmp
ORDER BY rank ASC, gender ASC;
To avoid having to order both the inner and outer selects I have used separate counters (#mr - male rank, #fr - female rank) in the inner select.
I've got a table containing persons gender-coded as 0 and 1
Then why would you make assumptions on the order of rows in the result set? Seems to me transforming the 0/1 into 'male'/'female' is far more robust:
select name, case gender when 0 then 'male' else 'female' end
from Person
SELECT alias.*, ROW_NUMBER() OVER (PARTITION BY GENDER ORDER BY GENDER) rnk
FROM TABLE_NAME
ORDER BY rnk, GENDER DESC