SELECT subquery with 2 return values - sql

I want to select multiple columns from a subquery. Here my minimal example:
A function that returns two values:
CREATE OR REPLACE FUNCTION dummy_function(my_text text)
RETURNS TABLE (id Integer, remark text) AS $$
BEGIN
RETURN QUERY SELECT 42, upper(my_text);
END;
$$ LANGUAGE plpgsql;
My not working query:
SELECT
id,
city_name,
dummy_function(city_name)
FROM
(SELECT 1 as id, 'Paris' as city_name
UNION ALL
SELECT 2 as id, 'Barcelona' as city_name
) AS dummy_table
My wrong result:
id | city_name | dummy_function
----+-----------+----------------
1 | Paris | (42,PARIS)
2 | Barcelona | (42,BARCELONA)
But I would like to have a result like this:
id | city_name | number | new_text
----+-----------+---------------------
1 | Paris | 42 | PARIS
2 | Barcelona | 42 | BARCELONA
Do you know how to achieve this without running the function twice?

Use the function returning row (or set of rows) in the FROM clause:
SELECT
dummy_table.id,
city_name,
dummy_function.id,
remark
FROM
(SELECT 1 as id, 'Paris' as city_name
UNION ALL
SELECT 2 as id, 'Barcelona' as city_name
) AS dummy_table,
LATERAL dummy_function(city_name)
id | city_name | id | remark
----+-----------+----+-----------
1 | Paris | 42 | PARIS
2 | Barcelona | 42 | BARCELONA
(2 rows)
Per the documentation:
Table functions appearing in FROM can also be preceded by the key word LATERAL, but for functions the key word is optional; the function's arguments can contain references to columns provided by preceding FROM items in any case.

SELECT
dummy_table.id,
city_name,
df.id as number,
df.remark as new_text
FROM
(SELECT 1 as id, 'Paris' as city_name
UNION ALL
SELECT 2 as id, 'Barcelona' as city_name
) AS dummy_table,
dummy_function(city_name) df

Related

Trying to match a column's value with another container multiple values

I have two tables, with the same column name/type.
Table A: Property listings
ID | Postal Code | Town
12 | xxxxx | California
13 | xxxxx | Nashville
14 | xxxxx | New York
Table B: User preferences
ID | Name | Preferred Towns
909| Dave | ["California", "New York"]
The town column in Table A is a string.
The preferred towns in Table B is a json array.
The goal is to match Dave with property listings located in specific town(s).
Expected output:
User to Property Matches
User ID | User Name | Matched Property ID
909 | Dave | 12, 14
Consider below approach (BigQuery)
select ID as User_ID, Name as User_Name,
( select string_agg(p.ID)
from t.Preferred_Towns Town
join Property_listings p
using (Town)
) Matched_Property_ID
from User_Preferences t
If applied to sample data in your question as in below example
with Property_listings as (
select '12' ID, 'xxxxx' PostalCode, 'California' Town union all
select '13', 'xxxxx', 'Nashville' union all
select '14', 'xxxxx', 'New York'
), User_Preferences as (
select '909' ID, 'Dave' Name, ["California", "New York"] Preferred_Towns
)
select ID as User_ID, Name as User_Name,
( select string_agg(p.ID)
from t.Preferred_Towns Town
join Property_listings p
using (Town)
) Matched_Property_ID
from User_Preferences t
the output is

There is a table having country and city columns as shown in the below table input. I need the output as mentioned below

I need a SQL query to get the desired output from the input table
You can do this with a UNION query, first selecting the distinct country names, and then each of the cities for that country. The output is then ordered by the country; whether the value is a country or a city; and then by the value:
SELECT DISTINCT country AS data, country, 1 AS ctry
FROM cities
UNION ALL
SELECT city, country, 0
FROM cities
ORDER BY country, ctry DESC, data
Output:
data country ctry
India India 1
BNG India 0
CHN India 0
HYD India 0
Sweden Sweden 1
GOTH Sweden 0
STOCK Sweden 0
VAXO Sweden 0
Demo on dbfiddle
It really looks like you are willing to interleave the records, with each country followed by its related countries.
The actual solution heavily depends on your datase, so let me assume that yours supports window functions, row constructor values() and lateral joins (SQL Server and Postgres are two candidates).
In SQL Server, you could do:
select distinct rn, idx, val
from (
select t.*, dense_rank() over(order by country) rn
from mytable t
) t
cross apply (values (t.country, 1), (t.city, 2)) as v(val, idx)
order by rn, idx, val
Demo on DB Fiddle:
rn | idx | val
-: | --: | :-----
1 | 1 | INDIA
1 | 2 | BNG
1 | 2 | CHN
1 | 2 | HYD
2 | 1 | SWEDEN
2 | 2 | STOCK
2 | 2 | VAXO
In Postgres, you would just replace outer apply with cross join lateral: Demo.

CASE expression on multiple columns

I have a table with below mentioned columns and values
StudentId | Geography | History | Maths
_______________________________________________
1 | NULL | 25 | NULL
2 | 20 | 23 | NULL
3 | 20 | 22 | 21
I need the output like below:
StudentId | Subject
___________________________
1 | History
2 | Geography
2 | History
3 | Geography
3 | History
3 | Maths
Wherever the value in subject columns (Geography, History and Maths) is NON NULL, I need the 'subject' value of the recepective column name.
I have an idea to pull it for one column using CASE, but not sure how to do it for multiple columns.
Here is what I tried:
SELECT StudentId, CASE WHEN IsNUll(Geography, '#NULL#') <> '#NULL#' THEN 'Geography'
CASE WHEN IsNUll(History, '#NULL#') <> '#NULL#' THEN 'History'
CASE WHEN IsNUll(Maths, '#NULL#') <> '#NULL#' THEN 'Maths' END Subject
FROM MyTable
You need to normalise your data. You can do this with a VALUES operator:
--Create sample data
WITH YourTable AS(
SELECT V.StudentID,
V.[Geography],
V.History,
V.Maths
FROM (VALUES(1,NULL,25,NULL),
(2,20,23,NULL),
(3,20,22,21))V(StudentID,[Geography], History, Maths))
--Solution
SELECT YT.StudentID,
V.[Subject]
FROM YourTable YT
CROSS APPLY (VALUES('Geography',YT.[Geography]),
('History',YT.History),
('Maths',YT.Maths))V([Subject],SubjectMark)
WHERE V.SubjectMark IS NOT NULL
ORDER BY YT.StudentID;
DB<>Fiddle
Use union all
select subjectid, Geography from table
union all
select subjectid, history from table
union all
select subjectid, Maths from table
You can use UNPIVOT. It shows you all grades row by row. Below code works fine
SELECT * FROM MyTable t
UNPIVOT
(
[Grade] FOR [Subject] IN ([Geography], [History], [Maths])
) AS u

query from tables where table name is in the current query

I have this query:
select main_value, subtable, subtable_column from table1
I want to know can I query values from subtable at the same time that this query is running?
EDIT
----
table1 structure
main_value varchar
subtable varchar
subtable_column varchar
branches structure(subtable in the query)
id number
branch_name varchar
when I query from table1 and if got subtable's value as branches, and subtable_column's value as branch_name then query branch_name from branches.(becuase subtable column's value is branches retrieved from the query and column names vise versa).
EDIT for tables and samples
---------------------------
table1
+-----------------+--------------------+
| Field | Type |
+-----------------+--------------------+
| ID | number(20,0) |
| subtable | varchar2(50 BYTE) |
| subtable_column | varchar2(100 BYTE) |
+-----------------+--------------------+
branches
+-------------+-------------------+
| Field | Type |
+-------------+-------------------+
| ID | number(20,0) |
| branch_name | varchar2(50 BYTE) |
+-------------+-------------------+
NB: these are just examples, so please dont suggest other ways rather than select from tables where table names and column names are in the current query at the same time.
i have records in table1 as below:
ID | subtable | subtable_column
-------------------------------
1 | branches | branch_nmae
2 | null | null
and branches table as below:
ID | branch_name |
------------------
1 | new york
2 | colombo
output:
+-------------+-------------------+---------------------------------+
| subtable | subtable_column | selected values from branches |
+-------------+-------------------+---------------------------------+
| branches | branch_name | new york, colombo |
| null | null | |
+-------------+-------------------+---------------------------------+
If you know all possible tables and all possible columns which table1 may contain then you could build query with case when containing all possibilities. Something like this:
select id, subtable, subtable_column,
case
when subtable = 'branches' and subtable_column = 'branch_name' then
(select listagg(branch_name, ', ') within group (order by branch_name)
from branches)
when subtable = 'branches' and subtable_column = 'id' then
(select listagg(id, ', ') within group (order by id)
from branches)
end as subtable_values
from table1;
But more common solution for such problems is to use dynamic SQL. So write a function like here:
create or replace function get_subtable_values(i_table in varchar2, i_column in varchar2)
return varchar2 is
v_sql varchar2(32767);
v_ret varchar2(32767);
begin
if i_table is null then
return null;
end if;
v_sql := 'select listagg('||i_column||', '', '') within group (order by null) from '||i_table;
execute immediate v_sql into v_ret;
return v_ret;
end;
... and use it in query:
select id, subtable, subtable_column,
get_subtable_values(subtable, subtable_column) as subtable_values
from table1;
My test data and output for both queries:
create table table1(id, subtable, subtable_column) as (
select 1, 'branches', 'branch_name' from dual union all
select 2, 'branches', 'id' from dual union all
select 3, null, null from dual);
create table branches(id, branch_name) as (
select 1, 'New York' from dual union all
select 2, 'Colombo' from dual );
Result:
ID SUBTABLE SUBTABLE_COLUMN SUBTABLE_VALUES
------ -------- --------------- --------------------
1 branches branch_name Colombo, New York
2 branches id 1, 2
3 null null null
I used function listagg() which glues all values into one string, comma separated. It's also possible to return table of varchars or user defined types. Of course there may be many issues like strings too long, data types other than varchar, exceptions should be handled, etc. But you see that's possible.
SELECT DISTINCT
table1.id,
table1.subtable,
table1.subtable_column,
t.*
FROM
table1
JOIN branches
LEFT JOIN ( SELECT group_concat( branch_name ) AS NAMES FROM branches ) AS t ON 1 = 1
this sql is okļ¼Œbut it is less efficient

How to get the column name of a value in PostgreSQL?

Suppose we have 3 tables:
Table1:
ID FrenchCity
1 Paris
2 Lille
3 Lyon
Table2:
ID IntlCity
1 Lille
2 Geneva
3 Toulouse
Table3:
ID BritishCity
1 London
2 Leeds
I would like to get the column name correspondent with a value.
For instance, I give a value Lille and SQL should return Table1.FrenchCity Table2.IntlCity.
As I said, I would like to get the column name of a value. So Lille exists in 2 tables, I would like SQL to return the {{table name}}.{{column name}}
How to write a query to do that?
This work for you ?
SELECT 'Table1.FrenchCity' as fieldName
FROM Table1
WHERE FrenchCity = 'Lille'
UNION ALL
SELECT 'Table2.IntlCity' as fieldName
FROM Table2
WHERE IntlCity = 'Lille'
UNION ALL
SELECT 'Table3.BritishCity' as fieldName
FROM Table3
WHERE BritishCity = 'Lille'
Then you can use array_agg
SELECT array_agg(fieldName)
FROM (
previous union query
) t
you better create one table with 3 columns:
ID COUNTRY fieldName CITY
1 France FrenchCity Paris
2 France FrenchCity Lille
3 France FrenchCity Lyon
4 Intl IntlCity Lille
5 Intl IntlCity Geneva
6 Intl IntlCity Toulouse
ect.
then use query:
SELECT country || '.' || fieldName
FROM three_col_table
WHERE CITY = 'Lille'
If you don't wont to use DB metadata then you can to convert table data into the series of (column_name, column_value) pairs using row_to_json and json_each_text functions:
with
-- Demo data start
Table1(ID, FrenchCity) as (values
(1, 'Paris'),
(2, 'Lille'),
(3, 'Lyon')),
Table2(ID, IntlCity) as (values
(1, 'Lille'),
(2, 'Geneva'),
(3, 'Toulouse')),
-- Demo data end
my_data as (
select 'Table1' as tbl, j.*
from Table1 as t, json_each_text(row_to_json(t.*)) as j(fld,val)
union all
select 'Table2' as tbl, j.*
from Table2 as t, json_each_text(row_to_json(t.*)) as j(fld,val)
-- ... and so on ...
)
select *, format('%s.%s', tbl, fld) as desirede_value from my_data
where val ilike 'lille';
tbl | fld | val | desirede_value
--------+------------+-------+-------------------
Table1 | frenchcity | Lille | Table1.frenchcity
Table2 | intlcity | Lille | Table2.intlcity
(2 rows)