sql question about ordering after a sum - sql

i have a products table that contains all the sales as in quantity made at a time .. so the table is :
id | product_department_id | product_id | quantity_sold
i need to list for all the product_department_ids the best 2 selling products . Any ideas how i can do so ?
if you can do it in pl/sql it would be great but sql is ok also !
Thanks !

drop table quantity;
create table quantity (
id number primary key,
product_department_id number,
product_id number,
quantity_sold number,
unique (product_department_id, product_id)
);
insert into quantity values (1, 1, 1, 10);
insert into quantity values (2, 1, 2, 20);
insert into quantity values (3, 1, 3, 30);
insert into quantity values (4, 2, 1, 60);
insert into quantity values (5, 2, 2, 50);
insert into quantity values (6, 2, 3, 40);
select * from (
select quantity_sold, product_id, product_department_id,
row_number() over (partition by product_department_id order by quantity_sold desc) r
from quantity
) where r < 3;
Edit Still not sure about what exactly was asked, but if the combination prodcut/department can have multple entries then it would be:
drop table quantity;
create table quantity (
id number primary key,
product_department_id number,
product_id number,
quantity_sold number
);
insert into quantity values ( 1, 1, 1, 15);
insert into quantity values ( 2, 1, 1, 15);
insert into quantity values ( 3, 1, 1, 15);
insert into quantity values ( 4, 1, 2, 20);
insert into quantity values ( 5, 1, 3, 30);
insert into quantity values (10, 2, 1, 60);
insert into quantity values (11, 2, 2, 50);
insert into quantity values (12, 2, 3, 40);
insert into quantity values (13, 2, 3, 30);
select * from (
select sum(quantity_sold),
product_id, product_department_id,
row_number() over (partition by product_department_id
order by sum(quantity_sold) desc
) r
from quantity
group by product_department_id, product_id
) where r < 3
order by product_department_id, product_id;

If a product can have only one department, you can simply order by:
select product_department_id
from YourTable
where rownum < 3
order by
quantity_sold desc

Related

Why it is showing No such column

/* Triggers*/
create table acustomer( id integer primary key, desc text, last_order_id integer);
create table bcustomer ( id integer primary key, item_id int, customer_id int, quan int, price int);
insert into acustomer (desc) values ( 'rohan');
insert into acustomer (desc) values ('mohan');
insert into acustomer (desc) values ('sohan');
select * from acustomer;
create trigger ccustomer after insert on bcustomer
begin
update acustomer set last_order_id = NEW.id where acustomer.id = NEW.customer_id;
end;
insert into bcustomer (item_id, customer_id, quan, price) values (1, 2, 3, 4);
insert into bcustomer (item_id, customer_id, quan, price) values (5, 6, 7, 8);
insert into bcustomer (item_id, customer_id, quan, price) values (8, 9, 10, 20);
insert into bcustomer (item_id, customer_id, quan, price) values (4, 12, 19, 13);
select * from acustomer;
select * from bcustomer;
#On executing insertion in table ccustomer, it is showing error : no such column : NEW.customer.id
Your last insert for bcustomer table is
insert into bcustomer (item_id, customer_id, quan, price) values (4, 12, 19, 13);
which doesn't have any matching last_order_id in acustomer.
If you will run
insert into bcustomer (item_id, customer_id, quan, price) values (4, 1, 19, 13);
customer_id = 1, it will update your acustomer table since it has a matching id, which is 1.
try this dbfiddle.

Find all records in a table where the most recent record in a join table has a column which is not equal to one is this table?

Image the tables:
CREATE TABLE items (
id INT,
price INT
);
CREATE TABLE item_price_history (
id INT,
item_id INT,
price INT
);
With the following data:
INSERT INTO items (id, price) VALUES (1, 199);
INSERT INTO items (id, price) VALUES (2, 159);
INSERT INTO items (id, price) VALUES (3, 129);
INSERT INTO items (id, price) VALUES (4, 119);
INSERT INTO item_price_history (id, item_id, price) VALUES (1, 1, 249);
INSERT INTO item_price_history (id, item_id, price) VALUES (2, 1, 239);
INSERT INTO item_price_history (id, item_id, price) VALUES (3, 1, 229);
INSERT INTO item_price_history (id, item_id, price) VALUES (4, 1, 199);
INSERT INTO item_price_history (id, item_id, price) VALUES (5, 2, 299);
INSERT INTO item_price_history (id, item_id, price) VALUES (6, 2, 259);
INSERT INTO item_price_history (id, item_id, price) VALUES (7, 2, 159);
INSERT INTO item_price_history (id, item_id, price) VALUES (8, 2, 109);
INSERT INTO item_price_history (id, item_id, price) VALUES (9, 3, 129);
INSERT INTO item_price_history (id, item_id, price) VALUES (10, 4, 159);
INSERT INTO item_price_history (id, item_id, price) VALUES (11, 4, 119);
INSERT INTO item_price_history (id, item_id, price) VALUES (13, 4, 99);
Now I would like to find all the items for which the items.price is not equal to the most recent item_price_history.price (id DESC) in item_price_history. In this case that should produce item with id 3 and 4. As their prices does NOT match the most recent price in item_price_history.
I did the following which works:
SELECT items.id, items.price i_price, item_price_history.id, item_price_history.price as ih_price
FROM items
LEFT JOIN item_price_history ON item_price_history.item_id = items.id
AND item_price_history.id = (SELECT MAX(id) FROM item_price_history WHERE item_id = items.id)
WHERE items.price != item_price_history.price
LIMIT 100
However I have a table of about 2 million rows in items and 20 million rows in item_price_history which I need to scan through, so performance is important.
How would I write such a query in a more performant way e.g by using DISTINCT ON or something?
https://www.db-fiddle.com/f/wPkdHvvzS2tmZq2vKUwgNi/2
Here is a query to do it in bulk with DISTINCT ON:
SELECT items.id, items.price i_price, item_price_history.id, item_price_history.price as ih_price
from items
join (select distinct on (item_id) id, item_id, price from item_price_history order by item_id desc, id desc) item_price_history
on item_price_history.item_id = items.id
WHERE items.price != item_price_history.price;
It should be well supported by an index on item_price_history (item_id , id, price); which will let it do a presorted index-only scan.

Get most recent row inserted with the least specificity

I'll first explain the data model then the desired results and what I have tried.
I have vehicles and sales tables:
CREATE TABLE VEHICLE
(
ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
BRAND INT NOT NULL,
MODEL VARCHAR(255),
VERSION VARCHAR(255),
UNIQUE(BRAND, MODEL, VERSION),
FOREIGN KEY(BRAND) REFERENCES BRAND(ID)
)
CREATE TABLE SALES
(
ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
VEHICLE_ID INT NOT NULL,
DATE DATE NOT NULL,
SALE INT NOT NULL,
CREATED_DATE DATETIME NOT NULL DEFAULT GETDATE(),
FOREIGN KEY (VEHICLE_ID) REFERENCES VEHICLE(ID)
)
This way I can insert several entries for the same vehicle for the same date (when I want to update, I insert a new row)
INSERT INTO SALES (VEHICLE_ID, DATE, SALE, USER_ID)
VALUES (1, '2018-01-01', 2, 3) -- then later i update by inserting a new row
(1, '2018-01-01', 4, 3)
I want to retrieve the last sale inserted for a specific date range (using the DATE), then filter for a specific BRAND, or model or version.
I got it working by doing this
SELECT
S.DATE AS date, SUM(S.SALE_PROJECTION) AS saleProjection
FROM
SALE_PROJECTION S,
(SELECT MAX(ID) AS id
FROM SALE_PROJECTION
WHERE DATE >= CAST(#dateStart AS DATE)
AND DATE <= CAST(#dateEnd AS DATE)
GROUP BY DATE, VEHICLE_ID) S_M,
VEHICLE V
WHERE
1 = 1
AND S.ID = S_M.ID
AND S.VEHICLE_ID = V.ID
AND V.BRAND = 1
AND V.MODEL = 'A6'
AND V.VERSION = '1.0'
GROUP BY S.DATE
ORDER BY DATE
The problem is i want to get the sales for the brand 1 that has the least specificity, meaning:
If i have 3 vehicles:
(1, 'A3', '1.0'),
(1, 'A3', '2.0'),
(1, 'A3', null),
(1, null, null);
if i insert a sale (1, 2018-01-01, 2, 3)
if i insert a sale (2, 2018-01-01, 3, 3) -- the sum for 2018-01-01 would be 5
but then insert a sale for (2, 2018-01-01, 3, 3) -- the sum for 2018-01-01 has to be 3, because it's the last inserted with the least specifity
But the oposite must be true as well
if i insert a sale (3, 2018-01-01, 4, 3)
then insert a sale for (1, 2018-01-01, 1, 3)
then insert a sale for (2, 2018-01-01, 1, 3)
the sum for 2018-01-01 has to be 2, because it's the last inserted
The most general combination of Brand, Model, Version has to "hide" the most specific.
Do i need to change my data model? or this is possible?
I can give more examples if needed.
Thanks in advance

How to retrieve WTD,YTD,MTD users from a user traffic table in the same query?

In a user traffic table as below, I would like to compute the week to date (WTD), month to date ( MTD ), year to date ( YTD ) user and returned user counts.
Test data :
create table user_traffic (session_id number(6), session_day date,
user_id number(6), product_id number(6));
insert into user_traffic values ( 1, date '2016-09-07', 101, 1);
insert into user_traffic values ( 2, date '2016-09-07', 101, 4);
insert into user_traffic values ( 3, date '2016-09-07', 102, 1);
insert into user_traffic values ( 4, date '2016-09-08', 101, 2);
insert into user_traffic values ( 5, date '2016-09-08', 101, 4);
insert into user_traffic values ( 6, date '2016-09-09', 102, 1);
insert into user_traffic values ( 7, date '2016-09-10', 102, 1);
insert into user_traffic values ( 8, date '2016-09-10', 103, 3);
insert into user_traffic values ( 9, date '2016-09-25', 104, 3);
insert into user_traffic values ( 10, date '2016-10-01', 103, 1);
insert into user_traffic values ( 11, date '2016-10-02', 104, 3);
Expected Output :-
Week_Start_Day, WTD_new_cnt, WTD_returned_cnt
Month_Start_Day, MTD_new_cnt, MTD_returned_cnt
Year_Start_Day, YTD_new_cnt, YTD_returned_cnt
Comments :-
For eg: In the above user traffic table userid=104 visited on Oct 02nd and the WTD,MTD,YTD new/returned counts would be as below.
WTD,new,return
2016-09-26(Mon)(Week start day ), 1,0 ( For userid = 104 )
MTD,new,return
2016-09,1,1
2016-10,0,1
YTD,new,return
2016,0,1
What I have tried?
select session_day,
COUNT( distinct user_id ) AS user_cnt,
count(distinct user_id) - lag(count(distinct user_id))
over (order by session_day) gain,
count(newu) AS newu, count(returnu) AS returnu
from
(
select session_id,
session_day,
user_id,
CASE WHEN
count(*) over ( partition by user_id ORDER BY
session_day,session_id ROWS
BETWEEN UNBOUNDED PRECEDING AND
CURRENT ROW
)
= 1
THEN 1
END
AS newu,
CASE WHEN
lag( session_day,1 ) over ( partition by user_id ORDER
BY session_day,session_id
)
<>
lag( session_day,1 ) over ( order by
session_day,session_id
)
THEN 1
END AS returnu
from user_traffic u
)
group by session_day
order by session_day;
I have built this sql in computing the new/returned users from the user traffic table at sessionday level.

Firebird and SQL Server delete highest rows in groups

I need to delete the 3rd and greater records, sorted by product, type and display order, from the following tables:
Stockist table:
CREATE TABLE INVENTORYWEBSTOCKISTS
(
STOCKISTID NUMERIC(10,0) NOT NULL,
PRODUCTID NUMERIC(10,0) NOT NULL,
DISPLAYORDER NUMERIC(10,0),
CUSTOMERID NUMERIC(10,0),
CONSTRAINT PK_INVENTORYWEBSTOCKISTS PRIMARY KEY (STOCKISTID)
);
STOCKISTID is the unique autoinc column for the table and can be used as an ID. PRODUCTID and CUSTOMERID both reference other tables.
Customer table:
CREATE TABLE CUSTOMERS
(
CUSTOMERID NUMERIC(10,0) NOT NULL,
WEBSTOCKISTSTOCKISTTYPE VARCHAR(100),
CONSTRAINT PK_CUSTOMERS PRIMARY KEY (CUSTOMERID)
);
Customers table has records:
insert into CUSTOMERS (CUSTOMERID, WEBSTOCKISTSTOCKISTTYPE) values (1, 'Reseller');
insert into CUSTOMERS (CUSTOMERID, WEBSTOCKISTSTOCKISTTYPE) values (2, 'Reseller');
insert into CUSTOMERS (CUSTOMERID, WEBSTOCKISTSTOCKISTTYPE) values (3, 'Reseller');
insert into CUSTOMERS (CUSTOMERID, WEBSTOCKISTSTOCKISTTYPE) values (4, 'Installer');
insert into CUSTOMERS (CUSTOMERID, WEBSTOCKISTSTOCKISTTYPE) values (5, 'Installer');
insert into CUSTOMERS (CUSTOMERID, WEBSTOCKISTSTOCKISTTYPE) values (6, 'Installer');
insert into CUSTOMERS (CUSTOMERID, WEBSTOCKISTSTOCKISTTYPE) values (7, 'Installer');
Stockist table has records:
insert into INVENTORYWEBSTOCKISTS (STOCKISTID, PRODUCTID, DISPLAYORDER, CUSTOMERID) values (1, 1, -100, 1);
insert into INVENTORYWEBSTOCKISTS (STOCKISTID, PRODUCTID, DISPLAYORDER, CUSTOMERID) values (2, 1, -101, 2);
insert into INVENTORYWEBSTOCKISTS (STOCKISTID, PRODUCTID, DISPLAYORDER, CUSTOMERID) values (3, 1, -102, 3);
insert into INVENTORYWEBSTOCKISTS (STOCKISTID, PRODUCTID, DISPLAYORDER, CUSTOMERID) values (4, 1, -103, 4);
insert into INVENTORYWEBSTOCKISTS (STOCKISTID, PRODUCTID, DISPLAYORDER, CUSTOMERID) values (5, 1, -104, 5);
insert into INVENTORYWEBSTOCKISTS (STOCKISTID, PRODUCTID, DISPLAYORDER, CUSTOMERID) values (6, 1, -105, 6);
insert into INVENTORYWEBSTOCKISTS (STOCKISTID, PRODUCTID, DISPLAYORDER, CUSTOMERID) values (7, 1, -106, 7);
insert into INVENTORYWEBSTOCKISTS (STOCKISTID, PRODUCTID, DISPLAYORDER, CUSTOMERID) values (10, 2, -107, 3);
insert into INVENTORYWEBSTOCKISTS (STOCKISTID, PRODUCTID, DISPLAYORDER, CUSTOMERID) values (13, 2, -108, 6);
insert into INVENTORYWEBSTOCKISTS (STOCKISTID, PRODUCTID, DISPLAYORDER, CUSTOMERID) values (14, 2, -109, 7);
For the combined results:
select
INVENTORYWEBSTOCKISTS.STOCKISTID,
INVENTORYWEBSTOCKISTS.PRODUCTID,
INVENTORYWEBSTOCKISTS.DISPLAYORDER,
CUSTOMERS.WEBSTOCKISTSTOCKISTTYPE
from
INVENTORYWEBSTOCKISTS
left join
CUSTOMERS on CUSTOMERS.CUSTOMERID = INVENTORYWEBSTOCKISTS.CUSTOMERID
order by
INVENTORYWEBSTOCKISTS.PRODUCTID,
CUSTOMERS.WEBSTOCKISTSTOCKISTTYPE,
INVENTORYWEBSTOCKISTS.DISPLAYORDER
Output:
STOCKISTID, PRODUCTID, DISPLAYORDER, WEBSTOCKISTSTOCKISTTYPE
7, 1, -106, Installer
6, 1, -105, Installer
5, 1, -104, Installer
4, 1, -103, Installer
3, 1, -102, Reseller
2, 1, -101, Reseller
1, 1, -100, Reseller
14, 2, -109, Installer
13, 2, -108, Installer
10, 2, -107, Reseller
I need to delete the 3rd and above record for each product/stockist type group ordered by display order, so would expect it to delete rows 5, 4, and 1.
I have tried heaps of different queries from here and the web generally, I can't find anything that will group and order for the delete and work on both MS SQL and Firebird 1.5.
SQL Fiddle to experiment with: http://sqlfiddle.com/#!3/7101de/1
So after deleting, the table should look like:
Output:
STOCKISTID, PRODUCTID, DISPLAYORDER, WEBSTOCKISTSTOCKISTTYPE
7, 1, -106, Installer
6, 1, -105, Installer
3, 1, -102, Reseller
2, 1, -101, Reseller
14, 2, -109, Installer
13, 2, -108, Installer
10, 2, -107, Reseller
The delete should be executed as one query, so I can pass it to the server.
*** Edit:
Maybe to simplify, if I can do a select command that returns the STOCKISTID's to be deleted, I can then execute the delete commands seperately.
*** Edit 2:
As a test, I added the stockist type field to the INVENTORYWEBSTOCKISTS table, and can run this query:
SELECT INVENTORYWEBSTOCKISTS.STOCKISTID
FROM INVENTORYWEBSTOCKISTS IWS1
WHERE
(SELECT COUNT(*)
FROM INVENTORYWEBSTOCKISTS IWS2
WHERE IWS2.PRODUCTID = IWS1.PRODUCTID
AND IWS2.STOCKISTTYPE = IWS1.STOCKISTTYPE
AND IWS2.DISPLAYORDER <= IWS1.DISPLAYORDER) > 2
and return the correct fields (ie the ones that should be deleted). This was based on a question asked previously on SO.
As soon as I try and link in the CUSTOMER table as the query should be, it gives different results, less rows. Maybe someone might be able to help with that?
Try this, I have used ROW_NUMBER to eliminate your case (From what I understood from your question)
;WITH CTE AS(
SELECT ROW_NUMBER()OVER(PARTITION BY PRODUCTID,WEBSTOCKISTSTOCKISTTYPE
ORDER BY (SELECT 1) )SNO, INVENTORYWEBSTOCKISTS.*,
CUSTOMERS.WEBSTOCKISTSTOCKISTTYPE FROM
INVENTORYWEBSTOCKISTS
LEFT JOIN
CUSTOMERS ON INVENTORYWEBSTOCKISTS.CUSTOMERID = CUSTOMERS.CUSTOMERID
)
SELECT * FROM CTE WHERE SNO <3
Edit: From your comments:
;WITH CTE AS(
SELECT ROW_NUMBER()OVER(PARTITION BY PRODUCTID,WEBSTOCKISTSTOCKISTTYPE
ORDER BY (SELECT DISPLAYORDER) )SNO, INVENTORYWEBSTOCKISTS.*,
CUSTOMERS.WEBSTOCKISTSTOCKISTTYPE FROM
INVENTORYWEBSTOCKISTS
LEFT JOIN
CUSTOMERS ON INVENTORYWEBSTOCKISTS.CUSTOMERID = CUSTOMERS.CUSTOMERID
)
SELECT STOCKISTID FROM CTE WHERE SNO>2
If it is a problem with CTE
DELETE FROM INVENTORYWEBSTOCKISTS WHERE STOCKISTID IN (
SELECT STOCKISTID FROM (
SELECT ROW_NUMBER()OVER(PARTITION BY PRODUCTID,WEBSTOCKISTSTOCKISTTYPE
ORDER BY (SELECT DISPLAYORDER) )SNO, INVENTORYWEBSTOCKISTS.*,
CUSTOMERS.WEBSTOCKISTSTOCKISTTYPE FROM
INVENTORYWEBSTOCKISTS
LEFT JOIN
CUSTOMERS ON INVENTORYWEBSTOCKISTS.CUSTOMERID = CUSTOMERS.CUSTOMERID
) AS A WHERE SNO>2
)