Qlik/SQL Server - sql

Long time reader first time poster.
I need some help in returning a corresponding value when a particular value appears.
Example, I want to return the spot A and the value associated with that position. In the first row, that would be 1, second row 3, and third row 1.
+-----------+-----------+
| Column A | Column B |
+-----------+-----------+
| A;B;C;D;E | 1;2;3;4;5 |
| B;A;C;D;E | 2;3;4;5;1 |
| D;C;E;A;B | 5;2;3;1;4 |
+-----------+-----------+

You can use SubField function to split both column values. Using SubField function (without the field_no parameter) creates row for each value.
For example: if we have A;B;C;D as value in MyField field and we use SubField(MyField, ';'). As a result Qlik will create 4 rows:
MyField
-------
A
B
C
D
So if we use SubField on both ColumnA and ColumnB fields (and keep an index between the new rows) we can create new "flat" table that contains the link between both column split values
Reloading the script below will result in two tables: Data and Flatten. Data is the "raw" data and Flatten is the result one
Have a look at Value and Index columns from Flatten table. They are the result of using SubField on ColumnA and ColumnB
// Load the sample data
// preceeding load to add ID to each row
Data:
Load
ColumnA,
ColumnB,
RowNo() as RowId
;
Load * inline [
ColumnA, ColumnB
A;B;C;D;E, 1;2;3;4;5
B;A;C;D;E, 2;3;4;5;1
D;C;E;A;B, 5;2;3;1;4
];
// Passing RowId across just in case if we want to link back
// to the Data table
Flatten_Temp:
// Preceding load to add ID (ValueId) for each value in ColumnA
// this ID will be used to join back to the values in ColumnB
Load
RowId,
ColumnASplit as Value,
RowNo() as ValueId
;
// Using SubField function to create row for each value in ColumnA
Load
RowId,
SubField(ColumnA, ';') as ColumnASplit
Resident
Data
;
JOIN
// Preceding load to add ID (ValueId) for each value in ColumnB
Load
RowId,
ColumnBSplit as Index,
RowNo() as ValueId
;
// Using SubField function to create row for each value in ColumnB
Load
RowId,
SubField(ColumnB, ';') as ColumnBSplit
Resident
Data
;
NoConcatenate
// Load only 'A' values from the table above
Flatten:
Load
*
Resident
Flatten_Temp
Where
Value = 'A'
;
Drop Table Flatten_Temp;
Update: Script updated to return only values for A

Related

filter a column based on another column in oracle query

I have table like this :
ID | key | value
1 | A1 |o1
1 | A2 |o2
1 | A3 |o3
2 | A1 |o4
2 | A2 |o5
3 | A1 |o6
3 | A3 |o7
4 | A3 |o8
I want to write a oracle query that can filter value column based on key column .
some thing like this
select ID
where
if key = A1 then value ='o1'
and key = A3 then value ='o4'
please help me to write this query.
***To clarify my question ,I need list of IDs in result that all condition(key-value) are true for them. for each IDs I should check key-values (with AND ) and if all conditions are true then this ID is acceptable .
thanks
IF means PL/SQL. In SQL, we use CASE expression instead (or DECODE, if you want). Doing so, you'd move value out of the expression and use something like this:
where id = 1
and value = case when key = 'A1' then 'o1'
when key = 'A3' then 'o4'
end
You are mixing filtering and selection. List the columns that you want to display in the SELECT list and the columns used to filter in the WHERE clause
SELECT key, value
FROM my_table
WHERE ID = 1 AND key IN ('A1', 'A2')
If there is no value column in your table, you can use the DECODE function
SELECT key, DECODE(key, 'A1', 'o1', 'A2', 'o4', key) AS value
FROM my_table
WHERE ID = 1
After the key, you must specify pairs of search and result values. The pairs can be followed by a default value. In this example, since we did not specify a result for 'A3', the result will be the key itself. If no default value was specified, NULL would be returned for missing search values.
update
It seems that I have misunderstood the question (see #mathguy's comment). You can filter the way you want by simply using the Boolean operators AND and OR
SELECT * FROM
FROM my_table
WHERE
ID = 1 AND
(
key = 'A1' AND value ='o1' OR
key = 'A3' AND value ='o4'
)
By using this pattern it is easy to add more constraints of this kind. Note that AND has precedence over OR (like * over +).

How to combine two queries where one of them results in an array and the second is the element place in the array?

I have the following two queries:
Query #1
(SELECT ARRAY (SELECT (journeys.id)
FROM JOURNEYS
JOIN RESPONSES ON scenarios[1] = responses.id) AS arry);
This one returns an array.
Query #2:
SELECT (journeys_index.j_index)
FROM journeys_index
WHERE environment = 'env1'
AND for_channel = 'ch1'
AND first_name = 'name1';
This second query returns the element index in the former array.
How do I combine the two to get only the element value?
I recreated a simpler example with a table containing an array column (the result of your first query)
create table my_array_test (id int, tst_array varchar[]);
insert into my_array_test values (1,'{cat, mouse, frog}');
insert into my_array_test values (2,'{horse, crocodile, rabbit}');
And another table containing the element position for each row I want to extract.
create table my_array_pos_test (id int, pos int);
insert into my_array_pos_test values (1,1);
insert into my_array_pos_test values (2,3);
e.g. from the row in my_array_test with id=1 I want to extract the 1st item (pos=1) and from the row in my_array_test with id=2 I want to extract the 3rd item (pos=3)
defaultdb=> select * from my_array_pos_test;
id | pos
----+-----
1 | 1
2 | 3
(2 rows)
Now the resulting statement is
select *,
tst_array[my_array_pos_test.pos]
from
my_array_test join
my_array_pos_test on my_array_test.id = my_array_pos_test.id
with the expected result
id | tst_array | id | pos | tst_array
----+--------------------------+----+-----+-----------
1 | {cat,mouse,frog} | 1 | 1 | cat
2 | {horse,crocodile,rabbit} | 2 | 3 | rabbit
(2 rows)
Now, in your case I would probably do something similar to the below, assuming your 1st select statement returns one row only.
with array_sel as
(SELECT ARRAY (SELECT (journeys.id)
FROM JOURNEYS
JOIN RESPONSES ON scenarios[1] = responses.id) AS arry)
SELECT arry[journeys_index.j_index]
FROM journeys_index cross join array_sel
WHERE environment = 'env1'
AND for_channel = 'ch1'
AND first_name = 'name1';
I can't validate fully the above sql statement since we can't replicate your tables, but should give you a hint on where to start from

matching array in Postgres with string manipulation

I was working with the "<#" operator and two arrays of strings.
anyarray <# anyarray → boolean
Every string is formed in this way: ${name}_${number}, and I would like to check if the name part is included and the number is equal or lower than the one in the other array.
['elementOne_10'] & [['elementOne_7' , 'elementTwo20']] → true
['elementOne_10'] & [['elementOne_17', 'elementTwo20']] → false
what would be an efficient way to do this?
Assuming your sample data elementTwo20 in fact follows your described schema and should be elementTwo_20:
step-by-step demo:db<>fiddle
SELECT
id
FROM (
SELECT
*,
split_part(u, '_', 1) as name, -- 3
split_part(u, '_', 2)::int as num,
split_part(compare, '_', 1) as comp_name,
split_part(compare, '_', 2)::int as comp_num
FROM
t,
unnest(data) u, -- 1
(SELECT unnest('{elementOne_10}'::text[]) as compare) s -- 2
)s
GROUP BY id -- 4
HAVING
ARRAY_AGG(name) #> ARRAY_AGG(comp_name) -- 5
AND MAX(comp_num) BETWEEN MIN(num) AND MAX(num)
unnest() your array elements into one element per record
JOIN and unnest() your comparision data
split the element strings into their name and num parts
unnest() creates several records per original array, they can be grouped by an identifier (best is an id column)
Filter with your criteria in the HAVING clause: Compare the name parts for example with array operators, for BETWEEN comparing you can use MIN and MAX on the num part.
Note:
As #a_horse_with_no_name correctly mentioned: If possible think about your database design and normalize it:
Don't store arrays -> You don't need to unnest them on every operation
Relevant data should be kept separated, not concatenated as a string -> You don't need to split them on every operation
id | name | num
---------------------
1 | elementOne | 7
1 | elementTwo | 20
2 | elementOne | 17
2 | elementTwo | 20
This is exactly the result of the inner subquery. You have to create this every time you need these data. It's better to store the data like this.

One-statement Insert+delete in PostgreSQL

Suppose I have a PostgreSQL table t that looks like
id | name | y
----+------+---
0 | 'a' | 0
1 | 'b' | 0
2 | 'c' | 0
3 | 'd' | 1
4 | 'e' | 2
5 | 'f' | 2
With id being the primary key and with a UNIQUE constraint on (name, y).
Suppose I want to update this table in such a way that the part of the data set with y = 0 becomes (without knowing what is already there)
id | name | y
----+------+---
0 | 'a' | 0
1 | 'x' | 0
2 | 'y' | 0
I could use
DELETE FROM t WHERE y = 0 AND name NOT IN ('a', 'x', 'y');
INSERT INTO t (name, y) VALUES ('a', 0), ('x', 0), ('y', 0)
ON CONFLICT (name) DO NOTHING;
I feel like there must be a one-statement way to do this (like what upsert does for the task "update the existing entries and insert missing ones", but then for "insert the missing entries and delete the entries that should not be there"). Is there? I heard rumours that oracle has something called MERGE... I'm not sure what it does exactly.
This can be done with a single statement. But I doubt whether that classifies as "simpler".
Additionally: your expected output doesn't make sense.
Your insert statement does not provide a value for the primary key column (id), so apparently, the id column is a generated (identity/serial) column.
But in that case, news rows can't have the same IDs as the ones before because when the new rows were inserted, new IDs were generated.
Given the above change to your expected output, the following does what you want:
with data (name, y) as (
values ('a', 0), ('x', 0), ('y', 0)
), changed as (
insert into t (name, y)
select *
from data
on conflict (name,y) do nothing
)
delete from t
where (name, y) not in (select name, y from data);
That is one statement, but certainly not "simpler". The only advantage I can see is that you do not have to specify the list of values twice.
Online example: https://rextester.com/KKB30299
Unless there's a tremendous number of rows to be updated, do it as three update statements.
update t set name = 'a' where id = 0;
update t set name = 'x' where id = 1;
update t set name = 'y' where id = 2;
This is simple. It's easily done in a loop with a SQL builder. There's no race conditions as there are with deleting and inserting. And it preserves the ids and other columns of those rows.
To demonstrate with some psuedo-Ruby code.
new_names = ['a', 'x', 'y']
# In a transaction
db.transaction {
# Query the matching IDs in the same order as their new names
ids_to_update = db.select("
select id from t where y = 0 order by id
")
# Iterate through the IDs and new names together
ids_to_update.zip(new_names).each { |id,name|
# Update the row with its new name
db.execute("
update t set name = ? where id = ?
", name, id)
}
}
Fooling around some, here's how I did it in "one" statement, or at least one thing sent to the server, while preserving the IDs and no race conditions.
do $$
declare change text[];
declare changes text[][];
begin
select array_agg(array[id::text,name])
into changes
from unnest(
(select array_agg(id order by id) from t where y = 0),
array['a','y','z']
) with ordinality as a(id, name);
foreach change slice 1 in array changes
loop
update t set name = change[2] where id = change[1]::int;
end loop;
end$$;
The goal is to produce an array of arrays matching the id to its new name. That can be iterated over to do the updates.
unnest(
(select array_agg(id order by id) from t where y = 0),
array['a','y','z']
) with ordinality as a(id, name);
That bit produces rows with the IDs and their new names side by side.
select array_agg(array[id::text,name])
into changes
from unnest(...) with ordinality as a(id, name);
Then those rows of IDs and names are turned into an array of arrays like: {{1,a},{2,y},{3,z}}. (There's probably a more direct way to do that)
foreach change slice 1 in array changes
loop
update t set name = change[2] where id = change[1]::int;
end loop;
Finally we loop over the array and use it to perform each update.
You can turn this into a proper function and pass in the y value to match and the array of names to change them to. You should verify that the length of the ids and names match.
This might be faster, depends on how many rows you're updating, but it isn't simpler, and it took some time to puzzle out.

How to create SQL query to sort JSON array using 1 attribute?

I have a table with a column that contains a JSON body that has arrays that I want to sort based on a attribute associated with that array.
I have tried selecting the array name and displaying the attribute which will display the entire array
The column name is my_column and the JSON is formatted as follows -
{
"num": "123",
"Y/N": "Y",
"array1":[
{
"name": "Bob",
"sortNum": 123
},
{
"name": "Tim Horton",
"sortNum": 456
}
]
}
I want the output to be based on the highest value of sortNum so the query should display the attributes for Tim Horton. The code I have played around with is below but get an error when trying to query based on sortNum.
SELECT my_column
FROM
my_table,
jsonb_array_elements(my_column->'array1') elem
WHERE elem->>'sortNum' = INT
Order by the filed 'sortNum' of the array element descending and use LIMIT 1 to only get the top record.
SELECT jae.e
FROM my_table t
CROSS JOIN LATERAL jsonb_array_elements(t.my_column->'array1') jae (e)
ORDER BY jae.e->'sortNum' DESC
LIMIT 1;
Edit:
If you want to sort numerically rather than lexicographically, get the element as text and cast it to an integer prior sorting on it.
SELECT jae.e
FROM my_table t
CROSS JOIN LATERAL jsonb_array_elements(t.my_column->'array1') jae (e)
ORDER BY (jae.e->>'sortNum')::integer DESC
LIMIT 1;
This answer assumes that your table has a column (or maybe a combination of columns) that can be use to uniquely identify a record. Let's call it myid.
To start with, we can use json_array_elements to split the JSON array into rows, as follows:
select myid, x.value, x->>'sortNum'
from
mytable,
json_array_elements(mycolumn->'array1') x
;
This returns:
myid | value | sortnum
---------------------------------------------------------
1 | {"name":"Bob","sortNum":123} | 123
1 | {"name":"Tim Horton","sortNum":456} | 456
Now, we can turn this to a subquery, and use ROW_NUMBER() to filter in the array element with the highest sortNum attribute:
select value
from (
select
x.value,
row_number() over(partition by myid order by x->>'sortNum' desc) rn
from
mytable,
json_array_elements(mycolumn->'array1') x
) y
where rn = 1;
Yields:
value
-----------------------------------
{"name":"Tim Horton","sortNum":456}
Demo on DB Fiddle