Cant figure out self join query - mysql

I have a table with the following structure.
+-----------------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------+--------------+------+-----+---------+-------+
| linq_order_num | char(32) | NO | PRI | NULL | |
| order_status_id | int(11) | YES | MUL | NULL | |
| order_id | varchar(100) | YES | | NULL | |
| item_name | varchar(120) | YES | | NULL | |
| item_cost | float | YES | | NULL | |
| custmer_id | int(11) | YES | MUL | NULL | |
| order_date_time | datetime | YES | | NULL | |
| order_category | varchar(120) | YES | | NULL | |
| ordered_by | int(11) | YES | MUL | NULL | |
| linq_shipping_cost | float | YES | | NULL | |
| website_shipping_cost | float | YES | | NULL | |
| total_cost | float | YES | | NULL | |
| advance_amount | float | YES | | NULL | |
| website | varchar(120) | YES | | NULL | |
| other | varchar(120) | YES | | NULL | |
| rvn | int(11) | YES | | NULL | |
| received_date | datetime | YES | | NULL | |
| delivered_date | datetime | YES | | NULL | |
| store_id | int(11) | YES | MUL | NULL | |
+-----------------------+--------------+------+-----+---------+-------+
So for every day I need to find the total order cost.I can get it by using this query.
select sum(total_cost), date_format(order_date_time,"%Y-%m-%d") from
order_item group by date_format(order_date_time,"%Y-%m-%d")
Also I need the total remaining amount paid on the delivered dates.
select sum(total_cost-advance_amount),date_format(delivered_date,"%Y-%m-%d")
from order_item group by date_format(delivered_date,"%Y-%m-%d")
Not all the days, orders will happen and not all the days deliveries will happen.If there is day with no orders the total cost for that day should be shown as zero and the total remaining amount shown should be sum of (total_cost-advance_amount) for the day.
Is there a way I could combine the above two queries in one query and get the result?
So to summarise for a particular day d:
I need sum(total_cost) where ordered_date_time = d ,
I need sum(total_cost -advance_amount) where delivered_date = d
Essentially looking for a table like this:
Date Total Cost Total Delivery Amounts
d 500 2000
d1 0 900
d2 900 0
I tried using a subquery. The problem is it doesn't display the cases for d1, where is total cost for that day is 0.
Query:
select
date_format(order_date_time,"%Y-%m-%d") date,
sum(total_cost) total,
sum(advance_amount) advance_amount,
IFNULL( (select sum(total_cost-advance_amount)
from order_item a
where date_format(a.delivered_date,"%Y-%m-%d") = date_format(d.order_date_time,"%Y-%m-%d") ),0 ) delivery_amount
from order_item d
group by date_format(order_date_time,"%Y-%m-%d"), delivery_amount

You can use your two queries as derived tables and join them on date. The problem is, that you would need a FULL OUTER JOIN, which is not supported by MySQL. So you first need to extract all the dates from both columns
select date(order_date_time) as d from order_item
union
select date(delivered_date) as d from order_item
und use a left join with your queries
select
dates.dt,
coalesce(tc.total_cost, 0),
coalesce(tm.total_remaining, 0)
from (
select date(order_date_time) as dt from order_item
union
select date(delivered_date) as dt from order_item
) dates
left join (
select sum(total_cost) as total_cost, date(order_date_time) as dt
from order_item
group by dt
) tc using(dt)
left join (
select sum(total_cost-advance_amount) as total_remaining, date(delivered_date)
from order_item
group by dt
) tm using(dt)
I also replaced date_format(..) with date(..). You can format the dates in the outer select or in your application.

Related

MYSQL (MariaDB) - Invalid use of group function

I have two tables called addresses and house_sales
addresses
+-------------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| house_number_name | varchar(150) | NO | | NULL | |
| address_line1 | varchar(150) | NO | MUL | NULL | |
| address_line2 | varchar(150) | YES | | NULL | |
| address_line3 | varchar(150) | YES | MUL | NULL | |
| town_city | varchar(150) | NO | MUL | NULL | |
| district | varchar(150) | YES | MUL | NULL | |
| county | varchar(150) | YES | MUL | NULL | |
| post_code | varchar(8) | NO | MUL | NULL | |
| updated_at | datetime | NO | | NULL | |
| created_at | datetime | NO | | NULL | |
+-------------------+------------------+------+-----+---------+----------------+
house_sales
+---------------+------------------------------------------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------------------------------------------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| address_id | int(11) unsigned | NO | MUL | NULL | |
| price | int(11) unsigned | NO | MUL | NULL | |
| date | datetime | NO | MUL | NULL | |
| updated_at | datetime | NO | | NULL | |
| created_at | datetime | NO | | NULL | |
+---------------+------------------------------------------------------------+------+-----+---------+----------------+
I'm trying to select all the addresses grouped by address_line1 and then getting the average price for that street. The query works but I want to only select where there is more than one house on the same street. However when I add the AND count(*) > 1 I get the error "Invalid use of group function". Below is the query
SELECT count(*) as total_sales, avg(price) as average_price, `address_line1`, `town_city`
FROM `house_sales` `hs`
LEFT JOIN `addresses` `a` ON `hs`.`address_id` = `a`.`id`
WHERE `town_city` = 'London'
AND count(*) > 1
GROUP BY `address_line1`
ORDER BY `average_price` desc
I'm not sure why I'm getting this error. I've tried a sub query so I can use HAVING but haven't got this to work. Any help or pointers would be appreciated
You need a having clause to filter on the aggregate expression:
SELECT count(*) as total_sales, avg(price) as average_price, `address_line1`, `town_city`
FROM `house_sales` `hs`
LEFT JOIN `addresses` `a` ON `hs`.`address_id` = `a`.`id`
WHERE `town_city` = 'London'
GROUP BY `address_line1`, `town_city`
HAVING count(*) > 1
ORDER BY `average_price` desc
MySQL extends the SQL standard by allowing the use of aliases in the having clause, so you can also do:
having total_sales > 1
Side notes:
as commented by jarlh, it is a good practice to qualify (prefix) all column names with the table they belong to
it is also a good practice to put all non-aggregated columns in the group by clause (I added town_city, which was missing in your original query) - newer versions of MySQL do not allow this by default
quoting all identifiers is usually not necessary (unless they contain special characters)
There are two ways to go here. One would be to add town_city to the GROUP BY list:
SELECT
address_line1,
town_city,
COUNT(*) AS total_sales,
AVG(price) AS average_price
FROM house_sales hs
LEFT JOIN addresses a ON hs.address_id = a.id
WHERE town_city = 'London'
GROUP BY address_line1, town_city
HAVING COUNT(*) > 1
ORDER BY average_price DESC;
The other would be to just keep your current query but remove town_city from the select list, since you are restricting to just London anyway.
SELECT
address_line1,
COUNT(*) AS total_sales,
AVG(price) AS average_price
FROM house_sales hs
LEFT JOIN addresses a ON hs.address_id = a.id
WHERE town_city = 'London'
GROUP BY address_line1
HAVING COUNT(*) > 1
ORDER BY average_price DESC;

Compute column value with CASE syntax

I am pretty confused and absolutely not sure if this is the right way.
In the example below I am trying to check if the promotion type is 1 ( percentage e.g. 10% ) or 2 ( hard price e.g 10 EUR ) and compute the price after it and that only if main_product_id IS NOT NULL. Otherwise the price stays the same.
SELECT p.price
FROM product as p
LEFT JOIN promotion_product as pp ON p.id=pp.main_product_id
LEFT JOIN promotion as pr ON pp.promo_id=pr.id
(
CASE
WHEN pp.main_product_id IS NOT NULL THEN
CASE
WHEN pr.type=1 THEN p.price = p.price - (p.price * pr.value/100)
WHEN pr.type=2 THEN p.price = p.price - pr.value
END
END
)
What I get as error is:
#1305 - FUNCTION pr.id does not exist
This is pretty clear I know. But how to compute the new price and is it possible with CASE syntax ?
product table :
+----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| category_id | int(11) | YES | MUL | NULL | |
| brand_id | int(11) | YES | MUL | NULL | |
| sort | int(11) | NO | | 999 | |
| enable | tinyint(1) | NO | MUL | 2 | |
| product_number | varchar(255) | YES | MUL | NULL | |
| price | float | YES | | NULL | |
| quantity | float | YES | | NULL | |
| rating | tinyint(4) | NO | | 0 | |
+----------------+--------------+------+-----+---------+----------------+
promotion table:
+------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| start_date | timestamp | YES | | NULL | |
| end_date | timestamp | YES | | NULL | |
| type | tinyint(4) | NO | | NULL | |
| value | float | NO | | NULL | |
| enable | tinyint(4) | NO | | 2 | |
+------------+------------+------+-----+---------+----------------+
promotion_product table:
+-----------------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| promo_id | int(11) | NO | MUL | NULL | |
| product_id | int(11) | YES | MUL | NULL | |
| main_product_id | int(11) | YES | MUL | NULL | |
+-----------------+---------+------+-----+---------+----------------+
CASE...WHEN...THEN expressions are for use in SELECT clauses. You have yours wrapped in parentheses at the end of your query. Because your query looks like pr.id(yadda yadda) MySQL thinks pr.id should be a function. It isn't, so MySQL throws your error.
Try something like this:
SELECT p.price,
CASE
WHEN pr.type=1 THEN p.price = p.price - (p.price * pr.value/100)
WHEN pr.type=2 THEN p.price = p.price - pr.value
ELSE p.price
END promoted_price
FROM product as p
LEFT JOIN promotion_product as pp ON p.id=pp.main_product_id
LEFT JOIN promotion as pr ON pp.promo_id=pr.id
I refactored your case expressions so it's not nested. The ELSE clause deals with the default cases where pr.type isn't 1 or 2, and where the ON clauses of your left joins don't match anything.
This is the way I will approach your goal:
SELECT
IF(pp.main_product_id IS NOT NULL,
CASE
WHEN pr.type = 1 THEN p.price - (p.price * pr.value / 100)
WHEN pr.type = 2 THEN p.price - pr.value
ELSE p.price -- Added a default case.
END,
p.price) AS finalPrice
FROM
product AS p
LEFT JOIN
promotion_product AS pp ON p.id = pp.main_product_id
LEFT JOIN
promotion AS pr ON pp.promo_id = pr.id
Note also, that you have start_date and end_date on your promotion table that you are currently ignoring.

MYSQL Counting matching results across multiple tables

I have the following tables
Business
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| b_id | bigint(20) | NO | PRI | NULL | auto_increment |
| b_name | varchar(255) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
Locations
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| l_id | bigint(20) | NO | PRI | NULL | auto_increment |
| l_name | varchar(255) | NO | | NULL | |
| b_id | big(20) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
Jobs
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| j_id | bigint(20) | NO | PRI | NULL | auto_increment |
| j_name | varchar(255) | NO | | NULL | |
| b_id | bigint(20) | NO | | NULL | |
| l_id | bigint(20) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
People
+-------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------+------+-----+---------+----------------+
| u_id | bigint(20) | NO | PRI | NULL | auto_increment |
| salutation | varchar(10) | NO | | NULL | |
| first_name | varchar(25) | NO | | NULL | |
| last_name | varchar(25) | NO | | NULL | |
+-------------+---------------+------+-----+---------+----------------+
People's Jobs
+-------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------+------+-----+---------+----------------+
| pj_id | bigint(20) | NO | PRI | NULL | auto_increment |
| u_id | bigint(20) | NO | | NULL | |
| j_id | bigint(20) | NO | | NULL | |
| l_id | bigint(20) | NO | MUL | NULL | |
+-------------+------------+------+-----+---------+----------------+
I need to produce a table that shows
+----------+-------------------------+------------+------------+------------+
| b_id | b_name | Locations | Jobs | People |
+----------+-------------------------+------------+------------+------------+
| 21 | Widgets Inc | 0 | x | 0 |
| 24 | Prince Privates | 0 | 0 | 0 |
| 23 | Halon plc | x | 0 | 0 |
| 18 | Stinky Hotels | x | x | x |
| 20 | Pylon Catering Corps | x | x | x |
| 22 | Skytrain Biscuits | 0 | 0 | 0 |
+----------+-------------------------+------------+------------+------------+
I can achieve a correct count of matching locations for each business with:
SELECT b.b_id,
b.b_name,
count(l.l_id) AS locations
FROM business AS b
LEFT JOIN locations AS l ON b.b_id=l.b_id
GROUP BY b.b_id
ORDER BY b_name
If I extend it to include a count of the jobs at each business and then the count of people at each business it all goes pear shaped.
I know that the following is inherently wrong with regards to getting the count of people (as people can hold more than 1 job). I don't know if I need to use sub selects or COALESCE?
SELECT b.b_id,
b.b_name,
count(l.l_id) AS locations,
count(j.j_id) AS jobs,
count(p.u_id) AS people
FROM business AS b
LEFT JOIN locations AS l ON b.b_id=l.b_id
LEFT JOIN job AS j ON b.b_id=j.b_id
LEFT JOIN people_jobs AS p ON l.l_id=p.l_id
GROUP BY b.b_id
ORDER BY b_name
I think you can do a quick-and-dirty fix of your query by using count(distinct):
SELECT b.b_id, b.b_name,
count(distinct l.l_id) AS locations,
count(distinct j.j_id) AS jobs,
count(distinct p.u_id) AS people
FROM business b LEFT JOIN
locations l
ON b.b_id = l.b_id LEFT JOIN
job j
ON b.b_id = j.b_id LEFT JOIN
people_jobs p
ON l.l_id = p.l_id
GROUP BY b.b_id
ORDER BY b_name ;
It is also possible that the problem is simply that the join to people_jobs needs more conditions:
people_jobs p
ON l.l_id = p.l_id and j.j_id = p.j_id
And maybe a condition on u.
Your problem is that you are trying to do aggregation across multiple dimensions and getting a cartesian product for each business. An alternative that is sometimes necessary is to do the counts in subqueries.
This query should do what you need:
SELECT
b.b_id,
b.b_name,
(SELECT COALESCE(COUNT(l_id ),0) FROM locations WHERE b_id=b.b_id) AS locations,
(SELECT COALESCE(COUNT(j_id ),0) FROM jobs WHERE b_id=b.b_id) AS jobs,
(SELECT COALESCE(COUNT(DISTINCT u_id),0)
FROM jobs j
JOIN people_jobs pj ON pj.j_id=j.j_id
WHERE j.b_id=b.b_id
) AS people
FROM business as b
ORDER BY b_name
You don't need the GROUP BY if you use subSELECTs, as the outer query will return 1 row per b_id, no more.
If instead you do JOIN the 4 tables at the main query level, like you were doing, you have two difficulties:
number of rows increases (avoidable with GROUP BY)
a simple COUNT does not work properly (avoidable with COUNT(DISTINCT
...))
(as shown in Gordon's answer)
You can try This Query:-
SELECT b.b_id,b.b_name,count(l.l_id) AS locations,count(j.j_id) AS jobs,count(p.u_id) AS people
FROM business as b LEFT JOIN locations as l ON b.b_id=l.b_id
LEFT JOIN job as j ON b.b_id=j.b_id
LEFT JOIN people_jobs as p ON l.l_id=p.l_id
GROUP BY b.b_id, b.b_name
ORDER BY b_name
I hope this will work for you.

Query returning multiple objects when only one is expected

I have a simple table:
+-------------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+--------------+------+-----+---------+-------+
| ID | bigint(20) | NO | PRI | NULL | |
| AdmissionDateTime | datetime | NO | | NULL | |
| AdmissionEvent | varchar(255) | YES | | NULL | |
| DischargeDateTime | datetime | YES | | NULL | |
| DischargeEvent | varchar(255) | YES | | NULL | |
| DemographicId | bigint(20) | NO | MUL | NULL | |
| FacilityId | bigint(20) | YES | MUL | NULL | |
| VisitId | bigint(20) | NO | MUL | NULL | |
| WardId | bigint(20) | NO | MUL | NULL | |
+-------------------+--------------+------+-----+---------+-------+
On which I run the following JPA (Spring-data) query:
#Query("SELECT w FROM WardTransaction w WHERE w.id = (SELECT MAX(x.id) FROM
WardTransaction x WHERE w = x AND w.visit = :visit)")
public WardTransaction findCurrent(#Param("visit") Visit visit);
On occasions I get the following exception.
org.springframework.dao.IncorrectResultSizeDataAccessException: More than one
result was returned from Query.getSingleResult(); nested exception is
javax.persistence.NonUniqueResultException: More than one result was returned from
Query.getSingleResult()
I have not been able to work out why this is happening. It does not seem to make a lot of sense to me as there can only be one 'MAX' - especially on Id (I have used 'admissionDate' in the past).
Any assistance appreciated.
why are you selecting table ? you should select columns .
try this
#Query("SELECT * FROM WardTransaction w WHERE w.id in (SELECT MAX(x.id)
FROM WardTransaction x WHERE w.id = x.id AND w.visit = :visit)")
This query is simpler and I think would get you what you want:
SELECT something
FROM sometable
Where something = someotherthing
ORDER BY sometable.id DESC
LIMIT 1
Basically it returns the results with the highest IDs at the top and grabs the first one.

Replacing right join with derived table to left join

How do i write this query, with left join. since the framework i use doesn't support right join i need to rewrite the query. Can any one suggest me a possible solution.
select Audit.history_id,Audit.field,modifiedtime,operation from Audit right join (select History.history_id from History where refid=2000000020088 order by
modifiedtime limit 5) as Hist on Audit.history_id=Hist.history_id;
desc Audit
+------------------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------------+------------+------+-----+---------+-------+
| AUDIT_ID | bigint(19) | | PRI | 0 | |
| HISTORY_ID | bigint(19) | | MUL | 0 | |
| FIELD | varchar(50) | | | |
| OLD_VALUE | varchar(50)| YES | | NULL | |
| NEW_VALUE | varchar(50)| YES | | NULL | |
+------------------+------------+------+-----+---------+-------+
desc History
+---------------+-------------+------+-----+---------------------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+-------------+------+-----+---------------------+-------+
| HISTORY_ID | bigint(19) | | PRI | 0 | |
| REFID | bigint(19) | | MUL | 0 | |
| OPERATION | varchar(50) | YES | | NULL | |
| MODIFIED_TIME | datetime | | | 0000-00-00 00:00:00 | |
+---------------+-------------+------+-----+---------------------+-------+
Simply switch the relations for a left join:
In practice, explicit right outer joins are rarely used, since they can always be replaced with left outer joins (with the table order switched) and provide no additional functionality.
Source:Wikipedia.org
select
Audit.history_id,Audit.field,modifiedtime,operation
from
(
select History.history_id
from History where refid=2000000020088
order by modifiedtime limit 5
) as Hist
left join Audit on (Audit.history_id = Hist.history_id);
Not sure if this will produce exactly same output as the one you have now, but it might give you the right idea:
select Audit.history_id, Audit.field, History.modifiedtime, History.operation
from History
left join Audit on Audit.history_id=History.history_id
where History.refid=2000000020088
order by History.modifiedtime