SQL Server Sum Distinct Group by - sql

For example this table, I need to sum this grouped by id and date with distinct
id amt date
1 100 2018/06/01
1 120 2018/06/02
1 100 2018/06/03
1 100 2018/06/03
1 100 2018/06/03
2 100 2018/06/01
2 100 2018/06/01
2 100 2018/06/01
2 130 2018/06/02
2 130 2018/06/02
2 130 2018/06/02
2 130 2018/06/02
2 100 2018/06/03
First i tried
SELECT SUM(DISTINCT amt) GROUP BY id
But the result are wrong, it's removing duplicate for example on id 1, instead of 320 it only results 220 because it remove the duplicated amt 100.
So I tried
SELECT SUM(DISTINCT amt) GROUP BY id, date
But I can't sum it.
Edit: Sorry i forgot to say the result should be
id amt
1 320
2 330

With a long version, but easy to understand query. The below query using CTE should help you
with records as
(select distinct id, date, amt from table_name)
select sum (amt), id
from records
group by
id;

tried with below.
SELECT id,SUM(DISTINCT amt) as amt,date #tmp from [yourTableName] GROUP BY id, date
select id,amt from #tmp

Try this:
select id, SUM(amt) from (
select id, SUM(distinct amt) amt, [date] from #tbl
group by id, [date]
) a group by id
First you need to group distinct amt grouped by id and date. Next, you have to to group the result by id, summing partially summed amt column (in first step we summed only distinct values from particular days).

Try this...
SELECT id,
Sum(amt) AS amt
FROM (SELECT DISTINCT *
FROM mytable) tbl
GROUP BY id
Output
+----+-----+
| id | amt |
+----+-----+
| 1 | 320 |
| 2 | 330 |
+----+-----+
SQL FIDDLE: http://sqlfiddle.com/#!18/2d356/14/0

The below query using CTE and Row_number should help you
with cte
as
(
select 1 id, 100 amt,cast('2018/06/01' as date) date
union all
select 1 id, 120 amt,cast('2018/06/02' as date) date
union all
select 1 id, 100 amt,cast('2018/06/03' as date) date
union all
select 1 id, 100 amt,cast('2018/06/03' as date) date
union all
select 1 id, 100 amt,cast('2018/06/03' as date) date
union all
select 2 id, 100 amt,cast('2018/06/01' as date) date
union all
select 2 id, 100 amt,cast('2018/06/01' as date) date
union all
select 2 id, 100 amt,cast('2018/06/01' as date) date
union all
select 2 id, 130 amt,cast('2018/06/02' as date) date
union all
select 2 id, 130 amt,cast('2018/06/02' as date) date
union all
select 2 id, 130 amt,cast('2018/06/02' as date) date
union all
select 2 id, 130 amt,cast('2018/06/02' as date) date
union all
select 2 id, 100 amt,cast('2018/06/03' as date) date
)
select id,sum(amt) as sum1 from
(
select *,ROW_NUMBER() over(partition by id,date order by id,date) s1 from cte
) b
where s1=1
group by id

Related

Oracle SQL - Displaying only net effect (unmatched) rows

Following is my sample table structure
Name Amount
A 100
A 100
A -100
A 100
A 100
A -100
B 10
A 100
There is no Primary Key in this table.
Desired Output:
Name Amount
A 100
A 100
B 10
A 100
Explanation:
I need to cancel out matching rows, i.e., one -100 nullifies one +100.
Therefore i need to display only rows that are not offset / not nullified one to one.
This can be done in PL/SQL by populating the rows to a temporary table and deleting one positive for every one corresponding negative. However, I require to do this on the fly using SQL statements.
Regards,
Raghu
You can enumerate the rows using row_number() and then use that to "cancel" them:
select t.name, t.amount
from (select t.*,
sum(amount) over (partition by name, abs(amount), seqnum) as sum_amount
from (select t.*,
row_number() over (partition by name, amount order by name) as seqnum
from t
) t
) t
where sum_amount <> 0;
Here is a db<>fiddle.
You can give each row a ROW_NUMBER unique to that name/amount pair and then count whether, for a name/ABS(amount) there are one or two values for each of those ROW_NUMBER and discard the rows where there are two (one positive and one negative):
SELECT name,
amount
FROM (
SELECT name,
amount,
COUNT( amount ) OVER ( PARTITION BY name, ABS( amount ), rn )
AS num_matches
FROM (
SELECT t.*,
ROW_NUMBER() OVER ( PARTITION BY name, amount ORDER BY ROWNUM ) AS rn
FROM table_name t
)
)
WHERE num_matches = 1
So, for your sample data:
CREATE TABLE table_name ( Name, Amount ) AS
SELECT 'A', +100 FROM DUAL UNION ALL
SELECT 'A', +100 FROM DUAL UNION ALL
SELECT 'A', -100 FROM DUAL UNION ALL
SELECT 'A', +100 FROM DUAL UNION ALL
SELECT 'A', +100 FROM DUAL UNION ALL
SELECT 'A', -100 FROM DUAL UNION ALL
SELECT 'B', +10 FROM DUAL UNION ALL
SELECT 'A', +100 FROM DUAL;
This outputs:
NAME | AMOUNT
:--- | -----:
A | 100
A | 100
A | 100
B | 10
db<>fiddle here
If there are never more negative values than positive it's a task for EXCEPT ALL. Oracle doesn't support it, but this is a rewrite:
select name, amount
from
(
select name, amount, row_number() over (partition by name, amount order by amount)
from tab
where amount > 0
minus
select name, -amount, row_number() over (partition by name, amount order by amount)
from tab
where amount < 0
) dt
or
with cte as
(
select name, amount, row_number() over (partition by name, amount order by amount) as rn
from tab
)
select name, amount
from
(
select name, amount, rn
from cte
where amount > 0
minus
select name, -amount, rn
from cte
where amount < 0
) dt

How to use SUM DISTINCT when the order has the same qty of items

I'm working on a query to show me total amount of orders sent and qty of items sent in a day. Due to the lots of joins I have duplicate rows. It looks like this:
DispatchDate Order Qty
2019-07-02 1 2
2019-07-02 1 2
2019-07-02 1 2
2019-07-02 2 2
2019-07-02 2 2
2019-07-02 2 2
2019-07-02 3 5
2019-07-02 3 5
2019-07-02 3 5
I'm using this query:
SELECT DispatchDate, COUNT(DISTINCT Order), SUM(DISTINCT Qty)
FROM TABLE1
GROUP BY DispatchDate
Obviously on this date there 3 orders with total of items that equals 9
However, the query is returning:
3 orders and 7 items
I don't have a clue how to resolve this issue. How can I sum the quantities for each orders instead of simply removing duplicates from only one column like SUM DISTINCT does
Could do a CTE
with cte1 as (
SELECT Order AS Order
, DispatchDate
, MAX(QTY) as QTY
FROM FROM TABLE1
GROUP BY Order
, DispatchDate
)
SELECT DispatchDate
, COUNT(Order)
, SUM(Qty)
FROM cte1
GROUP BY DispatchDate
You have major problems with your data model, if the data is stored this way. If this is the case, you need a table with one row per order.
If this is the result of a query, you can probably fix the underlying query so you are not getting duplicates.
If you need to work with the data in this format, then extract a single row for each group. I think that row_number() is quite appropriate for this purpose:
select count(*), sum(qty)
from (select t.*, row_number() over (partition by dispatchdate, corder order by corder) as seqnum
from t
) t
where seqnum = 1
Here is a db<>fiddle.
At first, you should avoid multiplicating of the rows while linking. Like, for example, using LEFT JOIN instead of JOIN. But, as we are where are:
SELECT DispatchDate, sum( Qty)
FROM (
SELECT distinct DispatchDate, Order, Qty
FROM TABLE1 )T
GROUP BY DispatchDate
you have typed SUM(DISTINCT Qty), which summed up distinct values for Qty, that is 2 and 5. This is 7, isn't it?
Due to the lots of joins I have duplicate rows.
IMHO, you should fix your primary data at first. Probably the Qty column is function of unique combination of DispatchDate,Order tuple. Delete duplicities in primary data source and ensure there cannot be different Qty for two rows with same DispatchDate,Order. Then go back to your task and you'll find your SQL much simpler. No offense regarding other answers but they just mask the mess in primary data source and are unclear about choosing Qty for duplicate DispatchDate,Order (some take max, some sum).
Try this:
SELECT DispatchDate, COUNT(DISTINCT Order), SUM(DISTINCT Qty)
FROM TABLE1
GROUP BY DispatchDate, Order
I think you need dispatch date and order wise sum of distinct quantity.
How about this? Check comments within the code.
(I renamed the order column to corder; order can't be used as an identifier).
SQL> WITH test (dispatchdate, corder, qty)
2 -- your sample data
3 AS (SELECT DATE '2019-07-02', 1, 2 FROM DUAL UNION ALL
4 SELECT DATE '2019-07-02', 1, 2 FROM DUAL UNION ALL
5 SELECT DATE '2019-07-02', 1, 2 FROM DUAL UNION ALL
6 --
7 SELECT DATE '2019-07-02', 2, 2 FROM DUAL UNION ALL
8 SELECT DATE '2019-07-02', 2, 2 FROM DUAL UNION ALL
9 SELECT DATE '2019-07-02', 2, 2 FROM DUAL UNION ALL
10 --
11 SELECT DATE '2019-07-02', 3, 5 FROM DUAL UNION ALL
12 SELECT DATE '2019-07-02', 3, 5 FROM DUAL UNION ALL
13 SELECT DATE '2019-07-02', 3, 5 FROM DUAL),
14 -- compute sum of distinct qty per BOTH dispatchdate AND corder
15 temp
16 AS ( SELECT t1.dispatchdate,
17 t1.corder,
18 SUM (DISTINCT t1.qty) qty
19 FROM test t1
20 GROUP BY t1.dispatchdate,
21 t1.corder
22 )
23 -- the final result is then simple
24 SELECT t.dispatchdate,
25 COUNT (*) cnt,
26 SUM (qty) qty
27 FROM temp t
28 GROUP BY t.dispatchdate;
DISPATCHDA CNT QTY
---------- ---------- ----------
02.07.2019 3 9
SQL>

SQL Server query - get items that exist in more than one column

I have a simple table which contains barcode ids of tools and associated room location in which the tool should belong to.
Unfortunately, I've noticed that some users have entered the same barcode id for another room location.
For example, I have these 2 columns:
barcodeNumber | RoomLocation
--------------+-------------
123456 | 400
654321 | 300
875421 | 200
654321 | 400
999999 | 250
878787 | 300
777777 | 400
999999 | 200
Note that barcodeNumber "654321" is stored in roomLocations 300 & 400 ad "999999" are stored in room locations 200 & 250
How do I write the SQL query to list the duplicate barcode Number and RoomLocation they are located in and not just the "count" of duplicates?
For example, the end result I wish to see is:
654321 | 300
654321 | 400
999999 | 200
999999 | 250
Using window functions (SQL:1999) you would get the result like this:
with c as (
select barcodeNumber, RoomLocation,
count(*) over(partition by barcodeNumber) cnt
from t)
select barcodeNumber, RoomLocation
from c where cnt > 1
order by 1,2
You can also use SQL-92 syntax:
select barcodeNumber, RoomLocation
from t
where barcodeNumber IN (
select barcodeNumber from t
group by barcodeNumber
having count(*) > 1)
order by 1,2
You can try this also. Use count(*) over (partition by barcodenumber) to determine the duplicate values.
create table #sample (barcodenumber nvarchar(30),roomlocation int)
insert into #sample (barcodenumber,roomlocation)
select '123456',400 union all
select '654321',300 union all
select '875421',200 union all
select '654321',400 union all
select '999999',250 union all
select '878787',300 union all
select '777777',400 union all
select '999999',200
select barcodenumber,roomlocation from (
select *, count(*) over (partition by barcodenumber) as rnk
from #sample
)t
group by barcodenumber,roomlocation,rnk
having rnk >1
Hope this could help.
Do you want to find the duplicate barcode?
;WITH tb(barcodenumber,roomlocation)AS(
SELECT '123456',400 UNION ALL
SELECT '654321',300 UNION ALL
SELECT '875421',200 UNION ALL
SELECT '654321',400 UNION ALL
SELECT '999999',250 UNION ALL
SELECT '878787',300 UNION ALL
SELECT '777777',400 UNION ALL
SELECT '999999',200
)
SELECT * FROM (
SELECT *,COUNT(0)OVER(PARTITION BY tb.barcodenumber) AS cnt FROM tb
) AS t WHERE t.cnt>1
barcodenumber roomlocation cnt
------------- ------------ -----------
654321 400 2
654321 300 2
999999 200 2
999999 250 2
Here is another way to achive your result:
SELECT barcodenumber, roomlocation
FROM table_name
WHERE barcodenumber IN (
SELECT barcodenumber
FROM table_name
GROUP BY barcodenumber
HAVING COUNT(DISTINCT roomlocation) > 1);
--If you dont have duplicate rows then just use COUNT(*)
Use JOIN and HAVING clause :
SELECT A.barcodenumber,roomlocation
FROM #sample
JOIN
(
SELECT barcodenumber
FROM #sample
GROUP BY barcodenumber
HAVING COUNT(*) > 1
) A ON A.barcodenumber = #sample.barcodenumber

Use SUM function in oracle

I have a table in Oracle which contains :
id | month | payment | rev
----------------------------
A | 1 | 10 | 0
A | 2 | 20 | 0
A | 2 | 30 | 1
A | 3 | 40 | 0
A | 4 | 50 | 0
A | 4 | 60 | 1
A | 4 | 70 | 2
I want to calculate the payment column (SUM(payment)). For (id=A month=2) and (id=A month=4), I just want to take the greatest value from REV column. So that the sum is (10+30+40+70)=150. How to do it?
You can also use below.
select id,sum(payment) as value
from
(
select id,month,max(payment) from table1
group by id,month
)
group by id
Edit: for checking greatest rev value
select id,sum(payment) as value
from (
select id,month,rev,payment ,row_number() over (partition by id,month order by rev desc) as rno from table1
) where rno=1
group by id
This presupposes you don't have more than one value per rev. If that's not the case, then you probably want a row_number analytic instead of max.
with latest as (
select
id, month, payment, rev,
max (rev) over (partition by id, month) as max_rev
from table1
)
select sum (payment)
from latest
where rev = max_rev
Or there's this, if I've understood the requirement right:
with demo as (
select 'A'as id, 1 as month, 10 as payment, 0 as rev from dual
union all select 'A',2,20,0 from dual
union all select 'A',2,30,1 from dual
union all select 'A',3,40,0 from dual
union all select 'A',4,50,0 from dual
union all select 'A',4,60,1 from dual
union all select 'A',4,70,2 from dual
)
select sum(payment) keep (dense_rank last order by rev)
from demo;
You can check the breakdown by including the key columns:
with demo as (
select 'A'as id, 1 as month, 10 as payment, 0 as rev from dual
union all select 'A',2,20,0 from dual
union all select 'A',2,30,1 from dual
union all select 'A',3,40,0 from dual
union all select 'A',4,50,0 from dual
union all select 'A',4,60,1 from dual
union all select 'A',4,70,2 from dual
)
select id, month, max(rev)
, sum(payment) keep (dense_rank last order by rev)
from demo
group by id, month;
select sum(payment) from tableName where id='A' and month=2 OR month=4 order by payment asc;

How to use distinct and sum both together in oracle?

For example my table contains the following data:
ID price
-------------
1 10
1 10
1 20
2 20
2 20
3 30
3 30
4 5
4 5
4 15
So given the example above,
ID price
-------------
1 30
2 20
3 30
4 20
-----------
ID 100
How to write query in oracle? first sum(distinct price) group by id then sum(all price).
I would be very careful with a data structure like this. First, check that all ids have exactly one price:
select id
from table t
group by id
having count(distinct price) > 1;
I think the safest method is to extract a particular price for each id (say the maximum) and then do the aggregation:
select sum(price)
from (select id, max(price) as price
from table t
group by id
) t;
Then, go fix your data so you don't have a repeated additive dimension. There should be a table with one row per id and price (or perhaps with duplicates but controlled by effective and end dates).
The data is messed up; you should not assume that the price is the same on all rows for a given id. You need to check that every time you use the fields, until you fix the data.
first sum(distinct price) group by id then sum(all price)
Looking at your desired output, it seems you also need the final sum(similar to ROLLUP), however, ROLLUP won't directly work in your case.
If you want to format your output in exactly the way you have posted your desired output, i.e. with a header for the last row of total sum, then you could set the PAGESIZE in SQL*Plus.
Using UNION ALL
For example,
SQL> set pagesize 7
SQL> WITH DATA AS(
2 SELECT ID, SUM(DISTINCT price) AS price
3 FROM t
4 GROUP BY id
5 )
6 SELECT to_char(ID) id, price FROM DATA
7 UNION ALL
8 SELECT 'ID' id, sum(price) FROM DATA
9 ORDER BY ID
10 /
ID PRICE
--- ----------
1 30
2 20
3 30
4 20
ID PRICE
--- ----------
ID 100
SQL>
So, you have an additional row in the end with the total SUM of price.
Using ROLLUP
Alternatively, you could use ROLLUP to get the total sum as follows:
SQL> set pagesize 7
SQL> WITH DATA AS
2 ( SELECT ID, SUM(DISTINCT price) AS price FROM t GROUP BY id
3 )
4 SELECT ID, SUM(price) price
5 FROM DATA
6 GROUP BY ROLLUP(id);
ID PRICE
---------- ----------
1 30
2 20
3 30
4 20
ID PRICE
---------- ----------
100
SQL>
First do the DISTINCT and then a ROLLUP
SELECT ID, SUM(price) -- sum of the distinct prices
FROM
(
SELECT DISTINCT ID, price -- distinct prices per ID
FROM tab
) dt
GROUP BY ROLLUP(ID) -- two levels of aggregation, per ID and total sum
SELECT ID,SUM(price) as price
FROM
(SELECT ID,price
FROM TableName
GROUP BY ID,price) as T
GROUP BY ID
Explanation:
The inner query will select different prices for each ids.
i.e.,
ID price
-------------
1 10
1 20
2 20
3 30
4 5
4 15
Then the outer query will select SUM of those prices for each id.
Final Result :
ID price
----------
1 30
2 20
3 30
4 20
Result in SQL Fiddle.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE MYTABLE ( ID, price ) AS
SELECT 1, 10 FROM DUAL
UNION ALL SELECT 1, 10 FROM DUAL
UNION ALL SELECT 1, 20 FROM DUAL
UNION ALL SELECT 2, 20 FROM DUAL
UNION ALL SELECT 2, 20 FROM DUAL
UNION ALL SELECT 3, 30 FROM DUAL
UNION ALL SELECT 3, 30 FROM DUAL
UNION ALL SELECT 4, 5 FROM DUAL
UNION ALL SELECT 4, 5 FROM DUAL
UNION ALL SELECT 4, 15 FROM DUAL;
Query 1:
SELECT COALESCE( TO_CHAR(ID), 'ID' ) AS ID,
SUM( PRICE ) AS PRICE
FROM ( SELECT DISTINCT ID, PRICE FROM MYTABLE )
GROUP BY ROLLUP ( ID )
ORDER BY ID
Results:
| ID | PRICE |
|----|-------|
| 1 | 30 |
| 2 | 20 |
| 3 | 30 |
| 4 | 20 |
| ID | 100 |