Select value from different row based on value of certain column - sql

For example I have data like this like this
date | col_1 | Col_2 | Col_3 | Col_4
---------------------------------------------
20021 | 1 | a | null | a
20022 | 2 | a | null | a
20023 | 3 | a | null | a
20024 | 4 | a | 4.5 | a
20031 | 1 | a | 11 | b
20032 | 2 | a | 2 | b
20033 | 3 | a | 9 | b
20034 | 4 | a | 11 | b
what I need is when value in Col_3 is null and Col_1 is not 4,
then select value in Col_3 where Col_1 = 4, that have the same
I tried using this case statement:
select col_2, date, col_1, col_4,
case when col_3 is null and col_1 != 1
then (select col_3 from table s where s.date = 4
and s.col_1= seg.col_1 and s.col_4= seg.col_4
and left(s.date,4) = left(seg.date,4))
else seg.col_3
end as col_3
from table seg
but for some reason it's not doing what I need it to do
I need it to change the results of the table above to become like this:
date | col_1 | Col_2 | Col_3 | Col_4
---------------------------------------------
20021 | 1 | a | 4.5 | a
20022 | 2 | a | 4.5 | a
20023 | 3 | a | 4.5 | a
20024 | 4 | a | 4.5 | a
20031 | 1 | a | 11 | b
20032 | 2 | a | 2 | b
20033 | 3 | a | 9 | b
20034 | 4 | a | 11 | b

Maybe use an OUTER APPLY (or CROSS APPLY if you can guarantee quarter 4 is there) to always have quarter 4 available per year per col4 then just use it where you have to.
select col_2, date, col_1, col_4,
case when col_3 is null and col_1 != 4
then backfill.col_3
else seg.col_3
end as col_3
from table seg
outer apply (select col_3 from table where col_1 = 4 and left(date,4) = left(seg.date,4)) and col_4 = seg.col_4) backfill
If you'd rather stick with CASE, this should work.
case when col_3 is null and col_1 != 4
then (select col_3 from table s where s.col_1 = 4
and s.col_4 = seg.col_4
and left(s.date,4) = left(seg.date,4))
else seg.col_3

Just use window functions and coalesce():
select date, col1, col2,
coalesce(max(case when col1 = 4 then col3 end) over (partition by col4) as col3,
col4
from t;

Related

tricky sql Ordering

i have a table on oracle 11g that looks like this
col_1 | col_2 | col_3 |
1 | 111222001 | A
2 | 111222001 | B
3 | 111222002 | A
4 | 111222002 | B
5 | 111555001 | B
6 | 111555003 | A
7 | 111555003 | B
i want to order it, to get this
col_1 | col_2 | col_3 |
2 | 111222001 | B
4 | 111222002 | B
1 | 111222001 | A
3 | 111222002 | A
5 | 111555001 | B
7 | 111555003 | B
6 | 111555003 | A
Logic behind it :
notice how col_2 values are values of three triplets 111-222-333.
I want to order col_2 according to the the third triplet 111-222-"333" and get only the entries that have col_3 = 'B' at first, then get those that have col_3 = 'A' .
when the second triplet (changes / goes up) 111-"222"-333 we redo what was described before.
Thanks in advance, i fureg out a way to do it, but it's really ugly, if someone can figure out a way to do it beautifully
Select *
from table
order by col_3 desc, col2, col1;
HI i don't know why i didn't see it, but it was quite simple, sorry for troubling you
select col_1, col_2, col_3 from table_name
order by substr(col_2,3,8), col_3, substr(col_2,9,11);

Get column with two two rows having specific values

I have a table that looks like this:
| col1 | col2 |
|------|------|
| a | 1 |
| a | 2 |
| a | 3 |
| b | 1 |
| b | 3 |
| c | 1 |
| c | 2 |
I need to find the value of col1 where two rows with the same col1 value exist that has a col2 value of 1 and 2
results would be:
| col1 |
|------|
| a |
| c |
You can filter the rows with the col2 values you want, then group by col1 and only take the groups with count = 2
select col1
from yourTable
where col2 in (1, 2)
group by col1
having count(distinct col2) = 2
Another solution would be
select col1
from your_table
group by col1
having sum(case when col2 = 1 then 1 else 0 end) > 0
and sum(case when col2 = 2 then 1 else 0 end) > 0

Oracle SQL group data by value

I have this tables:
TABLE_A
ID | ATTR_NAME | ATTR_ID | START_DATE
-------------------------------------
1 | XPTA | 12 | 01-10-2013
1 | XPTO | 167 | 01-10-2013
1 | XPTA | 13 | 04-12-2013
2 | XPTA | 12 | 03-09-2015
2 | XPTO | 6 | 05-08-2012
TABLE_B (just to help understand)
ATTR_NAME | ATTR_ID | DESCRIPTION
---------------------------------
XPTA | 12 | ASM5
XPTO | 167 | weird attr
XPTA | 13 | DBSO12
XPTO | 6 | gosh...
Is there a way to make a select return this?
ID | XPTA | XPTO | START_DATE
-----------------------------
1 | 12 | 167 | 01-10-2013
1 | 13 | | 04-12-2013
2 | 12 | | 03-09-2015
2 | | 6 | 05-08-2012
Basically, the COL1 represents an attribute and COL2 the id value of COL1 associated to the ID starting from START_DATE
Example: Resource Mike (ID) has a cost profile (COL1) of ASM5 (COL2) starting from 01-10-2013 (START_DATE)
The ideia is to group the attributes by date for each ID.
The only way I see I can do it e first do a select start_date distinct from TABLE_A and then for each date fetched do a select ID, COL1, COL2, :date_start from TABLE_A where START_DATE = :date_start and then somehow make COL1 values my columns and COL2 values my values.
Assuming I'm understanding your question correctly, you're looking for conditional aggregation, something you can do with max and case:
select id,
max(case when attr_name = 'XPTA' then attr_id end) xpta,
max(case when attr_name = 'XPTO' then attr_id end) xpto,
start_date
from table_a
group by id, start_date

Find list of values in list of values

I'm trying to write a sql with a where clause, that checks if any element in a list is in another list. Is there a shorter way to accomplish this rather than check each member of the first list?
SELECT * from FOO
WHERE FOO.A IN ('2','3', '5', '7','11','13','17','19') OR
FOO.B IN ('2','3', '5', '7','11','13','17','19') OR
FOO.C IN ('2','3', '5', '7','11','13','17','19') OR
FOO.D IN ('2','3', '5', '7','11','13','17','19') OR
FOO.E IN ('2','3', '5', '7','11','13','17','19') OR
FOO.F IN ('2','3', '5', '7','11','13','17','19')
That is the simplified sql.
Was trying not to muddy waters too much, but since you ask:
Ultimately what I am trying to do here is, select rows from FOO, that has columns fulfilling various criteria. These criteria are stored in a second table (call it BAR), mainly db, name, type must match and flag must be 1. Was planning to build the IN list from BAR, comparing them with column names in INFORMATION_SCHEMA.COLUMNS containing FOO
FOO:
+--------+--------+---------+---------+--------+-------+
| DB | Name | Type | Col1 | Col2 | Col3 |
+--------+--------+---------+---------+--------+-------+
| 4 | AC1 | LO | 1 | 10 | 2 |
| 4 | AC1 | HI | 2 | 20 | 4 |
| 1 | DC2 | HI-HI | 11 | 5 | 2 |
| 1 | DC2 | HI | 22 | 10 | 4 |
| 1 | DC2 | LO | 33 | 15 | 6 |
+--------+--------+---------+---------+--------+-------+
BAR:
+--------+--------+---------+---------+--------+
| DB | Name | Type | Field | Flag |
+--------+--------+---------+---------+--------+
| 4 | AC1 | LO | Col1 | 1 |
| 4 | AC1 | HI | Col1 | 1 |
| 1 | DC2 | HI-HI | Col1 | 1 |
| 1 | DC2 | HI | Col1 | 1 |
| 1 | DC2 | LO | Col1 | 1 |
| 4 | AC1 | LO | Col2 | 0 |
| 4 | AC1 | HI | Col2 | 0 |
| 1 | DC2 | LO | Col2 | 0 |
| 1 | DC2 | HI-HI | Col2 | 0 |
| 1 | DC2 | HI | Col2 | 0 |
| 4 | AC1 | LO | Col3 | 0 |
| 4 | AC1 | HI | Col3 | 0 |
| 1 | DC2 | LO | Col3 | 0 |
| 1 | DC2 | HI-HI | Col3 | 0 |
| 1 | DC2 | HI | Col3 | 0 |
+--------+--------+---------+---------+--------+
On first examination, it would seem your schema is not appropriate for the type of query you're performing. It seems like you would want a FOOVAL table with a type and a value then you're query simply becomes:
CREATE TABLE FOOVAL
{
ID int, -- References FOO.ID
TYPE char, -- A, B, C, D, E, F
VAL int
}
SELECT * FROM FOO WHERE FOO.ID IN
(SELECT DISTINCT FOOVAL.ID WHERE FOOVAL.VAL IN ('2','3', '5', '7','11','13','17','19'))
Your method probably performs the best. Here is an alternative that only requires creating the list once. It uses a CTE to create a list of the values and then an exists clause to check whether any values match:
with vals as (
select '2' as p union all
select '3' union all
select '5' union all
select '7' union all
select '11' union all
select '13' union all
select '17' union all
select '19'
)
select *
from foo
where exists (select 1 from vals where vals.p in (foo.A, foo.B, foo.C, foo.D, foo.E, foo.F))
If you are using a database that doesn't support CTEs, you can just put the code in the where clause:
select 8
from foo
where exists (select 1
from (select '2' as p union all
select '3' union all
select '5' union all
select '7' union all
select '11' union all
select '13' union all
select '17' union all
select '19'
) t
where vals.p in (foo.A, foo.B, foo.C, foo.D, foo.E, foo.F)
)
If you are using Oracle, then you need to add from dual in the statements after the string constants. Otherwise, I think one or the other should work in any SQL database.
While it is not exactly clear what you want to do with the data, since you are using SQL Server my suggestion would be to use the UNPIVOT function to turn the col1, col2 and col3 columns into rows which will make it easier to filter the data:
select db, name, type, col, value
from foo
unpivot
(
value
for col in (Col1, Col2, Col3)
) unpiv;
See SQL Fiddle with Demo. This gives the data in the following format:
| DB | NAME | TYPE | COL | VALUE |
------------------------------------
| 4 | AC1 | LO | Col1 | 1 |
| 4 | AC1 | LO | Col2 | 10 |
| 4 | AC1 | LO | Col3 | 2 |
| 4 | AC1 | HI | Col1 | 2 |
Once the is in the row format, it should be significantly easier to apply any filters or even join to your BAR table.

SQL Check if ungroup column values match

I have a sql table with following values
| col1 | col2| source | values
| 1 | 2 | A | null
| 1 | 2 | B | 1.0
| 1 | 2 | C | null
| 1 | 4 | A | 2.0
| 1 | 4 | B | 2.0
| 1 | 4 | C | 2.0
| 1 | 5 | A | null
| 1 | 5 | B | null
| 1 | 5 | C | null
How can I get an output with a group by of col1 and col2 with a flag:
all values match for a group ( flag = 1)
all values are null ( flag = 2)
some values is null (flag = 3)
Output:
| col1 | col2| flag
| 1 | 2 | 3
| 1 | 4 | 1
| 1 | 5 | 2
Or: based on your updated question:
SELECT
col1,
col2,
SUM(CASE WHEN SomeConditionHere THEN 1 ELSE 0 END) AS Flag
FROM Table1
GROUP BY col1, col2;
SQL Fiddle Demo
This will give you:
| COL1 | COL2 | FLAG |
----------------------
| 1 | 2 | 2 |
| 1 | 4 | 0 |
| 1 | 5 | 3 |
Note that: I assumed that the flag is how many NULL values are in the VALUES column, so I used "Values" IS NULL instead of SomeConditionHere.
I couldn't understand how the flag should be computed in the expected results you posted. You have to use the predicate that define your flag instead of "Values" IS NULL.
Update:
Try this:
WITH Flags
AS
(
SELECT
col1, col2,
COUNT(*) ValuesCount,
SUM(CASE WHEN "Values" IS NULL THEN 1 ELSE 0 END) AS NULLValues
FROM Table1
GROUP BY col1, col2
)
SELECT
col1,
col2,
Flag = CASE WHEN ValuesCount = NULLValues THEN 2
WHEN NULLVALUES = 0
AND ValuesCount = (SELECT COUNT(*)
FROM Table1 t2
WHERE t1.col1 = t2.col1
AND t1.col2 = t2.col2) THEN 1
ELSE 3
END
FROM Flags t1;
Updated SQL Fiddle Demo
This will give you:
| COL1 | COL2 | FLAG |
----------------------
| 1 | 2 | 3 |
| 1 | 4 | 1 |
| 1 | 5 | 2 |
In SQLServer2005+
;WITH cte AS
(
SELECT col1, col2, [values],
COUNT(CASE WHEN [values] IS NULL THEN 1 END) OVER(PARTITION BY col1, col2) AS cntNULL,
COUNT(*) OVER(PARTITION BY col1, col2) AS cntCol
FROM dbo.test5
)
SELECT col1, col2, MAX(CASE WHEN cntNULL = 0 THEN 1
WHEN cntNULL = cntCol THEN 2
ELSE 3 END) AS flag
FROM cte
GROUP BY col1, col2
Demo on SQLFiddle
...And solution without CTE if you want more portable SQL:
select col1,
col2,
case
when DistinctValuesWithoutNulls = 1 and NullCount = 0 then 1
when DistinctValuesWithoutNulls = 0 then 2
when NullCount > 0 then 3
end flag
from
(
select col1,
col2,
count(distinct [values]) DistinctValuesWithoutNulls,
sum(case when [values] is null then 1 else 0 end) NullCount
from Table1
group by col1, col2
) tmp