Modify MySQL Query or Run in PHP - mysql

I'm trying to run a query to find which inventory I should promote and which campaign I should run so I can move that inventory.
I have three tables:
campaigns lists different campaigns that I can run, each campaign has a unique id. Some campaigns promote only one item and some promote multiple items.
inventory has all the items I have in stock and the quantity of those items.
campaign_to_inventory matches the unique campaign id to the inventory item.
campaigns:
name | id
-------------|---
blue-widgets | 1
gluten-free | 2
gadget | 3
inventory:
item | qty
-------|----
thing1 | 0
thing2 | 325
thing3 | 452
thing5 | 123
thing7 | 5
campaign_to_inventory:
id | item
---|-------
1 | thing1
1 | thing2
1 | thing5
2 | thing1
2 | thing3
3 | thing7
I'd like to run a query to find all the campaigns I could run where I have the needed inventory in stock. I'm currently running this query:
SELECT * FROM `campaigns` LEFT JOIN `campaign_to_inventory` ON `campaigns`.`id` = `campaign_to_inventory`.`id` LEFT JOIN `inventory` ON `campaign_to_inventory`.`item` = `inventory`.`item`
Which returns:
name | id | item | qty
-------------|----|--------|----
blue-widgets | 1 | thing1 | 0
blue-widgets | 1 | thing2 | 325
blue-widgets | 1 | thing5 | 123
gluten-free | 2 | thing1 | 0
gluten-free | 2 | thing3 | 452
gadget | 3 | thing7 | 5
Should I use PHP to process this data to find only campaigns where all item quantities are greater than a minimum threshold, or is there a way to modify the query to limit the rows there? Is there a rule of thumb of when I can/should do it in one and not the other?

There's no need to process the data in PHP.
One way to do this would be to select the campaign_to_inventory.id column where the number of items is less than your threshold, like this:
SET #min_qty = 1;
SELECT `c_to_i`.`id` FROM `campaign_to_inventory` AS `c_to_i`
INNER JOIN `inventory` ON `inventory`.`item` = `c_to_i`.`item`
WHERE `inventory`.`qty` <= #min_qty;
... And then do a left outer join from campaign_to_inventory to that like this:
SET #min_qty = 1;
SELECT `id`, `name` FROM `campaigns`
LEFT JOIN (
/* Table of campaigns which contain items with not enough qty*/
SELECT `c_to_i`.`id` FROM `campaign_to_inventory` AS `c_to_i`
INNER JOIN `inventory` ON `inventory`.`item` = `c_to_i`.`item`
WHERE `inventory`.`qty` <= #min_qty
) AS `campaigns_with_not_enough_items`
ON `campaigns`.`id` = `campaigns_with_not_enough_items`.`id`
WHERE `campaigns_with_not_enough_items`.`id` is NULL;
The result should be a table of campaigns which have the needed inventory in stock.
As an aside, you should rename your campaign_to_inventory.id column to campaign since the name id implies that the column is the primary key for the table.

Related

SQL insert query with some condition

I am using MySQL.
There are three tables presented: Patient, Occupies, Room and Privte_Patient. I need to identify the first available room and allocate the room to a newly admitted patient with PIN '314' (the patient is already added to a database). Note that the room should be either single or multiple occupancy depending whether the patient is private or not.
As a result, I want get the SQL query which will allocate the patient with PIN '314' to the room number 1. Because this patient is not a private patient and room number 1 is the first room with empty bed (size is 2 beds).
Any idea of how to identify this room? Can I do it using Conditional INSERT?
Table Patient
+-------+---------+
| PIN | name |
+-------+---------+
|314 | Lana |
|778899 | Michael |
|345566 | Jone |
+-------+---------+
Table Occupies
+--------+--------+
|patient | room |
+--------+--------+
|778899 | 1 |
|345566 | 4 |
+-------+---------+
Table Room
+--------+--------+
|number | size |
+--------+--------+
| 1 | 2 |
| 2 | 12 |
| 3 | 1 |
| 4 | 1 |
+-------+---------+
Private_Patient
+--------+--------+
|patient |consultant|
+--------+--------+
|345566 | 345566 |
+-------+---------+
Consider:
insert into occupies(patient, room)
select 314, r.number
from room r
left join (select room, count(*) size from occupies group by room) o
on o.room = r.number
where coalesce(o.size, 0) < r.size
order by r.number
limit 1
For each room, the query brings the number of occupants and uses that information to filter out rooms that are full already. The first room where at least in bed is available is selected.
It might be simpler to understand with a correlated subquery:
insert into occupies(patient, room)
select 314, r.number
from room r
where r.size > (
select count(*)
from occupies o
where o.room = r.number
)
order by r.number
limit 1
INSERT INTO Occupies(room, patient)
SELECT number, '314'
FROM Room r
INNER JOIN
(SELECT room, COUNT(patient) amountOfPatientsInRoom from Occupies GROUP BY
room) ON
o.room = r.number
WHERE r.size < o.amountOfPatientsInRoom
LIMIT 1

LEFT - JOIN and WHERE is not working as expected in query

I have 3 tables: residual_types, containers, collections and collection_container. Each container has a residual_type and there is many-to-many relationship between containers and collections.
I need to make query that, in a given day, tells me how much mass has been collected for each residual_type, even though there is not any record associated with the residual_type. For example, in a given day, the "ORGANIC" residual_types has 850 kg collected, it shows "ORGANIC | 850", but if it had 0 kg collected, it would show "ORGANIC | 0".
This is the query I am using, but it seems that it does not respect the WHERE clause for collections.creation_time and it brings all the records
SELECT residual_types.name AS name, IFNULL(SUM(collection_container.mass),0) AS mass
FROM residual_types
INNER JOIN containers ON containers.residual_type_id = residual_types.id
INNER JOIN collection_container ON collection_container.container_id = containers.id
LEFT JOIN collections ON collection_container.collection_id = collections.id AND collections.creation_time BETWEEN 1557637200 AND 1557723599
GROUP BY residual_types.id
ORDER BY mass DESC
+---------+------+
| name | mass |
+---------+------+
| organic | 7580 |
+---------+------+
| paper | 1243 |
+---------+------+
| plastic | 123 |
+---------+------+
I've also tried this query, but it does not bring any records.
SELECT residual_types.name AS name, IFNULL(SUM(collection_container.mass),0) AS mass
FROM residual_types
INNER JOIN containers ON containers.residual_type_id = residual_types.id
INNER JOIN collection_container ON collection_container.container_id = containers.id
INNER JOIN collections ON collection_container.collection_id = collections.id
WHERE collections.creation_time BETWEEN 1557637200 AND 1557723599
GROUP BY residual_types.id
ORDER BY mass DESC
If there are not any collections associated with the residual_type, then the result set should look like this:
+---------+------+
| name | mass |
+---------+------+
| organic | 0 |
+---------+------+
| paper | 0 |
+---------+------+
| plastic | 0 |
+---------+------+
Your problem is that the value you are summing will always be a number, regardless of whether there was a collection or not. You need to condition the sum with whether there was a collection or not, which you can do by changing that expression from
IFNULL(SUM(collection_container.mass), 0)
to
SUM(CASE WHEN collections.id IS NOT NULL THEN collection_container.mass ELSE 0 END)
I think the problem is int the design in that the mass is held in collection_container. I would have expected it in collections. The effect is that the mass is found before the left join (which fails on date).
For example
drop table if exists residual_types,containers,collection_container,collections;
create table residual_types(id int,name varchar(3));
create table containers(id int,residual_type_id int);
create table collection_container(container_id int,collection_id int,mass int);
create table collections(id int,creation_time bigint);
insert into residual_types values(1,'aaa'),(2,'bbb'),(3,'ccc');
insert into containers values(1,1),(2,1),(3,1),(4,1);
insert into collection_container values(1,10,100),(1,20,100),(1,30,100);
insert into collections values(10,1557637100),(20,1557637200),(30,1557723599);
SELECT residual_types.name AS name, collections.creation_time,
IFNULL(SUM(collection_container.mass),0) AS mass
FROM residual_types
INNER JOIN containers ON containers.residual_type_id = residual_types.id
INNER JOIN collection_container ON collection_container.container_id = containers.id
LEFT JOIN collections ON collection_container.collection_id = collections.id AND collections.creation_time BETWEEN 1557637200 AND 1557723599
GROUP BY residual_types.id,collections.creation_time
ORDER BY mass DESC;
+------+---------------+------+
| name | creation_time | mass |
+------+---------------+------+
| aaa | NULL | 100 |
| aaa | 1557637200 | 100 |
| aaa | 1557723599 | 100 |
+------+---------------+------+
3 rows in set (0.00 sec)

Sort MySQL Query results by amount of recursion in foreign keys

I have the following table:
+----+--------+
| id | parent |
+----+--------+
| 1 | 4 |
| 2 | 1 |
| 3 | NULL |
| 4 | NULL |
| 5 | 2 |
| 6 | 3 |
+----+--------+
I want this table to be ordered like this:
+----+--------+------------------------------------------------------------+
| id | parent | Why it has to be ordered like this |
+----+--------+------------------------------------------------------------+
| 5 | 2 | 5 has parent 2 has parent 1 has parent 4. So 3 rows above. |
| 2 | 1 | 2 has parent 1 has parent 4. So 2 rows above. |
| 1 | 4 | 1 has parent 4. So 1 row above. |
| 6 | 3 | 6 has parent 3. So 1 row above. |
| 4 | NULL | No parent. So 0 rows above. |
| 3 | NULL | No parent. So 0 rows above. |
+----+--------+------------------------------------------------------------+
So I want to recursively count the ancestors of a row and sort on that. How can I do that?
Edit: I'm on MySQL version 5.7.21.
You could do this with a recursive CTE, but you didn't list your mysql version and not all versions can do that, so here is something that should work even for older versions. This does the recursion itself with a temporary table and a while statement. The temporary table gets built with one record for each record in the main table, which holds the parent count data. First we do all records with no parent, then the query inside the while does all the records for the next generation. Note that the syntax may be a little bit off, I haven't done mysql for some time.
--Create temp table to hold the parent count data
CREATE TEMPORARY TABLE ParentCount (id int, pcount int);
--First create a pcount record with count zero for all records with no parent
insert into ParentCount (id, pcount) Select id, 0 from TestData where parent is null;
--If we don't have a parentcount set for every record, keep going
-- This will run once for every level of depth
While (Select COUNT(id) from TestData) <> (Select COUNT(id) from ParentCount) Begin
--add a pcount record for all rows that don't have one yet, but whose
-- parents do have one (ie the next generation)
insert into ParentCount (id, pcount)
Select T.id, P.pcount + 1 as newpcount
from TestData T
inner join ParentCount P on P.id = T.parent
left outer join ParentCount P2 on P2.id = T.id
where P2.id is null;
End;
--final query
Select T.id, T.parent
from TestData T
inner join Parents P on T.id = p.id
order by P.pcount DESC, T.id ASC;

LEFT JOIN to newest row

I'm trying to get data from one table with additional data from second, but in second table i have many records connected to records in first table and I want take newest.
In first table i keep products and i second i keep prices with data. I want take products with actual(newest) price.
Products table:
ID | NAME
---+----------
1 | "jacket"
2 | "pants"
Prices table:
ID | PRODUCT_ID | DATE | PRICE
---+------------+------------+-------
1 | 1 | 2015-05-12 | 200
2 | 1 | 2015-07-12 | 100
3 | 2 | 2015-03-12 | 60
4 | 2 | 2015-08-12 | 90
Expected result:
1, "jacket", 100
2, "pants", 90
How can I do this?
Actually i've found solution - but with 2 subqueries. Doesn't look so good.
Find the max date for each price and then inner join with the rest of the tables.
SQL Fiddle
SELECT aa.id, aa.name, bb.price
FROM products AS aa
INNER JOIN prices AS bb
ON aa.id = bb.product_id
INNER JOIN (
SELECT product_id, MAX(date) AS max_date
FROM prices AS cc
GROUP BY product_id
) AS _aa
ON aa.id = _aa.product_id
WHERE bb.date = _aa.max_date;

MySQL Join two tables with condition

Based on these two tables:
products
| ID | Active | Name | No
--------------------------------------------------
| 1 | 1 | Shirt | 100
| 2 | 0 | Pullover | 200
variants
| MasterID | Active | Name | No
--------------------------------------------------
| 1 | 1 | Red | 101
| 1 | 0 | Yellow | 102
I want to get every product which is active and also their active variants in one sql.
Relation between those tables MasterID -> ID
Needed result:
ID (master) | Name | No
--------------------------------------------------
1 | Shirt | 100
1 | Red | 101
I tried it with using union, but then I am not able to get the belonging MasterIDs.
It looks like you just need a simple join:
select *
from products
left join variants
on products.ID = variants.MasterID
where products.Active = 1
and variants.Active = 1
Update after requirements were made clearer:
select ID, Name, No, 'products' as RowType
from products
where Active = 1
union
select variants.MasterID as ID, variants.Name, variants.No, 'variants' as RowType
from products
join variants
on products.ID = variants.MasterID
where products.Active = 1
and variants.Active = 1
order by ID, RowType, No
I've assumed you want the results ordered by ID, with products followed by variants. The No column may order it this way implicitly (it's impossible to know without real data), in which case the RowType column can be removed. The order by clause might need to be altered to match your specific RDBMS.
This should gives you the expected result:
select * from products left join variants on products.id = variants.masterId
where products.active=1 and variants.active=1
If not please add the expected result to your question.