Pivoting in Linq [duplicate] - linq

This question already has answers here:
Pivot data using LINQ
(5 answers)
Closed 9 years ago.
I am using LINQ-to-Entities, and would like to perform a pivot.
For exampe, I have this table:
| data1 | data2 |
+-------+-------+
| 1 | A |
| 1 | B |
| 2 | P |
| 2 | Q |
| 2 | R |
+---------------+
And I want to pivot it into the following results:
| data1 | first | second | third |
+-------+-------+--------+-------+
| 1 | A | B | NULL |
| 2 | P | Q | R |
+--------------------------------+
I would like to do this in LINQ, without needing to do client-side processing.
I have seen these SO posts, but they do not quite address the above situation (as far as I can tell).
Pivot data using LINQ
Is it possible to Pivot data using LINQ?
Note
I have tried the below, but it complains that I cannot use Skip() on an unordered collection, and I don't see a way to get the group's collapsed 'data2' info sorted.
from item in MyTable
group item by item.data1 into g
select new
{
data1 = g.Key,
first = g.Skip(0).FirstOrDefault().data2,
second = g.Skip(1).FirstOrDefault().data2,
third = g.Skip(2).FirstOrDefault().data2,
};

I assume that you could have more than three columns from the data2 field?
If so there's no way to do you query that returns an anonymous type with a variable number of properties. You need to return an array or some sort of list for the data2 set of values.
I think this is the kind of thing that you can do:
var query =
from mt in MyTable
group mt.data2 by mt.data1 into gmts
let d2 = gmts.ToArray()
select new
{
data1 = gmts.Key,
data2 = d2,
length = d2.Length,
};
var pending = query.ToArray();
var maxLength = pending.Max(p => p.length);
Func<string[], string[]> extend = xs =>
{
var r = new string[maxLength];
xs.CopyTo(r, 0);
return r;
};
var results =
from p in pending
select new
{
p.data1,
data2 = extend(p.data2),
};
This produces a series of anonymous type with the data2 array all being the same size to fit the maximum number of results for any of the data1 fields.
The query is still executed as a single SQL query. And the in-memory processing is fast.
Does this work for you?
EDIT
Since you know you have a fixed number of columns (as per comment) you can easily change my results query to meet your requirements:
var results =
from p in pending
let d2s = extend(p.data2)
select new
{
p.data1,
first = d2s[0],
second = d2s[1],
third = d2s[2],
};

Hmm, this seems to work, though I wonder how efficient it is.
from item in MyTable
group item by item.data1 into g
select new
{
data1 = g.Key,
first = g.OrderBy(x => x.data2).Skip(0).FirstOrDefault().data2,
second = g.OrderBy(x => x.data2).Skip(1).FirstOrDefault().data2,
third = g.OrderBy(x => x.data2).Skip(2).FirstOrDefault().data2,
};
The corresponding SQL generated (from LINQPad) is:
SELECT [t1].[data1], (
SELECT [t5].[data2]
FROM (
SELECT TOP (1) [t4].[data2]
FROM (
SELECT [t3].[data2], [t3].[ROW_NUMBER]
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [t2].[data2]) AS [ROW_NUMBER], [t2].[data2]
FROM [MyTable] AS [t2]
WHERE [t1].[data1] = [t2].[data1]
) AS [t3]
WHERE [t3].[ROW_NUMBER] > #p0
) AS [t4]
ORDER BY [t4].[ROW_NUMBER]
) AS [t5]
) AS [first], (
SELECT [t10].[data2]
FROM (
SELECT TOP (1) [t9].[data2]
FROM (
SELECT [t8].[data2], [t8].[ROW_NUMBER]
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [t7].[data2]) AS [ROW_NUMBER], [t7].[data2]
FROM (
SELECT [t6].[data2]
FROM [MyTable] AS [t6]
WHERE [t1].[data1] = [t6].[data1]
) AS [t7]
) AS [t8]
WHERE [t8].[ROW_NUMBER] > #p1
) AS [t9]
ORDER BY [t9].[ROW_NUMBER]
) AS [t10]
) AS [second], (
SELECT [t15].[data2]
FROM (
SELECT TOP (1) [t14].[data2]
FROM (
SELECT [t13].[data2], [t13].[ROW_NUMBER]
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [t12].[data2]) AS [ROW_NUMBER], [t12].[data2]
FROM (
SELECT [t11].[data2]
FROM [MyTable] AS [t11]
WHERE [t1].[data1] = [t11].[data1]
) AS [t12]
) AS [t13]
WHERE [t13].[ROW_NUMBER] > #p2
) AS [t14]
ORDER BY [t14].[ROW_NUMBER]
) AS [t15]
) AS [third]
FROM (
SELECT [t0].[data1]
FROM [MyTable] AS [t0]
GROUP BY [t0].[data1]
) AS [t1]

Related

Oracle: Value from main query is not available in subquery

I have this query, and one of its column is a subquery that should be bringing a list of values using a listagg function. This list has its starting point as the S.ID_ORGAO_INTELIGENCIA value. The list is a should be, it always has values.
The listagg function is consuming an inline view that uses a window function to create the list.
select *
from (
SELECT DISTINCT S.ID_SOLICITACAO,
S.NR_PROTOCOLO_SOLICITACAO,
S.DH_INCLUSAO,
S.ID_USUARIO,
U.NR_CPF,
OI.ID_MODULO,
OI.ID_ORGAO_INTELIGENCIA,
OI.NO_ORGAO_INTELIGENCIA,
R.ID_ATRIBUICAO,
P.ID_PERMISSAO,
1 AS TIPO_NOTIFICACAO,
(
select LISTAGG(oc6.ID_ORGAO_INTELIGENCIA || '-' || oc6.ord || '-', '; ') WITHIN GROUP (ORDER BY oc6.ord) eai
from (
SELECT oc1.ID_ORGAO_INTELIGENCIA,
oc1.ID_ORGAO_INTELIGENCIA_PAI,
oc1.SG_ORGAO_INTELIGENCIA,
rownum as ord
FROM TB_ORGAO_INTERNO oc1
WHERE oc1.DH_EXCLUSAO is null
-- THE VALUE FROM S.ID_ORGAO_INTELIGENCIA IS NOT AVAILBLE HERE
START WITH oc1.ID_ORGAO_INTELIGENCIA = S.ID_ORGAO_INTELIGENCIA
CONNECT BY prior oc1.ID_ORGAO_INTELIGENCIA_PAI = oc1.ID_ORGAO_INTELIGENCIA
) oc6) aproPrec
FROM TB_SOLICITACAO S
INNER JOIN TB_ORGAO_INTERNO OI ON S.ID_ORGAO_INTELIGENCIA = OI.ID_ORGAO_INTELIGENCIA
INNER JOIN TB_RELACIONAMENTO_ATRIBUICAO R
ON (R.ID_MODULO = OI.ID_MODULO AND R.ID_ORGAO_INTELIGENCIA IS NULL AND
R.ID_SOLICITACAO IS NULL)
INNER JOIN TB_PERMISSAO P
ON (P.ID_USUARIO = :usuario AND P.ID_ORGAO_INTELIGENCIA = :orgao AND
P.ID_ATRIBUICAO = R.ID_ATRIBUICAO)
INNER JOIN TB_USUARIO U ON (U.ID_USUARIO = S.ID_USUARIO)
WHERE 1 = 1
AND U.DH_EXCLUSAO IS NULL
AND P.DH_EXCLUSAO IS NULL
AND S.DH_EXCLUSAO IS NULL
AND OI.DH_EXCLUSAO IS NULL
AND R.ID_ATRIBUICAO IN :atribuicoes
AND P.ID_STATUS_PERMISSAO = 7
AND OI.ID_MODULO = 1
AND S.ID_STATUS_SOLICITACAO IN (1, 2, 5, 6)
and s.ID_ORGAO_INTELIGENCIA in (SELECT DISTINCT o.ID_ORGAO_INTELIGENCIA
FROM TB_ORGAO_INTERNO o
WHERE o.DH_EXCLUSAO IS NULL
START WITH o.ID_ORGAO_INTELIGENCIA = 3
CONNECT BY PRIOR o.ID_ORGAO_INTELIGENCIA = o.ID_ORGAO_INTELIGENCIA_PAI)
);
The problem is that the aproPrec column is always returning null as its result.
If I force the criteria to have the S.ID_ORGAO_INTELIGENCIA hardcoded, the list returns its true value.
If I chance this:
START WITH oc1.ID_ORGAO_INTELIGENCIA = S.ID_ORGAO_INTELIGENCIA
To this:
START WITH oc1.ID_ORGAO_INTELIGENCIA = 311
where 311 is the value that the S.ID_ORGAO_INTELIGENCIA column really has.
Is there a way to make this query works as 'I think' it should work?
To make it work, I changed the subquery by this another one:
(
select qt_.*
from (
SELECT QRY_NAME.*,
rownum as ord
FROM (
SELECT oc1.ID_ORGAO_INTELIGENCIA,
oc1.ID_ORGAO_INTELIGENCIA_PAI,
connect_by_root (oc1.ID_ORGAO_INTELIGENCIA) as root
FROM TB_ORGAO_INTERNO oc1
CONNECT BY NOCYCLE PRIOR oc1.ID_ORGAO_INTELIGENCIA_PAI = oc1.ID_ORGAO_INTELIGENCIA
) QRY_NAME
WHERE root = s.ID_ORGAO_INTELIGENCIA
) qt_
)

ORA-01427: Subquery returns more than one row

When I execute the query below, I get the message like this: "ORA-01427: Sub-query returns more than one row"
Define REN_RunDate = '20160219'
Define MOP_ADJ_RunDate = '20160219'
Define RID_RunDate = '20160219'
Define Mbr_Err_RunDate = '20160219'
Define Clm_Err_RunDate = '20160219'
Define EECD_RunDate = '20160219'
select t6.Member_ID, (Select 'Y' from MBR_ERR t7 where t7.Member_ID = t6.Member_ID and t7.Rundate = &Mbr_Err_RunDate ) Mbr_Err,
NVL(Claim_Sent_Amt,0) Sent_Claims, Rejected_Claims,Orphan_Claim_Amt,Claims_Accepted, MOP_Adj_Sent Sent_MOP_Adj,Net_Sent,
(Case
When Net_Sent < 45000 then 0
When Net_Sent > 25000 then 20500
Else
Net_Sent - 45000
End
)Net_Sent_RI,
' ' Spacer,
Total_Paid_Claims CMS_Paid_Claims, MOP_Adjustment CM_MOP_Adj, MOP_Adjusted_Paid_claims CM_Net_Claims, Estimated_RI_Payment CM_RI_Payment
from
(
select NVL(t3.Member_ID,t5.Member_ID)Member_ID, t3.Claim_Sent_Amt, NVL(t4.Reject_Claims_Amt,0) Rejected_Claims, NVL( t8.Orphan_Amt,0) Orphan_Claim_Amt,
(t3.Claim_Sent_Amt - NVL(t4.Reject_Claims_Amt,0) - NVL(t8.Orphan_Amt,0)) Claims_Accepted,
NVL(t2.MOP_Adj_Amt,0) MOP_Adj_Sent ,
( (t3.Claim_Sent_Amt - NVL(t4.Reject_Claims_Amt,0)) - NVL(t2.MOP_Adj_Amt,0) - NVL(t8.Orphan_Amt,0) ) Net_Sent,
t5.Member_ID CMS_Mbr_ID,t5.Total_Paid_Claims,t5.MOP_Adjustment, t5.MOP_Adjusted_Paid_Claims, t5.Estimated_RI_Payment
From
(
Select t1.Member_ID, Sum( t1.Paid_Amount) Claim_Sent_Amt
From RENS t1
where t1.rundate = &REN_RunDate
group by t1.Member_ID
) t3
Left Join MOP_ADJ t2
on (t3.Member_ID = t2.Member_ID and t2.rundate = &MOP_ADJ_RunDate)
Left Join
(select Member_ID, sum(Claim_Total_Paid_Amount) Reject_Claims_Amt from CLAIM_ERR
where Rundate = &Claim_Err_RunDate
and Claim_Total_Paid_Amount != 0
Group by member_ID
)t4
on (t4.Member_ID = t3.Member_ID )
Full Outer Join
(
select distinct Member_ID,Total_Paid_Claims,MOP_Adjustment,MOP_Adjusted_Paid_Claims, Estimated_RI_Payment
from RID
where Rundate = &RID_RunDate
and Estimated_RI_Payment != 0
)t5
On(t5.Member_ID = t3.Member_ID)
Left Outer Join
(
select Member_ID, Sum(Claim_Paid_Amount) Orphan_Amt
From EECD
where RunDate = &EECD_RunDate
group by Member_ID
)t8
On(t8.Member_ID = t3.Member_ID)
)t6
order by Member_ID
You have this expression among the select columns (at the top of your code):
(Select 'Y' from MBR_ERR t7 where t7.Member_ID = t6.Member_ID
and t7.Rundate = &Mbr_Err_RunDate ) Mbr_Err
If you want to select the literal 'Y', then just select 'Y' as Mbr_Err. If you want to select either 'Y' or null, depending on whether the the subquery returns exactly one row or zero rows, then write it that way.
I suspect this subquery (or perhaps another one in your code, used in a similar way) returns more than one row - in which case you will get exactly the error you got.

Inner or Outer joins with Pivot query

I have a query that pivots a rows into columns. The rows only exist if values are present, e.g. there are no rows with the values represented by NULL
Object Field_id value
1 1 value1
1 2 value2
1 3 value3
2 2 value4
And I create the output
Object field1 field2 field3
1 value1 value2 value3
2 value4
using a query like
select * from
(
select fs.field_name, s.text_value, s.id_object
from custom_field_str s ,
(select ad.id_field field_id, fd.name field_name,
num_display_order display_order, ad.text_table_name catalogue_table, ad.num_lines
from catalogues c
inner join attribute_definitions ad on c.id_object = ad.id_object_type
inner join field_definitions fd on ad.id_field = fd.id_object
where c.id_object = 'cA1') fs
where fs.catalogue_table = 'custom_field_str'
and fs.field_id = s.id_field
)
pivot
( max(text_value)
for field_name IN ('field1' as field1,'field2' as field2,'field3' as field3) )
So my question is, should I be using an outer join in the join between custom_field_str and the derived table fs. Or does the PIVOT not require a full set of results to build the output grid?
This line: where fs.field_id = s.id_field makes that for each row in table custom_field_str
must exist row in your derived table fs. So you could change your query to simple join-version, here is my try:
SQLFiddle
select * from (
select fd.name field_name, s.text_value, s.id_object
from custom_field_str s
join catalogues c on c.id_object = 'cA1'
join attribute_definitions ad on ad.id_field = s.id_field
and c.id_object = ad.id_object_type and ad.text_table_name = 'custom_field_str'
join field_definitions fd on ad.id_field = fd.id_object )
pivot (max(text_value)
for field_name in ('field1' as field1,'field2' as field2,'field3' as field3))

How to make null equal null in oracle

I have a variable being passed to my stored proc and it's a filter (basically). However, that field can sometimes be null, and if it is, I want to be able to check against the rows that have that field as null.
For example,
Table A:
VALUE_COLUMN | FILTER_COLUMN
----------------------------
A | (NULL)
B | (NULL)
C | (NULL)
D | (NULL)
A | 1
E | (NULL)
F | (NULL)
B | 1
The query (With inputs, val, filter):
SELECT COUNT(1)
FROM TableA
WHERE
wrap_up_cd = val
AND brn_brand_id = filter
Expected I/O:
val = A, filter = (null) = 1
val = A, filter = 1 = 1
val = C, filter = 1 = 0
How can I make Oracle behave this way?
How about:
SELECT COUNT(1)
FROM TableA
WHERE
wrap_up_cd = val
AND ((brn_brand_id = filter) OR (brn_brand_id IS NULL AND filter IS NULL))
I'm not an Oracle expert, but I'd expect that to work - basically make the query match if both the filter and the value are NULL.
Oracle doesn't have an ISNULL function. So you'd need something like
SELECT COUNT(*)
FROM tableA
WHERE brn_brand_id = filter
OR ( brn_brand_id IS NULL
AND filter IS NULL)
This can also be handy at times:
SELECT COUNT(*)
FROM tableA
WHERE
NVL(brn_brand_id, CHR(0)) = NVL(filter, CHR(0))

How to get the last element by date of each "type" in LINQ or TSQL

Imagine to have a table defined as
CREATE TABLE [dbo].[Price](
[ID] [int] NOT NULL,
[StartDate] [datetime] NOT NULL,
[Price] [int] NOT NULL
)
where ID is the identifier of an action having a certain Price. This price can be updated if necessary by adding a new line with the same ID, different Price, and a more recent date.
So with a set of a data like
ID StartDate Price
1 01/01/2009 10
1 01/01/2010 20
2 01/01/2009 10
2 01/01/2010 20
How to obtain a set like the following?
1 01/01/2010 20
2 01/01/2010 20
In SQL, there are several ways to say it. Here's one that uses a subquery:
SELECT *
FROM Price p
WHERE NOT EXISTS (
SELECT *
FROM Price
WHERE ID = p.ID
AND StartDate > p.StartDate
)
This translates fairly trivially to LINQ:
var q = from p in ctx.Price
where !(from pp in ctx.Price
where pp.ID == p.ID
&& pp.StartDate > p.StartDate
select pp
).Any()
select p;
Or should I say, I think it does. I'm not in front VS right now, so I can't verify that this is correct, or that LINQ will be able to convert it to SQL.
Minor quibble: Don't use the name ID to store a non-unique value (the type, in this case). It's confusing.
Assuming ID & StartDate will be unique:
SELECT p.ID, p.StartDate, p.Price
FROM Price p
JOIN
(
SELECT ID, MAX(StartDate) AS LatestDate
FROM Price
GROUP BY ID
) p2 ON p.ID = p2.ID AND p.StartDate = p2.LatestDate
Since you tagged your question with LINQ to SQL, here is an LINQ query to express what you want:
from price in db.Prices
group price by price.Id into group
let maxDateInGroup = group.Max(g => g.StartDate)
let maxDatePrice = group.First(g => g.StartDate == maxDateInGroup)
select
{
Id = group.Key,
StartDate = maxDatePrice.StartDate,
Price = maxDatePrice.Price
};

Resources