Create a Stored Procedure Debug - mysql

Using the data and schema from this site: Using the data from this site: https://www.sqlservertutorial.net/sql-server-sample-database/
I am given the prompt:
Create a stored procedure called placeOrder() that can be called to insert a new order in the database. It will receive a customerId as an INT, a productId as an INT and a qty as an INT and return (as anoutput parameter) the order_id of the new row created in table orders.
This stored procedure will find the store with the largest stock of that particular product and assign that store to the order. The order_status should be set to 1 (i.e. Pending), the current system date (see function CURDATE) will be assigned to order_date, column required_date will be 7 days from the current system date (see function ADDDATE) and the column staff_id will be assigned for anyone that works in the selected store (per previous requirement). Since the order_id column is not an auto-incremented column you need to calculate the value for it. You can use max(order_id) to find out the highest order_id in the table.
The order_item row shall be set to the productId and qty passed to the stored procedure. The item_id shall be set to 1 (since this order will only have one item).The list price should be retrieved from the products table using the passed productId. The discount value should be set to 0
If I'm understanding the prompt correctly, I need to first the store id which has the most of a particular product. Once I have that store, I need to insert a new row of data into the table "orders" with the essential data being order_id, customer_id, order_status, order_date, required, date, and staff_id. I do NOT understand what the last part of the question is asking/how to go about solving.
Here's my current code, but I'm almost positive it's chalk full of errors and notes and missing pieces so please help me out where you can:
DELIMITER //
CREATE procedure placeOrder (IN customerID INT, IN productID INT, IN QTY INT, OUT order_id INT)
BEGIN
DECLARE customerID INT;
DECLARE produtcID INT;
DECLARE quantity INT;
SELECT customer_id INTO customerID from customers where customer_id = customerID;
SELECT product_id INTO productID from order_items where product_id = productID;
SELECT quantity INTO qty from order_items where quantity = qty;
/**find store id with max stocks of product**/
select st.store_name, sk.store_id from stocks as sk
INNER JOIN
stores as st
ON sk.store_id = st.store_id
WHERE max(sk.quantity)
GROUP by sk.product_id;
select st.store_id from stores as st
INNER JOIN orders as o
ON st.store_id= o.store_id
Insert into orders (order_id, customer_id, order_status, order_date, required_date, staff_id)
WHERE order_status = '1',
AND order_date = select(curdate()),
AND required_date = adddate('order_date' +7),
AND staff_id = /**ANYONE from store listed in precious query (how do I relate these two queries)**
END

Don't re-declare function parameters.
You don't need to use a SELECT query to set variables that are already set in parameters.
You're not getting the store with the maximum quantity correctly. Use ORDER BY sk.quantity DESC LIMIT 1
You need to use INTO <variable> in a query to set variables from a query. If you don't do this, the result of the query will be turned into the result of the procedure, which isn't desired here.
You don't use WHERE in an INSERT statement. WHERE is used for finding existing rows that match a condition, but INSERT is for creating new rows. You use VALUES() to list all the values that should be assigned to the specified columns.
CREATE procedure placeOrder (IN customerID INT, IN productID INT, IN QTY INT, OUT orderId INT)
BEGIN
DECLARE topStoreId INT;
DECLARE staffId INT;
/**find store id with max stocks of product**/
select sk.store_id
from stocks as sk
INNER JOIN stores as st
ON sk.store_id = st.store_id
WHERE sk.product_id = productID
ORDER BY sk.quantity DESC
LIMIT 1
INTO topStoreId;
/* Pick an arbitrary staff from the selected store */
SELECT staff_id
FROM staffs
WHERE store_id = topStoreId
LIMIT 1
INTO staffId;
SELECT MAX(order_id)
FROM orders AS o
INTO orderId;
Insert into orders (order_id, customer_id, order_status, order_date, required_date, staff_id, store_id)
VALUES (orderId, customerId, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 1 WEEK), staffId, topStoreId);
END

Related

MySql: Referencing columns from main query in subquery

the following table is supposed to reflect the storage location (stockId) of
purchases, where a purchase is received in parts at different times. (I omitted the date column.)
CREATE TABLE stock (purchaseNo INT NOT NULL, quantity INT NOT NULL, stockId INT NOT NULL)
Let's say purchase 10 has arrived in three parts and is stored at two different locations (5 and 6).
INSERT INTO stock VALUES(10, 2000, 5)
INSERT INTO stock VALUES(10, 3000, 5)
INSERT INTO stock VALUES(10, 1000, 6)
Now I would like to select those stocks having more than 2000 kg (quantity) available. This can be done with
SELECT stockId AS id, SUM(quantity) AS q
FROM stock
WHERE purchaseNo=10
GROUP BY stockId
HAVING q>2000
But since I am only interested in the stockId, I would like to prevent the quantity to be returned as well. Is this possible?
One of my failing attempts is
SELECT stockId AS id
FROM stock
WHERE purchaseNo=10 AND (SELECT SUM(quantity) FROM stock WHERE purchaseNo=10 AND stockId=id) >2000
with or without GROUP BY the following error is returned:
ERROR 1054 (42S22): Unknown column 'id' in 'where clause'
You could pass the SUM to HAVING and skip it from the results.
SELECT stockId AS id
FROM stock
WHERE purchaseNo=10
GROUP BY stockId
HAVING SUM(quantity)>2000;
SQLFiddle example.
Have you tried something like this?
SELECT distinct S1.stockId AS S1.id from stock S1
inner join(
SELECT S2.stockId AS S2.id, SUM(S2.quantity) AS S2.q
FROM stock S2
WHERE S2.purchaseNo=10
GROUP BY S2.stockId
HAVING S2.q>2000)
on S1.stockId=S2.stockId

MYSQL reference to another table fields

I am trying to get for a specific period of time the quantity of products sold. For this example : the products table has a quantity field which increments whenever a certain product is sold. The orders table has a start date and end date. Is there a way i can hold a reference of the products table (like an object) in my orders table so i can see the quantity sold for each product every start - end date ? A quick example : I am having 2 products:
1 bike 25000 3 and 2 sportbike 30000 5 sold for my date. So an order would be something like : 1 05.07.2015 05.07.2015 and those products.
CREATE TABLE products (
product_no integer PRIMARY KEY,
name varchar(20),
price float,
quantity integer,
);
CREATE TABLE sells (
sell_id integer PRIMARY KEY,
start_date date,
end_date date
);
//New idea :
CREATE TABLE products (
product_no integer PRIMARY KEY,
name varchar(20),
price float
);
CREATE TABLE sells (
sell_id integer PRIMARY KEY,
date date,
quantity integer,
product_id integer FOREIGN KEY
);
Unless an order is always for a single product, I think you will need a third table. To me a minimal *) data model for this situation would look like this:
CREATE TABLE products (
product_no integer PRIMARY KEY,
name varchar(20) not null,
price float,
quantity_on_stock integer not null
);
CREATE TABLE orders (
order_id integer PRIMARY KEY,
order_date date not null
);
CREATE TABLE orderlines (
order_id integer not null REFERENCES orders.order_id,
product_no integer REFERENCES products.product_no,
price integer,
quantity integer not null,
PRIMARY KEY(order_id, product_no)
);
Then, a query to get sales in a certain period could look like this:
select
p.product_no,
p.name,
sum(ol.quantity)
from
products p
inner join orderlines ol on ol.product_no = p.product_no
inner join orderlines o on o.order_id = ol.order_id
where
ol.order_date between :start_date and :end_date
*) I say minimal, but it's actually less than minimal. You'd probably want to store invoices too, or at least some kind of indication that says whether an order is actually payed for and delivered, since sometimes orders are cancelled, or are just waiting open.
You definitely want a sales table showing what was sold and when, rather than a table showing the running inventory.
It is much easier in SQL to aggregate detail to create running totals than it is to reconstruct detail from running totals.
So, if you happen to have a sales table containing, among other things, columns for product_id, quantity, and ts (the timestamp of the sale), then you can get a summary of sales by product_id and sales date like this.
SELECT product_id,
DATE(ts) AS salesdate,
SUM(quantity) AS quantity
FROM sales
WHERE ts >= :start_date
AND ts < :end_date + INTERVAL 1 DAY
GROUP BY DATE(ts), product_id
One of the things that's cool about this is you can add a column for transaction_type to this table. If a customer returns a product you tag it return and use a negative quantity. If your shop receives a shipment of products you tag it restock and use a negative quantity. Then, if you want to show net sales -- sales less returned products -- it's a small change to the query.
SELECT product_id,
DATE(ts) AS salesdate,
SUM(quantity) AS quantity
FROM sales
WHERE ts >= :start_date
AND ts < :end_date + INTERVAL 1 DAY
AND transaction_type IN ('sale', 'return')
GROUP BY DATE(ts), product_id
If you want to know how much stock you had on on hand for each product for a particular end_date, you do this (starting from the beginning of time).
SELECT product_id,
SUM(quantity) AS quantity
FROM sales
GROUP BY product_id
In actual inventory / sale systems, it's common to take inventory once a year, close out the previous year's sales table, and start a new one with a starting value for each product. it's also common to organize sales by sales_order and sales_detail, so you can track the sale of multiple items in each transaction.

Return the proportionate share of the same type

just for example lets say I have this table sales(ID,type,price).
Now, for each entry I have to return its proportion of its type sellings.
For instance, if my total revenue from selling bikes is 1000, and I sold 1 pair of bike for 100 so it's proportion will be 0.1.
How do I implement it over SQL?
Thanks!
Set up:
create table sales (
ID numeric,
type varchar(20),
price decimal
);
insert into sales values (1,'bike','900.00');
insert into sales values (2,'bike','100.00');
Query:
select s1.ID, s1.type, s1.price, (s1.price/s2.sum_price) as proportion
from sales s1
inner join (
select type, sum(price) as sum_price
from sales
group by type
) s2
on s1.type = s2.type;
The inner query gets all the sums by type. This is an emulation of the sum(col2) over (partition by col2) which is available in some databases but not in MySQL.

Fetching unconditionad sum and conditioned in a single SQL query

I have a database
create table payments (
id int not null auto_increment primary key,
amount int,
source varchar(255),
dest varchar(255),
);
Is it possible to fetch data in a single query in a way so for each dest I'll have a row with SUM of all amounts, and SUM of amounts where source='XXX'.
You can use CASE in sum also for your specific condition so this will give you the sum of amounts per dest and sum_xxx will have a sum of amounts per dest where source is xxx
select dest,
sum(amount) dest_sum,
sum(case when source ='xxx' then amount else 0 end) sum_xxx
from payments
group by dest
select dest, (select sum(amount) from payments where source = 'XXX'), sum(amount) from payments
where source = 'XXX' group by dest;

How to GROUP BY consecutive data (date in this case)

I have a products table and a sales table that keeps record of how many items a given product sold during each date. Of course, not all products have sales everyday.
I need to generate a report that tells me how many consecutive days a product has had sales (from the latest date to the past) and how many items it sold during those days only.
I'd like to tell you how many things I've tried so far, but the only succesful (and slow, recursive) ones are solutions inside my application and not inside SQL, which is what I want.
I also have browsed several similar questions on SO but I haven't found one that lets me have a clear idea of what I really need.
I've setup a SQLFiddle here to show you what I'm talking about. There you will see the only query I can think of, which doesn't give me the result I need. I also added comments there showing what the result of the query should be.
I hope someone here knows how to accomplish that. Thanks in advance for any comments!
Francisco
http://sqlfiddle.com/#!2/20108/1
Here is a store procedure that do the job
CREATE PROCEDURE myProc()
BEGIN
-- Drop and create the temp table
DROP TABLE IF EXISTS reached;
CREATE TABLE reached (
sku CHAR(32) PRIMARY KEY,
record_date date,
nb int,
total int)
ENGINE=HEAP;
-- Initial insert, the starting point is the MAX sales record_date of each product
INSERT INTO reached
SELECT products.sku, max(sales.record_date), 0, 0
FROM products
join sales on sales.sku = products.sku
group by products.sku;
-- loop until there is no more updated rows
iterloop: LOOP
-- Update the temptable with the values of the date - 1 row if found
update reached
join sales on sales.sku=reached.sku and sales.record_date=reached.record_date
set reached.record_date = reached.record_date - INTERVAL 1 day,
reached.nb=reached.nb+1,
reached.total=reached.total + sales.items;
-- If no more rows are updated it means we hit the most longest days_sold
IF ROW_COUNT() = 0 THEN
LEAVE iterloop;
END IF;
END LOOP iterloop;
-- select the results of the temp table
SELECT products.sku, products.title, products.price, reached.total as sales, reached.nb as days_sold
from reached
join products on products.sku=reached.sku;
END//
Then you just have to do
call myProc()
A solution in pure SQL without store procedure : Fiddle
SELECT sku
, COUNT(1) AS consecutive_days
, SUM(items) AS items
FROM
(
SELECT sku
, items
-- generate a new guid for each group of consecutive date
-- ie : starting with day_before is null
, #guid := IF(#sku = sku and day_before IS NULL, UUID(), #guid) AS uuid
, #sku := sku AS dummy_sku
FROM
(
SELECT currents.sku
, befores.record_date as day_before
, currents.items
FROM sales currents
LEFT JOIN sales befores
ON currents.sku = befores.sku
AND currents.record_date = befores.record_date + INTERVAL 1 DAY
ORDER BY currents.sku, currents.record_date
) AS main_join
CROSS JOIN (SELECT #sku:=0) foo_sku
CROSS JOIN (SELECT #guid:=UUID()) foo_guid
) AS result_to_group
GROUP BY uuid, sku
The query is really not that hard. Declare variables via cross join (SELECT #type:=0) type. Then in the selects, you can set variables value row by row. It is necessary for simulating Rank function.
select
p.*,
sum(s.items) sales,
count(s.record_date) days_sold
from
products p
join
sales s
on
s.sku = p.sku
where record_date between '2013-04-18 00:00:00' and '2013-04-26 00:00:00'
group by sku;