Translate table values to text following a fixed pattern - sql

We use software to store combinations of financial elements. Those elements are allowed in certain combinations. Exceptions of these combinations are SQL-like statements in the front-end, and are saved as numerical values in a database table like the following example:
+------+------+------+------+------+
| Col1 | Col2 | Col3 | Col4 | Col5 |
+------+------+------+------+------+
| 1 | 2 | 4 | 5 | 1 |
+------+------+------+------+------+
| -1 | 2 | 6 | 4 | 5 |
+------+------+------+------+------+
| 1 | 2 | 5 | 7 | 1 |
+------+------+------+------+------+
I would like to translate those numerical values back to a SQL-statement like the following example:
+------+-----------+------+-----------+------+-----------+------+-----------+------+-----------+
| Col1 | Col1Trans | Col2 | Col2Trans | Col3 | Col3Trans | Col4 | Col4Trans | Col5 | Col5Trans |
+------+-----------+------+-----------+------+-----------+------+-----------+------+-----------+
| 1 | ( | 2 | SELECT | 4 | CODE | 5 | LIKE | 1 | * |
+------+-----------+------+-----------+------+-----------+------+-----------+------+-----------+
| -1 | | 2 | SELECT | 6 | NUMBER | 4 | = | 5 | AND |
+------+-----------+------+-----------+------+-----------+------+-----------+------+-----------+
| 1 | ( | 2 | SELECT | 5 | TOOL | 7 | <> | 1 | * |
+------+-----------+------+-----------+------+-----------+------+-----------+------+-----------+
The numerical values differ in each column so I can only imagine the use of a lot of case...when statements which I doubt will be efficiƫnt. I don't want to create tables to hold the translation values. Are there ways to do this with arrays?
Are there any code samples to easily loop through table/columns and translate the contents of it?

You can use below code and add more case statement as per the requirement.
SELECT Col1
,CASE
WHEN Col1 = 1 THEN '('
ELSE '' END AS Col1Trans
,Col2
,CASE
WHEN Col2 = 2 THEN 'SELECT'
END AS Col2Trans
,Col3
,CASE
WHEN Col3 = 4 THEN 'CODE'
WHEN Col3 = 6 THEN 'NUMBER'
WHEN Col3 = 5 THEN 'TOOL'
END AS Col3Trans
,Col4
,CASE
WHEN Col4 = 5 THEN 'LIKE'
WHEN Col4 = 4 THEN '='
WHEN Col4 = 7 THEN '<>'
END AS Col4Trans
,Col5
,CASE
WHEN Col5 = 1 THEN '*'
WHEN Col5 = 5 THEN 'AND'
END AS Col5Trans

The best way to avoid so many case when and decode and etc is to use with as clause as following:
With col1trans (value, translation) as
(Select 1, '(' from dual union all
Select -1, null from dual),
Col2trans (value, translation) as
(Select 2, 'SELECT' from dual)
..
... till col5trans
Select m.col1, t1.translation as col1trans,
.... till m.col5, t5.translation
From your_table m join col1trans t1 m.col1=t1.value
join col2trans t2 m.col2=t2.value
... till col5trans
Cheers!!

Related

SQL - Ordering second column based on the first column

I am trying to retrieve data from a table, but I need it to be ordered in a very specific way and I'm not sure if it's possible using Oracle SQL alone.
What I need to do is retrieve all of the rows, but order it in a way that where column 3 is null (indicated by a blank space in the graphs below) those are ordered first. Then, all the rows that aren't null in column 3 would be shown AFTER the row that has their column value in column 1.
What I have:
+------+-------+------+
| Col1 | Col2 | Col3 |
+------+-------+------+
| 1 | text | |
| 2 | text | 1 |
| 3 | text | 1 |
| 8 | text | 10 |
| 9 | text | 10 |
| 10 | text | |
+------+-------+------+
What I would like as a result:
+------+-------+------+
| Col1 | Col2 | Col3 |
+------+-------+------+
| 1 | text | |
| 2 | text | 1 |
| 3 | text | 1 |
| 10 | text | |
| 8 | text | 10 |
| 9 | text | 10 |
+------+-------+------+
What I have tried:
First thing I tried was using:
ORDER BY coalesce(Col3, Col1)
and it got me close to the result, but the Col1 value 10 needs to be shown before the Col3 value 10.
+------+-------+------+
| Col1 | Col2 | Col3 |
+------+-------+------+
| 1 | text | |
| 2 | text | 1 |
| 3 | text | 1 |
| 8 | text | 10 |
| 9 | text | 10 |
| 10 | text | |
+------+-------+------+
I've also tried creating a new column where if Col3 is null then Col4 is true and false other wise, but this was essentially the same thing as coalesce up above.
I also tried just running some basic order by's but had no success in achieving this.
In Oracle, you would just use nulls first:
order by coalesce(col3, col1), col3 nulls first, col1
Your table looks very much like hierarchical data, where in some sense col1 is a unique row identifier, and col3 points to a row's parent row.
If so, it may be better to use a hierarchical query (connect by) for this. The ordering is hierarchical, and siblings (descendants from the same parent) are ordered according to the order siblings by clause.
Like this:
with
sample_table(col1, col2, col3) as (
select 1, 'text', null from dual union all
select 2, 'text', 1 from dual union all
select 3, 'text', 1 from dual union all
select 8, 'text', 10 from dual union all
select 9, 'text', 10 from dual union all
select 10, 'text', null from dual
)
select *
from sample_table
start with col3 is null
connect by col3 = prior col1
order siblings by col1
;
COL1 COL2 COL3
---------- ---- ----------
1 text
2 text 1
3 text 1
10 text
8 text 10
9 text 10
The with clause is not part of the solution - I added it there so I can test the query. (Remember this "with clause" way to create sample tables for testing - you can include them yourself, instead of the formatted table in your original question, so that people can easily test their answers on your sample data.)

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

Fetch the column which has the Max value for a row in Hive

I have a scenario where i need to pick the greatest value in the row from three columns, there is a function called Greatest but it doesn't work in my version of Hive 0.13.
Please suggest better way to accomplish it.
Example table:
+---------+------+------+------+
| Col1 | Col2 | Col3 | Col4 |
+---------+------+------+------+
| Group A | 1 | 2 | 3 |
+---------+------+------+------+
| Group B | 4 | 5 | 1 |
+---------+------+------+------+
| Group C | 4 | 2 | 1 |
+---------+------+------+------+
expected Result:
+---------+------------+------------+
| Col1 | output_max | max_column |
+---------+------------+------------+
| Group A | 3 | Col4 |
+---------+------------+------------+
| Group B | 5 | col3 |
+---------+------------+------------+
| Group C | 4 | col2 |
+---------+------------+------------+
select col1
,tuple.col1 as output_max
,concat('Col',tuple.col2) as max_column
from (select Col1
,sort_array(array(struct(Col2,2),struct(Col3,3),struct(Col4,4)))[2] as tuple
from t
) t
;
sort_array(Array)
Sorts the input array in ascending order according to the natural ordering of the array elements and returns it
(as of version 0.9.0).
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF
hive> select col1
> ,tuple.col1 as output_max
> ,concat('Col',tuple.col2) as max_column
>
> from (select Col1
> ,sort_array(array(struct(Col2,2),struct(Col3,3),struct(Col4,4)))[2] as tuple
> from t
> ) t
> ;
OK
Group A 3 Col4
Group B 5 Col3
Group C 4 Col2

SQL Count across columns

I know that this table structure is horrible and that I should look into database normalization, but this is what I have to work with at the moment.
I need to find the most common number across the columns where one of them has a specific id (in my example 3). Both columns will never have the same value.
Query
SELECT Col1, Col2 FROM scores WHERE Col1 = 3 OR Col2 = 3
Result
+------+------+
| Col1 | Col2 |
+------+------+
| 1 | 3 |
| 3 | 1 |
| 2 | 3 |
| 6 | 3 |
| 3 | 7 |
| 3 | 9 |
| 2 | 3 |
| 5 | 3 |
+------+------+
I'm hoping to get a result like this (I don't need count for 3 since it's the ID, but it can be included)
+-------+-------+
| Value | Count |
+-------+-------+
| 1 | 2 |
| 2 | 2 |
| 5 | 1 |
| 6 | 1 |
| 7 | 1 |
| 9 | 1 |
+-------+-------+
I've tried a few things such as UNION and nested SELECT but that doesn't seem to solve this thing.
Any suggestions?
If you want a count of the values where the OTHER column is 3, then a UNION would work like this:
SELECT value, theCount = COUNT(*)
FROM (
SELECT value = col1
FROM scores
WHERE col2 = 3
UNION ALL
SELECT col2
FROM scores
WHERE col1 = 3) T
GROUP BY value
ORDER BY value;
One way is using case:
SELECT
case Col1 when 3 then Col2 else Col1 end,
count(*)
FROM scores
WHERE Col1 = 3 OR Col2 = 3
Group by
case Col1 when 3 then Col2 else Col1 end;

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.