LEFT JOIN and COUNT - php

I have three tables like so:
1. cosmetics with cosm_id as primary key, AI, cosm_name is unique.
| cosm_id | cosm_brand | cosm_name | cosm_volume
2. cosmInOut with cosmInOut_id as Primary key, AI, Foreign key as name cosmInOut_qtyIn is always 1
| cosmInOut_id | cosmInOut_name | cosmInOut_qtyIn | cosmInOut_4sale
3. cosmSpent with cosmSpent_id as Primary key, AI, Foreign key as name. cosmSpent_volume is a fraction of cosm_volume
| cosmSpent_id | cosmSpent_name | cosmSpent_volume
What I want is to create a summary table per cosm_name with such columns for each brand as:
Name = cosm_name (DISTINCT)
Quantity in stock = (SUM(cosm_volume) - SUM(cosmSpent_volume)) / cosm_volume
Current price = here I dont even know how to address the price of the currently used row. E.g. if cosmInOut table has 3 rows for needed cosm_name with cosm_volume = 100, and SUM(cosmSpent_volume) = 150, the I have to address the second row in CosmInOut table for my cosm_name for the current price. How do I do that?
So far I managed to create this query:
$sql = "SELECT cosmInOut_id, cosm_brand, cosm_name, cosm_volume, cosmInOut_priceIn,
SUM(cosm_volume) AS cosm_volume_total,
SUM(cosmSpent_volume) AS cosmSpent_total
FROM cosmInOut
JOIN cosmetics ON cosmInOut.cosmInOut_name=cosmetics.cosm_name
LEFT JOIN cosmSpent ON cosmInOut.cosmInOut_name=cosmSpent.cosmSpent_name
WHERE cosmInOut_4sale = '0' AND cosm_brand = '" . $row_brand['cosm_brand']. "'
GROUP BY cosm_name";
it is performed for each cosm_brand in a cycle.
Unfortunately the math is all wrong in this query due to (I think) LEFT JOIN used.
I have 2 questions:
1. How can I fix the query to count correctly
2. How can I address the N row based on count value
Your help will be greatly appreciated. I am a new to this so don't judge too strictly. If the problem is with the architecture, I am open to suggestions
As for sample data:
A little explanation of desired result:
Total cosmInOut_volume is 60*3 = 180,
total cosmSpent volume = 10+55+5+10 = 80,
thus qty in stock = (180-80)/60 = 1.66.
Currently, the second row is being used to determine price because we've already used one full row (60) and began to use the second
Here is MySQL: http://sqlfiddle.com/#!9/fdfad9/1
CREATE TABLE cosmetics
(`cosm_id` int, `cosm_brand` varchar(50), `cosm_name` varchar(50), `cosm_volume` int)
;
INSERT INTO cosmetics
(`cosm_id`, `cosm_brand`, `cosm_name`, `cosm_volume`)
VALUES
(1, 'Londa', 'L 5/66', '60')
;
CREATE TABLE cosmInOut
(`cosmInOut_id` int, `cosmInOut_name` varchar(50), `cosmInOut_qty` int, `cosmInOut_priceIn` float, `cosmInOut_4sale` tinyint)
;
INSERT INTO cosmInOut
(`cosmInOut_id`, `cosmInOut_name`, `cosmInOut_qty`, `cosmInOut_priceIn`, `cosmInOut_4sale`)
VALUES
(1, 'L 5/66', '1', '7', '0'),
(2, 'L 5/66', '1', '10', '0'),
(3, 'L 5/66', '1', '7', '0')
;
CREATE TABLE cosmSpent
(`cosmSpent_id` int, `cosmSpent_name` varchar(50), `cosmSpent_volume` int)
;
INSERT INTO cosmSpent
(`cosmSpent_id`, `cosmSpent_name`, `cosmSpent_volume`)
VALUES
(1, 'L 5/66', '10'),
(2, 'L 5/66', '55'),
(3, 'L 5/66', '5'),
(4, 'L 5/66', '10')
;

SQL Fiddle Demo
To produce your desire result you need create three querys and join it together.
First you need know how much volumen is for each cosmetic name, and how much items buy for those name
Then Calculate the total spend for each name
Using variables you calculate ranges of volumen and what price correspont to that range
.
SELECT v_in.*,
v_out.cosm_IO_vol,
(v_in.cosm_volume * v_in.cosm_qty - v_out.cosm_IO_vol)
/ v_in.cosm_volume as QtyStock,
price.`cosmInOut_priceIn`
FROM ( SELECT C.cosm_name,
C.cosm_volume,
SUM(IO.cosmInOut_qty) as cosm_qty
FROM cosmetics C
JOIN cosmInOut IO
ON C.`cosm_name` = IO.`cosmInOut_name`
GROUP BY C.cosm_name
) v_in
LEFT JOIN (SELECT cosmSpent_name, SUM(cosmSpent_volume) as cosm_IO_vol
FROM cosmSpent CS
GROUP BY cosmSpent_name
) v_out
ON v_in.`cosm_name` = v_out.`cosmSpent_name`
LEFT JOIN ( SELECT C.cosm_name,
IF(#cosm_name = C.cosm_name,
#volume,
0) minVolume,
#volume := IF(#cosm_name = C.cosm_name,
#volume + C.cosm_volume * IO.cosmInOut_qty,
IF(#cosm_name := C.cosm_name,
C.cosm_volume,
C.cosm_volume)
) as maxVolume,
`cosmInOut_priceIn`
FROM cosmetics C
JOIN cosmInOut IO
ON C.`cosm_name` = IO.`cosmInOut_name`
CROSS JOIN (SELECT #cosm_name := '', #volume := 0) Y
ORDER BY C.cosm_volume, cosmInOut_id
) price
ON v_in.cosm_name = price.cosm_name
AND v_out.cosm_IO_vol BETWEEN price.minVolume AND price.maxVolume
OUTPUT
| cosm_name | cosm_volume | cosm_qty | cosm_IO_vol | QtyStock | cosmInOut_priceIn |
|-----------|-------------|----------|-------------|----------|-------------------|
| L 5/66 | 60 | 3 | 80 | 1.6667 | 10 |

Related

I wanna create a new table from the data of two tables

I have a A table to store the product name, the quantity and input day of goods in stock. In adverse conditions, invoices for the above products will be sent later. I will put the product name and invoice quantity in B table. The problem here is that I want to check the quantity of goods with invoice and without invoice. Updating goods with invoices will follow FIFO.
Example:
Table A
id
good_id
num
created_at
1
1
10
2021-09-24
2
1
5
2021-09-25
Table B
id
good_id
num_invoice
1
1
12
I solved it by creating a new C table with the same data as the A table
Table C
id
good_id
current_number
created_at
invoice_number
1
1
10
2021-09-24
null
2
1
5
2021-09-25
null
Then I get the data in table B group by good_id and store it in $data.
Using php to foreach $data and check condition:
I updated C table ORDER BY created_at DESC limit 1 as follows:
if (tableC.current_num - $data['num'] < 0) then update current_number = 0, invoice_number = $data['num'] - tableC.current_num. Update value $data['num'] = $data['num'] - tableC.current_num
if (tableC.current_num - $data['num'] > 0) or (tableC.current_num - $data['num'] = 0) then update current_number = tableC.current_num - $data['num'], invoice_number = $data['num'].
table C after update
id
good_id
current_number
created_at
invoice_number
1
1
0
2021-09-24
10
2
1
3
2021-09-25
2
I solved the problem with php like that. However, with a dataset of about 100,000 rows, I think the backend processing will take a long time. Can someone give me a smarter way to handle this?
Updated solution for MySQL 5.7:
Test case to support MySQL 5.7+, PG, MariaDB prior to 10.2.2, etc:
Test case for MySQL 5.7+, etc
For MySQL 5.7:
This replaces the window function (for running SUM) and uses derived tables instead of WITH clause.
SELECT id, good_id
, num - GREATEST(num - GREATEST(balance, 0), 0) AS num
, created_at
, GREATEST(num - GREATEST(balance, 0), 0) AS invoice_num
FROM (
SELECT MIN(t2.id) AS id, MIN(t2.num) AS num
, t2.good_id, t2.created_at
, MIN(o.num_invoice) AS num_invoice
, SUM(t1.num) - MIN(o.num_invoice) AS balance
FROM tableA AS t1
JOIN tableA AS t2
ON t1.good_id = t2.good_id
AND t1.created_at <= t2.created_at
JOIN (
SELECT good_id, SUM(num_invoice) AS num_invoice
FROM tableB
GROUP BY good_id
) AS o
ON o.good_id = t1.good_id
GROUP BY t2.good_id, t2.created_at
) AS cte2
ORDER BY created_at
;
For databases that handle functional dependence in GROUP BY properly, we could just GROUP BY t2.id (the primary key of tableA) and remove the MIN(t2.id) and the MIN(t2.num).
Like this:
Test case
-- For MySQL 5.7
SELECT id, good_id
, num - GREATEST(num - GREATEST(balance, 0), 0) AS num
, created_at
, GREATEST(num - GREATEST(balance, 0), 0) AS invoice_num
FROM (
SELECT t2.id, t2.num
, t2.good_id, t2.created_at
, MIN(o.num_invoice) AS num_invoice
, SUM(t1.num) - MIN(o.num_invoice) AS balance
FROM tableA AS t1
JOIN tableA AS t2
ON t1.good_id = t2.good_id
AND t1.created_at <= t2.created_at
JOIN (
SELECT good_id, SUM(num_invoice) AS num_invoice
FROM tableB
GROUP BY good_id
) AS o
ON o.good_id = t1.good_id
GROUP BY t2.id
) AS cte2
ORDER BY created_at
;
Original Answer using window functions and WITH clause:
Here's a test case with PG 13, but works fine with MySQL 8 or MariaDB 10.2.2+.
Note: I left this as just a query that generates the requested detail. It's not clear the 3rd table is necessary. This can be used to update (or create) that table, if needed.
Test case:
Working test case
CTE terms:
cte1 - calculate the total requested goods
cte2 - Calculate the running balance based on running inventory by date
Finally, we use cte2 to determine the goods allocated and remaining, requested by the question.
WITH cte1 (good_id, num_invoice) AS (
SELECT good_id, SUM(num_invoice) AS num_invoice
FROM tableB
GROUP BY good_id
)
, cte2 AS (
SELECT a.*, o.num_invoice
, SUM(num) OVER (PARTITION BY a.good_id ORDER BY created_at) - o.num_invoice AS balance
FROM tableA AS a
JOIN cte1 AS o
ON o.good_id = a.good_id
)
SELECT id, good_id
, num - GREATEST(num - GREATEST(balance, 0), 0) AS num
, created_at
, GREATEST(num - GREATEST(balance, 0), 0) AS invoice_num
FROM cte2
ORDER BY created_at
;
The result:
+----+---------+------+------------+-------------+
| id | good_id | num | created_at | invoice_num |
+----+---------+------+------------+-------------+
| 1 | 1 | 0 | 2021-09-24 | 10 |
| 2 | 1 | 3 | 2021-09-25 | 2 |
| 3 | 1 | 7 | 2021-09-26 | 0 |
+----+---------+------+------------+-------------+
Note: I added one additional onhand entry for 7 goods (id = 3) to test an edge case.
Setup of the test case:
CREATE TABLE tableA (
id int primary key
, good_id int
, num int
, created_at date
);
CREATE TABLE tableB (
id int primary key
, good_id int
, num_invoice int
);
INSERT INTO tableA VALUES
(1,1,10,'2021-09-24')
, (2,1, 5,'2021-09-25')
, (3,1, 7,'2021-09-26')
;
INSERT INTO tableB VALUES
(1,1,12)
;

Mysql: Query which orders by default and rank (pin/hold up entries?)

I have a products table which contains all my products. Those products table gets filled permanently with new products. However, I want to have the possibility to "hold up"/"pin" certain products to a place in the returned query collection.
Means, I want to set something like rank_index which contains the number the product should have in the returned query collection.
Example:
id title rank_index
1 An awesome product
2 Another product 5
3 Baby car
4 Green carpet 2
5 Toy
Lets assume the default order would be the id. But because the rank_index is set for the product with the id 4 I would like to get the collection with the following order of ids returned: 1, 4, 3, 5, 2.
Is this somehow possible to do? The rank_index column was just an idea of mine. I mean.. I also could do this on the php side and do a normal query which does only include the products without an rank_index and one which only contains products with an index_rank and order them manually on the php side.
However, because this takes a lot of time and processing power I am looking for a solution which is done by the database... Any ideas?
Btw: I am using Laravel 8 if this makes any difference.
Kind regards
This is a very tricky problem. If you try the other approach setting consecutive values -- like 2 and 3 -- you will see that they do not work.
There may be simpler ways to solve this. But, here is a brute force approach.
It constructs a derived table by enumerating the rows in the original table.
It adds into this table (using a left join) all the force-ranked values.
It joins in the rest of the values by enumerating the empty slots both in table1 and in the derived table.
So:
with recursive n as (
select row_number() over (order by id) as n
from table1 t1
),
nid as (
select n.n, t1.id
from n left join
table1 t1
on t1.rank_index = n.n
),
nids as (
select n.n, coalesce(n.id, t1.id) as id
from (select nid.*, sum(nid.id is null) over (order by nid.n) as seqnum
from nid
) n left join
(select t1.*, row_number() over (order by id) as seqnum
from table1 t1
where rank_index is null
) t1
on n.seqnum = t1.seqnum
)
select t1.*
from nids join
table1 t1
on t1.id = nids.id
order by nids.n;
Use the rank_index if it's not null as the ordering, id otherwise:
Since you want the rank_index to be ahead of an id, a -0.5 adjustment is made:
SELECT *
FROM table
ORDER BY IF(rank_index IS NULL, id, rank_index - 0.5)
You can use IF clause and to have the correct the number to get te right order, so
CREATE TABLE table1 (
`id` INTEGER,
`title` VARCHAR(18),
`rank_index` INT
);
INSERT INTO table1
(`id`, `title`, `rank_index`)
VALUES
('1', 'An awesome product', NULL),
('2', 'Another product', '5'),
('3', 'Baby car', NULL),
('4', 'Green carpet', '2'),
('5', 'Toy', NULL);
SELECT *
FROM table1
ORDER BY IF(rank_index IS NULL, id, rank_index + .01)
+----+--------------------+------------+
| id | title | rank_index |
+----+--------------------+------------+
| 1 | An awesome product | NULL |
| 4 | Green carpet | 2 |
| 3 | Baby car | NULL |
| 5 | Toy | NULL |
| 2 | Another product | 5 |
+----+--------------------+------------+
db<>fiddle here

PHP - Find minimum value in an array and combine all possible numbers to reach a given sum

Not sure following scenario is belong to Knapsack Problem or Coin Change Approach. Would like to seek solution of this problem in PHP language. Require simple solution because not understand some algorithms (text theory) provided from Internet.
Given a Table (A) as following:
TABLE (A)
---------
Item | Name | Price ($)
-------- ------------ --------------
1 | Adidas | 35
2 | Nike Run | 70
3 | Puma | 100
4 | Nike | 85
5 | NB | 65
Combine any of 3 items in the table (A), sum up more than or equal to $200.
First, sorting the table (A).
Second, get the minimum/smallest amount (Price). In this case is $35.
Third, check one by one from the others amount.
Fourth, Sum up 3 possibilities of combination that more than or equal to $200.
Result:
Item | Name | Price ($)
-------- ------------ --------------
1 | Adidas | 35
5 | NB | 65
3 | Puma | 100
Given another sample Table (B) as following:
TABLE (B)
---------
Item | Name | Price ($)
-------- ------------ --------------
1 | Adidas | 5
2 | Nike Run | 35
3 | Puma | 110
4 | Nike | 65
5 | NB | 15
Combine any of 3 items in the table (B), sum up more than or equal to $200.
First, sorting the table (B).
Second, get the minimum/smallest amount (Price). In this case is $5.
Third, check one by one from the others amount.
Fourth, Sum up 3 possibilities of combination that more than or equal to $200.
Fifth, if the smallest combine with others and failed to sum up total of $200, get second smallest and repeat first step to fourth step.
Sixth, the best minimal/smallest value is $35 in this case.
Result:
Item | Name | Price ($)
-------- ------------ --------------
2 | Nike Run | 35
4 | Nike | 65
3 | Puma | 110
Say table A is an associative array, let's call it $a.
To find the first combination that will be >= 200 do this:
$rowsWithValueOver200 = array();
foreach($a as $value) {
foreach($a as $value2) {
// Make sure to ignore the value selected in the first loop.
if ($value2 == $value)
continue;
foreach($a as $value3) {
// Make sure to ignore the value selected in the first and second loop.
if ($value3 == $value)
continue;
if ($value3 == $value2)
continue;
$total = $value3['Price'] + $value2['Price'] + $value['Price'];
if ($total >= 200) {
// Store all of the rows in the new array.
$rowsWithValueOver200[] = $value;
$rowsWithValueOver200[] = $value2;
$rowsWithValueOver200[] = $value3;
break;
}
}
}
}
So we iterate through the array 3 times. Check the sum once we're looking at 3 unique values. All the time making sure that we don't include the previously selected array element ($value or $value2). This could certainly be cleaned up a little and made more universal, but it'll work for 3 elements. A refactor of this into a more universal function would make recursive calls to itself, allow for a start position (then use a for loop instead of foreach).
To find the minimum value is pretty simple.
$minVal = PHP_INT_MAX; // Initially something high, here it is the maximum integer value.
$minRow;
foreach($rowsWithValueOver200 as $value) {
if ($value['Price'] < $minVal){
$minVal = $value['Price'];
$minRow = $value;
}
}
Set your minimum value (minVal) to check against to something really high. Iterate through the array values and if the $value['Price'] is less than the current minVal, then it is now the minVal and store the array element in $minRow for later reference.
3 and 4 I'm not sure what you're asking. It's not clear.
If you want to find the sum of the three rows in $rowsWithValueOver200 then you could just use what I had in the first answer. If you want to do it with just the 3 final elements then do this:
$total = 0;
foreach($rowsWithValueOver200 as $value) {
$total += $value['Price'];
}
If you're looking for other possibilities that sum up to 200 or more then just use the first example, but instead of a foreach at the first loop use a for loop with an index. Start at each index.
Good luck!
MySQL is properly equip to accomplish this all on its own. You could pull your entire table for PHP to figure out, but this can get expensive on resources, and it most definitely will produce slower results, especially with larger databases. PHP usually needs to loop through the data several times to do enough array manipulation which MySQL can accomplish very easily.
Assuming your database looks like this:
CREATE TABLE IF NOT EXISTS `A` (
`Item` int(10) unsigned NOT NULL AUTO_INCREMENT,
`Name` varchar(10) NOT NULL,
`Price` decimal(10,2) NOT NULL,
PRIMARY KEY (`Item`),
UNIQUE KEY `Name` (`Name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
INSERT INTO `A` (`Item`, `Name`, `Price`) VALUES
(1, 'Adidas', '35.00'),
(2, 'Nike Run', '70.00'),
(3, 'Puma', '100.00'),
(4, 'Nike', '85.00'),
(5, 'NB', '65.00');
You can query it like so:
SELECT #leastprice := min(`Price`) FROM `A`;
SELECT t1.Name AS Name1, t2.Name AS Name2, t3.Name AS Name3,
t1.Price AS Price1, t2.Price AS Price2, t3.Price AS Price3,
(t1.Price+t2.Price+t3.Price) AS `Total`
FROM `A` t1
LEFT JOIN `A` t2 ON t1.Item != t2.Item
LEFT JOIN `A` t3 ON t1.Item != t2.Item AND t2.Item != t3.Item
WHERE t1.price = #leastprice
AND t2.price<=t3.price
AND t1.price+t2.price+t3.price >= 200;
Result:
Edit:
For your second case, assuming your database looks like this:
CREATE TABLE IF NOT EXISTS `B` (
`Item` int(10) unsigned NOT NULL AUTO_INCREMENT,
`Name` varchar(10) NOT NULL,
`Price` decimal(10,2) NOT NULL,
PRIMARY KEY (`Item`),
UNIQUE KEY `Name` (`Name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
INSERT INTO `B` (`Item`, `Name`, `Price`) VALUES
(1, 'Adidas', '5.00'),
(2, 'Nike Run', '35.00'),
(3, 'Puma', '110.00'),
(4, 'Nike', '65.00'),
(5, 'NB', '15.00');
You can query it like so:
SELECT #leastTalliablePrice := min(t1.Price) FROM `B` t1
LEFT JOIN `B` t2 ON t1.Item != t2.Item
LEFT JOIN `B` t3 ON t1.Item != t2.Item AND t2.Item != t3.Item
WHERE t2.price<=t3.price
AND t1.price+t2.price+t3.price >= 200;
SELECT t1.Name AS Name1, t2.Name AS Name2, t3.Name AS Name3,
t1.Price AS Price1, t2.Price AS Price2, t3.Price AS Price3,
(t1.Price+t2.Price+t3.Price) AS `Total`
FROM `B` t1
LEFT JOIN `B` t2 ON t1.Item != t2.Item
LEFT JOIN `B` t3 ON t1.Item != t2.Item AND t2.Item != t3.Item
WHERE t1.price = #leastTalliablePrice
AND t2.price <= t3.price
AND t1.price+t2.price+t3.price >= 200;
Result:
If for some reason your database doesn't have 3 or more distinct items, or their prices aren't enough to reach your desired total, the query would simply return 0 rows in the same manner that a SELECT results 0 rows if it can't find any rows to select. But if there were more than one match in the above case, it would result multiple rows like as the case in my first screenshot.

Difficult where clause, may require the use of two queries?

I've got a query to produce which I can't seem to get right. I've got three tables:
student: student details
link: links that exist for a student, links have a status which can be active or completed
email: shows what links have been sent out by email.
I need to get a list of student IDs (from the student table) based on the following criteria:
link.status = active and related email doesn't exist (i.e. a link has been created but it hasn't been sent in an email)
link.status is null and email is null (i.e. there are no existing links for that student)
link.status = completed, and there are no other links for this student which have an active status
So if I have the following data in my tables:
student
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
+----+
link
+----+-----------+------------+
| id | status | student_id |
+----+-----------+------------+
| 1 | completed | 1 |
| 2 | active | 1 |
| 3 | completed | 2 |
| 4 | active | 3 |
+----+-----------+------------+
email
+----+---------+
| id | link_id |
+----+---------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
+----+---------+
Then my query should return the following student IDs: 2,3,4
2 - because there is only a completed link for this student
3 - because there is an active link with no associated email
4 - because there are no links for this student
I currently have this query which gets part of what I need:
SELECT DISTINCT student.id
FROM student
LEFT JOIN link ON link.student_id = student.id
LEFT JOIN email ON email.link_id = link.id
WHERE student.course = 'phd'
AND student.institution_id = '2'
AND (
(link.status != "active" AND email.id IS NULL)
OR
(link.status IS NULL AND email.id IS NULL)
OR
(link.status = "active" AND email.id IS NULL)
)
This of course doesn't get any student IDs where link.status = completed and no other links exist for the student. I can of course do this by adding in:
(link.status = "completed" and email.id IS NOT NULL)
into the WHERE, but this will return the student ID if they have another active link or they don't have an active link. This being the bit I'm struggling with.
I get the feeling this may not be able to be accomplished by a single query, so would I need to do two queries then subtract them from one another? I.e. the query above and a separate query selecting the links with a 'completed' status then subtracting them from the first query?
My application using these queries is built in PHP so I'm happy to do some logic in PHP with two queries if needed.
(Didn't have a clue what to put for the title so if anyone can think of anything better please edit it!)
SELECT s.*
FROM student s
LEFT
JOIN link l
ON l.student_id = s.id
AND l.status <> 'completed'
LEFT
JOIN email e
ON e.link_id = l.id
WHERE e.id IS NULL;
?
Because your request is based on links and one student can have more than one link, you should start with query to links table, after that, you add joins and conditions.
Preparation SQL:
CREATE TABLE IF NOT EXISTS student
(
id int auto_increment primary key,
course tinytext,
institution_id int
);
INSERT INTO student (id, course, institution_id) VALUES
(1, 'phd', 2),
(2, 'phd', 2),
(3, 'phd', 2),
(4, 'phd', 2);
CREATE TABLE IF NOT EXISTS link
(
id int auto_increment primary key,
status tinytext,
student_id int
);
INSERT INTO link (id, status, student_id) VALUES
(1, 'completed', 1),
(2, 'active', 1),
(3, 'completed', 2),
(4, 'active', 3);
CREATE TABLE IF NOT EXISTS email
(
id int auto_increment primary key,
link_id int
);
INSERT INTO email (id, link_id) VALUES
(1, 1),
(2, 2),
(3, 3);
Query:
SELECT DISTINCT s.id
FROM link l
LEFT JOIN student s ON l.student_id = s.id
LEFT JOIN email e ON l.id = e.link_id
WHERE s.course = 'phd'
AND s.institution_id = '2'
AND (
(l.status != "active" AND e.id IS NULL)
OR
(l.status IS NULL AND e.id IS NULL)
OR
(l.status = "active" AND e.id IS NULL)
)
Play with it: http://sqlfiddle.com/#!2/dae16b/2
I don't really understand your question, because you have many mistakes in it. I will try dig deeper to find if your logic is ok.
EDIT: "Strawberry" approach by filtering JOIN's could be the thing you need
SELECT s.id
FROM student s
LEFT JOIN link l
ON l.student_id = s.id AND l.status = 'active' OR l.status IS NULL
LEFT JOIN email e
ON e.link_id = l.id
WHERE
e.id IS NULL
AND s.course = 'phd'
AND s.institution_id = '2';
Play with it: http://sqlfiddle.com/#!2/dae16b/26
We select "student" table and add only those links, that have "active" or "null" status (LEFT JOIN link l ON l.student_id = s.id AND l.status != 'completed'), which solves for rule #2 (there are no existing links for that student) and first part of rule #1 (a link has been created but it hasn't been sent in an email) and second part of rule #3 (link.status = completed, and there are no other links for this student which have an active status). After that, to solve for second par of rule #2 (a link has been created but it hasn't been sent in an email), we remove rows which does not have an e-mail (JOIN email e ON e.link_id = l.id and e.id IS NULL part).
Only thing left is to think, if you need to solve for first part of rule #3 (link.status = completed, and there are no other links for this student which have an active status), because I do not know, if situation where "student has no links" = "student has link.completed status".
For now, this query returns what you requested.

mysql select Where (subquery) IN

I have a mysql query of type
select some_value FROM table WHERE (subquery) IN ( values )
which seems to be extremly slow!
I have a mysql table with orders and a second one with the corresponding processing states of them. I want now to show all orders having their last status code = 1 .
table order (id = primary key)
id | value
---+-------
1 + 10
2 + 12
3 + 14
table state (id = primary key)
id | orderid | code
---+---------+-------
1 + 1 + 1
2 + 2 + 1
3 + 2 + 2
4 + 1 + 3
5 + 3 + 1
My query is:
select order.id FROM order WHERE
( select state.code FROM state WHERE state.orderid = order.id ORDER BY state.id DESC LIMIT 0,1 ) IN ( '1' )
It takes roughly 15 seconds to process this for a single order. How to modify the mysql statement in order to speed the query procession time up?
Update
Try this one:
select s1.orderid
from state s1
left outer join state s2 on s1.orderid = s2.orderid
and s2.id > s1.id
where s1.code = 1
and s2.id is null
You may need an index on state.orderid.
SQL Fiddle Example
CREATE TABLE state
(`id` int, `orderid` int, `code` int)
;
INSERT INTO state
(`id`, `orderid`, `code`)
VALUES
(1, 1, 1),
(2, 2, 1),
(3, 2, 2),
(4, 1, 3),
(5, 3, 1)
;
Query:
select s1.orderid
from state s1
left outer join state s2 on s1.orderid = s2.orderid
and s2.id > s1.id
where s1.code = 1
and s2.id is null
Results:
| ORDERID |
|---------|
| 3 |
in this situation I think you could simply forget of order, as all information stays in state.
SELECT x.id, x.orderid
FROM state AS x
WHERE x.code =1
AND x.id = (
SELECT max( id )
FROM a
WHERE orderid = x.orderid )
Maybe would be possible to change your CRUD by putting last state directly in order table too, this would be the better

Categories