Count days between dates in two rows based on condition - sql

I'm writing query which has to select few infos. Below table:
ID ID-Toner Quantity Location Order_date Send_date
1 2 1 55 20.01.2015 26.01.2015
2 2 1 41 22.02.2015 26.02.2015
3 2 1 35 23.02.2015 26.02.2015
4 5 1 77 25.02.2015 25.02.2015
5 2 1 55 25.02.2015 26.02.2015
I need to select all columns and additional column with number of days between two dates: Order_date and previous Order_date for location = ie.: 55.
Sample result should look like:
ID ID-Toner Quantity Location Order_date Send_date Number_of_days
1 2 1 55 20.01.2015 26.01.2015 0
5 2 1 55 25.02.2015 26.02.2015 36
How to select such a query?

updated after clarifications in the PO
let'say that it needs to do a sort of aggregation on data called ranking, that is a type of classification based on numbering in succession order tbale's rows.
In our case the order is given by the Orders dates.
This is a quite cross-dbms solution (date fields are suppased to be Datetime type and DATEDIFF is a function of MySql) so I think that you can adapt to your dbms quite easily.
You can try the sql on Sql Fiddle at http://sqlfiddle.com/#!9/290e9
Table
CREATE TABLE Orders
(`ID` int, `IDToner` int, `Quantity` int, `Location` int, `Order_date` Date, `Send_date` Date)
;
INSERT INTO Orders
(`ID`, `IDToner`, `Quantity`, `Location`, `Order_date`, `Send_date`)
VALUES
(1, 2, 1, 55, STR_TO_DATE('20.01.2015','%d.%m.%Y'), STR_TO_DATE('26.01.2015','%d.%m.%Y')),
(2, 2, 1, 41, STR_TO_DATE('22.02.2015','%d.%m.%Y'), STR_TO_DATE('26.02.2015','%d.%m.%Y')),
(3, 2, 1, 35, STR_TO_DATE('23.02.2015','%d.%m.%Y'), STR_TO_DATE('26.02.2015','%d.%m.%Y')),
(4, 5, 1, 77, STR_TO_DATE('25.02.2015','%d.%m.%Y'), STR_TO_DATE('25.02.2015','%d.%m.%Y')),
(5, 5, 1, 77, STR_TO_DATE('25.04.2015','%d.%m.%Y'), STR_TO_DATE('25.04.2015','%d.%m.%Y')),
(6, 5, 1, 77, STR_TO_DATE('25.06.2015','%d.%m.%Y'), STR_TO_DATE('25.06.2015','%d.%m.%Y')),
(7, 5, 1, 77, STR_TO_DATE('25.08.2015','%d.%m.%Y'), STR_TO_DATE('25.08.2015','%d.%m.%Y')),
(8, 2, 1, 55, STR_TO_DATE('25.02.2015','%d.%m.%Y'), STR_TO_DATE('26.02.2015','%d.%m.%Y'))
;
Query
SELECT
ID,
ID_Toner,
Quantity,
Location,
Order_date,
Send_date,
days_from_previous_order
FROM(
SELECT
current_ID AS ID,
current_IDToner AS ID_Toner,
current_Quantity AS Quantity,
current_Location AS Location,
current_Send_Date AS Send_date,
current_Order_Date AS Order_date,
previous_Order_Date,
COALESCE(DATEDIFF(current_Order_Date, previous_Order_Date),0) AS days_from_previous_order
FROM(
SELECT
TabOrdersRanking_currents.ID AS current_ID,
TabOrdersRanking_currents.IDToner AS current_IDToner,
TabOrdersRanking_currents.Quantity AS current_Quantity,
TabOrdersRanking_currents.Location AS current_Location,
TabOrdersRanking_currents.Send_Date AS current_Send_Date,
TabOrdersRanking_currents.Order_Date AS current_Order_Date,
TabOrdersRanking_previous.Order_Date AS previous_Order_Date
FROM(
SELECT Orders.*, #rank1 := #rank1 + 1 rank
FROM Orders
,(Select #rank1 := 0) r1
order by location, order_date
) TabOrdersRanking_currents
LEFT JOIN(
SELECT Orders.*, #rank2 := #rank2 + 1 rank
FROM Orders
,(Select #rank2 := 0) r2
order by location, order_date
) TabOrdersRanking_previous
on TabOrdersRanking_currents.Location = TabOrdersRanking_previous.Location
and TabOrdersRanking_currents.rank - TabOrdersRanking_previous.rank = 1
) TabOrdersSuccessionRanking
) TabWithDaysFromPrevious;

Related

Can I use SQL ROLLUP() instead of doing 2 request on DB for calculation total value for metrics in pagination?

I use SingleStore/MemSQL DB and try apply https://docs.singlestore.com/managed-service/en/reference/sql-reference/data-manipulation-language-dml/cube-and-rollup.html
CREATE TABLE sales(state VARCHAR(30), product_id INT, quantity INT);
INSERT sales VALUES
("Oregon", 1, 10), ("Washington", 1, 15), ("California", 1, 40),
("Oregon", 2, 15), ("Washington", 2, 25), ("California", 2, 70);
SELECT state, product_id,
SUM(quantity) as quantity
FROM sales
WHERE product_id = 1
GROUP BY ROLLUP(state, product_id)
HAVING (GROUPING(state) = 0 and GROUPING(product_id) = 0) OR (GROUPING(state) = 1 and GROUPING(product_id) = 1)
ORDER BY state, product_id
limit 2 offset 0;
Change on - "limit 2 offset 2;"
I have dynamic sql builder and I don't understand how get total on second page when I have sorting. Is it possible doing in sql?
You can't do this directly with ROLLUP/LIMIT because the query will only output the rollup once. However, this query will do what you want:
SELECT state, product_id,
SUM(quantity) as quantity
FROM sales
WHERE product_id = 1
GROUP BY state, product_id
ORDER BY state, product_id
limit 2 offset 0
UNION ALL
SELECT null, null,
SUM(quantity) as quantity
FROM sales
WHERE product_id = 1

How to value cost of sales using FIFO in SQL Server

I want value the cost of goods sold using the FIFO method.
I know how many beers I sold. Based on my price I bought those beers at, what is the cost of those sales? So, my sales of 7 Peronis are valued at £1.70 -- based on the FIFO valuation method.
How do I calculate in SQL Server.
I am going to be working this out for many products and from many branches at the same time, so I would like to use a method that does not involve cursors (or any other types of loops).
-- SETUP
DROP TABLE IF EXISTS #Deliveries;
CREATE TABLE #Deliveries (DeliveryDate DATE, ProductCode VARCHAR(10), Quantity INT, Cost DECIMAL(6,2));
INSERT INTO #Deliveries (DeliveryDate, ProductCode, Quantity, Cost)
VALUES
('2020-11-23', 'PERONI', 2, 0.20), ('2020-11-24', 'PERONI', 4, 0.30), ('2020-11-25', 'PERONI', 7, 0.10),
('2020-11-23', 'BUDWEISER', 5, 0.20), ('2020-11-24', 'BUDWEISER', 5, 0.50), ('2020-11-25', 'BUDWEISER', 4, 0.80);
DROP TABLE IF EXISTS #StockResults;
CREATE TABLE #StockResults (ProductCode VARCHAR(10), SalesQty INT, CostOfSalesValue DECIMAL(6,2));
INSERT INTO #StockResults (ProductCode, SalesQty)
VALUES ('PERONI', 7), ('BUDWEISER', 4);
SELECT * FROM #Deliveries;
SELECT * FROM #StockResults;
-- DESIRED RESULT
/*
ProductCode SalesQty CostOfSalesValue
PERONI 7 1.70
BUDWEISER 4 0.80
*/
This is probably not very efficient but it shows you one way in which this can be achieved which should help you come up with your finished solution. I would imagine that there needs to be a lot more complexity built into this process to account for things like stock wastage, but I'll leave that up to you:
Query
-- SETUP
declare #Deliveries table (DeliveryDate date, ProductCode varchar(10), Quantity int, Cost decimal(6,2));
insert into #Deliveries (DeliveryDate, ProductCode, Quantity, Cost) values ('2020-11-23', 'PERONI', 2, 0.20), ('2020-11-24', 'PERONI', 4, 0.30), ('2020-11-25', 'PERONI', 7, 0.10),('2020-11-23', 'BUDWEISER', 5, 0.20), ('2020-11-24', 'BUDWEISER', 5, 0.50), ('2020-11-25', 'BUDWEISER', 4, 0.80);
declare #StockResults table (ProductCode varchar(10), SalesQty int);
insert into #StockResults (ProductCode, SalesQty) values ('PERONI', 7), ('BUDWEISER', 4);
-- QUERY
with r as
(
select d.ProductCode
,d.DeliveryDate
,d.Quantity
,d.Cost
,isnull(sum(d.Quantity) over (partition by d.ProductCode order by d.DeliveryDate rows between unbounded preceding and 1 preceding),0) as RunningQuantityStart
,sum(d.Quantity) over (partition by d.ProductCode order by d.DeliveryDate) as RunningQuantityEnd
from #Deliveries as d
)
select r.ProductCode
,s.SalesQty
,sum(case when r.RunningQuantityEnd >= s.SalesQty
then (s.SalesQty - r.RunningQuantityStart) * r.Cost
else (r.RunningQuantityEnd - r.RunningQuantityStart) * r.Cost
end
) as CostOfSalesValue
from r
join #StockResults as s
on r.ProductCode = s.ProductCode
and r.RunningQuantityStart < s.SalesQty
group by r.ProductCode
,s.SalesQty;
##Output
+-------------+----------+------------------+
| ProductCode | SalesQty | CostOfSalesValue |
+-------------+----------+------------------+
| BUDWEISER | 4 | 0.80 |
| PERONI | 7 | 1.70 |
+-------------+----------+------------------+
Below query might help you:
declare #maxQty int
select #maxQty = max(SalesQty) from #StockResults
;WITH AllNumbers AS
(
SELECT 1 AS Number
UNION ALL
SELECT Number+1 FROM AllNumbers WHERE Number < #maxQty
)
select ProductCode, SalesQty, SUM(Cost) as CostOfSalesValue from
(
SELECT SR.ProductCode, SR.SalesQty, DLM.RN, DLM.Cost FROM #StockResults SR
outer apply
(
select ROW_NUMBER()OVER (order by DeliveryDate asc) as RN, Dl.Cost from #Deliveries Dl
inner join AllNumbers AL on AL.Number <= Dl.Quantity
where Dl.ProductCode = SR.ProductCode
) as DLM
) result
where RN <= SalesQty
group by ProductCode, SalesQty

1 distinct row having max value

This is the data I have
I need Unique ID(1 row) with max(Price). So, the output would be:
I have tried the following
select * from table a
join (select b.id,max(b.price) from table b
group by b.id) c on c.id=a.id;
gives the Question as output, because there is no key. I did try the other where condition as well, which gives the original table as output.
You could try something like this in SQL Server:
Table
create table ex1 (
id int,
item char(1),
price int,
qty int,
usr char(2)
);
Data
insert into ex1 values
(1, 'a', 7, 1, 'ab'),
(1, 'a', 7, 2, 'ac'),
(2, 'b', 6, 1, 'ab'),
(2, 'b', 6, 1, 'av'),
(2, 'b', 5, 1, 'ab'),
(3, 'c', 5, 2, 'ab'),
(4, 'd', 4, 2, 'ac'),
(4, 'd', 3, 1, 'av');
Query
select a.* from ex1 a
join (
select id, max(price) as maxprice, min(usr) as minuser
from ex1
group by id
) c
on c.id = a.id
and a.price = c.maxprice
and a.usr = c.minuser
order by a.id, a.usr;
Result
id item price qty usr
1 a 7 1 ab
2 b 6 1 ab
3 c 5 2 ab
4 d 4 2 ac
Explanation
In your dataset, ID 1 has 2 records with the same price. You have to make a decision which one you want. So, in the above example, I am showing a single record for the user whose name is lowest alphabetically.
Alternate method
SQL Server has ranking function row_number over() that can be used as well:
select * from (
select row_number() over( partition by id order by id, price desc, usr) as sr, *
from ex1
) c where sr = 1;
The subquery says - give me all records from the table and give each row a serial number starting with 1 unique to each ID. The rows should be sorted by ID first, then price descending and then usr. The outer query picks out records with sr number 1.
Example here: https://rextester.com/KZCZ25396

Select rows matching the pattern: greater_than, less_than, greater_than

Got a database with entries indicating units earned by staff. Am trying to find a query that can select for me entries where the units_earned by the staff follow this pattern: >30 then <30 and then >30
In this SQL Fiddle, I would expect the query to return:
For John, Rows:
2, 4, 6
9, 10, 11
For Jane, Rows:
3, 5, 8
12, 13, 14
Here is the relevant SQL:
CREATE TABLE staff_units(
id integer,
staff_number integer,
first_name varchar(50),
month_name varchar(3),
units_earned integer,
PRIMARY KEY(id)
);
INSERT INTO staff_units VALUES (1, 101, 'john', 'jan', 32);
INSERT INTO staff_units VALUES (2, 101, 'john', 'jan', 33);
INSERT INTO staff_units VALUES (3, 102, 'jane', 'jan', 39);
INSERT INTO staff_units VALUES (4, 101, 'john', 'feb', 28);
INSERT INTO staff_units VALUES (5, 102, 'jane', 'feb', 28);
INSERT INTO staff_units VALUES (6, 101, 'john', 'mar', 39);
INSERT INTO staff_units VALUES (7, 101, 'john', 'mar', 34);
INSERT INTO staff_units VALUES (8, 102, 'jane', 'mar', 40);
INSERT INTO staff_units VALUES (9, 101, 'john', 'mar', 36);
INSERT INTO staff_units VALUES (10, 101, 'john', 'apr', 18);
INSERT INTO staff_units VALUES (11, 101, 'john', 'may', 32);
INSERT INTO staff_units VALUES (12, 102, 'jane', 'jun', 31);
INSERT INTO staff_units VALUES (13, 102, 'jane', 'jun', 28);
INSERT INTO staff_units VALUES (14, 102, 'jane', 'jun', 32);
Using window function lead you can refer to the next two leading records of the current record and then compare the three against your desired pattern.
with staff_units_with_leading as (
select id, staff_number, first_name, units_earned,
lead(units_earned) over w units_earned_off1, -- units_earned from record with offset 1
lead(units_earned, 2) over w units_earned_off2, -- units_earned from record with offset 2
lead(id) over w id_off1, -- id from record with offset 1
lead(id, 2) over w id_off2 -- id from record with offset 2
from staff_units
window w as (partition by first_name order by id)
)
, ids_wanted as (
select unnest(array[id, id_off1, id_off2]) id --
from staff_units_with_leading
where
id_off1 is not null -- Discard records with no two leading records
and id_off2 is not null -- Discard records with no two leading records
and units_earned > 30 -- Match desired pattern
and units_earned_off1 < 30 -- Match desired pattern
and units_earned_off2 > 30 -- Match desired pattern
)
select * from staff_units
where id in (select id from ids_wanted)
order by staff_number, id;
To generate trigrams just get rid of the unnest
with staff_units_with_leading as (
select id, staff_number, first_name, units_earned,
lead(units_earned) over w units_earned_off1, -- units_earned from record with offset 1
lead(units_earned, 2) over w units_earned_off2, -- units_earned from record with offset 2
lead(id) over w id_off1, -- id from record with offset 1
lead(id, 2) over w id_off2 -- id from record with offset 2
from staff_units
window w as (partition by first_name order by id)
)
select staff_number, array[id, id_off1, id_off2] id, array[units_earned , units_earned_off1 , units_earned_off2 ] units_earned --
from staff_units_with_leading
where
id_off1 is not null -- Discard records with no two leading records
and id_off2 is not null -- Discard records with no two leading records
and units_earned > 30 -- Match desired pattern
and units_earned_off1 < 30 -- Match desired pattern
and units_earned_off2 > 30 -- Match desired pattern
I took cachique's answer (with excellent idea to use lead() ) and reformatted and extended it to generate 3-grams as you originally wanted:
with staff_units_with_leading as (
select
id, staff_number, first_name, units_earned,
lead(units_earned) over w units_earned_off1, -- units_earned from record with offset 1
lead(units_earned, 2) over w units_earned_off2, -- units_earned from record with offset 2
lead(id) over w id_off1, -- id from record with offset 1
lead(id, 2) over w id_off2 -- id from record with offset 2
from staff_units
window w as (partition by staff_number order by id)
), ids_wanted as (
select
id_off1, -- keep this to group 3-grams later
unnest(array[id, id_off1, id_off2]) id
from staff_units_with_leading
where
id_off1 is not null -- Discard records with no two leading records
and id_off2 is not null -- Discard records with no two leading records
and units_earned > 30 -- Match desired pattern
and units_earned_off1 < 30 -- Match desired pattern
and units_earned_off2 > 30 -- Match desired pattern
), res as (
select su.*, iw.id_off1
from staff_units su
join ids_wanted iw on su.id = iw.id
order by su.staff_number, su.id
)
select
staff_number,
array_agg(units_earned order by id) as values,
array_agg(id order by id) as ids
from res
group by staff_number, id_off1
order by 1
;
The result will be:
staff_number | values | ids
--------------+------------+------------
101 | {33,28,39} | {2,4,6}
101 | {36,18,32} | {9,10,11}
102 | {39,28,40} | {3,5,8}
102 | {31,28,32} | {12,13,14}
(4 rows)
The problem you're trying to solve is a bit complicated. It is probably easier to solve it if you'll use pl/pgsql and play with integer arrays inside pl/pgsql function, or probably with JSON/JSONB.
But it also can be solved in plain SQL, however such SQL is pretty advanced.
with rows_numbered as (
select
*, row_number() over (partition by staff_number order by id) as row_num
from staff_units
order by staff_number
), sequences (staff_number, seq) as (
select
staff_number,
json_agg(json_build_object('row_num', row_num, 'id', id, 'units_earned', units_earned) order by id)
from rows_numbered
group by 1
)
select
s1.staff_number,
(s1.chunk->>'id')::int as id1,
(s2.chunk->>'id')::int as id2,
(s3.chunk->>'id')::int as id3
from (select staff_number, json_array_elements(seq) as chunk from sequences) as s1
, lateral (
select *
from (select staff_number, json_array_elements(seq) as chunk from sequences) _
where
(s1.chunk->>'row_num')::int + 1 = (_.chunk->>'row_num')::int
and (_.chunk->>'units_earned')::int < 30
and s1.staff_number = _.staff_number
) as s2
, lateral (
select *
from (select staff_number, json_array_elements(seq) as chunk from sequences) _
where
(s2.chunk->>'row_num')::int + 1 = (_.chunk->>'row_num')::int
and (_.chunk->>'units_earned')::int > 30
and s2.staff_number = _.staff_number
) as s3
where (s1.chunk->>'units_earned')::int > 30
order by 1, 2;
I used several advanced SQL features:
CTE
JSON
LATERAL
window functions.

Delete rows per user and eventType keeping N rows

I want to delete the oldest entries in a table and keep N rows. This is fairly simple to do.
DELETE TOP(1000) from TABLE ORDER BY [date] DESC
But I want to delete rows based on User and EventType. So if we set N=50, I want to keep the newest 50 records per User and EventType.
My Table looks like this:
API_eventLog
- uid (PK, int, not null)
- eventTypeID (FK, int, not null)
- userGUID (FK, uniqueIdentifier, not null)
- date (datetime, not null)
- ...
There is a similar question already on SO that has the following answer, but unfortunately it is for SQL Server 2005.
;WITH Dealers AS (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY DealerID ORDER BY SomeTimeStamp DESC) RowID
FROM MyDealersTable
)
DELETE
FROM Dealers
WHERE RowID > 50
Is there a solution to this in SQL server 2000 with good performance? All I can think of myself is a cursor-based solution but that is way to slow run be executed frequently.
Example Data:
[uid] [EventTypeID] [userGUID] [date]
1 1 5B1DCB9D-4EC7-4AAE-BEB1-DC1EA90EA06B 2013-11-17
2 2 5B1DCB9D-4EC7-4AAE-BEB1-DC1EA90EA06B 2013-11-17
3 3 5B1DCB9D-4EC7-4AAE-BEB1-DC1EA90EA06B 2013-11-18
4 4 5B1DCB9D-4EC7-4AAE-BEB1-DC1EA90EA06B 2013-11-18
5 1 5B1DCB9D-4EC7-4AAE-BEB1-DC1EA90EA06B 2013-11-19
6 1 5B1DCB9D-4EC7-4AAE-BEB1-DC1EA90EA06B 2013-11-22
7 1 17941D18-CC79-4C29-BBBA-9CBE60993E43 2013-11-06
8 2 17941D18-CC79-4C29-BBBA-9CBE60993E43 2013-11-17
9 3 17941D18-CC79-4C29-BBBA-9CBE60993E43 2013-12-01
10 2 17941D18-CC79-4C29-BBBA-9CBE60993E43 2013-12-07
11 2 17941D18-CC79-4C29-BBBA-9CBE60993E43 2013-12-18
11 1 17941D18-CC79-4C29-BBBA-9CBE60993E43 2013-12-20
In above example, given N=2, I would like to delete row with [uid] 1 and 8. (ie keeping the 2 newest rows per User and EventTypeID.
The following query should simulate the ROW_NUMBER function in SQL Server 2000.
DELETE API_eventLog
WHERE id IN
(SELECT
id
FROM API_eventLog AS a1
WHERE
(SELECT COUNT(1)
FROM API_eventLog AS a2
WHERE a2.userguid = a1.userguid
AND a2.eventTypeID = a1.eventTypeID
AND (a2.date > a1.date)) > 0);
SELECT * FROM API_eventLog;
Replace the 0 by 50 or whatever number or rows you want to keep.
I can't test it in SQL Server 2000, but here is a working example in SQL Server 2008.
EDIT:
Seems like I made a small mistake. The date check should be greater than instead of less than if you want to keep the newest rows. I updated my example.
sorry #David i had to move then.mine is almost same. i have assume generate row number per userGUID per eventtypeid date ascending.also each userGUID per event may not have 50 or more rows even in real life.In that case what ?
Declare #API_eventLog table(uid int,EventTypeID int,userGUID uniqueIdentifier,date1 date)
insert into #API_eventLog
values(1, 1, '5B1DCB9D-4EC7-4AAE-BEB1-DC1EA90EA06B', '2013-11-17'),
(2, 2, '5B1DCB9D-4EC7-4AAE-BEB1-DC1EA90EA06B', '2013-11-17'),
(3, 3, '5B1DCB9D-4EC7-4AAE-BEB1-DC1EA90EA06B', '2013-11-18'),
(4, 4, '5B1DCB9D-4EC7-4AAE-BEB1-DC1EA90EA06B', '2013-11-18'),
(5, 1, '5B1DCB9D-4EC7-4AAE-BEB1-DC1EA90EA06B', '2013-11-19'),
(6, 1, '5B1DCB9D-4EC7-4AAE-BEB1-DC1EA90EA06B', '2013-11-22'),
(7, 1, '17941D18-CC79-4C29-BBBA-9CBE60993E43', '2013-11-06'),
(8, 2, '17941D18-CC79-4C29-BBBA-9CBE60993E43', '2013-11-17'),
(9, 3, '17941D18-CC79-4C29-BBBA-9CBE60993E43', '2013-12-01'),
(10, 2, '17941D18-CC79-4C29-BBBA-9CBE60993E43', '2013-12-07'),
(11, 2, '17941D18-CC79-4C29-BBBA-9CBE60993E43', '2013-12-18'),
(11, 1, '17941D18-CC79-4C29-BBBA-9CBE60993E43', '2013-12-20')
Declare #i int=1
--you can test this other sample data
select * from
(select *,
(select count(*) from #API_eventLog b where b.userGUID=a.userGUID and a.EventTypeID=b.EventTypeID and b.date1<=a.date1) rn
from #API_eventLog a)t4
where rn<=#i
-- you can perform this
delete from t4 from
(select *,
(select count(*) from #API_eventLog b where b.userGUID=a.userGUID and a.EventTypeID=b.EventTypeID and b.date1<=a.date1) rn
from #API_eventLog a)t4
where rn<=#i