case when condition with group by query with mysql - mysql

Below is my mysql query :
SELECT u.id,
CASE WHEN (p.PaymentType = "FreeCredit") THEN SUM(p.CreditedAmount) ELSE 0 END AS freecredit ,
CASE WHEN (p.PaymentType = "Online") THEN SUM(p.CreditedAmount) ELSE 0 END AS onlinepayment,
CASE WHEN (p.PaymentType = "Cash") THEN SUM(p.CreditedAmount) ELSE 0 END AS Cash
FROM users as u
LEFT JOIN payment as p on u.id = p.UserId AND p.PaymentSucc = "Yes"
WHERE
`u`.`UserType` = 'User'
GROUP BY p.UserId
ORDER BY u.id DESC;
Required result : in payment table I have 3 payment type in result I required to show sum for particular payment type for single user like below,
userid= 1974
PaymentType = FreeCredit,CreditedAmount= 120
PaymentType = Online ,CreditedAmount== 140
PaymentType = cash ,CreditedAmount==100
PaymentType = FreeCredit,CreditedAmount== 100
PaymentType = Online ,CreditedAmount== 120
PaymentType = cash ,CreditedAmount==170
in required result it should be as below,
id freecredit onlinepayment Cash
1972 220 260 270
from my query ,I am not able to get above result ,please can any person help me to resolve my this mysql query issue:
I will appreciate best answer.

This is a good question (y)
So,
Here's table user:
mysql> select * from user;
+----+-----------+
| id | user_type |
+----+-----------+
| 1 | krish |
| 2 | bala |
+----+-----------+
Here's table payment:
mysql> select * from payment;
+------+-------------+----------------+
| id | PaymentType | CreditedAmount |
+------+-------------+----------------+
| 1 | FreeCredit | 120 |
| 1 | Online | 140 |
| 1 | cash | 100 |
| 1 | FreeCredit | 100 |
| 1 | Online | 120 |
| 1 | cash | 170 |
| 2 | FreeCredit | 500 |
| 2 | Online | 450 |
| 2 | FreeCredit | 230 |
+------+-------------+----------------+
The goal is to have - for each ID - the sum of 'FreeCredit' / 'Online' / 'cash'
select u.id, sum(case when PaymentType = "FreeCredit" then
CreditedAmount else 0 end) as freeamnt, sum(case when PaymentType =
'Online' then CreditedAmount else 0 end) as Online, sum(case when
PaymentType = 'cash' then Creditedamount else 0 end) as cash from user
as u left join payment as p on u.id=p.id group by p.id order by u.id
desc;
+----+----------+--------+------+
| id | freeamnt | Online | cash |
+----+----------+--------+------+
| 3 | 0 | 0 | 0 |
| 2 | 730 | 450 | 0 |
| 1 | 220 | 260 | 270 |
+----+----------+--------+------+
3 rows in set (0.03 sec)
Note:
I simulated a sample based upon your examples
The query is based on my sample and it won't have all the 'where' conditions you have given

Related

How to make row as header then count the populated datas

I'm trying to count all the rows which has a status of checked out,
but I can only do is to populate it by rows then count it dynamically.
How can I make my rows as header then count the status after?
I need to make the dorm name rows as column header
then checked out as rows.
Query :
SELECT Room_Number as 'Room Number',Dorm_Name as 'Dorm Name',
COUNT(IF(action = 'Checked Out' , 1, NULL)) 'Checked Out' FROM billeting_history group by dorm_name;
+-----------+-------------+-------------+
| Dorm Name | Room_number | Checked Out |
+-----------+-------------+-------------+
| Arquitola | 205 | 1 |
| Hangar | 201 | 0 |
| Noble | 200 | 0 |
+-----------+-------------+-------------+
Desired output :
+-------------+-----------+--------+-------+
| Room Number | Arquitola | Hangar | Noble |
+-------------+-----------+--------+-------+
| 205 | 1 | 0 | 0 |
| 201 | 0 | 0 | 0 |
| 200 | 0 | 0 | 0 |
+-------------+-----------+--------+-------+
If you know the specific columns you want, you can use conditional aggregation:
SELECT Room_Number,
SUM( CASE WHEN Dorm_Name = 'Arquitola' THEN checked_out ELSE 0 END ) as Arquitola,
SUM( CASE WHEN Dorm_Name = 'Hangar' THEN checked_out ELSE 0 END ) as Hangar,
SUM( CASE WHEN Dorm_Name = 'Noble' THEN checked_out ELSE 0 END ) as Noble
FROM billeting_history
GROUP BY Room_Number;
If you don't know the full list of names, then you need to use dynamic SQL.

Joining and nesting queries in mysql

Currently, I'm using this nice query:
select
users.name,
sum(race_results.winnings) as total_winnings,
count(CASE WHEN race_results.place=1 THEN 1 ELSE 0 END) AS times_won_first_place
from users
inner join race_results
where race_results.userid = users.id and race_results.place = 1
group by users.id
order by total_winnings desc
to get this
************************************************
| name | total_winnings | times_won_first_place |
| Bob | 4000 | 4 |
| John | 1000 | 1 |
************************************************
the race_results table looks like this
*******************************************
| id | raceid | userid | place | winnings |
| 1 | 1 | 1 | 1 | 1000 |
| 2 | 1 | 2 | 5 | 50 |
| 3 | 1 | 3 | 6 | 50 |
| 4 | 2 | 1 | 1 | 1000 |
| 5 | 2 | 2 | 3 | 250 |
*******************************************
I would like to include four three more columns for something like this
***************************************************************************
| name | total_winnings | total_races | 1st_place | 2nd_place | 3rd_place |
| Bob | 4000 | 5 | 4 | 0 | 0 |
| John | 1000 | 5 | 1 | 1 | 1 |
***************************************************************************
If I were to do separate queries for the new columns, I'd use
select count(raceid) from race_results where userid = 1
select count(raceid) from race_results where userid = 1 and place = 1
select count(raceid) from race_results where userid = 1 and place = 2
select count(raceid) from race_results where userid = 1 and place = 3
to do separate queries would be easy but with the existing query I had to use CASE just to get the count of times a user won 1st place. (using
count(CASE WHEN race_results.place=2 THEN 1 ELSE 0 END)
returns the same results).
How would I nest these or join them into my existing query to get what I want?
You can do it this way:
select
users.name,
sum(race_results.winnings) as total_winnings,
count(*) AS total_races,
sum(race_results.place = 1) AS times_won_first_place ,
sum(race_results.place = 2) AS times_won_second_place,
sum(race_results.place = 3) AS times_won_third_place
from users
inner join race_results
where race_results.userid = users.id
group by users.id
order by total_winnings desc;
With ANSI standard SQL you could use case expressions inside the sum function but since MySQL (and some other databases) evaluate boolean expressions to 1 for true you can replace the case expression with the just the condition to evaluate and then just sum them.
So instead of CASE WHEN race_results.place=1 THEN 1 ELSE 0 END you can do sum(race_results.place=1) and save some space and typing :)
See this SQL Fiddle for an example.

Mind numbing SQL madness

This query runs on an invoices table to help me decide who I need to pay
Here's the base table:
The users table
+---------+--------+
| user_id | name |
+---------+--------+
| 1 | Peter |
| 2 | Lois |
| 3 | Stewie |
+---------+--------+
The invoices table:
+------------+---------+----------+--------+---------------+---------+
| invoice_id | user_id | currency | amount | description | is_paid |
+------------+---------+----------+--------+---------------+---------+
| 1 | 1 | usd | 140 | Cow hoof | 0 |
| 2 | 1 | usd | 45 | Cow tail | 0 |
| 3 | 1 | gbp | 1 | Cow nostril | 0 |
| 4 | 2 | gbp | 1500 | Cow nose hair | 0 |
| 5 | 2 | cad | 1 | eyelash | 1 |
+------------+---------+----------+--------+---------------+---------+
I want a resulting table that looks like this:
+---------+-------+----------+-------------+
| user_id | name | currency | SUM(amount) |
+---------+-------+----------+-------------+
| 1 | Peter | usd | 185 |
| 2 | Lois | gbp | 1500 |
+---------+-------+----------+-------------+
The conditions are:
Only consider invoices that have not been paid, so where is_paid = 0
Group them by user_id, by currency
If the SUM(amount) < $100 for the user_id, currency pair then don't bother showing the result, since we don't pay invoices that are less than $100 (or equivalent, based on a fixed exchange rate).
Here's what I've got so far (not working -- which I guess is because I'm filtering by a GROUP'ed parameter):
SELECT
users.user_id, users.name,
invoices.currency, SUM(invoices.amount)
FROM
mydb.users,
mydb.invoices
WHERE
users.user_id = invoices.user_id AND
invoices.is_paid != true AND
SUM(invoices.amount) >=
CASE
WHEN invoices.currency = 'usd' THEN 100
WHEN invoices.currency = 'gbp' THEN 155
WHEN invoices.currency = 'cad' THEN 117
END
GROUP BY
invoices.currency, users.user_id
ORDER BY
users.name, invoices.currency;
Help?
You can't use SUM in a WHERE. Use HAVING instead.
Use HAVING clause instead of SUM in WHERE condition
Try this:
SELECT u.user_id, u.name, i.currency, SUM(i.amount) invoiceAmount
FROM mydb.users u
INNER JOIN mydb.invoices i ON u.user_id = i.user_id
WHERE i.is_paid = 0
GROUP BY u.user_id, i.currency
HAVING SUM(i.amount) >= (CASE i.currency WHEN 'usd' THEN 100 WHEN 'gbp' THEN 155 WHEN 'cad' THEN 117 END)
ORDER BY u.name, i.currency;
Try something like this:
SELECT
user_id, name, currency, sum(amount) due
FROM
invoice i
JOIN users u ON i.user_id=u.user_id
WHERE
is_paid = 0 AND
GROUP BY user_id, currency
having due >= 100
do you store exchange rates? Multiply rates with amount to get actual amount with respect to base currency.
sum(amount*ex_rate) due

MySQL Dynamic inner join for combining multiple rows into one?

I am creating a database with the tables below, where shop_id in hours refers to a an id in shop.
Preferably I would have a query to return all data in one row, in stead of needing to post-process a lot of rows to "merge" the result from hours so the end result looks like this.
+-------------------+---------+------+---------+----------------+----------+---------------+---------------+-----+-----+-----+-----+-----+-----+-----+
| name | address | zip | city | municipal | phone | lat | lng | day | day | day | day | day | day | day |
+-------------------+---------+------+---------+----------------+----------+---------------+---------------+-----+-----+-----+-----+-----+-----+-----+
| Coop Marked Budal | false | 7298 | Budalen | Midtre gauldal | 72436410 | 62.8837013245 | 10.4836997986 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+-------------------+---------+------+---------+----------------+----------+---------------+---------------+-----+-----+-----+-----+-----+-----+-----+
Right now I have come to the query below, wich I feel there must be a better alternative to. So my question is: Is there really another solution to this?
Also, I've seen concat, but I want to avoid having to split strings later on when processing the data.
select shop.name, shop.address, shop.zip, shop.city, shop.municipal, shop.phone, shop.lat, shop.lng,
h.day, hh.day, hhh.day, hhhh.day, hhhhh.day, hhhhhh.day, hhhhhhh.day
from shop
/**
As it requires a unique table name, this was the solution I found.
Could this be shortened?
**/
inner join hours h on shop.id = h.shop_id and h.day = 1
inner join hours hh on shop.id = hh.shop_id and hh.day = 2
inner join hours hhh on shop.id = hhh.shop_id and hhh.day = 3
inner join hours hhhh on shop.id = hhhh.shop_id and hhhh.day = 4
inner join hours hhhhh on shop.id = hhhhh.shop_id and hhhhh.day = 5
inner join hours hhhhhh on shop.id = hhhhhh.shop_id and hhhhhh.day = 6
inner join hours hhhhhhh on shop.id = hhhhhhh.shop_id and hhhhhhh.day = 7;
Tables
shop
+----+-------------------+---------+------+---------+----------------+----------+---------------+---------------+----------+-----------+
| id | name | address | zip | city | municipal | phone | lat | lng | chain_id | county_id |
+----+-------------------+---------+------+---------+----------------+----------+---------------+---------------+----------+-----------+
| 1 | Test | false | 1234 | Test | Test | 12341234| 0.0000 | 0.0000 | 3 | 16 |
+----+-------------------+---------+------+---------+----------------+----------+---------------+---------------+----------+-----------+
hours
+-----+----------+----------+---------+
| day | open | close | shop_id |
+-----+----------+----------+---------+
| 1 | 09:00:00 | 18:00:00 | 1 |
| 2 | 09:00:00 | 18:00:00 | 1 |
| 3 | 09:00:00 | 18:00:00 | 1 |
| 4 | 09:00:00 | 18:00:00 | 1 |
| 5 | 09:00:00 | 18:00:00 | 1 |
| 6 | 09:00:00 | 20:00:00 | 1 |
| 7 | 14:00:00 | 20:00:00 | 1 |
+-----+----------+----------+---------+
You can also use a case .. when to do the pivot, and then group by the shop fields and use an aggregate function to process the day.
select
shop.NAME, shop.address, shop.zip, shop.city, shop.municipal, shop.phone, shop.lat, shop.lng,
MAX(CASE WHEN h.DAY = 1 THEN h.DAY ELSE 0 END) AS Day1,
MAX(CASE WHEN h.DAY = 2 THEN h.DAY ELSE 0 END) AS Day2,
MAX(CASE WHEN h.DAY = 3 THEN h.DAY ELSE 0 END) AS Day3,
MAX(CASE WHEN h.DAY = 4 THEN h.DAY ELSE 0 END) AS Day4,
MAX(CASE WHEN h.DAY = 5 THEN h.DAY ELSE 0 END) AS Day5,
MAX(CASE WHEN h.DAY = 6 THEN h.DAY ELSE 0 END) AS Day6,
MAX(CASE WHEN h.DAY = 7 THEN h.DAY ELSE 0 END) AS Day7
from shop
INNER JOIN HOURS h ON shop.id = h.shop_id
group by
shop.NAME, shop.address, shop.zip, shop.city, shop.municipal, shop.phone, shop.lat, shop.lng;
Just a note about what you want displayed in the day columns:
AFAIK if any of the hours rows for a shop : day is missing, your current query will drop the whole row? If you want this behaviour repeated, you will need to also add in a where clause.

Don't return the lowest value if

The goal
Don't return the lowest price whose its markets are suspended.
The problem
I don't know the syntax.
The scenario
There is the following stored procedure to get the lowest and the biggest price of a specific product:
BEGIN
Select Min(Case When product.PromotionalPrice = 0
Then product.OriginalPrice Else
Least(product.PromotionalPrice, product.OriginalPrice)
End) As minProductPrice,
Max(Case When product.PromotionalPrice = 0
Then product.OriginalPrice Else
Least(product.PromotionalPrice, product.OriginalPrice)
End) As maxProductPrice
From products As product
Where product.Name = 'Playstation 3';
END
The context is: there are markets and products. Products belong to markets. If some market is suspended, then doesn't display its products and nor add them to max/min price comparison.
Can you all understand? I want to exclude the products whose its markets are suspended from the Min or Max statement of above's query.
The tables
Here is the markets table:
+----+------+-------------+
| Id | Name | SituationId |
+----+------+-------------+
| 1 | A | 1 |
+----+------+-------------+
| 2 | B | 2 |
+----+------+-------------+
| 3 | C | 3 |
+----+------+-------------+
Here is the markets_situations table:
+----+-----------+
| Id | Name |
+----+-----------+
| 1 | Neutral |
+----+-----------+
| 2 | Premium |
+----+-----------+
| 3 | Suspended |
+----+-----------+
And finally, here is the products table:
+----+---------------+--------+------------------+---------------+
| Id | Name | Market | PromotionalPrice | OriginalPrice |
+----+---------------+--------+------------------+---------------+
| 1 | Xbox 360 | 1 | 0 | 225,00 |
+----+---------------+--------+------------------+---------------+
| 2 | Xbox 360 | 2 | 99,00 | 175,00 |
+----+---------------+--------+------------------+---------------+
| 3 | Xbox 360 | 3 | 0 | 135,00 |
+----+---------------+--------+------------------+---------------+
| 4 | Playstation 3 | 1 | 0 | 189,00 |
+----+---------------+--------+------------------+---------------+
| 5 | Playstation 3 | 2 | 125,00 | 165,00 |
+----+---------------+--------+------------------+---------------+
| 6 | Playstation 3 | 3 | 110,00 | 185,00 |
+----+---------------+--------+------------------+---------------+
To enhance the comprehension
I don't want to display 110,00 as the Min price of the stored procedure's result because its market (C) is Suspended.
What I already did
I already tried the following, but without success:
BEGIN
[...]
Where product.Name = 'Playstation 3'
And marketSituation.Id <> 3;
END
What happens? The And condition does nothing. The query keeps returning me the price of the suspended market.
Select Min(Case When product.PromotionalPrice = 0
Then product.OriginalPrice Else
Least(product.PromotionalPrice, product.OriginalPrice)
End) As minProductPrice,
Max(Case When product.PromotionalPrice = 0
Then product.OriginalPrice Else
Least(product.PromotionalPrice, product.OriginalPrice)
End) As maxProductPrice
From products As product
Inner join markets on product.market = markets.id AND markets.SituationId <> 3
Where product.Name = 'Playstation 3';
How about something like
Select Min(Case When product.PromotionalPrice = 0
Then product.OriginalPrice Else
Least(product.PromotionalPrice, product.OriginalPrice)
End) As minProductPrice,
Max(Case When product.PromotionalPrice = 0
Then product.OriginalPrice Else
Least(product.PromotionalPrice, product.OriginalPrice)
End) As maxProductPrice
From products As product INNER JOIN
Markets ON Product.Market = Markets.Id
Where product.Name = 'Playstation 3'
AND Markets.SituationID <> 3