I have a problem where for a single value there can be maximum of two rows. I have to select only one row based on the column value.Below example can show my data and what I am trying to achieve.
Table Item with the following information
Item Dist_Building Country_Building
I123 B123 B245
I980 B980 B345
I780 B780 B445
Table item_info with the following columns
Item_number Building_Nbr Building_Area Value
I123 B123 District 10
I123 B245 Country 20
I980 B980 District 50
I780 B445 Country 20
Select the items from the Item table and check for the corresponding Building information present in Item_info table. If for a certain item, values are present at District and Country level then select the District level VALUE else select the Country Level VALUE(i.e. row with district area will take the priority over the Country )
Item_number Value
I123 10
I980 50
I780 20
are the Dist_Building, Country_Building, and Building_Nbr important here? It looks like theyre not, in which case if you create a new table from the item_info table like so...
select
item_number
, building_nbr
, sum(case when building_area = 'District' then value end) as District_Val
, sum(case when building_area = 'Country' then value end) as Country_Val
, coalesce(District_Val, Country_Val) as Value
into new_item_info
from item_info
group by
1,2
you should be able to just left join it to the item table.
(im not that familiar with Oracle SQL, so you may have to break the above code in to two steps)
One way to do this is with a join to either building, and then keep the one with the 'higher' area value (which works here as 'District' sorts after 'Country'):
select i.item,
max(ii.value) keep (dense_rank last order by ii.building_area) as value
from item i
join item_info ii on ii.item_number = i.item
and (
(ii.building_nbr = i.dist_building and ii.building_area = 'District')
or (ii.building_nbr = i.country_building and ii.building_area = 'Country')
)
group by i.item;
ITEM VALUE
---- ----------
I123 10
I780 20
I980 50
You could make that more explicit with a case expression in the order-by clause if you don't want to rely on that sort order.
You could also do two outer joins and use coalesce to pick the one you want:
select i.item,
coalesce(iid.value, iic.value) as value
from item i
left join item_info iid on iid.item_number = i.item
and iid.building_nbr = i.dist_building
and iid.building_area = 'District'
left join item_info iic on iic.item_number = i.item
and iic.building_nbr = i.country_building
and iic.building_area = 'Country';
ITEM VALUE
---- ----------
I123 10
I780 20
I980 50
db<>fiddle
You can achieve your goal by simply using sub-queries and the NVL function:
SELECT Item
, NVL((SELECT VALUE FROM item_info ii
WHERE ii.item_number = i.item
AND ii.Building_Nbr = i.Dist_Building)
,(SELECT VALUE FROM item_info ii
WHERE ii.item_number = i.item
AND ii.Building_Nbr = i.Country_Building)) VALUE
FROM Item i
Related
I have a query that I need for it to return a record even when there are no records. In the case where there are records, I simply want those records returned. On the other hand, when there are no records, I need it to still return a record but with the value for the "context" column (the GROUP BY column) equal to the value of the GROUP BY column that did not meet the criteria and a default value for aggregate function/column (e.g., 0). I tried a subquery:
SELECT
(
SELECT
CONTEXT,
SUM(VAL)
FROM
A_TABLE
WHERE
COL = 'absent'
GROUP BY
CONTEXT
)
FROM
DUAL;
but anything greater than one column in the subquery SELECT clause fails w/ a "too many values" message.
I also tried a UNION (with a little more context to more faithfully represent my situation):
SELECT
*
FROM
(
SELECT
CONTEXT,
SUM(VAL)
FROM
A_TABLE
WHERE
COL = 'absent'
GROUP BY
CONTEXT
UNION
SELECT
CONTEXT,
0
FROM
B_TABLE
)
AB_TABLE
INNER JOIN C_TABLE C -- just a table that I need to join to
ON
C.ID = AB_TABLE.C_ID
WHERE
C.ID = 10
AND ROWNUM = 1 -- excludes 2nd UNION subquery result when 1st returns record;
This one does work but I don't know why since the 2nd UNION subquery does not seem to be expressly connected w/ the first (I need the 2nd CONTEXT value to be the same as the 1st for the case where the 1st returns no records). The problem is that the real query does not return any records when I try to implement a similar strategy. I would like to see if there's a better way to approach this problem and perhaps get it to work for the real query (not included as it is too large and somewhat sensitive).
I am not sure I understand the question, but let's try.
I believe what you are saying is this. You have a table called A_TABLE, with columns CONTEXT, VAL, COL (and perhaps others as well).
You want to group by CONTEXT, and get the sum of VAL but only for those rows where COL = 'absent'. Otherwise you want to return a default value (let's say 0).
This can be done with conditional aggregation. The condition is in a CASE expression within the SUM, not in a WHERE clause (as you saw already, if you filter by COL='absent', in a WHERE clause, the query - past the WHERE clause - has no knowledge of the CONTEXT values that don't appear in any rows with COL = 'absent').
If the "default value" was NULL, you could do it like this:
select context, sum(case when col = 'absent' then value end) as val
from a_table
group by context
;
If the default value is anything other than NULL, the temptation may be to use NVL() around the sum. However, if VAL may be NULL, then it is possible that SUM(VAL) is NULL even when there are rows with COL = 'absent'. To address that possibility, you must leave the sum as NULL in those cases, and instead set the value to 0 (or whatever other "default value") only when there are NO rows with COL = 'absent'. Here is one way to do that. Still a standard "conditional" aggregate query:
select context,
case when count(case when col = 'absent' then 1 end) > 0
then sum(case when col = 'absent' then value end)
else 0 -- or whatever "default value" you must assign here
end as val
from a_table
group by context
;
Here's another way you could handle it that avoids the two additional tables (B_TABLE and C_TABLE).
SELECT context
, MAX(val)
FROM (
SELECT context
, SUM(val) as val
FROM a_table
WHERE col = 'absent'
GROUP BY context
UNION
SELECT context
, 0 as val
FROM a_table
) t
GROUP BY context
This assumes the default value you want to return is 0 and that any value in A_TABLE.VAL will be a positive integer.
http://sqlfiddle.com/#!4/c6ca0/20
SELECT b.context
, sum(a.val)
FROM b_table b
LEFT OUTER JOIN a_table a
ON a.context = b.context
AND a.col = 'absent'
GROUP BY b.context
I need to create a report where for each columns there will be a previous column which will give the value from date 18 months back from the date that is given as input. Basically I am getting data from few columns into Spotfire for a particular date and want few of the columns to show the output that was 18 months back.
Code Summary-
This is the code i have to implement into Spotfire. This report takes input of a particular single day's date and gets column values for it. Sub-query gets for few of the values and they are sent to the main query. Typically this report has
few common columns and few other columns which have the value from current date and previous dates for the same columns. I can implement all the columns from the main query, but need suggestions to get values for the previous columns calculated in Spotfire or anyway to implement as an Oracle view since we will be getting only one input for the main query and sub-query will be deducting static no of days/month[in this case its 18 months]
Code Sample:-
select st.x1,
cs.x2 ,
sp.x3, sp.x4,
el.x5 current_zxc, --New data 2
el.xxxx current_zvvxx, --New data 3
por.x6 current_zczxc, --New data 4
el.x7 current_sdcvzv, --New data 5
prev_yr_data.prev_1 previous_czzxczxc,
prev_yr_data.prev_2 previous_xcvv,
prev_yr_data.prev_3 previous_zcvzxz,
prev_yr_data.prev_4 PREVIOUS_czxcvzxv,
prev_yr_data.prev_5 previous_vvvxcvxc,
prev_yr_data.prev_6 previous_zxvxvv,
from table1 cs
inner join table2 usr on cs.xxx = usr.zzzzz
inner join table3 emp on emp.xxx = usr.zzzzz
inner join table4 gbst on cs.xxxs = gbst.zzzzz
inner join table5 sp on cs.xxx = sp.zzzzz
inner join table6 st on sp.xxx = st.zzzzz
inner join table7 ol on ol.xxx = cs.zzzzz
inner join table8 el on el.xxx = ol.zzzzz
inner join table9 spt on trim(upper(el.xxxx)) = trim(upper(spt.xxx))
inner join table10 por on
por.xxx = el.xxxx and
por.xxxx = el.xxxx and
por.xxxx = cs.zzzzz
inner join
(select st.x1,
cs.zzzzz case_zzzzz,
cs.x2 prev_4,
sp.zzzzz ,
sp.x3, sp.x4,
spt.zzzzz ,
spt.xxx prev_1, --Old data 1
el.x5 prev_2, --Old data 2
el.x6 prev_3, --Old data 3
por.xxxx prev_5, --Old data 4
el.x7 prev_6 --Old data 5
from table1 cs
inner join table5 sp on cs.xxxx = sp.zzzzz
inner join table6 st on sp.xxxx = st.zzzzz
inner join table7 ol on ol.xxxx = cs.zzzzz
inner join table8 el on el.xxxxx = ol.zzzzz
inner join table9 spt on trim(upper(el.x_part_name)) = trim(upper(spt.x_part_number))
inner join table10 por on
por.xxx = el.xxxx and
por.xxxx = el.xxxx and
por.xxxx = cs.zzzzz
where ol.date_time between add_months(to_date('date_input','mm/dd/yyyy'), -18) and to_date('date_input','mm/dd/yyyy')
) prev_yr_data on
sp.zzzzz = prev_yr_data.zzzzz and
spt.zzzzz = prev_yr_data.zzzzzz
where ol.date_time >= to_date('date_input','mm/dd/yyyy') and ol.date_time < ( to_date('date_input','mm/dd/yyyy') + 1 )
I would suggest adding a transformation when you bring in the data set to calculate your date 18 months in the future (or past whichever you prefer). Then you can do a self join within Spotfire where [Date] = [18MonthsForward] and bring in the same value column(s) that you desire.
The steps to achieve this look like this when viewed under source information:
2. Select Insert > Transformations...
Add the transformations:
a. Calculate new column
Name: 18MonthsForward
Expression: DateAdd("month",18,[Date])
3. Select Insert > Columns...
Select 'From Current Analysis'
Source location: Data Table
Automatic update.
Match columns:
Date => 18MonthsForward
Ignore columns:
Date
Select join method: LeftOuterJoin
Treat empty values as equal: False
As a step by step instruction you would:
(1) Select Insert >> Transformation
(2) Select Calculation new column from the drop down
(3) Type the expression: DateAdd("month",18,[Date]) and name this whatever you prefer
(4) Select Insert >> Columns
(5) Select from current data and select your current data table
(6) Join on Date and your newly calculated 18MonthsForward date column
(7) Select your value column as the new column
Attached in an image of the data resultant data table. 18 months prior value
I have a question about Linq select statement. I am new to Linq so any help will be very helpful. I did a lot of research but I still didn't manage to write down correct Linq statement.
I have this two tables and attributes:
Table Titles(title_id(PK), title) and
Table Sales(title_id(PK), qty)
where are title_id and title string values and qty is a number which represents some quantity.
I need to write a select which will take five most selling titles from this two tables.
So, I need to make sum from qty (we can have more records with the same Sales.title_id attribute) and make group by title_id and order by sum(qty) descending and then return attributes title and title_id.
How can I make suitable solution for my question?
Regards,
Dahakka
You can do group join of tables by title_id (each group g will represent all sales of joined title). Then select title description and total of sales for that title. Order result by totals, select title and take required number of top sales titles:
var query = (from t in db.Titles
join s in db.Sales on t.title_id equals s.title_id into g
select new { Title = t.title, Total = g.Sum(x => x.qty) } into ts
orderby ts.Total descending
select ts.Title).Take(5);
Resulting SQL will look like:
SELECT TOP (5) [t2].[title] AS [Title], [t2].[value] AS [Total]
FROM (
SELECT [t0].[title_id], (
SELECT SUM([t1].[qty])
FROM [Sales] AS [t1]
WHERE [t0].[title_id] = [t1].[title_id]
) AS [value]
FROM [Titles] AS [t0]
) AS [t2]
ORDER BY [t2].[value] DESC
Following is the linq query in method syntax
sales.GroupBy(s=>s.title_id)
.Select ( x =>
new {
Title_id = x.Key,
Sales= x.Sum (x=> x.qty)
})
.OrderByDescending(x=>x.Sales).Take(5)
.Join( titles,
sale=>sale.Title_id,
title=> title.title_id,
(sale, title)=> new
{
Title = title.Title,
TotalSales=sale.Sales
}
);
How can I reference a column outside of a subquery using Oracle? I specifically need to use it in the WHERE statement of the subquery.
Basically I have this:
SELECT Item.ItemNo, Item.Group
FROM Item
LEFT OUTER JOIN (SELECT Attribute.Group, COUNT(1) CT
FROM Attribute
WHERE Attribute.ItemNo=12345) A ON A.Group = Item.Group
WHERE Item.ItemNo=12345
I'd like to change WHERE Attribute.ItemNo=12345 to WHERE Attribute.ItemNo=Item.ItemNo in the subquery, but I can't figure out if this is possible. I keep getting "ORA-00904: 'Item'.'ItemNo': Invalid Identifier"
EDIT:
Ok, this is why I need this kind of structure:
I want to be able to get a count of the "Error" records (where the item is missing a value) and the "OK" records (where the item has a value).
The way I have set it up in the fiddle returns the correct data. I think I might just end up filling in the value in each of the subqueries, since this would probably be the easiest way. Sorry if my data structures are a little convoluted. I can explain if need be.
My tables are:
create table itemcountry(
itemno number,
country nchar(3),
imgroup varchar2(10),
imtariff varchar2(20),
exgroup varchar2(10),
extariff varchar2(20) );
create table itemattribute(
attributeid varchar2(10),
tariffgroup varchar2(10),
tariffno varchar2(10) );
create table icav(
itemno number,
attributeid varchar2(10),
value varchar2(10) );
and my query so far is:
select itemno, country, imgroup, imtariff, im.error "imerror", im.ok "imok", exgroup, extariff, ex.error "exerror", ex.ok "exok"
from itemcountry
left outer join (select sum(case when icav.itemno is null then 1 else 0 end) error, sum(case when icav.itemno is not null then 1 else 0 end) ok, tariffgroup, tariffno
from itemattribute ia
left outer join icav on ia.attributeid=icav.attributeid
where (icav.itemno=12345 or icav.itemno is null)
group by tariffgroup, tariffno) im on im.tariffgroup=imgroup and imtariff=im.tariffno
left outer join (select sum(case when icav.itemno is null then 1 else 0 end) error, sum(case when icav.itemno is not null then 1 else 0 end) ok, tariffgroup, tariffno
from itemattribute ia
left outer join icav on ia.attributeid=icav.attributeid
where (icav.itemno=12345 or icav.itemno is null)
group by tariffgroup, tariffno) ex on ex.tariffgroup=exgroup and extariff=ex.tariffno
where itemno=12345;
It's also set up in a SQL Fiddle.
You can do it in a sub-query but not in a join. In your case I don't see any need to. You can put it in the join condition.
select i.itemno, i.group
from item i
left outer join ( select group, itemno
from attribute b
group by group itemno ) a
on a.group = i.group
and i.itemno = a.itemno
where i.itemno = 12345
The optimizer is built to deal with this sort of situation so utilise it!
I've changed the count(1) to a group by as you need to group by all columns that aren't aggregated.
I'm assuming that your actual query is more complicated than this as with the columns you're selecting this is probably equivilent to
select itemno, group
from item
where itemno = 12345
You could also write your sub-query with an analytic function instead. Something like count(*) over ( partition by group).
As an aside using a keyword as a column name, in this case group is A Bad Idea TM. It can cause a lot of confusion. As you can see from the code above you have a lot of groups in there.
So, based on your SQL-Fiddle, which I've added to the question I think you're looking for something like the following, which doesn't look much better. I suspect, given time, I could make it simpler. On another side note explicitly lower casing queries is never worth the hassle it causes. I've followed your naming convention though.
with sub_query as (
select count(*) - count(icav.itemno) as error
, count(icav.itemno) as ok
, min(itemno) over () as itemno
, tariffgroup
, tariffno
from itemattribute ia
left outer join icav
on ia.attributeid = icav.attributeid
group by icav.itemno
, tariffgroup
, tariffno
)
select ic.itemno, ic.country, ic.imgroup, ic.imtariff
, sum(im.error) as "imerror", sum(im.ok) as "imok"
, ic.exgroup, ic.extariff
, sum(ex.error) as "exerror", sum(ex.ok) as "exok"
from itemcountry ic
left outer join sub_query im
on ic.imgroup = im.tariffgroup
and ic.imtariff = im.tariffno
and ic.itemno = im.itemno
left outer join sub_query ex
on ic.exgroup = ex.tariffgroup
and ic.extariff = ex.tariffno
and ic.itemno = ex.itemno
where ic.itemno = 12345
group by ic.itemno, ic.country
, ic.imgroup, ic.imtariff
, ic.exgroup, ic.extariff
;
You can put WHERE attribute.itemno=item.itemno inside the subquery. You are going to filter the data anyway, filtering the data inside the subquery is usually faster too.
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
};