I'm designing a new database to track accounts achieved visits vs monthly targets. The final report shall be requested by start/end dates and one account to show the months inbetween with monthly target and sum of visits.
The complication started when I knew the number of accounts is more than 10 thousands and the targets should be changed monthly or not for the only changed accounts targets(i.e each target will have start and end date. if no end date then the target is always valid). At this point I lost and I need help
For simplicity I will assume I have table with dates periods and simplest situation as follow
accounts
+----+---------+
|id | name |
+----+---------+
| 1 | account1|
| 2 | account2|
+----+---------+
targets
+---+------------+------------+-----------+----------------+
|id | account_id | start_date | end_date | monthly_target |
+---+------------+------------+-----------+----------------+
|1 | 1 | 1-1-2016 | 31-1-2016 | 5 |
|2 | 1 | 1-2-2016 | 31-5-2016 | 4 |
|3 | 1 | 1-7-2016 | null | 7 |
|4 | 2 | 1-1-2016 | null | 10 |
+---+------------+------------+-----------+----------------+
visits
+---+-----------+------------+
|id | date | account_id |
+----------------------------+
|1 | 15-1-2016 | 1 |
|2 | 20-1-2016 | 1 |
|3 | 10-5-2016 | 1 |
|3 | 20-5-2016 | 1 |
|4 | 20-5-2016 | 2 |
+---+-----------+------------+
calendar (Optional)
----------+----------+
|start | end |
----------+----------+
|1-1-2016 | 31-1-2016|
|1-2-2016 | 29-2-2016|
|1-3-2016 | 31-3-2016|
|1-4-2016 | 30-4-2016|
|1-5-2016 | 31-5-2016|
|1-6-2016 | 30-6-2016|
|1-7-2016 | 31-7-2016|
|1-8-2016 | 31-7-2016|
+---------+----------+
Expected report for account1 coverage from 1-4-2016 to 31-7-2016
+---------+-----------+--------+----+
|start | end | target | sum|
+---------+-----------+--------+----+
|1-4-2016 | 30-4-2016 | 4 | 0 |
|1-5-2016 | 31-5-2016 | 4 | 2 |
|1-6-2016 | 30-6-2016 | 0 | 0 |
|1-7-2016 | 31-7-2016 | 7 | 0 |
+---------+-----------+--------+----+
I can accept changing my initial design if it causes problems but assuming the design of targets table is the most practical design for system admin.
I need help in SQL needed to generate the final report.
I modified the range of dates in targets to have an explicit end date even if that means end of year. This way, avoiding the null, the sql could range ok. It also uses the ISO 8601 Standard for dates. And it is implemented in a Stored Proc that takes 3 parameters: account_id, start and end date.
Alias v, the derived table, prevents double counts versus a flat out LEFT JOIN against the visits table. For instance, that 2 would be an errant 7 without that strategy. So it used the LAST_DAY() function.
Schema:
create table accounts
( id int not null,
name varchar(100) not null
);
insert accounts values
(1,'account1'),
(2,'account2');
-- drop table targets;
create table targets
( id int not null,
account_id int not null,
start_date date not null,
end_date date not null,
monthly_target int not null
);
-- truncate targets;
insert targets values
(1,1,'2016-01-01','2016-01-31',5),
(2,1,'2016-02-01','2016-05-31',4),
(3,1,'2016-07-01','2016-12-31',7),
(4,2,'2016-01-01','2016-12-31',10);
create table visits
( id int not null,
date date not null,
account_id int not null
);
-- truncate visits;
insert visits values
(1,'2016-01-15',1),
(2,'2016-01-20',1),
(3,'2016-05-10',1),
(4,'2016-05-20',1),
(5,'2016-05-20',2);
create table calendar
( start date not null,
end date not null
);
insert calendar values
('2016-01-01','2016-01-31'),
('2016-02-01','2016-02-29'),
('2016-03-01','2016-03-31'),
('2016-04-01','2016-04-30'),
('2016-05-01','2016-05-31'),
('2016-06-01','2016-06-30'),
('2016-07-01','2016-07-31'),
('2016-08-01','2016-08-31'),
('2016-09-01','2016-09-30'),
('2016-10-01','2016-10-31'),
('2016-11-01','2016-11-30'),
('2016-12-01','2016-12-31');
Stored Proc:
DROP PROCEDURE IF EXISTS uspGetRangeReport007;
DELIMITER $$
CREATE PROCEDURE uspGetRangeReport007
( p_account_id INT,
p_start DATE,
p_end DATE
)
BEGIN
SELECT c.start,c.end,
IFNULL(t.monthly_target,0) as target,
-- IFNULL(sum(v.id),0) as visits
IFNULL(v.theCount,0) as visits
FROM calendar c
LEFT JOIN targets t
ON account_id=p_account_id
AND c.start BETWEEN t.start_date AND t.end_date
AND c.end BETWEEN t.start_date AND t.end_date
LEFT JOIN
( SELECT LAST_DAY(date) as lastDayOfMonth,
count(id) as theCount
FROM VISITS
WHERE account_id=p_account_id
GROUP BY LAST_DAY(date)
) v
ON v.lastDayOfMonth BETWEEN c.start AND c.end
WHERE c.start BETWEEN p_start AND p_end
AND c.end BETWEEN p_start AND p_end
GROUP BY c.start,c.end,t.monthly_target
ORDER BY c.start;
END;$$
DELIMITER ;
Test:
call uspGetRangeReport007(1,'2016-04-01','2016-07-31');
+------------+------------+--------+--------+
| start | end | target | visits |
+------------+------------+--------+--------+
| 2016-04-01 | 2016-04-30 | 4 | 0 |
| 2016-05-01 | 2016-05-31 | 4 | 2 |
| 2016-06-01 | 2016-06-30 | 0 | 0 |
| 2016-07-01 | 2016-07-31 | 7 | 0 |
+------------+------------+--------+--------+
SELECT c.start,
c.end,
t.monthly_target AS target,
(
SELECT COUNT(*)
FROM visits
WHERE `date` BETWEEN c.start AND c.end
AND account_id = ? -- Specify '1'
) AS `sum` -- Correlated subquery for counting visits
FROM Calendar AS c
JOIN targets AS t ON c.start_date >= t.start_date
AND ( t.end_date IS NULL
OR c.start_date < t.end_date )
WHERE c.start >= ? -- Specify date range
AND c.end <= ?
Related
Example of a table:
ID
StartDate
EndDate
1
2021-01-01
2021-04-01
2
2021-03-01
2021-05-01
How do I get the list of months name between two date columns as the range?
Something like:
|ID| Months |
|--|--------|
|1 | January|
|1 | Febuary|
|1 | March |
|1 | April |
|2 | March |
|2 | April |
|2 | May |
Most of the examples I have found all manually input the date from one column. I tried monthname(), datediff() but the diff only calculates the difference and not list them
With MySQL8.0 you can do this:
WITH RECURSIVE cte AS (
SELECT
ID,
MONTH(StartDate) as M,
MONTHNAME(StartDate) as MonthName,
1 as X
FROM fromto
UNION ALL
SELECT
cte.ID,
MONTH(DATE_ADD(StartDate,INTERVAL X MONTH)),
MONTHNAME(DATE_ADD(StartDate,INTERVAL X MONTH)),
X+1
FROM cte
INNER JOIN fromto on fromto.ID=cte.ID
WHERE DATE_ADD(StartDate,INTERVAL X MONTH)<=EndDate
)
SELECT ID,M,MonthName FROM cte order by ID,M;
output:
+------+------+-----------+
| ID | M | MonthName |
+------+------+-----------+
| 1 | 1 | January |
| 1 | 2 | February |
| 1 | 3 | March |
| 1 | 4 | April |
| 2 | 3 | March |
| 2 | 4 | April |
| 2 | 5 | May |
+------+------+-----------+
The table fromto in above example is created like this:
CREATE TABLE `fromto` (
`id` int DEFAULT NULL,
`startdate` date DEFAULT NULL,
`enddate` date DEFAULT NULL
);
INSERT INTO `fromto` VALUES (1,'2021-01-01','2021-04-01'),(2,'2021-03-01','2021-05-01');
Without WITH (prior to MySQL 8.0), one could do:
DROP TABLE IF EXISTS counter;
CREATE TABLE counter(i int primary key);
INSERT INTO counter VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12);
SELECT
id,
MONTHNAME(DATE_ADD(StartDate,INTERVAL i MONTH))
FROM
fromto
CROSS JOIN counter
WHERE DATE_ADD(StartDate,INTERVAL i MONTH)<=EndDate
ORDER BY id,2;
As much values as needed should be added to this counter table.
I'm trying to set the value of another column on the first occurrence of any value in a username column in monthly intervals, if there's another column with an specific value.
create table table1
(
username varchar(30) not null,
`date` date not null,
eventid int not null,
firstFlag int null
);
insert table1 (username,`date`, eventid) values
('john','2015-01-01', 1)
, ('kim','2015-01-01', 1)
, ('john','2015-01-01', 1)
, ('john','2015-01-01', 1)
, ('john','2015-03-01', 2)
, ('john','2015-03-01', 1)
, ('kim','2015-01-01', 1)
, ('kim','2015-02-01', 1);
This should result in:
| username | date | eventid | firstFlag |
|----------|------------|---------|-----------|
| john | 2015-01-01 | 1 | 1 |
| kim | 2015-01-01 | 1 | 1 |
| john | 2015-01-01 | 1 | (null) |
| john | 2015-01-01 | 1 | (null) |
| john | 2015-03-01 | 2 | 1 |
| john | 2015-03-01 | 1 | (null) |
| kim | 2015-01-01 | 1 | (null) |
| kim | 2015-02-01 | 1 | 1 |
I've tried using joins as described here, but it updates all rows:
update table1 t1
inner join
( select username,min(`date`) as minForGroup
from table1
group by username,`date`
) inr
on inr.username=t1.username and inr.minForGroup=t1.`date`
set firstFlag=1;
As a1ex07 points out, it would need another per row unique constrain to update the rows I need to:
update table1 t1
inner join
( select id, username,min(`date`) as minForGroup
from table1
where eventid = 1
group by username,month(`date`)
) inr
on inr.id=t1.id and inr.username=t1.username and inr.minForGroup=t1.`date`
set firstFlag=1;
Add an Id column, and use it on the join on constrains.
To allow only those that satisfies a specific condition on another column you need the where clause inside the subquery, otherwise it would try to match different rows as the subquery would return rows with eventid=2 while the update query would return only those with eventid=1.
To use yearly intervals instead of monthly, change the group by statement to use years.
I'm trying to make a report of financial datas for my company:
I have actually two two tables:
___BillableDatas:
|--------|------------|----------|----------|--------------|---------------------|
| BIL_Id | BIL_Date | BIL_Type | BIL_Rate | BIL_Quantity | BIL_ApplicableTaxes |
|--------|------------|----------|----------|--------------|---------------------|
| 1 | 2017-01-01 | Night | 95 | 1 | 1 |
| 2 | 2017-01-02 | Night | 95 | 1 | 1 |
| 3 | 2017-01-15 | Night | 105 | 1 | 1 |
| 4 | 2017-01-15 | Item | 8 | 2 | 1,2 |
| 5 | 2017-02-14 | Night | 95 | 1 | 1 |
| 6 | 2017-02-15 | Night | 95 | 1 | 1 |
| 7 | 2017-02-16 | Night | 95 | 1 | 1 |
| 8 | 2017-03-20 | Night | 89 | 1 | 1 |
| 9 | 2017-03-21 | Night | 89 | 1 | 1 |
| 10 | 2017-03-21 | Item | 8 | 3 | 1,2 |
|--------|------------|----------|----------|--------------|---------------------|
___SalesTaxes:
|--------|------------|
| STX_Id | STX_Amount |
|--------|------------|
| 1 | 14.00 |
| 2 | 5.00 |
|--------|------------|
I need to know for each month the sum of my revenue with and without taxes.
Actually I can make the report but don't know how to loop into the ___SalesTaxes table.
What I have actually:
SELECT month(BIL_Date) AS month,
sum(BIL_Rate * BIL_Quantity) AS sumval
FROM `___BillableDatas`
WHERE BIL_Date BETWEEN "2017-01-01" AND "2017-12-31"
AND BIL_Type = "Night" OR BIL_Type = "Item"
GROUP BY year(BIL_Date), month(BIL_Date)
Thanks for your help.
as kbball mentioned you have an unresolved many to many relationship in your main table. A proper table should never be designed to have more than one value per field. Resolving many to many relationships is quite simple. You will need to create a new table something like bill_taxType or some relation like that. The new table would have two fields as well as the standard primary key, it will have bill_id and applicable tax id. In the case of your 1,2 fields like bill id 4 in the new table it will look like
primary key, bill id, applicable tax id
1 4 1
2 4 2
In your final query you will join all three together on the appropriate primary key-foreign key relationship. This final query should have the data that you need.
This will work, I've created following example will help you lot for debugging and implementation. try to implement as below :
If(OBJECT_ID('tempdb..#___BillableDatas') Is Not Null)
Begin
Drop Table #___BillableDatas
End
If(OBJECT_ID('tempdb..#___SalesTaxes') Is Not Null)
Begin
Drop Table #___SalesTaxes
End
CREATE TABLE #___BillableDatas
(
BIL_Id INT IDENTITY (1,1),
BIL_Date DATETIME,
BIL_Type VARCHAR(50),
BIL_Rate FLOAT,
BIL_Quantity INT,
BIL_ApplicableTaxes VARCHAR(10)
);
INSERT INTO #___BillableDatas (BIL_Date,BIL_Type,BIL_Rate,BIL_Quantity,BIL_ApplicableTaxes)
VALUES ('2017-01-01','Night',95,1,'1'),
('2017-01-02','Night',95,1,'1'),
('2017-01-15','Night',105,1,'1'),
('2017-01-15','Item',8,2,'1,2'),
('2017-02-14','Night',95,1,'1'),
('2017-02-15','Night',95,1,'1'),
('2017-02-16','Night',95,1,'1'),
('2017-03-20','Night',89,1,'1'),
('2017-03-21','Night',89,1,'1'),
('2017-03-21','Item',8,1,'1,2')
CREATE TABLE #___SalesTaxes
(
STX_Id INT IDENTITY (1,1),
STX_Amount FLOAT
);
INSERT INTO #___SalesTaxes (STX_Amount) VALUES (14.00),(5.00)
-----------------------------------------------------------------
SELECT * FROM #___BillableDatas
SELECT * FROM #___SalesTaxes
SELECT MONTH(BD.BIL_Date) AS [Month],SUM(BD.BIL_Rate * BD.BIL_Quantity) AS 'Without Tax'
,(SUM(BD.BIL_Rate * BD.BIL_Quantity)+((SUM(BD.BIL_Rate * BD.BIL_Quantity)/100)*BD.Tax1)) AS 'With Tax 1'
,(SUM(BD.BIL_Rate * BD.BIL_Quantity)+((SUM(BD.BIL_Rate * BD.BIL_Quantity)/100)*BD.Tax2)) AS 'With Tax 2'
FROM
(
SELECT *,
(SELECT ST1.STX_Amount FROM Func_Split(BIL_ApplicableTaxes,',') AS F LEFT JOIN #___SalesTaxes AS ST1 ON F.Element=ST1.STX_Id WHERE F.Element='1') AS Tax1 ,
(SELECT ST1.STX_Amount FROM Func_Split(BIL_ApplicableTaxes,',') AS F LEFT JOIN #___SalesTaxes AS ST1 ON F.Element=ST1.STX_Id WHERE F.Element='2') AS Tax2
FROM #___BillableDatas) AS BD
WHERE BD.BIL_Date BETWEEN '2017-01-01' AND '2017-12-31' AND BD.BIL_Type = 'Night' OR BD.BIL_Type = 'Item'
GROUP BY YEAR(BD.BIL_Date), MONTH(BD.BIL_Date),BD.Tax1,BD.Tax2
You will require function Func_Split for above solution, use this :
CREATE FUNCTION [dbo].[func_Split]
(
#DelimitedString varchar(8000),
#Delimiter varchar(100)
)
RETURNS #tblArray TABLE
(
ElementID int IDENTITY(1,1), -- Array index
Element varchar(1000) -- Array element contents
)
AS
BEGIN
-- Local Variable Declarations
-- ---------------------------
DECLARE #Index smallint,
#Start smallint,
#DelSize smallint
SET #DelSize = LEN(#Delimiter)
-- Loop through source string and add elements to destination table array
-- ----------------------------------------------------------------------
WHILE LEN(#DelimitedString) > 0
BEGIN
SET #Index = CHARINDEX(#Delimiter, #DelimitedString)
IF #Index = 0
BEGIN
INSERT INTO
#tblArray
(Element)
VALUES
(LTRIM(RTRIM(#DelimitedString)))
BREAK
END
ELSE
BEGIN
INSERT INTO
#tblArray
(Element)
VALUES
(LTRIM(RTRIM(SUBSTRING(#DelimitedString, 1,#Index - 1))))
SET #Start = #Index + #DelSize
SET #DelimitedString = SUBSTRING(#DelimitedString, #Start , LEN(#DelimitedString) - #Start + 1)
END
END
RETURN
END
Hi everybody,
I have this visitors table:
ID | Date | Purpose
1 | 20/10/2016 | Purpose1
2 | 22/10/2016 | Purpose1
3 | 25/10/2016 | Purpose2
4 | 12/11/2016 | Purpose1
5 | 14/11/2016 | Purpose2
6 | 16/11/2016 | Purpose2
Currently I'm using this query:
select case
when date like '%/10/2016' then '10/2016'
when date like '%/11/2016' then '11/2016'
end as month, count(*) as total
from visitors
where
date like '%/10/2016'
or date like '%/11/2016'
GROUP by month
I can only get month and total column count from query above. How can I achieve this output?
Month | Total | Purpose1 | Purpose2
10/2016 | 3 | 2 | 1
11/2016 | 3 | 1 | 2
Thanks!
Consider the following...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,Date DATE NOT NULL
,Purpose VARCHAR(12) NOT NULL
);
INSERT INTO my_table VALUES
(1,'2016-10-20','Purpose1'),
(2,'2016-10-22','Purpose1'),
(3,'2016-10-25','Purpose2'),
(4,'2016-11-12','Purpose1'),
(5,'2016-11-14','Purpose2'),
(6,'2016-11-16','Purpose2');
SELECT DATE_FORMAT(date,'%Y-%m') month
, SUM(purpose = 'purpose1') purpose1
, SUM(purpose = 'purpose2') purpose2
, COUNT(*) total
FROM my_table
GROUP
BY month;
+---------+----------+----------+-------+
| month | purpose1 | purpose2 | total |
+---------+----------+----------+-------+
| 2016-10 | 2 | 1 | 3 |
| 2016-11 | 1 | 2 | 3 |
+---------+----------+----------+-------+
..or (and in my view, better, provided you have access to application code)...
SELECT DATE_FORMAT(date,'%Y-%m') month
, purpose
, COUNT(*) total
FROM my_table
GROUP
BY month
, purpose;
+---------+----------+-------+
| month | purpose | total |
+---------+----------+-------+
| 2016-10 | Purpose1 | 2 |
| 2016-10 | Purpose2 | 1 |
| 2016-11 | Purpose1 | 1 |
| 2016-11 | Purpose2 | 2 |
+---------+----------+-------+
Transposing tables isn't very fast. It is better to do so in some small program.
If you do a
select case
when date like '%/10/2016' then '10/2016'
when date like '%/11/2016' then '11/2016'
end as month, count(*) as total, Purpose
from visitors
where
date like '%/10/2016'
or date like '%/11/2016'
GROUP by month, Purpose
You'll have a good starting point.
You might need to add an ORDER BY clause (depending on your DBMS).
If (and only if) you only have two purposes in your table and the table isn't huge in size, you can create two views and join them.
I'm having trouble finding duplicates in a database table with the following setup:
==========================================================================
| stock_id | product_id | store_id | stock_qty | updated_at |
==========================================================================
| 9990 | 51 | 1 | 13 | 2014-10-25 16:30:01 |
| 9991 | 90 | 2 | 5 | 2014-10-25 16:30:01 |
| 9992 | 161 | 1 | 3 | 2014-10-25 16:30:01 |
| 9993 | 254 | 1 | 18 | 2014-10-25 16:30:01 |
| 9994 | 284 | 2 | 12 | 2014-10-25 16:30:01 |
| 9995 | 51 | 1 | 11 | 2014-10-25 17:30:02 |
| 9996 | 90 | 2 | 5 | 2014-10-25 17:30:02 |
| 9997 | 161 | 1 | 3 | 2014-10-25 17:30:02 |
| 9998 | 254 | 1 | 16 | 2014-10-25 17:30:02 |
| 9999 | 284 | 2 | 12 | 2014-10-25 17:30:02 |
==========================================================================
Stock updates are imported into this table every hour, I'm trying to find duplicate stock entries (any rows which have a matching product id and store id) so I can delete the oldest. The query below is my attempt, by comparing product ids and store ids on a join like this I can find one set of duplicates:
SELECT s.`stock_id`, s.`product_id`, s.`store_id`, s.`stock_qty`, s.`updated_at`
FROM `stock` s
INNER JOIN `stock` j ON s.`product_id`=j.`product_id` AND s.`store_id`=j.`store_id`
GROUP BY `stock_id`
HAVING COUNT(*) > 1
ORDER BY s.updated_at DESC, s.product_id ASC, s.store_id ASC, s.stock_id ASC;
While this query will work, it doesn't find ALL duplicates, only 1 set, which means if an import goes awry and isn't noticed until the morning, there's a possibility that we'll be left with tons of duplicate stock entries. My MySQL skills are sadly lacking and I'm at a complete loss about how to find and delete all duplicates in a fast, reliable manner.
Any help or ideas are welcome. Thanks
You can use this query:
DELETE st FROM stock st, stock st2
WHERE st.stock_id < st2.stock_id AND st.product_id = st2.product_id AND
st.store_id = st2.store_id;
This query will delete older record having same product_id and store_id and will keep latest record.
A self join on store_id, product_id and 'is older' in combination with DISTINCT should give you all rows where also a newer version exists:
> SHOW CREATE TABLE stock;
CREATE TABLE `stock` (
`stock_id` int(11) NOT NULL,
`product_id` int(11) DEFAULT NULL,
`store_id` int(11) DEFAULT NULL,
`stock_qty` int(11) DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`stock_id`)
> select * from stock;
+----------+------------+----------+-----------+---------------------+
| stock_id | product_id | store_id | stock_qty | updated_at |
+----------+------------+----------+-----------+---------------------+
| 1 | 1 | 1 | 1 | 2001-01-01 12:00:00 |
| 2 | 2 | 2 | 1 | 2001-01-01 12:00:00 |
| 3 | 2 | 2 | 1 | 2002-01-01 12:00:00 |
+----------+------------+----------+-----------+---------------------+
> SELECT DISTINCT s1.stock_id, s1.store_id, s1.product_id, s1.updated_at
FROM stock s1 JOIN stock s2
ON s1.store_id = s2.store_id
AND s1.product_id = s2.product_id
AND s1.updated_at < s2.updated_at;
+----------+----------+------------+---------------------+
| stock_id | store_id | product_id | updated_at |
+----------+----------+------------+---------------------+
| 2 | 2 | 2 | 2001-01-01 12:00:00 |
+----------+----------+------------+---------------------+
> DELETE stock FROM stock
JOIN stock s2 ON stock.store_id = s2.store_id
AND stock.product_id = s2.product_id
AND stock.updated_at < s2.updated_at;
Query OK, 1 row affected (0.02 sec)
> select * from stock;
+----------+------------+----------+-----------+---------------------+
| stock_id | product_id | store_id | stock_qty | updated_at |
+----------+------------+----------+-----------+---------------------+
| 1 | 1 | 1 | 1 | 2001-01-01 12:00:00 |
| 3 | 2 | 2 | 1 | 2002-01-01 12:00:00 |
+----------+------------+----------+-----------+---------------------+
Or you can use a stored Procedure:
DELIMITER //
DROP PROCEDURE IF EXISTS removeDuplicates;
CREATE PROCEDURE removeDuplicates(
stockID INT
)
BEGIN
DECLARE stockToKeep INT;
DECLARE storeID INT;
DECLARE productID INT;
-- gets the store and product value
SELECT DISTINCT store_id, product_id
FROM stock
WHERE stock_id = stockID
LIMIT 1
INTO
storeID, productID;
SELECT stock_id
FROM stock
WHERE product_id = productID AND store_id = storeID
ORDER BY updated_at DESC
LIMIT 1
INTO
stockToKeep;
DELETE FROM stock
WHERE product_id = productID AND store_id = storeID
AND stock_id != stockToKeep;
END //
DELIMITER ;
And afterwards call it for every pair of the product id and store id via a cursor procedure:
DELIMITER //
CREATE PROCEDURE updateTable() BEGIN
DECLARE done BOOLEAN DEFAULT FALSE;
DECLARE stockID INT UNSIGNED;
DECLARE cur CURSOR FOR SELECT DISTINCT stock_id FROM stock;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;
OPEN cur;
testLoop: LOOP
FETCH cur INTO stockID;
IF done THEN
LEAVE testLoop;
END IF;
CALL removeDuplicates(stockID);
END LOOP testLoop;
CLOSE cur;
END//
DELIMITER ;
And then just call the second procedure
CALL updateTable();