MySQL insert values if condition is met - mysql

I am trying to insert into a table only if the qty has changed down in another table see example
INSERT INTO sales_items (sale_id, item_id, quantity_purchased, item_cost_price, item_unit_price)
VALUES ('1', '1546', '3', '10', '10')
WHEN (SELECT quantity FROM location_items WHERE location_id =4 AND item_id =1546) < 10;

You can do the following:
INSERT INTO sales_items
(sale_id, item_id, quantity_purchased, item_cost_price, item_unit_price)
VALUES
(SELECT '1', '1546', '3', '10', '10'
FROM location_items
WHERE location_id = 4
AND item_id = 1546
AND quantity < 10
);
Or, if you want to do it all in one query, including updates:
REPLACE INTO sales_items
(item_id, quantity_purchased, item_cost_price, item_unit_price)
VALUES
(SELECT item_id, ??, ??, ??
FROM location_items
WHERE quantity < 10
AND quantity > 0
);
...where you have to fill the ?? with references to columns holding the values for item_cost_price and item_unit_price, and you have a unique constraint on item_id

Not possible like that. An INSERT query cannot have a where clause, period.
You can, hover, do an insert select from:
INSERT INTO ...
SELECT ... FROM ... WHERE (...) < 10
If the SELECT finds no rows, then nothing gets inserted.

Related

Select highest value based on aggregate function - SQL

I have a GiftSales table, it contains the id of the item (giftId), and the category of that item (categoryId)
I need to get the best selling item for each category.
Right now my query looks like this
SELECT giftId, categoryId, COUNT(giftId) as Total
FROM GiftSales
GROUP BY giftId, categoryId
And its giving me
==================================
|| giftId || categoryId || Total||
==================================
|| 1 || 1 || 8 ||
==================================
|| 2 || 1 || 5 ||
==================================
|| 23 || 2 || 12 ||
==================================
I need to only show the highest value per each category, so basically, the table shouldn't contain the second item.
I'd recommend using a window function, and dense_rank can be helpful when looking at top selling products by category as you may want to include any ties.
Schema (MySQL v8.0)
CREATE TABLE IDs (
`gift_id` INTEGER,
`category_id` INTEGER
);
INSERT INTO IDs
(`gift_id`, `category_id`)
VALUES
('1', '1'),
('1', '1'),
('1', '1'),
('1', '1'),
('1', '1'),
('1', '1'),
('1', '1'),
('1', '1'),
('2', '1'),
('2', '1'),
('2', '1'),
('2', '1'),
('2', '1');
Query #1
select a.category_id,a.gift_id,a.total from (
select
category_id,
gift_id,
count(gift_id) as total,
dense_rank() over (partition by category_id order by count(gift_id) desc) as ranking
from IDs group by 1,2) as a where ranking = 1;
category_id
gift_id
total
1
1
8
View on DB Fiddle
Use a window function such as MAX OVER per category:
select giftid, categoryid, total
from
(
select
giftid,
categoryid,
count(*) as total,
max(count(*)) over (partition by categoryid) as category_max
from giftsales
group by giftid, categoryid
) aggregated
where total = category_max;
SELECT DISTINCT categoryId, MAX(Total) as total FROM(
SELECT giftId, categoryId, COUNT(giftId) as Total FROM GiftSales GROUP BY giftId, categoryId
) AS T GROUP BY giftId, categoryId;
I got it working by using the distinct with the categoryId and since you need the total by category I removed the giftId and everything worked fine :) I used a playground to test this and the playground can be found here -> https://www.db-fiddle.com/f/qsGLKUZyos2ZKTftykkazd/0

How to get difference or delta of counts entries of each days with window functions?

I have a table with few fields like id, country, ip, created_at. Then I am trying to get the deltas between total entry of one day and total entry of the next day.
CREATE TABLE session (
id int NOT NULL AUTO_INCREMENT,
country varchar(50) NOT NULL,
ip varchar(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
INSERT INTO `session` (`id`, `country`, `ip`, `created_at`) VALUES
('1', 'IN', '10.100.102.11', '2021-04-05 20:26:02'),
('2', 'IN', '10.100.102.11', '2021-04-05 19:26:02'),
('3', 'US', '10.120.102.11', '2021-04-17 10:26:02'),
('4', 'US', '10.100.112.11', '2021-04-16 12:26:02'),
('5', 'AU', '10.100.102.122', '2021-04-12 19:36:02'),
('6', 'AU', '10.100.102.122', '2021-04-12 18:20:02'),
('7', 'AU', '10.100.102.122', '2021-04-12 23:26:02'),
('8', 'US', '10.100.102.2', '2021-04-16 21:33:01'),
('9', 'AU', '10.100.102.122', '2021-04-18 20:46:02'),
('10', 'AU', '10.100.102.111', '2021-04-04 13:19:12'),
('11', 'US', '10.100.112.11', '2021-04-16 12:26:02'),
('12', 'IN', '10.100.102.11', '2021-04-05 15:26:02'),
('13', 'IN', '10.100.102.11', '2021-04-05 19:26:02');
Now I have written this query to get the delta
SELECT T1.date1 as date, IFNULL(T1.cnt1-T2.cnt2, T1.cnt1) as delta from (
select TA.dateA as date1, MAX(TA.countA) as cnt1 from (
select DATE(created_at) AS dateA, COUNT(*) AS countA
FROM session
GROUP BY DATE(created_at)
UNION
select DISTINCT DATE(DATE(created_at)+1) AS dateA, 0 AS countA
FROM session
) as TA
group by TA.dateA
) as T1
LEFT OUTER JOIN (
select DATE(DATE(created_at)+1) AS date2,
COUNT(*) AS cnt2
FROM session
GROUP BY DATE(created_at)
) as T2
ON T1.date1=T2.date2
ORDER BY date;
http://sqlfiddle.com/#!9/4f5fd26/60
Then I am getting the results as
date delta
2021-04-04 1
2021-04-05 3
2021-04-06 -4
2021-04-12 3
2021-04-13 -3
2021-04-16 3
2021-04-17 -2
2021-04-18 0
2021-04-19 -1
Now, is there any place of improvements/optimizes on it with/or window functions? (I am zero with SQL, still playing around).
Try a shorter version
with grp as (
SELECT t.dateA, SUM(t.cnt) AS countA
FROM session,
LATERAL (
select DATE(created_at) AS dateA, 1 as cnt
union all
select DATE(DATE(created_at)+1), 0 as cnt
) t
GROUP BY dateA
)
select t1.dateA as date, IFNULL(t1.countA-t2.countA, t1.countA) as delta
from grp t1
left join grp t2 on DATE(t2.dateA + 1) = t1.dateA
order by t1.dateA
db<>fiddle

I want to to find a way to get my appropriate result in 1 mysql query

I have a table name order_history where I store both old_status and new_status of company orders.
the schema of table :
CREATE TABLE order_history (
id int(11) NOT NULL AUTO_INCREMENT,
old_status longtext COLLATE utf8_unicode_ci,
new_status longtext COLLATE utf8_unicode_ci,
created_at datetime NOT NULL,
order_id int(11) DEFAULT NULL,
PRIMARY KEY (id)
}
The insert to populate is :
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (1, '56', '714', '2020-12-20 21:37:54', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (2, '714', '61', '2020-12-20 21:37:56', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (3, '61', '713', '2020-12-20 21:38:17', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (4, '713', '42', '2020-12-20 21:38:26', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (5, '42', '51', '2020-12-20 21:59:17', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (6, '56', '714', '2020-12-20 22:21:27', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (7, '714', '61', '2020-12-20 22:21:29', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (8, '61', '713', '2020-12-20 22:24:28', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (9, '713', '42', '2020-12-20 22:24:43', 94471496);
And Now the question I want to find the TIMEDIFF of created_ats between rows that new_status=61 and rows that new_status=42 and old_status=713.
So in the example the affected rows should be (2,4,7,9) , and the right answer will be the TIMEDIFF between rows with ids (2,4) and rows with ids (7,9). But my query returns 3 results instead of 2 and it also calculate the TIMEDIFF between rows (2,9).
How can I exclude this result?
Here is my query:
select *
from (select oschStart.order_id as order_id, TIMEDIFF(oschEnd.created_at, oschStart.created_at) as confirm_time
from (select osch1.order_id, osch1.created_at
from order_history osch1
where osch1.old_status = 713
and osch1.new_status = 42
) oschEnd
join (select osch1.order_id, osch1.created_at
from order_history osch1
where osch1.new_status = 61
) oschStart
on oschStart.order_id = oschEnd.order_id and oschEnd.created_at > oschStart.created_at) order_time;
A simpler approach is to use a correlated sub query
select *,
timediff(
(select created_at from order_history oh1
where oh1.order_id = oh.order_id and
oh1.id > oh.id and
oh1.old_status = '713' and oh1.new_status = '42'
order by oh1.id asc limit 1),oh.created_at) diff
from order_history oh
where new_status = 61;
Why you have the unwanted results?
oschStart will result rows[2,7] and oschEnd will result rows [4,9]. Joining these subqueries will result in 4 rows [(2,4),(2,9),(7,4),(7,9)]. Your condition (on oschStart.order_id = oschEnd.order_id and oschEnd.created_at > oschStart.created_at) will result in these three rows: [(2,4),(2,9),(7,9)]. It wont prune (2,9) because also 9[created_date] > 2[created_date]. So your query will match a oschStart with all oschEnds that occurs after it. But You need it to be matched with the first occurring oschEnd
Solution
Use group by. If you group by your query results on a field and put other fields on your select part, Mysql will fill those fields with first row of that "group". So assuming that order_history is sorted on created_date you may use this query:
select order_time.id , order_time.*
from (
select oschStart.id as id, oschStart.order_id as order_id,
TIMEDIFF(oschEnd.created_at, oschStart.created_at) as confirm_time
from (select osch1.order_id, osch1.created_at
from order_history osch1
where osch1.old_status = 713
and osch1.new_status = 42
) oschEnd
join (select osch1.id as id, osch1.order_id, osch1.created_at
from order_history osch1
where osch1.new_status = 61
) oschStart
on oschStart.order_id = oschEnd.order_id
and oschEnd.created_at > oschStart.created_at)
order_time
group by order_time.id;

Tracking Increasing or decreasing prices in MySQL

I tried to write a query that returns the id, product, price, and change columns. The change column should follow this logic. If the item price has increased it should write positive and if it has decreased negative depending on the product and excluding the first initial product price. The last product price should be taken into consideration.
This is how the result should look like.
id product price change
1 apple 1
2 apple 1.5 positive
3 apple 3 positive
4 melon 4
5 melon 3 negative
6 apple 2 negative
I have tried to use Case When statement but failed.
select
p.id,
p.product,
p.price,
CASE
WHEN p.product = p.product AND p.price > p.price THEN 'Positive'
WHEN p.product = p.product AND p.price > p.price THEN 'Negative'
END AS 'Change'
from products p
Create and insert statements
CREATE TABLE `products` (
`id` INT(11) NOT NULL,
`product` VARCHAR(50) NOT NULL COLLATE 'utf8_unicode_ci',
`price` DECIMAL(10,2) NOT NULL DEFAULT '0.00'
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
;
INSERT INTO products (id, product, price)
VALUES (1, 'apple', 1);
INSERT INTO products (id, product, price)
VALUES (2, 'apple', 1.5);
INSERT INTO products (id, product, price)
VALUES (3, 'apple', 3);
INSERT INTO products (id, product, price)
VALUES (4, 'melon', 4);
INSERT INTO products (id, product, price)
VALUES (5, 'melon', 3);
INSERT INTO products (id, product, price)
VALUES (6, 'apple', 2);
Use lag():
select p.*,
(case when lag(price) over (partition by product order by id) < price
then 'negative'
when lag(price) over (partition by product order by id) > price
then 'positive'
end)
from products p;
In archaic versions of MySQL, you can use a correlated subquery:
select p.*
(case when prev_price < price
then 'negative'
when prev_price > price
then 'positive'
end)
from (select p.*,
(select p2.price
from product p2
where p2.product = p.product and p2.id < p.id
order by p2.id desc
limit 1
) as prev_price
from product p
) p;

case when wont work with update statement

For my MySQL query,
update Products
set new_price = (
case when change_date>'2019-08-16' then new_price=100 else new_price end
)
;
the update statement sets the new price to 0. why?
table details:
insert into Products (product_id, new_price, change_date) values ('1', '20', '2019-08-14');
insert into Products (product_id, new_price, change_date) values ('2', '50', '2019-08-14');
insert into Products (product_id, new_price, change_date) values ('1', '30', '2019-08-15');
insert into Products (product_id, new_price, change_date) values ('1', '35', '2019-08-16');
insert into Products (product_id, new_price, change_date) values ('2', '65', '2019-08-17');
insert into Products (product_id, new_price, change_date) values ('3', '20', '2019-08-18');
select * from Products;
You are attempting to set new_price to the result of the boolean expression new_price=100, which will be 0 unless new_price already has the value 100. Just remove the new_price= and the code will work fine:
update Products
set new_price = case when change_date>'2019-08-16' then 100
else new_price
end
Demo on dbfiddle
It sets the value to zero because new_price = 100 is a boolean expression -- and that evaluates to either 0 or 1.
If this is all you are doing, you should filter in the where clause:
update Products
set new_price = 100
where change_date > '2019-08-16';