Multiple rows to one row Oracle SQL - sql

I have the following table
P_ID, PROGR, DATA
1 , 1 , 'DATO A'
1 , 2 , 'DATO B'
1 , 3 , 'DATO C'
2 , 1 , 'DATO D'
2 , 2 , 'DATO E'
3 , 1 , 'DATO G'
and I want to get this result
P_ID, DATA , DATA_1 , DATA_2
1 , 'DATO A', 'DATO B', 'DATO C'
2 , 'DATO D', 'DATO E', NULL
3 , 'DATO G', NULL , NULL
this can be done with a left join with the same table, something like this (not the exact result, but as an example)
select * from
(select * from MYTABLE where PROGR = 1) a
left join
(select * from MYTABLE where PROGR = 2) b
on a.P_ID = b.P_ID
left join
(select * from MYTABLE where PROGR = 3) c
on a.P_ID = c.P_ID;
The problem is that this query is fixed, and need to be rewritten if some P_ID get PROGR = 4. I think that I need to make a procedure, but I have been trying without success.
Thanks in advance.

You can use conditional aggregation:
select t.pid,
max(case when t.progr = 1 then t.data end) as data_1,
max(case when t.progr = 2 then t.data end) as data_2,
max(case when t.progr = 3 then t.data end) as data_3
from mytable t
group by t.pid;
To handle a variable number of columns, I can think of three solutions:
Put in enough columns to handle your data (some reasonable maximum).
Use dynamic SQL (execute immediate in PL/SQL).
Or, combine them into a single column.
Here is the last approach:
select t.pid, listagg(t.data, ', ') within group (order by t.progr)
from mytable t
group by t.pid;

Use below query.
select p_id,max(data_1) as data_1,max(data_2)as data_2,max(data_3) as data_3
from
(select P_ID,
case when progr=1 then
data
end data_1,
case when progr=2 then
data
end data_2,
case when progr=3 then
data
end data_3
from thursday_check)
group by p_id

Related

unexpected results of a left join

I have a table named test_table_1 and a view named temp_test_view_1.
The goal is to find the NEW_CATEGORY_ID for every ID in the test_table_1. The relationship between NEW_CATEGORY_ID and ID is in the view temp_test_view_1
I use the left join for that purpose, with the base table being test_table_1.
In my opinion, the left join returns some unexpected results.
Here is all the data contained in table test_table_1:
select * from test_table_1:
id name
1 'a'
null 'd'
3 'd'
2 'c'
2 'b'
Here is the view script:
create view temp_test_view_1 as
select
id,
id_description,
case when id_category = 'phone_id' then 'phone' else 'other' end as new_id_category
from (
select
1 as id,
'first id' as id_description,
null as id_category
from dummy
union all
select
2 as id,
'second id' as id_description,
'phone_id' as id_category
from dummy
) x
;
Here is the query I use to left join the view to a table and then project the results to see the corresponding new_id_category for every ID in test_table_1:
select
t1.id,
t1.name,
t2.id,
t2.id_description,
t2.new_id_category
from test_table_1 t1
left join temp_test_view_1 t2 on t1.id = t2.id
output is:
id name,id id_description new_id_category
null 'd' null null, 'other'
1 'a' 1 'first id' 'other'
2 'b' 2 'second id' 'phone'
2 'c' 2 'second id' 'phone'
3 'd' null null 'other'
desired output:
id name,id id_description new_id_category
null 'd' null null, null
1 'a' 1 'first id' 'other'
2 'b' 2 'second id' 'phone'
2 'c' 2 'second id' 'phone'
3 'd' null null null
Can someone explain whether the result the query produces is correct and why if so? I expect this left join to return null on the columns retrieved from the view, as u can see from my desired outcome.
I did not test the query in DB systems offered by other vendors.
EDIT:
I tested it on sqlfiddle MS SQL, and it produces the desired output. Here is the link:
sqlfiddle.com/#!18/1c788/2
If someone needs the code itself to reproduce the results in MS SQL, (while he will fail to reproduce the resulst and instead get the desired result):
create view temp_test_view_1 as
select
id,
id_description,
case when id_category = 'phone_id' then 'phone' else 'other' end as new_id_category
from (
select
1 as id,
'first id' as id_description,
null as id_category
union all
select
2 as id,
'second id' as id_description,
'phone_id' as id_category
) x
;
select *
into test_table_1
from (
select
1 as id,
'a' as name
union all
select
2 as id,
'b' as name
union all
select
2 as id,
'c' as name
union all
select
3 as id,
'd' as name
union all
select
null as id,
'd' as name
) x;
select
t1.id,
t1.name,
t2.id,
t2.id_description,
t2.new_id_category
from test_table_1 t1
left join temp_test_view_1 t2 on t1.id = t2.id

Pulling data while pivoting at the same time

ID | Type | Code
1 Purchase A1
1 Return B1
1 Exchange C1
2 Purchase D1
2 Return NULL
2 Exchange F1
3 Purchase G1
3 Return H1
3 Exchange I1
4 Purchase J1
4 Exchange K1
Above is sample data. What I want to return is:
ID | Type | Code
1 Purchase A1
1 Return B1
1 Exchange C1
3 Purchase G1
3 Return H1
3 Exchange I1
So if a field is null in code or the values of Purchase, Return and Exchange are not all present for that ID, ignore that ID completely. However there is one last step. I want this data to then be pivoted this way:
ID | Purchase | Return | Exchange
1 A1 B1 C1
3 G1 H1 I1
I asked this yesterday without the pivot portion which you can see here:
SQL query to return data only if ALL necessary columns are present and not NULL
However I forgot to note the last part. I tried to play around with excel but had no luck. I tried to make a temp table but the data is too large to do that so I was wondering if this could all be done in 1 sql statement?
I personally used this query with success:
select t.*
from t
where 3 = (select count(distinct t2.type)
from t t2
where t2.id = t.id and
t2.type in ('Purchase', 'Exchange', 'Return') and
t2.Code is not null
);
So how can we adjust that to include the pivot part. Is that possible?
Quite easily. Just use conditional aggregation:
select t.id,
max(case when type = 'Purchase' then code end) as Purchase,
max(case when type = 'Exchange' then code end) as Exchange,
max(case when type = 'Return' then code end) as Return
from t
where 3 = (select count(distinct t2.type)
from t t2
where t2.id = t.id and
t2.type in ('Purchase', 'Exchange', 'Return') and
t2.Code is not null
)
group by t.id;
This is actually simpler to express (in my opinion) using having without the subquery:
select t.id,
max(case when type = 'Purchase' then code end) as Purchase,
max(case when type = 'Exchange' then code end) as Exchange,
max(case when type = 'Return' then code end) as Return
from t
group by t.id
having max(case when type = 'Purchase' then code end) is not null and
max(case when type = 'Exchange' then code end) is not null and
max(case when type = 'Return' then code end) is not null;
Many databases would allow:
having Purchase is not null and Exchange is not null and Return is not null
But Oracle doesn't allow the use of table aliases in the having clause.
UPDATE - Based on discussion in the question comments, my previous query had a faulty assumption (which I carried over from what I thought I saw in the original query in the question); I've eliminated the bad assumption.
select id
, max(case when type='Purchase' then Code end) Purchase
, max(case when type='Return' then Code end) Return
, max(case when type='Exchange' then Code end) Exchange
from t
where code is not null
and type in ('Purchase', 'Return', 'Exchange')
group by id
having count(distinct type) = 3
I will point out again (as I did in your other thread) that analytic functions will do the job much faster - they need the base table to be read just once, and there are no explicit or implicit joins.
with
test_data ( id, type, code ) as (
select 1, 'Purchase', 'A1' from dual union all
select 1, 'Return' , 'B1' from dual union all
select 1, 'Exchange', 'C1' from dual union all
select 2, 'Purchase', 'D1' from dual union all
select 2, 'Return' , null from dual union all
select 2, 'Exchange', 'F1' from dual union all
select 3, 'Purchase', 'G1' from dual union all
select 3, 'Return' , 'H1' from dual union all
select 3, 'Exchange', 'I1' from dual union all
select 4, 'Purchase', 'J1' from dual union all
select 4, 'Exchange', 'K1' from dual
)
-- end of test data; actual solution (SQL query) begins below this line
select id, purchase, return, exchange
from ( select id, type, code
from ( select id, type, code,
count( distinct case when type in ('Purchase', 'Return', 'Exchange')
then type end
) over (partition by id) as ct_type,
count( case when code is null then 1 end
) over (partition by id) as ct_code
from test_data
)
where ct_type = 3 and ct_code = 0
)
pivot ( min(code) for type in ('Purchase' as purchase, 'Return' as return,
'Exchange' as exchange)
)
;
Output:
ID PURCHASE RETURN EXCHANGE
--- -------- -------- --------
1 A1 B1 C1
3 G1 H1 I1
2 rows selected.

Find missing values within groups of data using SQL

Recently I need to generate a report based on the fact:
TableA has the following 2 columns UserID and DocumentType
I have been provided a list of 'mandatory' document types: Type1, Type2, Type3 and I need to return every UserID that doesn't have all three of these types along with the types they are missing.
For example if TableA contains the following rows
12 Type1
12 Type2
12 Type4
13 Type1
13 Type2
13 Type3
14 Type1
15 Type6
15 Type7
15 Type8
Then ideally the output would be something like:
12 Type3
14 Type2, Type3
15 Type1, Type2, Type3
Ideally, the query to generate the results should be able to handle up to tens of millions of records.
We recently implemented a solution to a similar question (which is a bit more complicated than this) with SQL server 2012. It takes 3 and a half minutes to get the full report among multiple tables with around 4 million records in total. We wonder whether there are better ideas which can do this faster.
Please feel free to share your ideas which can solve this problem.
Thank you! :)
DEMO
WITH
base ([DocumentType]) as (
SELECT 'Type1' UNION ALL
SELECT 'Type2' UNION ALL
SELECT 'Type3'
),
users as (
SELECT DISTINCT [userID]
FROM Table1 t
),
pairs as (
SELECT *
FROM users, base
)
SELECT p.userID, p.[DocumentType], t.[DocumentType]
FROM pairs p
LEFT JOIN Table1 t
ON p.[DocumentType] = t.[DocumentType]
AND p.[userID] = t.[userID]
WHERE t.[DocumentType] IS NULL
OUTPUT
i think you need something like this
select x2.*
(select *
from (select distinct UserID from [table])x
cross join
(select 'type1' DocumentType union
select 'type2' union
select 'type3' ) y
) x2
left join [table] y2
on y2.UserID = x2.UserID
and y2.DocumentType = x2.DocumentType
where y2.DocumentType is null
order by x2.UserID
here is the FOR XML path concatenation method:
CREATE TABLE TableA (ID INT, TypeCol CHAR(5));
INSERT INTO TableA (ID,TypeCol) VALUES (12,'Type1')
,(12,'Type2')
,(12,'Type4')
,(13,'Type1')
,(13,'Type2')
,(13,'Type3')
,(14,'Type1')
,(15,'Type6')
,(15,'Type7')
,(15,'Type8')
;WITH cteRequiredTypes AS (
SELECT 'type1' as TypeCol
UNION ALL
SELECT 'type2'
UNION ALL
SELECT 'type3'
)
, cteTableAIds AS (
SELECT DISTINCT Id
FROM
TableA
)
, cteMissingTypes AS (
SELECT
i.ID
,r.TypeCol
FROm
cteRequiredTypes r
CROSS JOIN cteTableAIds i
LEFT JOIN TableA a
ON r.TypeCol = a.TypeCol
AND i.ID = a.ID
WHERE
a.ID IS NULL
)
SELECT
DISTINCT a.ID
,STUFF(
(SELECT ',' + TypeCol
FROM
cteMissingTypes t
WHERE t.ID = a.ID
FOR XML PATH(''))
,1,1,'')
FROM
cteMissingTypes a
I believe for checking a large dataset a conditional aggregation query will probably be better performance.
Conditional Aggregation
SELECT
ID
,CASE WHEN SUM(IIF(TypeCol = 'type1',1,0)) = 0 THEN 'type1' ELSE '' END as Type1
,CASE WHEN SUM(IIF(TypeCol = 'type2',1,0)) = 0 THEN 'type2' ELSE '' END as Type2
,CASE WHEN SUM(IIF(TypeCol = 'type3',1,0)) = 0 THEN 'type3' ELSE '' END as Type3
,STUFF(
REPLACE (
REPLACE (
+ ',' + CASE WHEN SUM(IIF(TypeCol = 'type1',1,0)) = 0 THEN 'type1' ELSE '' END
+ ',' + CASE WHEN SUM(IIF(TypeCol = 'type2',1,0)) = 0 THEN 'type2' ELSE '' END
+ ',' + CASE WHEN SUM(IIF(TypeCol = 'type3',1,0)) = 0 THEN 'type3' ELSE '' END
,',,,',',,')
,',,',',')
,1,1,'') as MissingTypeList
FROM
TableA
GROUP BY
ID
HAVING
SUM(IIF(TypeCol = 'type1',1,0)) = 0
OR SUM(IIF(TypeCol = 'type2',1,0)) = 0
OR SUM(IIF(TypeCol = 'type3',1,0)) = 0
The easy way
SELECT DISTINCT UserID
FROM your_table
EXCEPT
(
SELECT UserID
FROM your_table
WHERE DocumentType = ('Type1')
UNION
SELECT UserID
FROM your_table
WHERE DocumentType = ('Type2')
UNION
SELECT UserID
FROM your_table
WHERE DocumentType = ('Type3')
)
Can't be more exact in the rules than this -- so your compiler will look at indexes etc and optimize.
Unless there are parts of the problem you are not telling us.

SQL Server case when or enum

I have a table something like:
stuff type price
first_stuff 1 43
second_stuff 2 46
third_stuff 3 24
fourth_stuff 2 12
fifth_stuff NULL 90
And for every type of stuff is assigned a description which is not stored in DB
1 = Bad
2 = Good
3 = Excellent
NULL = Not_Assigned
All I want is to return a table which count each type separately, something like:
Description Count
Bad 1
Good 2
Excellent 1
Not_Assigned 1
DECLARE #t TABLE ([type] INT)
INSERT INTO #t ([type])
VALUES (1),(2),(3),(2),(NULL)
SELECT
[Description] =
CASE t.[type]
WHEN 1 THEN 'Bad'
WHEN 2 THEN 'Good'
WHEN 3 THEN 'Excellent'
ELSE 'Not_Assigned'
END, t.[Count]
FROM (
SELECT [type], [Count] = COUNT(*)
FROM #t
GROUP BY [type]
) t
ORDER BY ISNULL(t.[type], 999)
output -
Description Count
------------ -----------
Bad 1
Good 2
Excellent 1
Not_Assigned 1
;WITH CTE_TYPE
AS (SELECT DESCRIPTION,
VALUE
FROM (VALUES ('BAD',
1),
('GOOD',
2),
('EXCELLENT',
3))V( DESCRIPTION, VALUE )),
CTE_COUNT
AS (SELECT C.DESCRIPTION,
Count(T.TYPE) TYPE_COUNT
FROM YOUR_TABLE T
JOIN CTE_TYPE C
ON T.TYPE = C.VALUE
GROUP BY TYPE,
DESCRIPTION
UNION ALL
SELECT 'NOT_ASSIGNED' AS DESCRIPTION,
Count(*) TYPE_COUNT
FROM YOUR_TABLE
WHERE TYPE IS NULL)
SELECT *
FROM CTE_COUNT
Hope, this helps.
SELECT ISNULL(D.descr, 'Not_Assigned'),
T2.qty
FROM
(SELECT T.type,
COUNT(*) as qty
FROM Table AS T
GROUP BY type) AS T2
LEFT JOIN (SELECT 1 as type, 'Bad' AS descr
UNION ALL
SELECT 2, 'Good'
UNION ALL
SELECT 3, 'Excellent') AS D ON D.type = T2.type
If you are using Sql server 2012+ use this
SELECT
[Description] = coalesce(choose (t.[type],'Bad','Good' ,'Excellent'), 'Not_Assigned'),
t.[Count]
FROM (
SELECT [type], [Count] = COUNT(*)
FROM yourtable
GROUP BY [type]
) t

Joining two tables and while pivoting sql server 2008

I have two tables like so
Table1
SegmentNo PassingPotencies
1 8
2 10
Table2
BatchAvg Total TotalSegments TotalPassed
106.22 20 2 18
I want to join two tables with a simple login. If the Passingpotencies in table 1 is not equal to 10 then the segment is failed and vice versa. The final result should look something like this
TableResult
BatchAvg Total TotalSegments TotalPassed Segment1 Segment2
106.22 20 2 18 Fail Pass
Any help is greatly appreciated. Thanks.
With your current design, this is what you can achieve (something closest).
See a demo here http://sqlfiddle.com/#!3/e86f5/5
select distinct BATCHAVG,
TOTAL,
TOTALSEGMENTS,
TOTALPASSED,
SegmentNo,
case when PASSINGPOTENCIES <> 10 then 'Failed'
else 'Passed' end as SegmentStatus
from
(
select * from table2,table1
) X
Above query will results in
This will work in your actual scenario. See a Demo here: SQLFiddle
First, join both tables:
SELECT
T2.BATCHAVG
, T2.TOTAL
, T2.TOTALSEGMENTS
, T2.TOTALPASSED
, T1.SEGMENTNO
, (CASE WHEN T1.PASSINGPOTENCIES >= 10 THEN 'PASSED' ELSE 'FAILED' END) AS SEGMENT
INTO TABLE3
FROM TABLE1 T1
CROSS JOIN TABLE2 T2
Then, select this table like this. It's some kind of PIVOT:
SELECT
T.BATCHAVG
, T.TOTAL
, T.TOTALSEGMENTS
, T.TOTALPASSED
, MAX(T.SEGMENT1) AS SEGMENT1
, MAX(T.SEGMENT2) AS SEGMENT2
FROM (
SELECT
T1.BATCHAVG
, T1.TOTAL
, T1.TOTALSEGMENTS
, T1.TOTALPASSED
, (CASE WHEN T1.SEGMENTNO = '1' THEN T1.SEGMENT END) AS SEGMENT1
, (CASE WHEN T1.SEGMENTNO = '2' THEN T1.SEGMENT END) AS SEGMENT2
FROM TABLE3 T1
) T
GROUP BY
T.BATCHAVG
, T.TOTAL
, T.TOTALSEGMENTS
, T.TOTALPASSED