Oracle SQL Transforming sql rows into columns grouping by another column - sql

I am facing trouble designing SQL for the below scenario.
My table structure looks like this
TABLE IMSK
id | key | value | group_id
1 | k1 | a1 | g1
2 | k2 | a2 | g1
3 | k3 | a3 | g1
4 | k1 | b1 | g2
5 | k2 | b2 | g2
6 | k3 | b3 | g2
As you can see, I store 3 keys for every group id. Values will be different for each group_id. I need to show this information in a report in the following manner.
k1 | k2 | k3
a1 | a2 | a3
b1 | b2 | b3
Keys will become the column headers and every row will correspond to a group_id

One method is conditional aggregation:
select group_id,
max(case when key = 'k1' then value end) as key1,
max(case when key = 'k2' then value end) as key2,
max(case when key = 'k3' then value end) as key3
from imsk
group by group_id;

Using Pivot
Demo
WITH CTE1 AS(
SELECT KEY, VALUE FROM TABLE1)
SELECT * FROM CTE1
PIVOT (MAX(VALUE) FOR KEY IN ('k1', 'k2', 'k3'))
UNION
SELECT * FROM CTE1
PIVOT (MIN(VALUE) FOR KEY IN ('k1', 'k2', 'k3'));

Related

SQL query for many to many exclusive IN query

I have a table Table1 with columns A and B (many to many table).
|---------------------|------------------|
| ColumnA | ColumnB |
|---------------------|------------------|
| a1 | b1 |
|---------------------|------------------|
| a1 | b2 |
|---------------------|------------------|
| a2 | b1 |
|---------------------|------------------|
| a2 | b3 |
|---------------------|------------------|
| a3 | b2 |
|---------------------|------------------|
I want a list of As whose Bs are ONLY in list of Bs.
So, from above table, if list is [b1, b2]
Expected [a1, a3]
Not including a2as it is associated with b3 also.
You can use aggregation and having:
select a
from ab
group by a
having sum(case when b not in ('b1', 'b2') then 1 else 0 end) = 0;
The having clause is checking the number of rows that are not in the list. The = 0 says there are none.
Assuming there are not any nulls in ColumnB you can use NOT EXISTS:
select t.*
from tablename t
where not exists (select 1 from tablename where ColumnA = t.ColumnA and ColumnB not in ('b1', 'b2'))
If you want only the distinct values of ColumnA:
select distinct t.ColumnA
from tablename t
where not exists (select 1 from tablename where ColumnA = t.ColumnA and ColumnB not in ('b1', 'b2'))
See the demo.

Delete almost duplicates in a table

I have a table where strings 1 and 2 are almost duplicate - they have the same values but in reverse order. How can I delete these duplicates?
+--------+-------+
| COL_1 | COL_# |
+--------+-------+
| a1 | b1 |
| b1 | a1 | <- same as 1stline but in reversed order, needs to be removed
| a2 | b2 |
| a3 | b3 |
| b3 | a3 |<-- also is duplicate of string above, one of these 2str need
+--------+-------+ to be removed
expected result:
+--------+-------+
| COL_1 | COL_# |
+--------+-------+
| b1 | a1 |
| a2 | b2 |
| a3 | b3 |
+--------+-------+
or
+--------+-------+
| COL_1 | COL_# |
+--------+-------+
| a1 | b1 |
| a2 | b2 |
| a3 | b3 |
+--------+-------+
Do you mean like this :
DELETE(
select e.COL_#, f.COL_1
from example as e
join example as f on e.COL_# = f.COL_1 and e.COL_# < f.COL_1 )
If col_1 and col_# cannot be null, you can use LEASTand GREATEST to keep one row per combination:
delete from tbl
where rowid not in
(
select min(rowid) -- one rowid per combination to keep
from tbl
group by least(col_1, col_#), greatest(col_1, col_#)
);
I think it is a bit complicated to achieve it with a delete query.
Maybe it is OK for you to have such a select query first:
select A,B from (
select A,
B,
ROW_NUMBER() over (PARTITION BY ORA_HASH(A) * ORA_HASH(B) ORDER BY A) as RANK
FROM <your_table_name>
) where RANK = 1;
You could save the result of this query as a new table with CREATE TABLE AS SELECT ...
And then you simply DROP your old table.
One possible solution is:
DELETE FROM T
WHERE (COL_1, "COL_#") IN (SELECT COL_1, "COL_#"
FROM (SELECT ROWNUM AS RN, t1.COL_1, t1."COL_#"
FROM T t1
INNER JOIN T t2
ON t2."COL_#" = t1.COL_1 AND
t2.COL_1 = t1."COL_#")
WHERE RN / 2 <> TRUNC(RN / 2));
Note that COL_# must be quoted in Oracle as # is not a legal character in an unquoted identifier.
dbfiddle here
This doesn't seem so complicated:
delete t
where not (col1 < col2 or
not exists (select 1
from t t2
where t2.col1 = t.col2 and
t2.col2 = t.col1
)
);
Or:
delete t
where col1 > col2 and
exists (select 1
from t t2
where t2.col1 = t.col2 and
t2.col2 = t.col1
);

SQL query transposing columns

I have a table in the following structure:
id | att1 | att2 | att3
-----------------------
1 | a1 | b1 | c1
2 | a2 | b2 | c2
3 | a3 | b3 | c3
And I want to transpose the columns to become rows for each id. Like this:
id | attname | value
------------------------
1 | att1 | a1
1 | att2 | b1
1 | att3 | c1
2 | att1 | a2
2 | att2 | b2
2 | att3 | c2
3 | att1 | a3
3 | att2 | b3
3 | att3 | c3
I was reading up on the PIVOT function and wasn't sure if it would do the job or how to use it. Any help would be appreciated.
You can use unpivot for this task.
SELECT *
FROM table
UNPIVOT (
value FOR att_name
IN (att1 as 'att1', att2 as 'att2', att3 as 'att3')
);
Here is another method :
SELECT id,attname, value
FROM Yourtable
CROSS APPLY ( VALUES ('att1',att1), ('att2',att2), ('att3',att3)) t(attname, value)
Do UNION ALL:
select id, 'att1' as attname, att1 as value from tablename
union all
select id, 'att2' as attname, att2 as value from tablename
union all
select id, 'att3' as attname, att3 as value from tablename
Note that VALUE is a reserved word in SQL, so if that's your real column name you need to double quote it, "value".

Sql Server get first matching value

I have two tables History and Historyvalues:
History
HID(uniqeidentifier) | Version(int)
a1 | 1
a2 | 2
a3 | 3
a4 | 4
Historyvalues
HVID(uniqeidentifier) | HID(uniqeidentifier) | ControlID(uniqeidentifier) | Value(string)
b1 | a1 | c1 | value1
b2 | a2 | c1 | value2
b3 | a2 | c2 | value3
Now I Need a query where I can get a list with the last historyvalue of each control from a specific Version like:
Get the last values from Version 3 -> receiving ->
HVID | ControlID | Value
b2 | c1 | value2
b3 | c2 | value3
I tried something like this:
Select HVID, ControlId, max(Version), Value from
(
Select HVID, ControlId, Version, Value
from History inner JOIN
Historyvalues ON History.HID = Historyvalues.HID
where Version <= 3
) as a
group by ControlId
order by Version desc
but this does not work.
Are there any ideas?
Thank you very much for your help.
Best regards
Latest version from each control with your specific Version (WHERE t1.Version <= 3)
Query:
SQLFIDDLEExample
SELECT HVID, ControlId, Version, Value
FROM
(
SELECT t2.HVID, t2.ControlId, t1.Version, t2.Value,
ROW_NUMBER() OVER(PARTITION BY t2.ControlId ORDER BY t1.Version DESC) as rnk
FROM History t1
JOIN Historyvalues t2
ON t1.HID = t2.HID
WHERE t1.Version <= 3
) AS a
WHERE a.rnk = 1
ORDER BY a.Version desc
Result:
| HVID | CONTROLID | VERSION | VALUE |
|------|-----------|---------|--------|
| b2 | c1 | 2 | value2 |
| b3 | c2 | 2 | value3 |
here is your solution
Select Historyvalues.HVID,Historyvalues.ControlID,Historyvalues.Value
from Historyvalues
inner join History on Historyvalues.hid=History.hid
where Historyvalues.hvid in (
select MAX(Historyvalues.hvid) from Historyvalues
inner join History on Historyvalues.hid=History.hid
group by ControlID)

Can I alias multiple columns? How?

I'm using pyodbc and postgres.
Can I alias multiple columns?
Here's the description of my problem:
Data structure:
data
id | c1 | c2
-------------
1 | 11 | 12
2 | 21 | 22
Notation: c is for column
dictionary
id | key | value
----------------
1 | k1 | v11
1 | k2 | v12
2 | k1 | v21
2 | k2 | v22
Notation: k is for key, v is for value
You can think of k1 and k2 as two more columns. The data structure is this way because it's constantly changing. I didn't design it, I just have to go with it.
I can't figure out an sql query to give me something like the following (most importantly, for some row, I can access k1 and k2 columns by some name):
data
id | c1 | c2 | k1 | k2
-------------------------
1 | 11 | 12 | v11 | v12
2 | 21 | 22 | v21 | v22
The problem I keep running into is if I alias the tables, then the sql result will contain two "key" columns from the dictionary table, meaning I can't control which column I access of the two, but if I alias the rows, then I can't control which tables are being referenced inside the sql statement.
The fix I'm thinking is to alias two columns:
SELECT * FROM data
FULL JOIN dictionary AS a1,a2,a3
ON data.id = a1
FULL JOIN dictionary AS a4,a5,a6
ON data.id = a4
WHERE a2 = k1 and a5 = k2
Notation: a is for alias
The result of this would theoretically look like
data
id | c1 | c2 | a3 | a6
-------------------------
1 | 11 | 12 | v11 | v12
2 | 21 | 22 | v21 | v22
Note all a's would technically be here, but 3 and 6 are the ones I'm interested in
You can alias the entire table, for example dictionary as d1. Then refer to the column names in that table as d1.col1. For example:
SELECT d.id
, d.c1
, d.c2
, d1.value as a3
, d2.value as a6
FROM data as d
LEFT JOIN
dictionary as d1
ON data.id = d1.id
and d1.key = 'k1'
LEFT JOIN
dictionary as d2
ON data.id = d2.id
and d2.key = 'k2'