MySQL JOIN with expressions - mysql

I have two tables. One contains offers with amount of items, and second contains allocations of items to offers. I am trying to write a query that produces table showing offered amount of items and total allocated amount, per offer.
Sample table `offers`:
+----+----+----+
|oid |uid |amt |
+----+----+----+
| 1| 413| 10|
| 2| 297| 7|
+----+----+----+
Sample table `allocations`:
+----+----+------+
|aid |oid |alloc |
+----+----+------+
| 1| 2| 4|
| 2| 2| 2|
+----+----+------+
I want the following result:
+----+----+------+
|oid |amt |alloc |
+----+----+------+
| 1| 10| 0|
| 2| 7| 6|
+----+----+------+
I tried the following query:
SELECT `offers`.`oid`, `offers`.`amt`, COALESCE(SUM(`allocations`.`alloc`),0)
FROM `offers`
LEFT JOIN `allocations` ON `allocations`.`oid`=`offers`.`oid`
However, the sum works on the whole table, not just entries that satisfy
`allocations`.`oid`=`offers`.`oid`
and offers that have no allocations don't get printed.

Try using GROUP BY.
SELECT `offers`.`oid`, `offers`.`amt`, COALESCE(SUM(`allocations`.`alloc`),0)
FROM `offers`
LEFT JOIN `allocations` ON `allocations`.`oid`=`offers`.`oid`
GROUP BY offers.oid
(I assume offers.oid is your primary key?)
Strictly speaking, it should be
GROUP BY offers.oid, offers.amt
which is to say any column not the result of an aggregate function must be listed in the GROUP BY clause. Some servers are configured to enforce this, while some aren't.

Any aggregate function, such as sum() will operate in the entire resultset, unless you provide a group by clause.
SELECT `offers`.`oid`, `offers`.`amt`, COALESCE(SUM(`allocations`.`alloc`),0)
FROM `offers`
LEFT JOIN `allocations` ON `allocations`.`oid`=`offers`.`oid`
GROUP BY `offers`.`oid`, `offers`.`amt`
However, I do not get the 'offers that do not have allocations do not get printed' part, since the left join should take care of that, unless in your real query the order of the 2 tables are reversed.

So here you have to use group by clause to do sum for each oid...
SELECT `offers`.`oid`, `offers`.`amt`, COALESCE(SUM(`allocations`.`alloc`),0) FROM `offers`
LEFT JOIN `allocations` ON `allocations`.`oid`=`offers`.`oid` group by oid

Related

How do you get the max date from duplicate queries and remove duplicate

How do you get the records that contain the max value for each grouped set?
Product | Date out
Book 1| 01-01-2019
Book 2| 05-03-2021
Book 3| 06-05-2021
Book 1| 01-02-2019
Book 1| 30-11-2021
Book 1| 01-12-2022
I tried select book_id, max(book_dateout) from products;
Desired result set:
Book 1| 01-12-2022
Book 2| 05-03-2021
Book 3| 06-05-2021
MySQL usually represents dates in the format yyyy-mm-dd. In that case you could just use
select book_id, max(book_dateout)
from products
group by book_id;
If you have them stored as strings, you can use
select book_id, date_format(max(str_to_date(book_dateout, '%d-%m-%Y')), '%d-%m-%Y')
from products
group by book_id;

SQL statement to count corresponding entries while having a strict sequence (logical)

I want to aggregate the number of (identical) parts in my production plan while preserving the sequence.
My table looks like this:
-------------------------------------------------
|Part Number | Production Sequence Number | .... |
-------------------------------------------------
| 1| 1| .... |
--------------------------------------------------
| 1| 2| .... |
--------------------------------------------------
| 2| 3| .... |
--------------------------------------------------
| 2| 4| .... |
--------------------------------------------------
| 1| 5| .... |
--------------------------------------------------
And I need to count the amount of same parts in a row:
Expected result:
-------------------------------------------------
|Part Number | Nr of pieces in a row | .... |
-------------------------------------------------
| 1| 2| .... |
-------------------------------------------------
| 2| 2| .... |
-------------------------------------------------
| 1| 1| .... |
-------------------------------------------------
Can this be done by only unsing SQL (MySQL)?
You may use variable to emulate numbers and then perform GROUP BY
SELECT id, COUNT(*)
FROM
(
SELECT
#row_number:=CASE
WHEN #seq_num = id THEN #row_number
ELSE #row_number + 1
END AS num,
#seq_num:=id as id,
seq_num
FROM tab, (SELECT #row_number:=0, #seq_num := 1) t
ORDER BY seq_num
) t
GROUP BY num, id
demo
This is tricky in MySQL. One method is to use a correlated subquery to define the groups. You can define the groups by counting the number of part numbers before the current row that are different from the part number on that row. This uniquely identifies the groups:
select partnum, count(*)
from (select t.*,
(select count(*)
from t t2
where t2.seqnum < t.seqnum and
t2.partnum <> t.partnum
) as grp
from t
) t
group by partnum, grp;
I should note that you would use window functions in most databases for this purpose. The correlated subquery is used because MySQL lacks this important functionality.
I like this question it make me search and discover new things, I already have the idea I just want to make it real, about the idea is simple :
Combine all the rows (11221)
Separate each difference of numbers with a separator (11,22,1)
Split with this separator ([11, 22, 1])
Count the length of each part (2,2,1)
PostgreSQL solution:
I already solved the problem in PostgreSQL i think there are a way to translate it to MySQL Syntax
SELECT
SUBSTRING(regexp_split_to_table(regexp_replace(array_to_string(array_agg(part), ''), '((\d)\2+)', '\1,', 'g'), ','), 1, 1) as part_number,
length(regexp_split_to_table(regexp_replace(array_to_string(array_agg(part), ''), '((\d)\2+)', '\1,', 'g'), ',')) as production_sequence_number
FROM
mytable
Output

Find stores missing at least one item in a list of items

I have the following (simplified) table:
------------------
|store|item|value|
------------------
| 1| 1| 4|
| 1| 2| 3|
| 1| 3| 0|
| 2| 2| 2|
| 2| 3| 1|
| 3| 4| 2|
I want to know which stores are missing any item from a list of item, and I want to know the value for the items they do have. The list of items is normally larger than above, but is usually only searched by a few at a time.
So let's say I want to know which stores are missing any of item 1 or item 2. I would like results such as this ideally:
-------------
|store| 1| 2|
-------------
| 2| 0| 2|
| 3| 0| 0|
Store 1 is not returned because it has entries for both item 1 and item 2 where the value is greater than 0 for each. Initially I only needed the store ids but putting the items as columns with their values in the rows would be very helpful for what we need now. I started out with a monstrosity of 3 queries union'd together that worked okay to give the list of stores (first column). As I was thinking about how to add the other columns I decided to see if I could simplify the query. I came up with this:
select store
from table t1
where
(select count(*)
from (
select store,item
from table t2
where t2.item in (1,2) and value != 0
group by item,store
) t3
where t3.store = t1.store
) != 2
It does not perform as well as my first longer query though, and it doesn't give me the columns I'd like. The idea was to get a count of each store's matching unique items and if that count did not match the number of items queried for then return that store because it was missing at least one item from the list.
Any pointers or help on how to achieve the desired results would be appreciated!
If you want one row per store, then you can use conditional aggregation:
select store,
sum(case when item = 1 then value else 0 end) as item1,
sum(case when item = 2 then value else 0 end) as item2
from table
group by store
having least(item1, item2) = 0;
This assumes that all stores have at least one item of any type.

CakePHP (sub)query to get youngest record of each group

In a project I manage invoices that have a status which is changed throughout their lifetime. The status changes are saved in another database table which is similar to this:
|id|invoice_id|user_id|old_status_id|new_status_id|change_date |
-----------------------------------------------------------------------
| 1| 1| 1| 1| 3|2013-11-11 12:00:00|
| 2| 1| 2| 3| 5|2013-11-11 12:30:00|
| 3| 2| 3| 1| 2|2013-11-10 08:00:00|
| 4| 1| 1| 5| 6|2013-11-11 13:10:00|
| 5| 2| 2| 2| 5|2013-11-10 09:00:00|
For each invoice, I would like to retrieve the last status change. Thus the result should contain the records with the ids 4 and 5.
|id|invoice_id|user_id|old_status_id|new_status_id|change_date |
-----------------------------------------------------------------------
| 4| 1| 1| 5| 6|2013-11-11 13:10:00|
| 5| 2| 2| 2| 5|2013-11-10 09:00:00|
If I group by the invoice_id and use max(change_date), I will retrieve the youngest date, but the field values of the other fields are not taken from those records containing the youngest date in the group.
That's challenge #1 for me.
Challenge #2 would be to realize the query with CakePHP's methods, if possible.
Challenge #3 would be to filter the result to those records belonging to the current user. SO if the current user has the id 1, the result is
|id|invoice_id|user_id|old_status_id|new_status_id|change_date |
-----------------------------------------------------------------------
| 4| 1| 1| 5| 6|2013-11-11 13:10:00|
If he or she has user id 2, the result is
|id|invoice_id|user_id|old_status_id|new_status_id|change_date |
-----------------------------------------------------------------------
| 5| 2| 2| 2| 5|2013-11-10 09:00:00|
For the user with id 3 the result would be empty.
In other words, I do not want to find all latest changes that a user has made, regardless whether he was the last one that made a change. Instead, I want to find all invoice changes where that user was the ast one so far who made a change. The motivation is that I want to enable a user to undo his change, which is only possible if no other user after him performed another change.
In case anyone needs an answer
Strictly focusing on:
I want to find all invoice changes where that user was the last one so far who made a change
Write the SQL as
SELECT foo.*
FROM foo
LEFT JOIN foo AS after_foo
ON foo.invoice_id = after_foo.invoice_id
AND foo.change_date < after_foo.change_date
WHERE after_foo.id IS NULL
AND foo.user_id = 1;
Implement using the JOIN clause within Cakephp's find.
The SQL for the suggested algorithm is something like:
SELECT foo.*
FROM foo
JOIN (SELECT invoice_id, MAX(change_date) AS most_recent
FROM foo
GROUP BY invoice_id) AS recently
ON recently.invoice_id = foo.invoice_id
AND recently.most_recent = foo.change_date
WHERE foo.user_id = 1;

Know which condition didn't match in mysql consult

Into a very high selective MySQL query, is there a way to know which condition did not match?
Eg.: If I'm looking for 12 conditions and my MySQL returns 0, wich one of these 12 conditions did not match?
SELECT
article.id,
article.name,
GROUP_CONCAT(tags.name order by tags.name) AS nameTags,
GROUP_CONCAT(tags.id order by tags.id) AS idTags,
MAX(IF(article.name LIKE '%var1%',1,0)) AS var1match,
MAX(IF(article.name LIKE '%var2%',1,0)) AS var2match,
and more 10 conditions.
FROM
article
LEFT JOIN ....
LEFT JOIN ....
GROUP BY id
HAVING
(nameTags LIKE '%var1%' OR var1match=1)
AND (nameTags LIKE '%var2%' OR var2match=1)
AND more 10 conditions.
I mean, do it without make 12 single consults to see which one results nothing.
Update: Got some evolution
If I do this:
SELECT
article.id,
MAX(IF(article.name LIKE '%var1%',1,0) AS var1,
MAX(IF(article.name LIKE '%var2%',1,0) AS var2,
MAX(IF(article.name LIKE '%var3%',1,0) AS var3,
FROM
article
LEFT JOIN ....
LEFT JOIN ....
GROUP BY article.id
I'll get an aliased table like this
|id|var1|var2|var3|
| 1| 0| 0| 1|
| 2| 0| 1| 0|
| 3| 0| 0| 0|
| 4| 0| 1| 1|
So I know that 'var 1' doesn't match with the condition because all column value is equal 0. How I get the return of that column name?
Tried to sum value and get columns that sum = 0. Don't know how to do.
Update:
Found to do that is using temporary tables.
1) Create tmp table:
CREATE TEMPORARY TABLE tmp_tagReport AS
SELECT
article.id,
MAX(IF(article.name LIKE '%var1%',1,0) AS l_var1,
MAX(IF(article.name LIKE '%var2%',1,0) AS l_var2,
MAX(IF(article.name LIKE '%var3%',1,0) AS l_var3,
FROM
article
LEFT JOIN ....
LEFT JOIN ....
GROUP BY article.id
You'll have something like this:
|id|l_var1|l_var2|l_var3|
| 1| 0| 0| 1|
| 2| 0| 1| 0|
| 3| 0| 0| 0|
| 4| 0| 1| 1|
2) Sum values and you will know that column with 0 has no match
SELECT
SUM(l_var1) as l_var1,
SUM(l_var2) as l_var2,
SUM(l_var3) as l_var3
You'll get something like this:
|l_var1|l_var2|l_var3|
| 0| 2| 2|
3) Get name of columns with value = 0 in PHP
$querySelect = "SELECT
SUM(l_var1) as l_var1,
SUM(l_var2) as l_var2,
SUM(l_var3) as l_var3
";
$result = mysql_query($querySelect);
$arrayUnfound = array_keys(#mysql_fetch_assoc($result), "0");
print_r($arrayUnfound)
4) It is a temporary table, but I added a drop table just to make sure and try to free memory:
mysql_query("DROP TABLE tmp_tagReport; ");
I found myself a way to do it, but it still has to make 2 or more queries. However I hope it could be helpful.
sorry my english
Thanks