Pivot table with multi values of one column - sql

Everything find with one value for each column, but does it support multi value?
Example for my query:
WITH
INPUT_LIST AS
(SELECT 1 PRODUCT_ID, 1 TYPE_ID, 1000 PRICE FROM DUAL
UNION ALL
SELECT 2 PRODUCT_ID, 1 TYPE_ID, 1500 PRICE FROM DUAL
UNION ALL
SELECT 3 PRODUCT_ID, 2 TYPE_ID, 500 PRICE FROM DUAL
UNION ALL
SELECT 4 PRODUCT_ID, 3 TYPE_ID, 2000 PRICE FROM DUAL
UNION ALL
SELECT 1 PRODUCT_ID, 4 TYPE_ID, 1000 PRICE FROM DUAL
UNION ALL
SELECT 2 PRODUCT_ID, 5 TYPE_ID, 1500 PRICE FROM DUAL
UNION ALL
SELECT 3 PRODUCT_ID, 2 TYPE_ID, 500 PRICE FROM DUAL
UNION ALL
SELECT 2 PRODUCT_ID, 3 TYPE_ID, 2000 PRICE FROM DUAL
)
SELECT * FROM
(SELECT PRODUCT_ID, TYPE_ID, SUM(PRICE) TOTAL FROM INPUT_LIST GROUP BY PRODUCT_ID, TYPE_ID)
PIVOT (SUM(TOTAL) FOR TYPE_ID IN (1 AS "FIRST_TYPE", 2 AS "SECOND_TYPE", 3 AS "THIRD_TYPE", 4 AS "FOURTH_TYPE", 5 AS "FIFTH"))
ORDER BY PRODUCT_ID;
Multi value mean I want to mark TYPE_ID in (3,4,5) to "OTHER_TYPE". Something like:
PIVOT (SUM(TOTAL) FOR TYPE_ID IN (1 AS "FIRST_TYPE", 2 AS "SECOND_TYPE", (3,4,5) AS "OTHER_TYPE"))
I can use other way to query but I want to know can pivot do that?

No PIVOT clause does not have such a feature.
But You can still do a pivot the old fashioned way:
SELECT PRODUCT_ID,
sum( case when type_id = 1 then PRICE end ) As FIRST_TYPE,
sum( case when type_id = 2 then PRICE end ) As SEcOND_TYPE,
sum( case when type_id in ( 3,4,5) then PRICE end ) ANOTHER_TYPE
FROM INPUT_LIST
GROUP BY PRODUCT_ID
ORDER BY PRODUCT_ID;

Just group the types in the sub-query first:
SELECT *
FROM (
SELECT PRODUCT_ID,
CASE
WHEN TYPE_ID IN (1,2)
THEN TYPE_ID
ELSE 3
END AS TYPE_ID,
PRICE
FROM INPUT_LIST
)
PIVOT (
SUM(PRICE) FOR TYPE_ID IN (
1 AS "FIRST_TYPE",
2 AS "SECOND_TYPE",
3 AS "OTHER_TYPE"
)
)
ORDER BY PRODUCT_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 find max of counts of a group where count is calculated using a function in SQL?

I have a table made up of customer_id and keyword_id. There are multiple occurance of different combinations of customer_id and keyword_id, and I want to find the highest occurring keyword_id for each customer_id. How should I do that?
Customer_ID . Keyword_ID
1 a
1 a
1 a
1 b
1 b
2 c
2 c
2 c
2 d
Expected Result
Customer_ID . Max_Keyword_ID . Count
1 a 3
2 c 3
You can make use of count and dense_rank to get your expected output. Get the rank =1 to make sure that you are getting the rows where you have maximum occurrences of a given output.
with cte as (
select 1 as customer_id, 'a' as Keyword_ID union all
select 1 as customer_id, 'a' as Keyword_ID union all
select 1 as customer_id, 'a' as Keyword_ID union all
select 1 as customer_id, 'b' as Keyword_ID union all
select 1 as customer_id, 'b' as Keyword_ID union all
select 2 as customer_id, 'c' as Keyword_ID union all
select 2 as customer_id, 'c' as Keyword_ID union all
select 2 as customer_id, 'c' as Keyword_ID union all
select 2 as customer_id, 'd' as Keyword_ID)
SELECT customer_id, Keyword_ID, [COUNT] FROM (
select customer_id, Keyword_ID, count(1) [COUNT],
dENSE_RANK() OVER (PARTITION BY customer_id ORDER BY COUNT(1) DESC) RANKED from cte C
group by customer_id, Keyword_ID ) Z
WHERE Z.RANKED = 1
Output:
customer_id Keyword_ID COUNT
1 a 3
2 c 3
You can try below - using correlated subquery
with cte as
(
select Customer_ID,Keyword_ID,count(Keyword_ID) as cnt
from tablename
group by Customer_ID,Keyword_ID
)
select * from cte a where cnt in (select max(cnt) from cte b where a.Customer_ID=b.Customer_ID )
You can try the following query
select Customer_ID,Keyword_ID,Count(Keyword_ID) as Count from tab group by
Customer_ID,Keyword_ID
Having Count(Keyword_ID)=(
SELECT MAX(mycount)
FROM (
SELECT Keyword_ID, COUNT(Keyword_ID) mycount
FROM tab
GROUP BY Keyword_ID) checkMaxValue)
Click here to view the reference
Another way of doing it using ROW_NUMBER() with PARTITION BY Customer_ID column.
You can try like following.
select *
from
(
select *, row_number() over(partition by Customer_ID order by ct desc) rn
from
(
select Customer_ID , Keyword_ID, count(*) ct
from YOURTABLE
GROUP BY Customer_ID , Keyword_ID
) t
) t1
where rn=1

SQL Server Sum Distinct Group by

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

BigQuery Join Array in standard SQL

I am using standard SQL and I have table Order:
"Order" table
and I am trying to join it with table MenuItem
"MenuItem" table
on Order item_ids array and MenuItem __id__ integer column and get array of MenuItem prices, but I am getting an error:
Correlated subqueries that reference other tables are not supported unless they can be de-correlated, such as by transforming them into an efficient JOIN.
How to avoid this error?
Query:
WITH menu_items AS
(
SELECT
__id__,
price
FROM
`potykion.MenuItem`
)
SELECT
*,
ARRAY(
SELECT
price
FROM
UNNEST(item_ids) AS id
JOIN
menu_items
ON
id = menu_items.__id__
)
FROM
`potykion.Order`
Try below (BigQuery Standard SQL)
WITH Orders AS (
SELECT 1 AS id, ARRAY[1,2,3] AS item_ids UNION ALL
SELECT 2 AS id, ARRAY[4,5] AS item_ids UNION ALL
SELECT 3 AS id, ARRAY[1,4,6] AS item_ids
),
MenuItems AS (
SELECT 1 AS __id__, 1.1 AS price UNION ALL
SELECT 2 AS __id__, 1.2 AS price UNION ALL
SELECT 3 AS __id__, 1.3 AS price UNION ALL
SELECT 4 AS __id__, 1.4 AS price UNION ALL
SELECT 5 AS __id__, 1.5 AS price UNION ALL
SELECT 6 AS __id__, 1.6 AS price UNION ALL
SELECT 7 AS __id__, 1.7 AS price
)
SELECT
*,
ARRAY(
SELECT price
FROM UNNEST(item_ids) AS id
JOIN MenuItems
ON __id__ = id
) AS prices
FROM Orders
Table Orders:
Table MenuItems:
Result:
Solution with join inside array creation expression is correct, but it doesn't work with separate tables. Alternative solution is array aggregation:
WITH Orders AS (
SELECT 1 AS id, ARRAY[1,2,3] AS item_ids UNION ALL
SELECT 2 AS id, ARRAY[4,5] AS item_ids UNION ALL
SELECT 3 AS id, ARRAY[1,4,6] AS item_ids
),
MenuItems AS (
SELECT 1 AS __id__, 1.1 AS price UNION ALL
SELECT 2 AS __id__, 1.2 AS price UNION ALL
SELECT 3 AS __id__, 1.3 AS price UNION ALL
SELECT 4 AS __id__, 1.4 AS price UNION ALL
SELECT 5 AS __id__, 1.5 AS price UNION ALL
SELECT 6 AS __id__, 1.6 AS price UNION ALL
SELECT 7 AS __id__, 1.7 AS price
)
SELECT
id, ARRAY_AGG(price)
FROM Orders
JOIN MenuItems ON __id__ in UNNEST(item_ids)
GROUP BY id

Oracle SUM returns false summary with identicals values returing from an SELECT UNION

I am facing a problem with SUM statement.
This query returns MY_ID = 1 and QTY = 7
select my_id, sum(qty) qty
from
(
select 1 my_id ,2 qty from dual
union
select 1 my_id, 5 qty from dual
)
group by my_id;
But this one returns MY_ID = 1 and QTY = 5 instead of QTY = 10.
select my_id, sum(qty) qty
from
(
select 1 my_id ,5 qty from dual
union
select 1 my_id, 5 qty from dual
)
group by my_id;
How can I summary the two quantity in case of the two values are the same?
Use union all:
select my_id, sum(qty) qty
from
(
select 1 my_id ,5 qty from dual
union all
select 1 my_id, 5 qty from dual
)
group by my_id;
Try using union all:
The below works:
select my_id, sum(qty) qty
from
(
select 1 my_id ,5 qty from dual
union all
select 1 my_id, 5 qty from dual
)
group by my_id;
this is because 5 union 5 is always 5. if you do union all it includes everything irrespective of it being the same!
In the second query, the two rows in the union are identical.
There are two forms of UNION: UNION ALL and UNION DISTINCT. Which one is the default varies, but it looks like you're getting a UNION DISTINCT, which since the two (1, 5) rows are the same is only returning one of them. Change it to:
select my_id, sum(qty) qty
from
(
select 1 my_id ,5 qty from dual
union ALL
select 1 my_id, 5 qty from dual
)
group by my_id;
That should give you what you want: (1, 10).
EDIT: Briefly I had union DISTINCT in the query which was wrong! Now corrected....