How to aggregate on the same column? - database

I am trying to figure out which seems very simple but I am not able to figure out a way to do same.
Consider following table and data,
create table dummy_query
(id varchar2(20), amount number(10,2), memo varchar(20));
insert into dummy_query values('1', 10.00, 'Memo');
insert into dummy_query values('1', 20.00, 'Memo1');
I want to get the values as:
Id MemoValue Memo1Value TotalSum
----------
1 10.00 20.00 30.00
Is there any way to get data in this manner?
Thanks!

SQL Fiddle
Oracle 11g R2 Schema Setup:
create table dummy_query (id, amount, memo) AS
SELECT '1', 10.00, 'Memo' FROM DUAL
UNION ALL SELECT '1', 20.00, 'Memo1' FROM DUAL
UNION ALL SELECT '1', 30.00, 'Memo2' FROM DUAL;
Query 1:
If the TotalSum is the total of all memo amounts (and not just Memo and Memo1) then you can do:
SELECT ID,
SUM( CASE memo WHEN 'Memo' THEN amount END ) AS MemoValue,
SUM( CASE memo WHEN 'Memo1' THEN amount END ) AS Memo1Value,
SUM( amount ) AS TotalSum
FROM dummy_query
GROUP BY id
Results:
| ID | MEMOVALUE | MEMO1VALUE | TOTALSUM |
|----|-----------|------------|----------|
| 1 | 10 | 20 | 60 |
Query 2:
But if the TotalSum is just MemoValue + Memo1Value then add in a where clause:
SELECT ID,
SUM( CASE memo WHEN 'Memo' THEN amount END ) AS MemoValue,
SUM( CASE memo WHEN 'Memo1' THEN amount END ) AS Memo1Value,
SUM( amount ) AS TotalSum
FROM dummy_query
WHERE memo IN ( 'Memo', 'Memo1' )
GROUP BY id
Results:
| ID | MEMOVALUE | MEMO1VALUE | TOTALSUM |
|----|-----------|------------|----------|
| 1 | 10 | 20 | 30 |
Query 3:
Or, if you need to include all rows for another reason, then you could do:
SELECT ID,
SUM( CASE memo WHEN 'Memo' THEN amount END ) AS MemoValue,
SUM( CASE memo WHEN 'Memo1' THEN amount END ) AS Memo1Value,
SUM( CASE WHEN memo IN ( 'Memo', 'Memo1' ) THEN amount END ) AS TotalSum
FROM dummy_query
GROUP BY id
Results:
| ID | MEMOVALUE | MEMO1VALUE | TOTALSUM |
|----|-----------|------------|----------|
| 1 | 10 | 20 | 30 |

Im not very familiar with PIVOT but what you have tried I did build query on that. Hope this is out put you need -
SELECT a.*, b.SUM_AMT
FROM ((SELECT ID, amount, memo FROM dummy_query) PIVOT (SUM (amount)
FOR (memo)
IN ('Memo', 'Memo1'))) a,
( SELECT ID, SUM (AMOUNT) "SUM_AMT"
FROM dummy_query
GROUP BY id) b
WHERE a.id = b.id
There can be better ways to reslove this so lets see what other folks have to share.

Solution with pivot, which you already started:
select id, memo, memo1, memo+memo1 msum from (
select * from dummy_query
pivot (sum(amount) for (memo) in ('Memo' memo,'Memo1' memo1)))
and more traditional, without pivot:
select id, memo, memo1, memo1+memo msum from (
select id,
sum(case when memo='Memo' then amount end) memo,
sum(case when memo='Memo1' then amount end) memo1
from dummy_query group by id)
SQLFiddle

Related

Split two columns in one row

I have a table with two columns: Salary and Department_id
|Salary|Department_id|
|---------------------
|1000 |10 |
|2000 |90 |
|3000 |10 |
|4000 |90 |
Now I need to split this colums in one row and calculate sum of salary for every department.
Output:
|Dep10|Dep90|
|-----------|
|4000 |6000 |
NOTE: "Dep10" and "Dep90" are aliases.
I try to use decode or case
SELECT DECODE(department_id, 10, SUM(salary),NULL) AS "Dep10",
DECODE(department_id, 90, SUM(salary), NULL) AS "Dep90"
FROM employees
GROUP BY department_id
but I obtain this:
select
sum(case when Department_id = '10' then Salary end) as Dep10,
sum(case when Department_id = '90' then Salary end) as Dep90
from employees
Use PIVOT:
Oracle Setup:
CREATE TABLE test_data ( Salary, Department_id ) AS
SELECT 1000, 10 FROM DUAL UNION ALL
SELECT 2000, 90 FROM DUAL UNION ALL
SELECT 3000, 10 FROM DUAL UNION ALL
SELECT 4000, 90 FROM DUAL
Query:
SELECT *
FROM test_data
PIVOT ( SUM( salary ) FOR Department_id IN ( 10 AS Dep10, 90 AS Dep90 ) )
Output:
DEP10 | DEP90
----: | ----:
4000 | 6000
db<>fiddle here
I think you should:
1 - use GROUP BY clause on your first table.
2 - use PIVOT feature you can learn about it here. In a few words, you can transpose columns and rows using it.
Good luck!

Field equal 1 display

I am using SQL Server 2008 and I would like to only get the activityCode for the orderno when it equals 1 if there are duplicate orderno with the activityCode equals 0.
Also, if the record for orderno activityCode equals 0 then display those records also. But I would only like to display the orderno when the activityCode equals 0 if the same orderno activityCode does not equal 1 or the activityCode only equals 0. I hope this is clear and makes sense but let me know if I need to provide more details. Thanks
--create table
create table po_v
(
orderno int,
amount number,
activityCode number
)
--insert values
insert into po_v values
(170268, 2774.31, 0),
(17001988, 288.82, 0),
(17001988, 433.23, 1),
(170271, 3786, 1),
(170271, 8476, 0),
(170055, 34567, 0)
--Results
170268 | 2774.31 | 0
17001988 | 433.23 | 1
170271 | 3786 | 1
170055 | 34567 | 0
*****Updated*****
I have inserted two new records and the results have been updated. The data in the actual table has other numbers besides 0 and 1. The select statement displays the correct orderno's but I would like the other records for the orderno to display also. The partition only populates one record per orderno. If possible I would like to see the records with the same activityCode.
--insert values
insert into po_v values
(170271, 3799, 1),
(172525, 44445, 2)
--select statement
SELECT Orderno,
Amount,
Activitycode
FROM (SELECT orderno,
amount,
activitycode,
ROW_NUMBER()
OVER(
PARTITION BY orderno
ORDER BY activitycode DESC) AS dup
FROM Po_v)dt
WHERE dt.dup = 1
ORDER BY 1
--select statement results
170055 | 34567 | 0
170268 | 2774.31 | 0
170271 | 3786 | 1
172525 | 44445 | 2
17001988 | 433.23 | 1
--expected results
170055 | 34567 | 0
170268 | 2774.31 | 0
170271 | 3786 | 1
170271 | 3799 | 1
172525 | 44445 | 2
17001988 | 433.23 | 1
Not totally clear what you are trying to do here but this returns the output you are expecting.
select orderno
, amount
, activityCode
from
(
select *
, RowNum = ROW_NUMBER() over(partition by orderno order by activityCode desc)
from po_v
) x
where x.RowNum = 1
---EDIT---
With the new details this is a very different question. As I understand it now you want all row for that share the max activity code for each orderno. You can do this pretty easily with a cte.
with MyGroups as
(
select orderno
, Activitycode = max(activitycode)
from po_v
group by orderno
)
select *
from po_v p
join MyGroups g on g.orderno = p.orderno
and g.Activitycode = p.Activitycode
Try this
SELECT Orderno,
Amount,
Activitycode
FROM (SELECT orderno,
amount,
activitycode,
ROW_NUMBER()
OVER(
PARTITION BY orderno
ORDER BY activitycode DESC) AS dup
FROM Po_v)dt
WHERE dt.dup = 1
ORDER BY 1
Result
Orderno Amount Activitycode
------------------------------------
170055 34567.00 0
170268 2774.31 0
170271 3786.00 1
17001988 433.23 1

Correct way to convert rows to columns in sql query

This is my regular select query:
SELECT [category], [price], [company]
FROM [prices]
INNER JOIN [company] AS [co] ON [co].[company] = [pr].[company]
WHERE [co].[id] IN (1,2,3);
The result is:
category | price | company
---------+-------+-------------
Srv | 1200 | CoA
Srv | 2800 | CoB
EQ | 5400 | CoA
Deduc | 400 | CoA
Deduc | 150 | CoB
And I need this result:
PriceASrv | PriceBSrv | PriceAEQ | PriceBEQ | PriceADeduc | PriceBDeduc
-----------+-----------+----------+----------+-------------+-------------
1200 | 2800 | 5400 | NULL | 400 | 150
It seems I need twice PIVOT, am I right? does any one have any idea?
No, you do not need to pivot twice, but you may need a dynamic query if:
There can be more than two companies, OR
Your company names can change, OR
Your categories can change.
If those three conditions are all false, either of the following queries would work:
DECLARE #T TABLE (Category VARCHAR(10), Price INT, Company CHAR(3));
INSERT #T VALUES
('Srv', 1200, 'CoA'),
('Srv', 2800, 'CoB'),
('EQ', 5400, 'CoA'),
('Deduc', 400, 'CoA'),
('Deduc', 150, 'CoB');
-- With PIVOT
SELECT *
FROM (
SELECT CatCom = 'Price' + RIGHT(Company, 1) + Category,
Price
FROM #T) AS T
PIVOT (MAX(Price) FOR CatCom IN ([PriceASrv], [PriceBSrv], [PriceAEQ], [PriceBEQ], [PriceADeduc], [PriceBDeduc])) AS P
-- With CASE aggregation
SELECT PriceASrv = MAX(CASE WHEN Category = 'Srv' AND Company = 'CoA' THEN Price END),
PriceBSrv = MAX(CASE WHEN Category = 'Srv' AND Company = 'CoB' THEN Price END),
PriceAEQ = MAX(CASE WHEN Category = 'EQ' AND Company = 'CoA' THEN Price END),
PriceBEQ = MAX(CASE WHEN Category = 'EQ' AND Company = 'CoB' THEN Price END),
PriceADeduc = MAX(CASE WHEN Category = 'Deduc' AND Company = 'CoA' THEN Price END),
PriceBDeduc = MAX(CASE WHEN Category = 'Deduc' AND Company = 'CoB' THEN Price END)
FROM #T;
If any of the three conditions are true, then you may need a dynamic query (which would basically be a modification of either of the above queries to fit your needs).
No you do not need PIVOT twice. See query below. You can convert to dynamic pivot if you don't know the how many companies may be present
Select * from
(
Select
[data]='Price' + RIGHT([company],1) +[category] ,
[price]
FROM [prices]
INNER JOIN [company] as [co] On [co].[company] = [pr].[company]
WHERE [co].[id] In (1,2,3))src
PIVOT
(
MAX([price]) FOR [data] in (PriceASrv,PriceBSrv,PriceAEQ,PriceBEQ,PriceADeduc,PriceBDeduc)
)p
see working demo

MSSQL: Create incremental row label per group

In my table, I have a primary key and a date. What I'd like to achieve is to have an incremental label based on whether or not there is a break between the dates - column Goal.
Now, below is an example. The break column was calculated using LEAD function (I thought it might help).
I am able to solve it using T-SQL, but this would be last resort. Nothing I tried has worked so far. I am using MSSQL 2014.
PK | Date | break | Goal |
-------------------------------
1 | 03/2017 | 0 | 1 |
1 | 04/2017 | 0 | 1 |
1 | 08/2017 | 1 | 2 |
1 | 09/2017 | 0 | 2 |
1 | 10/2017 | 0 | 2 |
1 | 02/2018 | 1 | 3 |
1 | 03/2018 | 0 | 3 |
Here is a code to reproduce this example:
CREATE TABLE #test
(
ConsumerId INT,
FullDate DATE,
Goal INT
)
INSERT INTO #test (ConsumerId, FullDate, Goal) VALUES (1,'2017-03-01',1)
INSERT INTO #test (ConsumerId, FullDate, Goal) VALUES (1,'2017-04-01',1)
INSERT INTO #test (ConsumerId, FullDate, Goal) VALUES (1,'2017-08-01',2)
INSERT INTO #test (ConsumerId, FullDate, Goal) VALUES (1,'2017-09-01',2)
INSERT INTO #test (ConsumerId, FullDate, Goal) VALUES (1,'2017-10-01',2)
INSERT INTO #test (ConsumerId, FullDate, Goal) VALUES (1,'2018-02-01',3)
INSERT INTO #test (ConsumerId, FullDate, Goal) VALUES (1,'2018-03-01',3)
SELECT ConsumerId,
FullDate,
CASE WHEN (datediff(month,
isnull(
LEAD (FullDate,1) OVER (PARTITION BY ConsumerId ORDER BY FullDate DESC),
FullDate),
FullDate) > 1)
THEN 1
ELSE 0
END AS break,
Goal
FROM #test
ORDER BY FullDate ASC
EDIT
This is apparently a famous problem "Islands and gaps" as pointed out in the comments. And Google offers many solutions as well as other questions here at SO.
Try this...
WITH
cte_TestGap AS (
SELECT
t.ConsumerId, t.FullDate,
Gap = CASE
WHEN DATEDIFF(mm, t.FullDate, LAG(t.FullDate, 1) OVER (PARTITION BY t.ConsumerId ORDER BY t.FullDate)) = -1
THEN 0
ELSE ROW_NUMBER() OVER (PARTITION BY t.ConsumerId ORDER BY t.FullDate)
END
FROM
#test t
),
cte_SmearGap AS (
SELECT
tg.ConsumerId, tg.FullDate,
GV = MAX(tg.Gap) OVER (PARTITION BY tg.ConsumerId ORDER BY tg.FullDate ROWS UNBOUNDED PRECEDING)
FROM
cte_TestGap tg
)
SELECT
sg.ConsumerId, sg.FullDate,
GroupValue = DENSE_RANK() OVER (PARTITION BY sg.ConsumerId ORDER BY sg.GV)
FROM
cte_SmearGap sg;
An explanation of the code an how it works...
The 1st query, in cte_TestGap, uses the LAG function along with ROW_NUMBER() function to mark the location of gap in the data. We can see that by breaking it out and looking at it's results...
WITH
cte_TestGap AS (
SELECT
t.ConsumerId, t.FullDate,
Gap = CASE
WHEN DATEDIFF(mm, t.FullDate, LAG(t.FullDate, 1) OVER (PARTITION BY t.ConsumerId ORDER BY t.FullDate)) = -1
THEN 0
ELSE ROW_NUMBER() OVER (PARTITION BY t.ConsumerId ORDER BY t.FullDate)
END
FROM
#test t
)
SELECT * FROM cte_TestGap;
cte_TestGap results...
ConsumerId FullDate Gap
----------- ---------- --------------------
1 2017-03-01 1
1 2017-04-01 0
1 2017-08-01 3
1 2017-09-01 0
1 2017-10-01 0
1 2018-02-01 6
1 2018-03-01 0
At this point we want the 0 values to take on the value of the preceding non-0 values, allowing them to be grouped together. This is done in the 2nd query (cte_SmearGap) using the MAX function with a "window frame". So if we look at the output of cte_SmearGap, we can see that...
WITH
cte_TestGap AS (
SELECT
t.ConsumerId, t.FullDate,
Gap = CASE
WHEN DATEDIFF(mm, t.FullDate, LAG(t.FullDate, 1) OVER (PARTITION BY t.ConsumerId ORDER BY t.FullDate)) = -1
THEN 0
ELSE ROW_NUMBER() OVER (PARTITION BY t.ConsumerId ORDER BY t.FullDate)
END
FROM
#test t
),
cte_SmearGap AS (
SELECT
tg.ConsumerId, tg.FullDate,
GV = MAX(tg.Gap) OVER (PARTITION BY tg.ConsumerId ORDER BY tg.FullDate ROWS UNBOUNDED PRECEDING)
FROM
cte_TestGap tg
)
SELECT * FROM cte_SmearGap;
cte_SmearGap results...
ConsumerId FullDate GV
----------- ---------- --------------------
1 2017-03-01 1
1 2017-04-01 1
1 2017-08-01 3
1 2017-09-01 3
1 2017-10-01 3
1 2018-02-01 6
1 2018-03-01 6
At this point All of the rows are in distinct groups... but... We'd like to have our group numbers in a contiguous sequence (1,2,3) as opposed to (1,3,6).
Of course that's easy enough to fix using the DENSE_Rank() function, which is what's happening in the final select...
WITH
cte_TestGap AS (
SELECT
t.ConsumerId, t.FullDate,
Gap = CASE
WHEN DATEDIFF(mm, t.FullDate, LAG(t.FullDate, 1) OVER (PARTITION BY t.ConsumerId ORDER BY t.FullDate)) = -1
THEN 0
ELSE ROW_NUMBER() OVER (PARTITION BY t.ConsumerId ORDER BY t.FullDate)
END
FROM
#test t
),
cte_SmearGap AS (
SELECT
tg.ConsumerId, tg.FullDate,
GV = MAX(tg.Gap) OVER (PARTITION BY tg.ConsumerId ORDER BY tg.FullDate ROWS UNBOUNDED PRECEDING)
FROM
cte_TestGap tg
)
SELECT
sg.ConsumerId, sg.FullDate,
GroupValue = DENSE_RANK() OVER (PARTITION BY sg.ConsumerId ORDER BY sg.GV)
FROM
cte_SmearGap sg;
The end result...
ConsumerId FullDate GroupValue
----------- ---------- --------------------
1 2017-03-01 1
1 2017-04-01 1
1 2017-08-01 2
1 2017-09-01 2
1 2017-10-01 2
1 2018-02-01 3
1 2018-03-01 3
The comment from David Browne was actually extremely useful. If you google "Islands and Gaps", there are many variations of the solution. Below is the one I liked the most.
In the end, I needed the Goal column to be able to group the dates into MIN/MAX. This solution skips this step and directly creates the aggregated range.
Here is the source.
SELECT MIN(FullDate) AS range_start,
MAX(FUllDate) AS range_end
FROM (
SELECT FullDate,
DATEADD(MM, -1 * ROW_NUMBER() OVER(ORDER BY FullDate), FullDate) AS grp
FROM #test
) a
GROUP BY a.grp
And the output:
range_start | range_end |
--------------------------
2017-03-01 | 2017-04-01 |
2017-08-01 | 2017-10-01 |
2018-02-01 | 2018-03-01 |

How can I get multiple columns values into single row in oracle?

My exact requirement is that if the output of the query 'select amount, quantity from temp_table where type = 5;' is:
amount | quantity
10 | 5
20 | 7
12 | 10
Then, the output should be displayed as:
amount1 | amount2 | amount3 | quantity1 | quantity2 | quantity3
10 | 20 | 12 | 5 | 7 | 10
A possible solution might be:
SELECT LISTAGG(amount, '|') WITHIN GROUP (order by amount)
|| LISTAGG(quantity, '|') WITHIN GROUP (order by amount) as result
FROM temp_table where type = 5;
*Have in mind, that the values of the columns amount and quantity are separated by spaces, thus the ' ' in the listagg() expression. You can change it to '|' or anything else if you like.
Cheers
Use a PIVOT:
SELECT "1_AMOUNT" AS Amount1,
"2_AMOUNT" AS Amount2,
"3_AMOUNT" AS Amount3,
"4_AMOUNT" AS Amount4,
"5_AMOUNT" AS Amount5,
"1_QUANTITY" AS Quantity1,
"2_QUANTITY" AS Quantity2,
"3_QUANTITY" AS Quantity3,
"4_QUANTITY" AS Quantity4,
"5_QUANTITY" AS Quantity5
FROM ( SELECT amount, quantity, ROWNUM rn FROM temp_table WHERE type = 5 )
PIVOT ( MAX( amount ) AS amount,
MAX( quantity ) AS quantity
FOR rn IN ( 1, 2, 3, 4, 5 ) );

Resources