How to convert column names to multiple rows? - sql

I've a legacy database containing a table with multiple columns of type boolean. E.g.:
Table_1
id name has_lights has_engine has_brakes has_tyres can_move
1 bullock_cart false false false true true
2 car true true true true true
3 tank true true true false true
I'd like to write an SQL query for Table1 to fetch the id and name and the attributes (represented by the name of the column) that are true.
Expected output:
id name attributes
-- ---- ----------
1 bullock_cart has_tyres
1 bullock_cart can_move
2 car has_lights
2 car has_engine
2 car has_brakes
2 car has_tyres
2 car can_move
3 tank has_lights
3 tank has_engine
3 tank has_brakes
3 tank can_move
I wrote:
SELECT id, name,
CASE
WHEN has_lights THEN 'has_lights'
WHEN has_engine THEN 'has_engine'
WHEN has_brakes THEN 'has_brakes'
WHEN has_tyres THEN 'has_tyres'
WHEN can_move THEN 'can_move'
END
FROM TABLE1;
But this gets me only the 1st matching attribute for each row in Table1 (by virtue of CASE-WHEN).
What is the correct way to retrieve the data in the format I want? Any inputs/help will be greatly appreciated?
Notes:
The table structure isn't ideal but this is a legacy system and we cannot modify the schema.
Nested queries are ok as long as they aren't too slow - say for the sample above (I understand the number of matching rows/column factor in the slow-ness).

The simplest method is union all:
select id, name, 'has_lights' as attribute from t where has_lights union all
select id, name, 'has_engine' from t where has_engine union all
select id, name, 'has_brakes' from t where has_brakes union all
select id, name, 'has_tyres' from t where has_tyres union all
select id, name, 'can_move' from t where can_move;
If you have a very large table, then a lateral join is probably more efficient:
select t.id, t.name, v.attribute
from t, lateral
(select attribute
from (values (has_lights, 'has_lights'),
(has_engine, 'has_engine'),
(has_brakes, 'has_brakes'),
(has_tyres, 'has_tyres'),
(can_move, 'can_move')
) v(flag, attribute)
where flag
) v;

You can do it using UNION ALL :
SELECT name,'has_lights' as attributes FROM YourTable where has_lights = 'TRUE'
UNION ALL
SELECT name,'has_engine' as attributes FROM YourTable where has_engine= 'TRUE'
UNION ALL
SELECT name,'has_brakes' as attributes FROM YourTable where has_brakes = 'TRUE'
UNION ALL
SELECT name,'has_tyres' as attributes FROM YourTable where has_tyres = 'TRUE'
UNION ALL
SELECT name,'can_move' as attributes FROM YourTable where can_move = 'TRUE'

This is much like the brilliant query #Gordon posted:
SELECT t.id, t.name, v.attribute
FROM table1 t
JOIN LATERAL (
VALUES (has_lights, 'has_lights')
, (has_engine, 'has_engine')
, (has_brakes, 'has_brakes')
, (has_tyres , 'has_tyres')
, (can_move , 'can_move')
) v(flag, attribute) ON v.flag;
Just a bit shorter since the VALUES expression can stand on its own.

Related

PostgreSQL Recursive CTE - Return Empty if false value found

Using a recursive query, I'm querying for all ancestors of a field using a parent id field. If a false value is found in the enabled field for any ancestor, then the query should return empty rows.
Say I have the following table called my_table
Field
Type
id
integer
parent
integer
enabled
boolean
It's populated with the following data:
id
parent
enabled
1
null
true
2
1
true
3
1
true
4
2
true
I have the following recursive query:
WITH recursive my_rec AS
(
SELECT id, parent, enabled
FROM my_table
WHERE id = 4
UNION ALL
SELECT t.id, t.parent, t.enabled
FROM my_table t
INNER JOIN my_rec
ON t.id = my_rec.parent
)
SELECT *
FROM my_rec
For id = 4 this correctly returns
id
parent
enabled
4
2
true
2
1
true
1
null
true
But say I were to update id 2 so that it's changed from enabled true to false.
Example:
id
parent
enabled
1
null
true
2
1
false
3
1
true
4
2
true
Now I want my query to return for id = 4 nothing. This is because in the "ancestry" tree of id 4, enabled is false for id 2.
How can I update my query above to achieve this?
If I understand correctly you can try to use NOT EXISTS subquery to
judge cte if any enabled is false didn't show anything as your expect, otherwise show all of them.
WITH recursive my_rec AS
(
SELECT id, parent, enabled
FROM my_table
WHERE id = 4
UNION ALL
SELECT t.id, t.parent, t.enabled
FROM my_table t
INNER JOIN my_rec
ON t.id = my_rec.parent
)
SELECT *
FROM my_rec
WHERE NOT EXISTS (
SELECT 1
FROM my_rec
WHERE enabled = false
)
or we can use COUNT condition aggregate function to make it, getting false_cnt and judgement.
WITH recursive my_rec AS
(
SELECT id, parent, enabled
FROM my_table
WHERE id = 4
UNION ALL
SELECT t.id, t.parent, t.enabled
FROM my_table t
INNER JOIN my_rec
ON t.id = my_rec.parent
)
SELECT *
FROM (
SELECT *,COUNT(*) FILTER(WHERE enabled = false) OVER() false_cnt
FROM my_rec
) t1
WHERE false_cnt = 0
sqlfiddle

Aggregate on a non group by column check if any value matches a criteria

Let's say I have a table Category with columns
id, childCategory, hasParts
Let's say I want to group by id and check if any value in hasParts has value true.
How to do this efficiently?
this has got to be the most vague post that i've seen on here but i'll take a stab at it. based on my own imagination and the 3 sentences provided, here we go:
create table category (id int, childcategory nvarchar(25), hasparts bit)
insert category
select 1, 'stroller', 1
union all
select 1, 'rocker', 1
union all
select 2, 'car', 0
union all
select 2, 'doll', 0
union all
select 3, 'nasal sprayer', 0
union all
select 3, 'thermometer', 1
select *,
case when exists (select 1 from category b where a.id = b.id and b.hasparts = 1) then 'has true value' end as truecheck
from
(
select id, count(*) as inventory
from category
group by id
) a
drop table category
this should theoretically get you want you want. adjust as needed.

SQL: return true/false if a related record is presented

I have two tables:
ASSIGNMENTS (ID)
ASSIGNMENT_REVIEWS (ID, ASSIGNMENT_ID)
As a result of selecting I'd like to retrieve a flag if a review is already presented for the assignment. How to do it in the best way?
You are looking for the exists statement:
select
id,
case when exists (
select 1 from assignment_reviews where assignment_reviews.assignment_id = assignments.id
) then 1 else 0 end as hasReview
from
assignments
You can use a left join with nvl2() function(returns the
value in the 2nd argument if the 1st argument is not null, otherwise
returns the 3rd argument practically )
with assignments(id) as
(
select 101 from dual union all
select 102 from dual
), assignments_reviews(id,assignment_id) as
(
select 855, 101 from dual
)
select a.id,
nvl2(r.assignment_id,1,0) as already_presented
from assignments a
left join assignments_reviews r
on r.assignment_id = a.id;
ID ALREADY_PRESENTED
101 1
102 0
Demo

Expand rows from existing data in SQL

I have a table that looks like this:
User HasPermA HasPermB HasPermC
---------------------------------------
Joe True False True
Sally True True True
And I need to transform it into the following format using SQL:
User PermissionType
-----------------------
Joe A
Joe C
Sally A
Sally B
Sally C
How would I go about doing that?
You can use UNION ALL:
select *
from
(
select user
, case when HasPermA is true then 'A' else null end as PermissionType
from table
union all
select user
, case when HasPermB is true then 'B' else null end as PermissionType
from table
union all
select user
, case when HasPermC is true then 'C' else null end as PermissionType
from table
) sub
where sub.PermissionType is not null
One method is union all, which I would phrase as:
select user, 'A' as PermissionType from t where HasPermA union all
select user, 'B' from t where HasPermB union all
select user, 'C' from t where HasPermC ;
This assumes that your dialect of SQL understands boolean variables. You might need something like HasPermA = 'true'.
Several dialects of SQL support lateral joins -- using either the lateral keyword or the apply keyword (or both). If so, I like:
select t.user, v.PermissionType
from t outer apply
(value ('A', HasPermA), ('B', HasPermA), ('C', HasPermA)) v(PermissionType, hasPerm)
where hasPerm;
Using a lateral join (or unpivot query) has the advantage of only scanning the table once.

How to transpose dynamically in Oracle

Here is my table
Equipmentid Application Value
=========== =========== =====
k001 THK True
k001 BHK False
k001 KHK True
Here is what I expected:
Equipmentid THK BHK KHK
=========== === === ===
k001 True False True
I'm trying to use normal transpose Oracle using max decode but in the end need to mention AS [tablename], I want to dynamically create row to column base on row name, this database will involve very much application. Thank guys
Hi try using PIVOT,
WITH x(equipment_id, application, VALUE )
AS (SELECT 'k001', 'THK', 'TRUE' FROM DUAL UNION ALL
SELECT 'k001', 'BHK', 'FALSE' FROM DUAL UNION ALL
SELECT 'k001', 'KHK', 'TRUE' FROM DUAL UNION ALL
SELECT 'k002', 'KHK', 'FALSE' FROM DUAL UNION ALL
SELECT 'k002', 'THK', 'FALSE' FROM DUAL UNION ALL
SELECT 'k002', 'BHK', 'FALSE' FROM DUAL )
SELECT * FROM
(
SELECT equipment_id, value, application
FROM x
)
PIVOT
(
MAX(value)
FOR application IN ('THK', 'BHK', 'KHK')
) order by equipment_id;
Alternatively, if you want to have dynamic column, you can use subquery in the IN clause then use PIVOT XML,but result will be of XML TYPE which i dont know how to extract the values.(just saying) if you want to know more about how to do it dynamically with pl/sql. Read here .Here's the source
SELECT * FROM
(
SELECT equipment_id, value, application
FROM x
)
PIVOT XML
(
MAX(value)
FOR application IN (SELECT DISTINCT application from x)
) order by equipment_id;
Try this one.
SELECT EQUIPMENTID,
max(case when APPLICATION = 'THK' then VALUE end) as "THK",
max(case when APPLICATION = 'BHK' then VALUE end) as "BHK",
max(case when APPLICATION = 'KHK' then VALUE end) as "KHK"
FROM [tablename]
group by EQUIPMENTID;
You can left join in this case.
SELECT t1.Equipmentid, t2.Value AS 'THK', t3.Value AS 'BHK', t4.Value AS 'KHK' FROM TABLE t1
LEFT JOIN (SELECT Equipmentid, Value FROM TABLE WHERE Application = 'THK') AS t2 ON (t1.Equipmentid = t2.Equipmentid)
LEFT JOIN (SELECT Equipmentid, Value FROM TABLE WHERE Application = 'BHK') AS t3 ON (t1.Equipmentid = t3.Equipmentid)
LEFT JOIN (SELECT Equipmentid, Value FROM TABLE WHERE Application = 'KHK') AS t4 ON (t1.Equipmentid = t4.Equipmentid)
Even though it can be solve. But this method is not good in my opinion. Hope it help you anyway