MYSQL: SELECT sum of field values while also SELECTing unique values? - mysql

I'd like to count the number of purchases of each item while also, depending on who's viewing the content, show whether the user has purchased the content. Because the number of items and purchases could become large I'm reluctant to throw in more JOINs to accomplish this because that would seem not performant.
Basically, I'd like to have a did_i_buy field somewhere in the following query without adding another JOIN. Is this possible? Let's say for user_name=tom:
SELECT Items.item_id, item_name, COUNT(purchase_status='bought') as number_bought
FROM Purchases
JOIN Items ON Purchases.item_id=Items.item_id
GROUP BY Items.item_id
Here's my DB structure:
Table Items
item_id item_name
1 item_1
2 item_2
3 item_3
Table Purchases
item_id purchase_status user_name
1 bought joe
2 bought joe
1 bought tom
1 bought bill
Desired result for tom
item_id item_name number_bought did_i_buy
1 item_1 3 yes
2 item_2 1 no

If I understand correctly, the did_i_buy column means "did Tom buy". You can do that like this:
SELECT
Items.item_id,
item_name,
COUNT(CASE WHEN purchase_status='bought' THEN 1 END) as number_bought,
MAX(CASE WHEN purchase_status='bought' AND user_name='Tom' THEN 'yes' ELSE 'no' END) AS did_i_buy
FROM Purchases
JOIN Items ON Purchases.item_id=Items.item_id
GROUP BY Items.item_id
Alternatively (one CASE statement, see comments below)
SELECT
Items.item_id,
item_name,
COUNT(purchase_status='bought') as number_bought,
MAX(CASE WHEN user_name='Tom' THEN 'yes' ELSE 'no' END) AS did_i_buy
FROM Purchases
JOIN Items ON Purchases.item_id=Items.item_id
WHERE purchase_status='bought'
GROUP BY Items.item_id
And one more tweak: Because of the WHERE clause, the COUNT is only going to see rows where purchase_status='bought', so the expression checking the status can be left out (the only change from above is in line 4):
SELECT
Items.item_id,
item_name,
COUNT(*) as number_bought,
MAX(CASE WHEN user_name='Tom' THEN 'yes' ELSE 'no' END) AS did_i_buy
FROM Purchases
JOIN Items ON Purchases.item_id=Items.item_id
WHERE purchase_status='bought'
GROUP BY Items.item_id

You must (I think) use subqueries. Each request for a count is a separate query, so there is no way to optimize this (except to compress it all into one query with subqueries). There is no special relation between the horizontal data in items with the vertical data in purchases.
Here is an example query to count transactions for users:
SELECT user_id,(SELECT count(*) FROM transactions WHERE buyer_id=u.user_id) as count FROM users as u
I did a comparison with this query versus a similar query of the other JOIN type. The result: 0.0005 for this one vs. 0.0018 Ed Gibbs. However, if sorting of the number_bought (ORDER BY count DESC) is required, the latter query is significantly faster.

Related

MySQL: Output ID based on SUM data in one column based attributes in another column

SQL Table:
Customer
Type
Payment
1
Apples
5
1
Apples
5
1
Oranges
1
1
Oranges
2
2
Apples
7
2
Oranges
3
2
Oranges
6
Based on the above, looking to determine which customers have paid more for apples compared to oranges as a sum of all their payments.
In the case of the above table,
Customer 1 - Apples 10 > Oranges 3
Customer 2 - Apples 7 < Oranges 9
Thus the SQL should output Customer 1
I have attempted multiple queries, with the following as the most promising but getting an invalid use of group function error code 1111.
SELECT a.customer
FROM (SELECT customer, SUM(payment) AS orangespaid FROM table
WHERE type ='Oranges'
GROUP BY customer) o
JOIN table AS a ON a.customer = o.customer
WHERE type = 'Apples' and SUM(payment) > orangespaid
GROUP BY customer
ORDER BY customer;
There are a lot of ways to achieve that.
Here's how you do without sub-query:
SELECT Customer,
SUM(CASE WHEN Type='Apples' THEN Payment ELSE 0 END) AS Apples,
SUM(CASE WHEN Type='Oranges' THEN Payment ELSE 0 END) AS Oranges
FROM table1
GROUP BY Customer
HAVING Apples > Oranges;
Or like this:
SELECT Customer,
SUM(IF(Type='Apples',Payment,0)) > SUM(IF(Type='Oranges',Payment,0)) Chk
FROM table1
GROUP BY Customer
HAVING Chk=1
Or a slight modification of the query above, instead of checking the value in SELECT then filter from HAVING, why not just directly do the checking in HAVING:
SELECT Customer
FROM table1
GROUP BY Customer
HAVING SUM(IF(Type='Apples',Payment,0)) > SUM(IF(Type='Oranges',Payment,0)) != 0;
The first query can also be done in similar way.
Demo fiddle
Side note:
As for the difference between using CASE or IF, it's basically operates the same so it's more to individual preference. I mostly opt to use CASE because of readability and easier to edit (not much usage of parentheses/brackets) but using IF almost every time is shorter to write.
Try moving the SUM into a second subquery instead
SELECT a.customer
FROM (SELECT customer, SUM(payment) AS orangespaid FROM table
WHERE type ='Oranges'
GROUP BY customer) o
JOIN (SELECT customer, SUM(payment) AS applespaid FROM table
WHERE type ='Apples'
GROUP BY customer) AS a ON a.customer = o.customer
WHERE applespaid > orangespaid
ORDER BY customer;
You should try with sum(case when) for each type you want, it might not the best solution but it works.
select a.customer
from (select as1.Customer,
sum(case when type = 'Oranges' then payment else 0 end) AS orangespaid,
sum(case when type = 'Apples' then payment else 0 end) AS applespaid
from as1 group by as1.Customer) a
where applespaid > orangespaid
dbfiddle here

Combine multiple column values into new column(s) if it's a duplicate

I'm even sure if this can be done or not, but I'm hopeful. Currently using mysql 5.7
I want to show all columns if they're duplicate entries based on the phone value. Then I want to select all the data from the higher id number into multiple new columns.
I have no idea how to create a view that will correctly show all duplicates, otherwise this would be easier to figure out. I used the following query to get the data.
Original table
id customer_name customer_email customer_phone comments
1 Jack jack#jack.com 111-111-1111
2 Jill jill#jill.com 111-111-1111
3 Tim tim#tim.com 222-222-2222
4 Tonya tonya#tonya.com 222-222-2222
Expected results
id customer_name customer_email customer_phone spouse_name spouse_email comments
1 Jack jack#jack.com 111-111-1111 Jill jill#jill.com Jill jill#jill.com
3 Tim tim#tim.com 222-222-2222 Tonya tonya#tonya.com Tonya tonya#tonya.com
Ideally, all 3 columns would be populated, but spouse_name and spouse_email are more important since I can always combine them and insert into comments.
If you are running MysQL 8.0, you can do this with window functions and conditional aggregation:
select
max(case when rn = 1 then customer_name end) as customer_name_1,
max(case when rn = 1 then customer_email end) as customer_email_1,
customer_phone,
max(case when rn = 2 then customer_name end) as customer_name_2,
max(case when rn = 2 then customer_email end) as customer_email_2
from (
select t.*,
row_number() over(partition by customer_phone order by id) rn
from mytable t
) t
group by customer_phone
When two rows have the same customer_phone, this puts the name and email of the row that has the smallest id in the first two columns, then those of the other row.
Notes:
if there are more than two rows, rows after the second one are ignored
if there is just one row, the last two columns are empty
nothing in your data allows distinguishing the customer from the "spouse", so I used numeric prefixes for the column names instead

how to select orders that contains certain sku

I need to find items that contain SKUs starting with AB but would still want the query to return orders that contains AB and others.
order number 12334: sku AB12 & AB24
order number 22356: sku AB523 & KC5145
order number 123556: sku CD5641 & BG521
I expect the query to return order number 12334 and 22356 but not 123556
I would also like to know if an order is purely AB or AB with other items
so the table would return count of orders that contains only AB and count of order that contains AB with other items.
I have two tables: an orders table and line_items
orders table gives order_id to link to line_items table ID
select * from xx.line_items l
left join xx.orders o on l.order_id=o.id
where sku like 'AB%'
You haven't shared much details about the tables and columns for us to provide the full solution, but see if you can build something off of this mock up
select
sku,
(char_length(sku)-char_length(replace(sku,'AB','')))/2 as item_count
from your_table
where sku like '%AB%';
he following query gives you all order IDs with AB items. It shows their AB count and their total count, so you can compare the two. You could also add a CASE WHEN expression to compute a flag showing whether the order is pure AB or not.
After all this simply groups the items per order, counts AB items and dismisses all orders that have no AB item.
select
order_id,
count(*) as count_all,
count(case when sku like 'AB%' then 1 end) as count_ab
from line_items
group by order_id
having count(case when sku like 'AB%' then 1 end) > 0;
You can replace count(case when sku like 'AB%' then 1 end) with sum(sku like 'AB%') in MySQL by the way.
Here is a query building up on the above to count pure AB orders and mixed orders:
select (count_all = count_ab) as ab_only, count(*)
from
(
select count(*) as count_all, count(case when sku like 'AB%' then 1 end) as count_ab
from line_items
group by order_id
having count(case when sku like 'AB%' then 1 end) > 0
) counted
group by (count_all = count_ab);

Retrieve rows which meets a certain condition

I would like to start by explaining what my query should do.
At my store,we sell products A,B,C and D(Product ID)
Let's say I am interested in only those transactions where Item A was sold
This is how i wrote my query
Select [Transaction_No],[Product ID]
from [MystoreDB$Transaction lines]
where Date = '01-Jan-2016'
and (Product ID) = 'A'
The query executes without any errors,and I get the results only filtered to Product ID A.
But if I really look into the filtered transactions, I can see that there were other products bought in the same transaction(Product B was bought as well)
But the query only filtered 'the rows' with Product A
For Instance
There were total of 4 transactions done on 1-Jan-2016
Transaction 1 had
Product A + B
Transaction 2 had
Product A only
Transaction 3 had
Product A + C
Transaction 4 had
Product A only
At the end I want my query to retrieve only 2 transactions
Which is Transaction 2 and 4(since only product A was purchased)
I will ignore Transactions 1 and 3 since another product was purchased along with product A
What I want to find out is all transactions that had only Product A.
This means, the customer only bought product A and no other products.
Not sure how to get this.
I am using MYSQL for the DB engine
SELECT
Transaction_No
FROM
Transactions
WHERE
Date = '01-Jan-2016'
GROUP BY
Transaction_No
HAVING
COUNT(CASE WHEN Product_Id = 'A' THEN Product_Id END) = COUNT(*)
Doing a group by with conditional aggregation will give you the desired result and as there are no sub selects etc it should preform faster than a NOT EXISTS solution.
Edit Per Your Comment:
To test to see if a customer bought both Product A & B but no other products you would have to add a couple of additional constraints in your HAVING clause. Test that COUNT of A > 0 and COUNT of B > 0 and then that the COUNT of A & B is the same as the COUNT of All Products.
SELECT
Transaction_No
FROM
Transactions
WHERE
Date = '01-Jan-2016'
GROUP BY
Transaction_No
HAVING
COUNT(CASE WHEN Product_Id = 'A' THEN Product_Id END) > 0
AND COUNT(CASE WHEN Product_Id = 'B' THEN Product_Id END) > 0
AND COUNT(CASE WHEN Product_Id IN ('A','B') THEN Product_Id END) = COUNT(*)
Add this to your WHERE clause:L
AND [Transaction_No] NOT IN (
SELECT [Transaction_No]
FROM [MystoreDB$Transaction lines]
where [Product ID] <> 'A'
)
to exclude customers who bought some other product.

MYSQL sum the total up and down votes by all users for the items bought by a single user

I'd like to sum the total up and down votes on only the items bought by a single user. I have a big table so I don't want to sum all votes made by everyone for EVERY item, just the items that a particular user bought.
Here's my query so far:
select SUM(purchaseyesno) AS tots, SUM(rating=1) AS yes, SUM(rating=0) AS no, item_id
from items_purchased
where purchaser_account_id=12373
group by item_id
as you can expect, these sums are only the summing user 12373's info, so its just one value. I'm not sure how to get ALL the purchases of item_ids that are bought by user 12373.
I'm sure there is some kind of subquery,nesting thing I need to include but I'm clueless.
here's how I'd like my data to look, item_id=3,4,5 are all bought by user=12373. Whereas item_id=1,2,6 were bought by other users.
item_id tots yes no
3 7 4 2
4 5 1 3
5 1 0 1
thoughts?
select item_id, SUM(purchaseyesno) tots, SUM(rating = 1) yes, SUM(rating = 0) no
from items_purchased
where item_id in (
select item_id from items_purchased
where purchaser_account_id = 12373
)
group by item_id