Oracle : sum this and this and this in a single query - sql

I have on order table containing orders for last week and the ID of the driver who delivered them. It looks a little like this:
ORDERDATE, ORDERNO, DRIVER
23/01/2013, 901398503, 1
23/01/2013, 901332159, 1
23/01/2013, 901334158, 2
24/01/2013, 901338455, 1
25/01/2013, 902907513, 1
25/01/2013, 902338553, 2
25/01/2013, 903936533, 2
27/01/2013, 903944523, 1
27/01/2013, 903981522, 2
27/01/2013, 911334951, 1
28/01/2013, 911338851, 1
28/01/2013, 911339259, 1
28/01/2013, 912332555, 2
28/01/2013, 912336650, 2
29/01/2013, 912337655, 1
29/01/2013, 913969582, 1
29/01/2013, 913973583, 1
29/01/2013, 913982552, 1
29/01/2013, 916379158, 1
I'd like to select ORDERDATE, ORDERCOUNT, DRIVER_1_COUNT, DRIVER_2_COUNT.
so, date | total orders | total orders for driver 1 | total orders for driver 2
Also, I need zeros if ORDERDATE, ORDERCOUNT, DRIVER_1_COUNT or DRIVER_2_COUNT are 0 (or null).
(In oracle) I can select dates for each day last week, and a zero order count (placeholder) for each day like this:
select
TRUNC(NEXT_DAY(sysdate,'SUNDAY')-7 +i) ORDERDATE,
0 as ORDERCOUNT
from
(select rownum i from all_objects where rownum < 8)
I should be able to use this output to make sure there are no days missing in the final results (no orders on 26th in this example)
ORDERDATE,ORDERCOUNT
23/01/2013,0
24/01/2013,0
25/01/2013,0
26/01/2013,0
27/01/2013,0
28/01/2013,0
29/01/2013,0
I need this output:
ORDERDATE,ORDERCOUNT,DRIVER_1_COUNT,DRIVER_2_COUNT
23/01/2013,3,2,1
24/01/2013,1,1,0
25/01/2013,3,1,2
26/01/2013,0,0,0
27/01/2013,3,2,1
28/01/2013,4,2,2
29/01/2013,5,5,0
I can get ORDERDATE & ORDERCOUNT(simple sum) and union with the other query to avoid missing days, but I can't work out how to sum for each driver too.
Thanks in advance for you help.
Ed

In Oracle 11g, you can do this:-
SELECT *
FROM orders
PIVOT (
COUNT( ORDERNO )
FOR DRIVER IN (1,2,3)
)
For further explanation see pivot and unpivot queries in 11g

Firstly you need to summarise and crosstab results:
SELECT ORDERDATE, SUM(ORDERCOUNT) ORDERCOUNT,
SUM(DECODE(DRIVER,1,ORDERCOUNT,0)) DRIVER_1_COUNT,
SUM(DECODE(DRIVER,2,ORDERCOUNT,0)) DRIVER_2_COUNT
FROM (
SELECT ORDERDATE, DRIVER, COUNT(*) ORDERCOUNT
FROM YourTable
GROUP BY ORDERDATE, DRIVER
) S
GROUP BY ORDERDATE
There might be smarter ways to do this in Oracle
Then you need to fill in the blanks using by outer joining this to your dates:
(note the query above is aliased as 'T' in this query:)
SELECT D.ORDERDATE,
NVL(T.ORDERCOUNT,0) ORDERCOUNT,
NVL(T.DRIVER_1_COUNT,0) DRIVER_1_COUNT,
NVL(T.DRIVER_1_COUNT,0) DRIVER_2_COUNT
FROM
(
SELECT ORDERDATE, SUM(ORDERCOUNT) ORDERCOUNT,
SUM(DECODE(DRIVER,1,ORDERCOUNT,0)) DRIVER_1_COUNT,
SUM(DECODE(DRIVER,2,ORDERCOUNT,0)) DRIVER_2_COUNT
FROM
(
SELECT ORDERDATE, DRIVER, COUNT(*) ORDERCOUNT
FROM YourTable
GROUP BY ORDERDATE, DRIVER
) S
GROUP BY ORDERDATE
) T
RIGHT OUTER JOIN
(
SELECT
TRUNC(NEXT_DAY(sysdate,'SUNDAY')-7 +i) ORDERDATE
FROM (select rownum i from all_objects where rownum < 8)
) D
ON D.ORDERDATE = T.ORDERDATE

You'll have to select from a subquery. Something like this should work.
select orderdate, ordercount, sum(driver1) driver1count, sum(driver2) driver2count
from (
select orderdate
, case when driver = 1 then 1 else 0 end driver1
, case when driver = 2 then 1 else 0 end driver2
, count(*) ordercount
from yourtable
where whatever
group by orderdate
, case when driver = 1 then 1 else 0 end driver1
, case when driver = 2 then 1 else 0 end driver2
) you_need_an_alias_here
group by orderdate, ordercount
order by orderdate

Related

Execution orders of SQL aggregate functions

I have a sales table in SQLite:
purchase_date
units_sold
customer_id
15
1
1
17
1
1
30
3
1
I want to get the total unit_solds for each customer on the first date and last date of their purchases. My query is:
select customer_id,
sum(units_sold) total_units_sold
from sales
group by customer_id
having purchase_date = min(purchase_date)
or purchase_date = max(purchase_date)
I was expecting results like:
customer_id
total_units_sold
1
4
but I got:
customer_id
total_units_sold
1
5
I would like to know why this solution doesn't work.
The order of the phrase is incorrect
Note: The having statement is executed after compilation.
You need to get the results as partial queries
For example, I arranged to know the first line of the date according to each customer
as well as the last line of the date (by getting the first line after descending order)
and then execute the group statement
The example is complete
select customer_id,sum(units_sold) from (
select customer_id, units_sold,purchase_date,
ROW_NUMBER() over(partition by customer_id order by purchase_date) As RowDatefirst,
ROW_NUMBER() over(partition by customer_id order by purchase_date desc)As RowDatelast
from sales
) t where t.RowDatefirst = 1 or t.RowDatelast=1
group by customer_id
Try this:
SELECT a.customer_id, SUM(a.units_sold) as total_units_sold
FROM sales a
INNER JOIN (
SELECT customer_id, MIN(purchase_date) as _first ,MAX(purchase_date) as _last
FROM sales
GROUP BY customer_id
) b ON a.customer_id = b.customer_id AND
(a.purchase_date = b._first OR a.purchase_date = b._last)
GROUP BY a.customer_id
http://sqlfiddle.com/#!7/0a4a4/7

On same row: last purchase quantity + date, and total quantity in stock (several stock places) - SQL server

I am trying to get the following result in SQL server:
From the purchase order rows, last purchase quantity + date from all item codes in the order rows table and from the warehouse table amount in stock for the item codes I get from the rows table.
Order rows:
ORDER_DATE ITEM_CODE QTY
2019-03-01 A 5
2019-03-02 A 3
2019-03-05 A 4
2019-03-03 B 3
2019-03-04 B 10
Warehouse:
ITEM_CODE INSTOCK STOCKPLACE
A 10 VV
A 3 LP
A 8 XV
B 5 VV
B 15 LP
Wanted result (Latest order date, latest order qty and total in stock):
ORDER_DATE ITEM_CODE QTY INSTOCK
2019-03-05 A 4 21
2019-03-04 B 10 20
I have tried some queries but only failed. I have a steep learning curve ahead of me :) Thanks in advance for all the help!
Here is one method:
select o.*, wh.*
from (select wh.item_code, sum(wh.instock) as instock
from warehouse wh
group by wh.item_code
) wh outer apply
(select top (1) o.*
from orders o
where o.item_code = wh.item_code
order by o.order_date desc
) o;
You can use row_number() with apply :
select t.*, wh.instock
from (select o.*, row_number () over (partition by item_code order by o.order_date desc) as seq
from Order o
) t cross apply
( select sum(wh.instock) as instock
from warehouse wh
where wh.item_code = t.item_code
) wh
where t.seq = 1;
Your Orders aren't identified with a unique ID, and therefore if multiple Orders were to coincide on the same date, you have no way of telling which is the most recent order on that day.
Anyway, assuming that the database you posted is correct and an Order date + Item Code combines to form a unique key, you could use grouping and some CTE to get the desired output as follows.
;WITH MostRecentOrders (ITEM_CODE, ORDER_DATE)
AS (
SELECT
O.ITEM_CODE
, MAX(O.ORDER_DATE) AS ORDER_DATE
FROM
#Order O
GROUP BY ITEM_CODE
)
SELECT
O.ORDER_DATE
, O.ITEM_CODE
, O.QTY
, SUM(WH.INSTOCK) AS INSTOCK
FROM
#Warehouse WH
INNER JOIN #Order O ON O.ITEM_CODE = WH.ITEM_CODE
INNER JOIN MostRecentOrders MRO ON MRO.ITEM_CODE = O.ITEM_CODE
AND MRO.ORDER_DATE = O.ORDER_DATE
GROUP BY
O.ORDER_DATE
, O.ITEM_CODE
, O.QTY
ORDER BY O.ITEM_CODE

sql query - find distinct user from table

I am trying to solve a problem using SQL query and need some expert's advice.
I have below transaction table.
-- UserID, ProductId, TransactionDate
-- 1 , 2 , 2014-01-01
-- 1 , 3 , 2014-01-05
-- 2 , 2 , 2014-01-02
-- 2 , 3 , 2014-05-07
.
.
.
What I am trying to achieve is to find all user who purchased more than one product WITHIN 30 DAYS .
My query so far is like
select UserID, COUNT(distinct ProductID)
from tableA
GROUP BY UserID HAVING COUNT(distinct ProductID) > 1
I am not sure where to apply "WITH IN 30 DAYS" logic in the query .
The outcome should be :
1, 2
2, 1
Thanks in advance for your help.
Edit: Within 30 Days
SQL Fiddle
SELECT
a.UserID,
COUNT(DISTINCT ProductID)
FROM TableA a
INNER JOIN (
SELECT UserID, TransactionDate = MAX(TransactionDate)
FROM TableA
GROUP BY UserID
) AS t
ON t.UserID = a.UserID
AND a.TransactionDate >= DATEADD(DAY, -30, t.TransactionDate)
AND a.TransactionDate <= t.TransactionDate
GROUP BY a.UserID
You can use GROUP BY YEAR(TransactionDate), MONTH(TransactionDate)
SELECT
UserID,
COUNT(DISTINCT ProductID)
FROM TableA
GROUP BY
UserID, YEAR(TransactionDate), MONTH(TransactionDate)
HAVING
COUNT(DISTINCT ProductID) > 1
Just add a where clause.
SELECT UserID, COUNT(DISTINCT ProductID) cnt
FROM tableA
WHERE TransactionDate >= CAST(DATEADD(DAY,-30,GETDATE()) AS DATE)
GROUP BY UserID
HAVING COUNT(DISTINCT ProductID) > 1
This works because the where clause is performed BEFORE the Group By and Having. So first it filters out all transactions over 30 days old and then returns only people who bought two distinct products.
Query Processing Order:
http://blog.sqlauthority.com/2009/04/06/sql-server-logical-query-processing-phases-order-of-statement-execution/

SQL Deduct value from multiple rows

I would like to apply total $10.00 discount for each customers.The discount should be applied to multiple transactions until all $10.00 used.
Example:
CustomerID Transaction Amount Discount TransactionID
1 $8.00 $8.00 1
1 $6.00 $2.00 2
1 $5.00 $0.00 3
1 $1.00 $0.00 4
2 $5.00 $5.00 5
2 $2.00 $2.00 6
2 $2.00 $2.00 7
3 $45.00 $10.00 8
3 $6.00 $0.00 9
The query below keeps track of the running sum and calculates the discount depending on whether the running sum is greater than or less than the discount amount.
select
customerid, transaction_amount, transactionid,
(case when 10 > (sum_amount - transaction_amount)
then (case when transaction_amount >= 10 - (sum_amount - transaction_amount)
then 10 - (sum_amount - transaction_amount)
else transaction_amount end)
else 0 end) discount
from (
select customerid, transaction_amount, transactionid,
sum(transaction_amount) over (partition by customerid order by transactionid) sum_amount
from Table1
) t1 order by customerid, transactionid
http://sqlfiddle.com/#!6/552c2/7
same query with a self join which should work on most db's including mssql 2008
select
customerid, transaction_amount, transactionid,
(case when 10 > (sum_amount - transaction_amount)
then (case when transaction_amount >= 10 - (sum_amount - transaction_amount)
then 10 - (sum_amount - transaction_amount)
else transaction_amount end)
else 0 end) discount
from (
select t1.customerid, t1.transaction_amount, t1.transactionid,
sum(t2.transaction_amount) sum_amount
from Table1 t1
join Table1 t2 on t1.customerid = t2.customerid
and t1.transactionid >= t2.transactionid
group by t1.customerid, t1.transaction_amount, t1.transactionid
) t1 order by customerid, transactionid
http://sqlfiddle.com/#!3/552c2/2
You can do this with recursive common table expressions, although it isn't particularly pretty. SQL Server stuggles to optimize these types of query. See Sum of minutes between multiple date ranges for some discussion.
If you wanted to go further with this approach, you'd probably need to make a temporary table of x, so you can index it on (customerid, rn)
;with x as (
select
tx.*,
row_number() over (
partition by customerid
order by transaction_amount desc, transactionid
) rn
from
tx
), y as (
select
x.transactionid,
x.customerid,
x.transaction_amount,
case
when 10 >= x.transaction_amount then x.transaction_amount
else 10
end as discount,
case
when 10 >= x.transaction_amount then 10 - x.transaction_amount
else 0
end as remainder,
x.rn as rn
from
x
where
rn = 1
union all
select
x.transactionid,
x.customerid,
x.transaction_amount,
case
when y.remainder >= x.transaction_amount then x.transaction_amount
else y.remainder
end,
case
when y.remainder >= x.transaction_amount then y.remainder - x.transaction_amount
else 0
end,
x.rn
from
y
inner join
x
on y.rn = x.rn - 1 and y.customerid = x.customerid
where
y.remainder > 0
)
update
tx
set
discount = y.discount
from
tx
inner join
y
on tx.transactionid = y.transactionid;
Example SQLFiddle
I usually like to setup a test environment for such questions. I will use a local temporary table. Please note, I made the data un-ordered since it is not guaranteed in a real life.
-- play table
if exists (select 1 from tempdb.sys.tables where name like '%transactions%')
drop table #transactions
go
-- play table
create table #transactions
(
trans_id int identity(1,1) primary key,
customer_id int,
trans_amt smallmoney
)
go
-- add data
insert into #transactions
values
(1,$8.00),
(2,$5.00),
(3,$45.00),
(1,$6.00),
(2,$2.00),
(1,$5.00),
(2,$2.00),
(1,$1.00),
(3,$6.00);
go
I am going to give you two answers.
First, in 2014 there are new windows functions for rows preceding. This allows us to get a running total (rt) and a rt adjusted by one entry. Give these two values, we can determine if the maximum discount has been exceeded or not.
-- Two running totals for 2014
;
with cte_running_total
as
(
select
*,
SUM(trans_amt)
OVER (PARTITION BY customer_id
ORDER BY trans_id
ROWS BETWEEN UNBOUNDED PRECEDING AND
0 PRECEDING) as running_tot_p0,
SUM(trans_amt)
OVER (PARTITION BY customer_id
ORDER BY trans_id
ROWS BETWEEN UNBOUNDED PRECEDING AND
1 PRECEDING) as running_tot_p1
from
#transactions
)
select
*
,
case
when coalesce(running_tot_p1, 0) <= 10 and running_tot_p0 <= 10 then
trans_amt
when coalesce(running_tot_p1, 0) <= 10 and running_tot_p0 > 10 then
10 - coalesce(running_tot_p1, 0)
else 0
end as discount_amt
from cte_running_total;
Again, the above version is using a common table expression and advanced windowing to get the totals.
Do not fret! The same can be done all the way down to SQL 2000.
Second solution, I am just going to use the order by, sub-queries, and a temporary table to store the information that is normally in the CTE. You can switch the temporary table for a CTE in SQL 2008 if you want.
-- w/o any fancy functions - save to temp table
select *,
(
select count(*) from #transactions i
where i.customer_id = o.customer_id
and i.trans_id <= o.trans_id
) as sys_rn,
(
select sum(trans_amt) from #transactions i
where i.customer_id = o.customer_id
and i.trans_id <= o.trans_id
) as sys_tot_p0,
(
select sum(trans_amt) from #transactions i
where i.customer_id = o.customer_id
and i.trans_id < o.trans_id
) as sys_tot_p1
into #results
from #transactions o
order by customer_id, trans_id
go
-- report off temp table
select
trans_id,
customer_id,
trans_amt,
case
when coalesce(sys_tot_p1, 0) <= 10 and sys_tot_p0 <= 10 then
trans_amt
when coalesce(sys_tot_p1, 0) <= 10 and sys_tot_p0 > 10 then
10 - coalesce(sys_tot_p1, 0)
else 0
end as discount_amt
from #results
order by customer_id, trans_id
go
In short, your answer is show in the following screen shot. Cut and paste the code into SSMS and have some fun.

TSQL- Finding the difference in days of multiple records in SQL Server

Is it possible to find the difference of days of different records in SQL Server 2008 R2?
SELECT OrderDate FROM OrdersTbl WHERE SKU='AA0000' ORDER BY ORDERDATE DESC
OrderDate
-----------------------
2009-12-03 00:00:00.000
2009-04-03 00:00:00.000
2008-02-22 00:00:00.000
2008-02-21 00:00:00.000
2007-02-18 00:00:00.000
2007-01-27 00:00:00.000
2006-10-13 00:00:00.000
I would like a way to get how many days in between there are for each order date so that I could find the average frequency. Thanks in advance.
You can do it with a common table expression and ROW_NUMBER:
WITH OrderDates AS (
SELECT
ROW_NUMBER() OVER (ORDER BY OrderDate DESC) AS RowNumber,
OrderDate
FROM OrdersTable
WHERE SKU = 'AA0000'
)
SELECT
AVG(DATEDIFF(DD, O2.OrderDate, O1.OrderDate)) AS AverageFrequency
FROM OrderDates O1
LEFT JOIN OrderDates O2
ON O2.RowNumber = O1.RowNumber + 1
Sucks there's no LEAD/LAG support in SQL Server:
SELECT z.orderdate,
z.prev_date,
DATEDIFF(dd, z.prev_date, z.orderdate)
FROM (SELECT OrderDate,
(SELECT MAX(y.orderdate)
FROM ORDERSTBL y
WHERE y.orderdate < x.orderdate
AND y.sku = x.sku) AS prev_date
FROM OrdersTbl x
WHERE x.sku ='AA0000') z
ORDER BY z.orderdate DESC
;With cteDifference as (
Select SKU, OrderDate, Row_Number() OVER (Partition by SKU Order by OrderDate) as RowNumber
from OrdersTbl
)
select cur.SKU,
cur.OrderDate as CurrentDate,
prev.OrderDate as PreviousDate,
DATEDIFF(DD,prev.OrderDate, cur.OrderDate) as DaysDifference
from cteDifference cur
left join cteDifference prev
on cur.SKU = prev.SKU
and cur.RowNumber = prev.RowNumber + 1
where cur.SKU = 'AA0000'
order by cur.OrderDate desc