mysql right join and conditions - mysql

In the following query I'm doing the join of a table calendar and a second table where I need to specify some parameters to the query.
The join of the tables is performed on the field calendar.datefield and store_product.created
The problem is that I'm filtering by id_store = 3 and another row in 07/20/2012 store_product table record has id_store = 4 and thus the query does not bring the result. Is there any way to show this result as NULL, forcing mysql to ignore this case?
Result: (the row containing the date 2012-07-20 is not being displayed because it has other id_store)
DATE price
2012-07-17 NULL
2012-07-18 700.00
2012-07-19 NULL
2012-07-21 NULL
2012-07-22 NULL
2012-07-23 NULL
I would like to display
DATE price
2012-07-17 NULL
2012-07-18 700.00
2012-07-19 NULL
->>> 2012-07-20 NULL
2012-07-21 NULL
2012-07-22 NULL
2012-07-23 NULL
The query:
set #id_store = 3;
set #id_product = 11;
SELECT
calendar.datefield as DATE,
t1.price
FROM
store_product t1
RIGHT JOIN
calendar ON (DATE(t1.created) = calendar.datefield)
WHERE
(calendar.datefield BETWEEN ('2012-07-17') and ('2012-07-23'))
AND (t1.id_store = #id_store OR t1.id_store is NULL)
AND (t1.id_product = #id_product OR t1.id_product is NULL)
AND (t1.created = (select
max(f2.created)
from
store_product f2
where
f2.id_store = t1.id_store
and f2.id_product = t1.id_product
and DATE(t1.created) = DATE(f2.created))
OR t1.created is NULL)
GROUP BY DATE , t1.id_store , t1.id_product

That record cannot be displayed (obviously) because you are filtering out stores only with ID of 3. The RIGHT JOIN does what it's supposed to do, then then you're further reducing results with the filter. I think you'll have to handle this in your main code to add the "missing" records for you. I don't think SQL can do what you want it to do.

Related

List join column as null value if there is no match in the other table

My problem is simple: I want to list everything from my "form" table and if there aren't any matches to this "form" in the "outstanding" table, then this row should be also listed only with NULL in the PAYED_GROSS_AMOUNT column (the "outstanding" table is used for example for this column). Right now with this query all I get are the "form" matches, which have also matching entries in the "outstanding" table:
SELECT
`f`.`ID` AS `ID`,
`f`.`FORM_NR` AS `form_nr`,
`f`.`DELIVERY_DATE` AS `delivery_date`,
`f`.`FORM_DATE` AS `form_date`,
`f`.`PAYMENT_DATE` AS `payment_date`,
MAX(`os`.`PAYED_DATE`) AS `payed_date`,
`fi`.`GROSS_MONEY` AS `gross_money`,
`fi`.`NET_PRICE` AS `net_price`,
ifnull(SUM(`os`.`PAYED_GROSS_AMOUNT`), 0) AS `payed_gross_amount`,
ifnull((`fi`.`GROSS_MONEY`
- SUM(`os`.`PAYED_GROSS_AMOUNT`)),
`fi`.`GROSS_MONEY`) AS `remaining_amount`
FROM `form` `f`
LEFT JOIN `outstanding` `os` ON `f`.`ID` = `os`.`INVOICE_ID`,
(SELECT form_id AS `FORM_ID`,
SUM(gross_money) AS `GROSS_MONEY`,
SUM(net_price) AS `NET_PRICE`
FROM form_item
GROUP BY form_id) fi
WHERE `f`.SUBTYPE <> 3
AND `fi`.FORM_ID = `f`.ID
AND `f`.STATUS = 2
AND `f`.DIRECTION = 1
AND `os`.DELETED <> 'deleted'
AND (`f`.PAYMENT_TYPE = 2 OR `f`.PAYMENT_TYPE = 4)
AND `f`.FORM_TYPE = 'Invoice'
AND `f`.deleted <> 'deleted'
GROUP BY `f`.`ID`,
`fi`.`form_id`
ORDER BY `f`.`FORM_DATE` DESC;
I also tried with LEFT OUTER JOIN but with no luck :(
Thank you!
EDIT:
Query simplified as possible (you can ignore the WHERE clauses in the end)
Expected result:
form table:
ID FORM_NR ...
1 2019/1
2 2019/2
3 2019/4
4 2019/7
...
outstanding table:
INVOICE_ID PAYED_DATE PAYED_GROSS_AMOUNT ...
2 2019-02-05 100
3 2019-02-06 200
...
Result:
FORM_NR FORM_DATE ... PAYED_DATE PAYED_GROSS_AMOUNT
2019/1 2019-02-01 null 0
2019/2 2019-02-02 2019-02-05 100
2019/4 2019-02-03 2019-02-06 200
2019/7 2019-02-04 null 0
...
PAYED_GROSS_AMOUNT is because of the IFNULL method 0 and not NULL.
Use NOT EXISTS. Something like this:
select . . .
from form f
where <conditions on form> and
not exists (select 1
from outstanding o
where o.form_item = f.id and
<conditions on outstanding>
);

I need help regarding JOIN query in mysql

I have started learning MySQL and I'm having a problem with JOIN.
I have two tables: purchase and sales
purchase
--------------
p_id date p_cost p_quantity
---------------------------------------
1 2014-03-21 100 5
2 2014-03-21 20 2
sales
--------------
s_id date s_cost s_quantity
---------------------------------------
1 2014-03-21 90 9
2 2014-03-22 20 2
I want these two tables to be joined where purchase.date=sales.date to get one of the following results:
Option 1:
p_id date p_cost p_quantity s_id date s_cost s_quantity
------------------------------------------------------------------------------
1 2014-03-21 100 5 1 2014-03-21 90 9
2 2014-03-21 20 2 NULL NULL NULL NULL
NULL NULL NULL NULL 2 2014-03-22 20 2
Option 2:
p_id date p_cost p_quantity s_id date s_cost s_quantity
------------------------------------------------------------------------------
1 2014-03-21 100 5 NULL NULL NULL NULL
2 2014-03-21 20 2 1 2014-03-21 90 9
NULL NULL NULL NULL 2 2014-03-22 20 2
the main problem lies in the 2nd row of the first result. I don't want the values
2014-03-21, 90, 9 again in row 2... I want NULL instead.
I don't know whether it is possible to do this. It would be kind enough if anyone helps me out.
I tried using left join
SELECT *
FROM sales
LEFT JOIN purchase ON sales.date = purchase.date
output:
s_id date s_cost s_quantity p_id date p_cost p_quantity
1 2014-03-21 90 9 1 2014-03-21 100 5
1 2014-03-21 90 9 2 2014-03-21 20 2
2 2014-03-22 20 2 NULL NULL NULL NULL
but I want 1st 4 values of 2nd row to be NULL
Since there are no common table expressions or full outer joins to work with, the query will have some duplication and instead need to use a left join unioned with a right join;
SELECT p_id, p.date p_date, p_cost, p_quantity,
s_id, s.date s_date, s_cost, s_quantity
FROM (
SELECT *,(SELECT COUNT(*) FROM purchase p1
WHERE p1.date=p.date AND p1.p_id<p.p_id) rn FROM purchase p
) p LEFT JOIN (
SELECT *,(SELECT COUNT(*) FROM sales s1
WHERE s1.date=s.date AND s1.s_id<s.s_id) rn FROM sales s
) s
ON s.date=p.date AND s.rn=p.rn
UNION
SELECT p_id, p.date p_date, p_cost, p_quantity,
s_id, s.date s_date, s_cost, s_quantity
FROM (
SELECT *,(SELECT COUNT(*) FROM purchase p1
WHERE p1.date=p.date AND p1.p_id<p.p_id) rn FROM purchase p
) p RIGHT JOIN (
SELECT *,(SELECT COUNT(*) FROM sales s1
WHERE s1.date=s.date AND s1.s_id<s.s_id) rn FROM sales s
) s
ON s.date=p.date AND s.rn=p.rn
An SQLfiddle to test with.
In a general sense, what you're looking for is called a FULL OUTER JOIN, which is not directly available in MySQL. Instead you only get LEFT JOIN and RIGHT JOIN, which you can UNION together to get essentially the same result. For a very thorough discussion on this subject, see Full Outer Join in MySQL.
If you need help understanding the different ways to JOIN a table, I recommend A Visual Explanation of SQL Joins.
The way this is different from a regular FULL OUTER JOIN is that you're only including any particular row from either table at most once in the JOIN result. The problem being, if you have one purchase record and two sales records on a particular day, which sales record is the purchase record associated with? What is the relationship you're trying to represent between these two tables?
It doesn't sound like there's any particular relationship between purchase and sales records, except that some of them happened to take place on the same day. In which case, you're using the wrong tool for the job. If all you want to do is display these tables side by side and line the rows up by date, you don't need a JOIN at all. Instead, you should SELECT each table separately and do your formatting with some other tool (or manually).
Here's another way to get the same result, but the EXPLAIN for this is horrendous; and performance with large sets is going to be atrocious.
This is essentially two queries UNIONed together. The first query is essentially "purchase LEFT JOIN sales", the second query is essentially "sales ANTI JOIN purchase".
Because there is no foreign key relationship between the two tables, other than rows matching on date, we have to "invent" a key we can join on; we use user variables to assign ascending integer values to each row within a given date, so we can match row 1 from purchase to row 1 from sales, etc.
I wouldn't normally generate this type of result using SQL; it's not a typical JOIN operation, in the sense of how we traditionally join tables.
But, if I had to produce the specified resultset using MySQL, I would do it like this:
SELECT p.p_id
, p.p_date
, p.p_cost
, p.p_quantity
, s.s_id
, s.s_date
, s.s_cost
, s.s_quantity
FROM ( SELECT #pl_i := IF(pl.date = #pl_prev_date,#pl_i+1,1) AS i
, #pl_prev_date := pl.date AS p_date
, pl.p_id
, pl.p_cost
, pl.p_quantity
FROM purchase pl
JOIN ( SELECT #pl_i := 0, #pl_prev_date := NULL ) pld
ORDER BY pl.date, pl.p_id
) p
LEFT
JOIN ( SELECT #sr_i := IF(sr.date = #sr_prev_date,#sr_i+1,1) AS i
, #sr_prev_date := sr.date AS s_date
, sr.s_id
, sr.s_cost
, sr.s_quantity
FROM sales sr
JOIN ( SELECT #sr_i := 0, #sr_prev_date := NULL ) srd
ORDER BY sr.date, sr.s_id
) s
ON s.s_date = p.p_date
AND s.i = p.i
UNION ALL
SELECT p.p_id
, p.p_date
, p.p_cost
, p.p_quantity
, s.s_id
, s.s_date
, s.s_cost
, s.s_quantity
FROM ( SELECT #sl_i := IF(sl.date = #sl_prev_date,#sl_i+1,1) AS i
, #sl_prev_date := sl.date AS s_date
, sl.s_id
, sl.s_cost
, sl.s_quantity
FROM sales sl
JOIN ( SELECT #sl_i := 0, #sl_prev_date := NULL ) sld
ORDER BY sl.date, sl.s_id
) s
LEFT
JOIN ( SELECT #pr_i := IF(pr.date = #pr_prev_date,#pr_i+1,1) AS i
, #pr_prev_date := pr.date AS p_date
, pr.p_id
, pr.p_cost
, pr.p_quantity
FROM purchase pr
JOIN ( SELECT #pr_i := 0, #pr_prev_date := NULL ) prd
ORDER BY pr.date, pr.p_id
) p
ON p.p_date = s.s_date
AND p.i = s.i
WHERE p.p_date IS NULL
ORDER BY COALESCE(p_date,s_date),COALESCE(p_id,s_id)

Challenging LEFT OUTER JOIN query grouping by MAX

I have the following two tables:
BillingMatrixDefinition
- id
- amount
BillingMatrix
- definition (FK to table above)
- service_id (FK)
- provider_id (FK)
- amount (Decimal)
I need to get all BillingMatrixDefinitions that have the service_id and provider_id that I specify. Here is the SQL query I currently have:
select def.id, service_id, provider_id,
(case when matrix.amount is not null then matrix.amount else def.amount end) amount
from billing_billingdefinition def
left outer join billing_billingmatrix matrix
on matrix.definition_id=def.id
where (service_id = 25 or service_id is null)
and (provider_id = 24 or provider_id is null)
This gives me the following results:
id service_id provider_id amount
1 25 24 200.00
1 NULL 24 300.00
2 NULL 24 800.00
3 NULL NULL 750.00
5 NULL NULL 450.00
6 NULL NULL 750.00
However, I need to get the billing amount per id, so I can only get ONE item/amount for each id. In which case, I want to get the item where the service_id=24, and if that doesn't exist, then get it where service_id=NULL.
The correct query should give me the following results:
id service_id provider_id amount
1 25 24 200.00
2 NULL 24 800.00
3 NULL NULL 750.00
5 NULL NULL 450.00
6 NULL NULL 750.00
Notice how now there is no duplicate entry for 1, and I use the line item where a service_id has been entered (use that one if it exists, else use NULL). What would be the correct query to do this?
Anothr way:
SELECT
def.id AS id,
COALESCE(matrix.service_id, matrix2.service_id) AS service_id,
COALESCE(matrix.provider_id, matrix2.provider_id) AS provider_id,
COALESCE(matrix.amount, matrix2.amount, def.amount) AS amount
FROM
billing_billingdefinition AS def
LEFT JOIN
billing_billingmatrix AS matrix
ON matrix.definition_id = def.id
AND matrix.service_id = 25
AND matrix.provider_id = 24
LEFT JOIN
billing_billingmatrix AS matrix2
ON matrix2.definition_id = def.id
AND matrix2.service_id IS NULL
AND matrix2.provider_id = 24 ;
Try something along these lines (utilizing a temporary table):
CREATE TEMPORARY TABLE Results
select def.id, service_id, provider_id,
(case when matrix.amount is not null then matrix.amount else def.amount end) amount
from billing_billingdefinition def
left outer join billing_billingmatrix matrix
on matrix.definition_id=def.id
where (service_id = 25 or service_id is null)
and (provider_id = 24 or provider_id is null);
SELECT *
FROM Results r1
WHERE IFNULL(r1.service_id, 0) =
( SELECT MAX(IFNULL(r2.service_id, 0))
FROM Results r2
WHERE r2.id = r1.id
);
SQL Fiddle for the 2nd part only (uses already created Results table)
You need to aggregate the amount using max() (and of course add a group-by clause) so you get the non-null value if one exists:
select
def.id, service_id, provider_id,
max(case when matrix.amount is not null then matrix.amount else def.amount end) amount
from billing_billingdefinition def
left outer join billing_billingmatrix matrix
on matrix.definition_id=def.id
where (service_id = 25 or service_id is null)
and (provider_id = 24 or provider_id is null)
group by def.id, service_id, provider_id
Something like this might work as well.
select def.id, service_id, provider_id, IFNULL(matrix.amount,def.amount) amount
from billing_billingdefinition def
left outer join (select definition_id, max(service_id) as maxsid from billing_billingmatrix matrix group by definition_id) as t1
on def.id = t1.definition_id
left outer join billing_billingmatrix matrix
on matrix.definition_id=def.id and maxsid <=> service_id
I believe PM 771's answer works here as well, but I decided to use a subselect in the OUTER JOIN table, to pre-filter the results before joining.
Here is the final SQL that worked for this:
SELECT *,
(CASE WHEN matrix.amount IS NOT NULL THEN matrix.amount ELSE def.amount END) calculated_amount,
FROM billing_billingdefinition def
LEFT OUTER JOIN
(SELECT t.* FROM (
select * from billing_billingmatrix
where (provider_id=25
or provider_id is null)
and (service_id=24 or service_id is null)
ORDER BY service_id DESC
) t GROUP BY t.definition_id) matrix
ON matrix.definition_id=def.id
Try this:
SELECT BMD.id, BM.service_id, BM.provider_id, IFNULL(BM.amount, BMD.amount) AS amount
FROM BillingMatrixDefinition BMD
LEFT JOIN BillingMatrix BM ON BMD.id = BM.definition_id AND (BM.service_id = 25 OR BM.provider_id = 24)
GROUP BY BMD.id;

SQL join and sub query in sql

I have a table employee having columns id (primary key), *employee_name* and another table called employee_works with columns *employee_id* (foreign key referencing employee.id), *start_date* (datetime), *finish_date* (datetime).
Here are some datas for employee table:
**id** **employee_name**
1 employee A
2 employee B
3 employee C
4 employee D
5 employee E
6 employee F
7 employee G
employee_works table:
1 2010-01-01 00:00:00 NULL
2 2010-01-01 00:00:00 2010-01-10 10:00:00"
2 2010-01-13 00:00:00 2010-01-15 10:00:00"
2 2010-01-31 00:00:00 NULL
4 2010-02-18 00:00:00 2011-01-31 00:00:00"
6 2010-02-18 00:00:00 NULL
NULL value means the employee still works.
I need to get a single query showing the list of persons in employee, if they worked with us, who still works in our company, who left and if possible, for how long they worked with us.
Example:
id employee_name status
1 Employee A Still with us
3 Employee C Never worked
4 Employee D Left
My attempt:
SELECT emp.id,emp.name,
CASE
WHEN occ.finish_date is NULL and occ.start_date is NOT NULL THEN 'Still working'
WHEN occ.finish_date is NULL and occ.start_date is NULL THEN 'Never Worked'
WHEN occ.finish_date is NOT NULL and occ.start_date is NOT NULL THEN 'Left'
END
AS status
FROM employee AS emp
LEFT JOIN employee_works AS occ ON emp.id=occ.employee_id
GROUP BY emp.id, occ.finish_date
I also want to get the total no of days the employees have worked in another column?
The problem is that you have a group by but no aggregations for the definition of status. Mysql does not give you a syntax error. Instead, it gives you a random status:
Try something like this instead:
select id, name,
(CASE WHEN statusint = 3
THEN 'Still working'
WHEN statusint = 1 or statusint is null
THEN 'Never Worked'
WHEN statusint = 2
THEN 'Left'
END) AS status,
days_worked
from (SELECT emp.id, emp.name,
max(CASE WHEN occ.departure_date is NULL and occ.start_date is NOT NULL
THEN 3
WHEN occ.departure_date is NULL and occ.start_date is NULL
THEN 1
WHEN occ.departure_date is NOT NULL and occ.start_date is NOT NULL
THEN 2
END) AS statusint,
sum(datediff(coalesce(departure_date, curdate()), occ.start_date
) as days_worked
FROM employee emp LEFT JOIN
employee_works occ
ON emp.id=occ.employee_id
GROUP BY emp.id, emp.name
) eg
This "feature" of mysql is called hidden columns. Folks who write mysql (and many who use it) think this is a great feature. Many people who use other databases just scratch their heads and wonder why any database would act so strangely.
By the way, you should check if someone who is employeed multiple times gets assigned a new id. If so, your query might need more advanced name matching methods.
Try to simplify your condition.
SELECT a.*,
CASE
WHEN b.employeeID IS NULL THEN 'NEVER WORKED'
WHEN b.finish_date IS NULL THEN 'STILL WORKING'
WHEN DATE(b.finish_date) < CURDATE() THEN 'LEFT'
END as `Status`
FROM employee a
LEFT JOIN employee_works b
on a.id = b.employeeID

MySQL merge amounts in a 2 rows

I'm looking to create a sql statement that will update a large set of data.
What I have is a table like
id, transid, amount, narative1, narative 2, total, active
1 1234 23.2 NULL NULL NULL 1
2 1234 120.33 NULL NULL NULL 1
3 1235 98.00 NULL NULL NULL 1
When there are two rows with the same transid I need to total them put the result in the total column of the first one with that transid and put the second amount in naritive2 of the first instance as well as make the second one inactive. It should ignore single rows for a transid.
The result of what I want to do should be:
id, transid, amount, narative1, narative 2, total, active
1 1234 23.2 NULL 120.33 143.53 1
2 1234 120.33 NULL NULL NULL 0
3 1235 98.00 NULL NULL NULL 1
I know a bit of a thong twister but..
Ideally I'd like to do this in just a MySQL statements. So I don't mind having to do multiple sql statements but I want to avoid connecting it to PHP etc. Its a very large set of data.
This will update only those transactions that have exactly 2 rows (not 1 and not 3 or more).
UPDATE mytable mtu
JOIN (
SELECT minid, maxid, mtmin.amount AS minamt, mtmax.amount AS maxamt
FROM (
SELECT MIN(id) AS minid, MAX(id) AS maxid
FROM mytable mti
GROUP BY
transid
HAVING COUNT(*) = 2
) mt
JOIN mytable mtmin
ON mtmin.id = minid
JOIN mytable mtmax
ON mtmax.id = maxid
) mts
ON id IN (minid, maxid)
SET narative2 = CASE id WHEN minid THEN minamt ELSE NULL END,
total = CASE id WHEN minid THEN minamt + maxamt ELSE NULL END,
active = (id = minid)