Get Value from column depending on another column value in SQL - sql

I have a table like this
id code text
100 1 short description 100
100 2 long descr 100
101 1 short description 101
Now I want to fetch data from text field depending on value in code field. if the code is 1 its short description and if code is 2 its long description.
My desire output is like this
id shortdescription longdescription
100 short description 100 long descr 100
101 short description 101 null

Try this:
SELECT id,
MAX(CASE WHEN code=1 THEN text END) as shortdescription,
MAX(CASE WHEN code=2 THEN text END) as longdescription
FROM TableName
GROUP BY id
Result in SQL Fiddle.
This query is same as:
SELECT id,MAX(shortdescription) as shortdescription,MAX(longdescription) as longdescription
FROM
(
SELECT id,
CASE WHEN code=1 THEN text END as shortdescription,
CASE WHEN code=2 THEN text END as longdescription
FROM TableName
) T
GROUP BY id
NB: To understand the working of this query, please execute the inner query first.
Result:
ID SHORTDESCRIPTION LONGDESCRIPTION
100 short description 100 long descr 100
101 short description 101 (null)
See result in SQL Fiddle.

I think the simplest approach is to treat this as conditional aggregation . . . put the case statement as an argument to the max() function:
select id,
max(case when code = 1 then text end) as ShortDescription,
max(case when code = 2 then text end) as LongDescription
from table t
group by id;

Create two subqueries with the requested where condition (code = 1|2), then join them together (full outer join if any of them could missing).
SELECT
COALESCE(SHORTDESC.id, LONGDESC.id) AS id
, SHORTDESC.[text] AS shortdesc
, LONGDESC.[text] AS longdesc
FROM (
SELECT
id, [text]
FROM
yourTable
WHERE
code = 1
) SHORTDESC
FULL OUTER JOIN (
SELECT
id, [text]
FROM
yourTable
WHERE
code = 2
) LONGDESC
ON SHORTDESC.id = LONGDESC.id

Related

Querying a subset

I want to write an SQL query to find records which contain a particular column and from that subset want to find records which doesn't contain a some other value. How do you write a query for that?
cid id2 attribute
--------------------------------
1 100 delete
1 100 payment
1 100 void
2 100 delete
2 102 payment
2 102 void
3 102 delete
3 103 payment
In above example, I want to list cid for which payment and delete attributes exist but void attribute doesn't exist. So it should list out 3 from above example because it doesn't have void attribute.
Forgot to mention that there could be more attributes. However, I need to list out records for which delete and payment exist regardless of other attributes but void doesn’t.
I call this a "set-within-sets" query, because you are looking for particular sets of attributes within each cid.
I would express this with group by and conditions in the having:
select cid
from t
group by cid
having sum(case when attribute = 'payment' then 1 else 0 end) > 0 and
sum(case when attribute = 'delete' then 1 else 0 end) > 0 and
sum(case when attribute = 'void' then 1 else 0 end) = 0 ;
In some databases, you can simplify this with string aggregation -- assuming there are no duplicate attributes for cids. For instance, using the MySQL function:
select cid
from t
where attribute in ('payment', 'delete' 'void')
group by cid
having group_concat(attribute order by attribute) = 'delete,payment';
You can use conditional aggregation:
select cid
from tablename
where attribute in ('delete', 'payment', 'void')
group by cid
having
count(distinct attribute) = 2
and
sum(
case attribute
when 'void' then 1
else 0
end
) = 0
If there are not more attributes than these 3, then you can omit the WHERE clause.
See the demo.
Results:
| cid |
| --- |
| 3 |
I'm assuming that there are only three attributes, so the logic behind this query is:
First COUNT the number of attributes GROUP BY cid, and then LEFT JOIN the original table ON attribute is void. You should grab cid that has exactly 2 attributes and no void.
The original table is named as temp:
SELECT
subq2.result_cid
FROM (
SELECT
*
FROM (
SELECT
T.cid AS result_cid,
COUNT(T.attribute) AS count
FROM
temp AS T
GROUP BY
T.cid
) AS subq
LEFT OUTER JOIN temp AS T2 ON subq.result_cid = T2.cid AND T2.attribute = 'void'
) AS subq2
WHERE subq2.count = 2 AND subq2.id2 IS NULL
use corelated subquery by using not exists
select t1.* from tablename t1
where not exists( select 1 from tablename t2
where t1.cid=t2.cid and attribute='void'
)
and exists ( select 1 from tablename t2
where t1.cid=t2.cid
having count(distinct attribute)=2
)
and attribute in ('payment','delete')
demo online

How to use Pivot on two columns in sql

I have data like below in my table. Flag is bit and label is varchar.
parentid code label flag
1 abc hello false
1 xyz bye false
1 qrt hi true
I need to fetch the records as
parentid label_abc flag_abc label_xyz flag_xyz label_qrt flag_qrt
I can only fetch only label right now using Pivot, but when i give second aggregate function for flag it gives error (Incorrect syntax near ','.). Is there any way to fetch two columns using Pivot.
I did something like this:
SELECT distinct
parentid
, [abc] as label_abc
, [xyz] as label_xyz
, [qrt] as label_qrt
FROM (
Select
parentid,
label,code
FROM items
) a
Pivot (
Max(label), max (flag)
FOR code in ([abc], [xyz], [qrt]
) as Pvt
I find it a bit tricky to do this using the pivot operator, and a lot easier to use conditional aggregation instead:
select
parentid,
max(case when code = 'abc' then label end) as label_abc,
max(case when code = 'abc' then flag end) as flag_abc,
max(case when code = 'xyz' then label end) as label_xyz,
max(case when code = 'xyz' then flag end) as flag_xyz,
max(case when code = 'qrt' then label end) as label_qrt,
max(case when code = 'qrt' then flag end) as flag_qrt
from (
select parentid, code, label, cast(flag as int) flag
from items
) src
group by parentid;
Sample SQL Fiddle
yes, but they need to be pivoted separately. it takes a little bit of fancy footwork, but it should look more like:
select distinct parentid
, [abc1] as label_abc
, [xyz1] as label_xyz
, [qrt1] as label_qrt
, [abc2] as flag_abc
, [xyz2] as flag_xyz
, [qrt2] as flag_qrt
from (
select parentid
, label
, label + '1' as code1
, label + '2' as code2
from items
) as a
pivot (
max(label) for code1 in ([abc1], [xyz1], [qrt1])
) as pvt1
pivot (
max(flag) for code2 in ([abc2], [xyz2], [qrt2])
) as pvt2

Replace NULL with values

Here is my challenge:
I have a log table which every time a record is changed adds a new record but puts a NULL value for each non-changed value in each record. In other words only the changed value is set, the rest unchanged fields in each row simply has a NULL value.
Now I would like to replace each NULL value with the value above it that is NOT a NULL value like below:
Source table: Task_log
ID Owner Status Flag
1 Bob Registrar T
2 Sue NULL NULL
3 NULL NULL F
4 Frank Admission T
5 NULL NULL F
6 NULL NULL T
Desired output table: Task_log
ID Owner Status Flag
1 Bob Registrar T
2 Sue Registrar T
3 Sue Registrar F
4 Frank Admission T
5 Frank Admission F
6 Frank Admission T
How do I write a query which will generate the desired output table?
One the new windowed function of SQLServer 2012 is FIRST_VALUE, wich have quite a direct name, it can be partitioned through the OVER clause, before using it is necessary to divide every column in data block, a block for a column begin when a value is found.
With Block As (
Select ID
, Owner
, OBlockID = SUM(Case When Owner Is Null Then 0 Else 1 End)
OVER (ORDER BY ID)
, Status
, SBlockID = SUM(Case When Status Is Null Then 0 Else 1 End)
OVER (ORDER BY ID)
, Flag
, FBlockID = SUM(Case When Flag Is Null Then 0 Else 1 End)
OVER (ORDER BY ID)
From Task_log
)
Select ID
, Owner = FIRST_VALUE(Owner) OVER (PARTITION BY OBlockID ORDER BY ID)
, Status = FIRST_VALUE(Status) OVER (PARTITION BY SBlockID ORDER BY ID)
, Flag = FIRST_VALUE(Flag) OVER (PARTITION BY FBlockID ORDER BY ID)
FROM Block
SQLFiddle demo
The UPDATE query is easily derived
As I mentioned in my comment, I would try to fix the process that is creating the records rather than fixing the junk data. If that is not an option, the code below should get you pointed in the right direction.
UPDATE t1
set t1.owner = COALESCE(t1.owner, t2.owner),
t1.Status = COALESCE(t1.status, t2.status),
t1.Flag = COALESCE(t1.flag, t2.flag)
FROM Task_log as t1
INNER JOIN Task_log as t2
ON t1.id = (t1.id + 1)
where t1.owner is null
OR t1.status is null
OR t1.flag is null
I can think of several approaches.
You could use a combination of COALESCE with an array aggregate function. Unfortunately it doesn't look like SQL Server supports array_agg natively (although some nice people have developed some workarounds).
You could also use a subselect for each column.
SELECT id,
(SELECT TOP 1 FROM (SELECT owner FROM ... WHERE id = outer_id AND owner IS NOT NULL order by ID desc )) AS owner,
-- other columns
You could probably do something with window functions, too.
A vanilla solution would be:
select id
, owner
, coalesce(owner, ( select owner from t t2
where id = (select max(id) from t t3
where id < t1.id and owner is not null))
) as new_owner
, flag
, coalesce(flag, ( select flag from t t2
where id = (select max(id) from t t3
where id < t1.id and flag is not null))
) as new_flag
from t t1
Rather inefficient, but should work on most DBMS

SQL : To get one of the duplicates by using a case statement

I have a table in which there are two columns with duplicates.
id name classname description
-----------------------------
1 a aa aa:abcd
2 a Unknown Unknown
3 b bb unknown
4 c cc abcd
Now I have a select query where in I have to filter out all the duplicates and my description is shown as identifier, my result should be like this,
id name identifier
-----------------
1 a aa
2 b NULL
3 c NULL
where all the description having either without ':' as its char index should display as NULL or Unknown as Null.
I'm using the below select query to filter the duplicates in the 'name' column , but I'm unable to use the same query for description as I'm using case for obtaining result in order to trim my description 'aa: abcd' to aa
select distinct
id,
(select top 1 name
from table1 t
where t.name = t1.name
order by case t1.classname
when 'Unknown Tag Class' then 0
else 1
end
) name,
(case when charindex(':',Description)> 0
then substring(Description,1,(charindex(':',Description)-1))
end
) as Identifier
from table1 t1
In the above query I want to modify the case statement of description so that i can filter duplicates and also trim the values like "aa:abcd" to "aa" and put them in identifier column.
Need help on this.
this is the query i am using
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[EXEC_REP_TransposedTagAttributes]')
AND type in (N'U'))
BEGIN
select distinct
[Att : 42674] as TagID
,Tagname
,isnull([Att : 14591],'-') as OriginatingContractor
,isnull([Att : 14594],'-') as System
,(case when charindex(':',TargetName)> 0 then
substring(TargetName,(charindex(':',TargetName)+1),len(TargetName))
end) as SystemDescription
,(case when charindex(':',TagClassDescription)> 0 then
substring(TagClassDescription,1,(charindex(':',TagClassDescription)-1))
end) as TagIdentifier
from EXEC_REP_TransposedTagAttributes t1
LEFT JOIN (SELECT SourceName, TargetName FROM EXEC_REP_Associations WHERE AssociationType = '3' and TargetClassName = 'SUB SYSTEM') b ON TagName = b.SourceName
where tagname='ZIH-210053' Order by [Att : 42674]
END
ELSE
select 'Reporting Database is being refreshed, please wait.' as errMsg
and the result i am geting is
TagID Tagname OriginatingContractor System SystemDescription TagIdentifier
2609005 ZIH-210053 Hyundai Heavy Industries (Topsides) 210 Slugcatcher NULL
2609005 ZIH-210053 Hyundai Heavy Industries (Topsides) 210 Slugcatcher ZIH
there are also rows which have tag identifier as null and donot have duplicates
select id,name,
CASE WHEN charindex(':',description)>0
THEN LEFT(description,charindex(':',description)-1)
ELSE NULL
END as identifier
from t as t1
where description like '%:%'
or NOT EXISTS (select * from t where t.id<>t1.id and t.name=t1.name);
SQLFiddle demo

Select records based on column priority

First of all, the title of this question is horrible, but I didn't find a better way to describe my issue.
There's probably a very easy way to do this, but I couldn't figure it out. This is very similar to this question, but I'm running on sqlite3 (iOS) so I suspect my options are much more limited.
I have a table with product records. All records have an ID (note: I'm not talking about the row ID, but rather an identification number unique to each product). Some products may have two entries in the table (both with the same ID). The only difference would be in a special column (let's say column COLOUR can be either RED or GREEN).
What I want to do is create a list of unique products based on the value of COLOUR, with priority to GREEN if both GREEN and RED records exist for the same product.
In short, if I have the following case:
id PRODUCT ID COLOUR
1 1001 GREEN
2 1002 GREEN
3 1002 RED
4 1003 RED
I would like my SELECT to return the rows 1, 2 and 4. How can I achieve this?
My current approach is to have separate tables, and do the join manually, but obviously this is a very bad idea..
Note: I've tried to use the same approach from here:
SELECT *
FROM xx
WHERE f_COLOUR = "GREEN"
UNION ALL
SELECT *
FROM xx
WHERE id not in (SELECT distinct id
FROM xx
WHERE f_COLOUR = "GREEN");
But the result I'm getting is rows 1,2,3,4 instead of just 1,2,4. What am I missing?
Edit: One more question please: how can this be made to work with a subset of records, ie. if instead of the entire table I wanted to filter some records?
For example, if I had something like SELECT * FROM table WHERE productID LIKE "1%" ... how can I retrieve each unique product, but still respecting the colour priority (GREEN>RED)?
Your query is nearly correct. Just use PRODUCTID and not ID.
SELECT *
FROM xx
WHERE f_COLOUR = "GREEN"
UNION
SELECT *
FROM xx
WHERE PRODUCTID not in
(SELECT PRODUCTID
FROM xx
WHERE f_COLOUR = "GREEN");
SQLFiddle Demo
Try this
SELECT *
FROM xx
WHERE COLOUR = 'GREEN'
UNION
SELECT *
FROM xx WHERE P_Id not in
(SELECT P_Id
FROM Persons
WHERE COLOUR = 'GREEN');
See ALSO SQL FIDDLE DEMO
I just want to offer that you can do this with a group by:
select (case when sum(case when colour = 'Green' then 1 else 0 end) > 0
then max(case when colour = 'Green' then id end)
else max(case when colour = 'Red' then id end)
end) as id,
product_id
(case when sum(case when colour = 'Green' then 1 else 0 end) > 0 then 'Green'
else 'Red'
end) as colour
from t
group by product_id
You can have it like this
WITH PriorityTable
AS
(
SELECT T.*,
ROW_NUMBER() OVER (PARTITION BY T.ID
ORDER BY PT.ColorPriority ) PriorityColumn
FROM XX AS T
INNER JOIN (
SELECT 'RED' AS f_COLOUR , 1 AS ColorPriority
UNION
SELECT 'GREEN' AS f_COLOUR , 2 AS ColorPriority
) AS PT
ON T.f_COLOUR = PT.f_COLOUR
)
SELECT * FROM PriorityTable
WHERE PriorityColumn = 1