Convert JSONB Keys to Columns - sql

I have a user table users containing id , name and information of type jsonb
User Table
id
name
information
1001
Alice
{"1":"Google","2":"1991-02-08"}
1002
Bob
{"1":"StackOverflow","3":"www.google.com"}
I have another Table having all the profile fields values named ProfileFields
profilefieldid
Value
1
Company
2
DateOfBirth
3
ProfileLink
The information jsonb column can only have keys present in the ProfileField Table.
You can expect the data is coming from a real world and the profile field will be updating.
I would like to output export this table in the format of
id
name
Company
DateOfBirth
ProfileLink
1001
Alice
Google
1991-02-08
1002
Bob
StackOverflow
www.google.com
My Trails :-
I was able to map profilefieldid with its respective values
SELECT
id ,
name ,
(SELECT STRING_AGG(CONCAT((SELECT "title" FROM "profile_fields" WHERE CAST("key" AS INTEGER)="id"),':',REPLACE("value",'"','')),',') FROM JSONB_EACH_TEXT("profile_fields")) "information"
FROM "users" ORDER BY "id";
I tried to use json_to record() but since the profilefield can have dynamic keys i was not able to come up with a solution because in the AS block i need to specify the columns in advance.
I sometimes encounter errors in Select Statement as Subquery returning more than 1 column.
Any suggestions and Solutions are greatly appreciated and welcomed.
Let me know if i need to improve my db structure , like its not in 2nd NormalForm or not well structured like that. Thank You

There is no way you can make this dynamic. A fundamental restriction of the SQL language is, that the number, names and data type of all columns of a query must be known before the database starts retrieving data.
What you can do though is to create a stored procedure that generates a view with the needed columns:
create or replace procedure create_user_info_view()
as
$$
declare
l_columns text;
begin
select string_agg(concat('u.information ->> ', quote_literal(profilefield_id), ' as ', quote_ident(value)), ', ')
into l_columns
from profile_fields;
execute 'drop view if exists users_view cascade';
execute 'create view users_view as select u.id, u.name, '||l_columns||' from users u';
end;
$$
language plpgsql;
After the procedure is executed, you can run select * from users_view and see all profile keys as columns.
If you want, you can create a trigger on the table profile_fields that re-creates the view each time the table is changed.
Online example

Related

Oracle SQL - using column name in one table as query parameter within another

Help needed please! Here is the problem:
I have 2 tables (one transactional, and one lookup/control) as below:
transactional table (A):
TXID, NAME, DESCRIPTION, GROUP, DATE, TYPE, AMOUNT, etc.
(e.g. 12345, 'SAMPLE TRANSACTION','test','TXGROUP1','FEB.15 2019',500.00, etc.)
lookup/control table (B):
COLID, COLNAME, FLAG
(e.g. 1,'NAME', 0; 2,'DATE',1, etc.)
In this scenario, entries for COLNAME in table B refer to actual column names in table A (i.e. B.COLNAME = 'DATE' refers to A.DATE)
Problem is, I need to write a query that fetches all COLNAME values in table B, and select their corresponding grouped value from table A. For example:
since B.COLNAME contains 'DATE', select max (DATE) from table A grouping by A.NAME
What I've tried:
select NAME, (SELECT column_name FROM all_tab_columns where table_name like '%TABLE_A%' AND ROWNUM = 1 GROUP BY COLUMN_NAME) AS COL from TABLE_A;
but this only gives me the literal value of the column name - (i.e. 'SAMPLE TRANSACTION', 'DATE') - NOT THE DERIVED VALUE as in what I actually need, if I were to run the query manually would be select NAME, DATE AS COL from TABLE_A;
and I might expect something like:
NAME, COL (e.g. 'SAMPLE TRANSACTION', 'FEB.15 2019')
Ideally am trying to do this only in raw SQL if possible (i.e. no stored procedures, PL/SQL, dynamic, etc....) but... am definitely open to anything that can just make it work.
input and/or suggestions would be very much greatly appreciated. Environment is Oracle 11g I believe, though I suspect this may not make a huge difference.
It is possible to run dynamic SQL in SQL, but the solutions are painful. The simplest way uses the package DBMS_XMLGEN and does not require any additional PL/SQL objects.
The below example works but is unrealistically simple. A real version would have to deal with many type conversion issues, retrieve other values, etc.
--Read a value based on the CONTROL table.
select
to_number(extractvalue(xml, '/ROWSET/ROW/COL')) COL
from
(
select xmltype(dbms_xmlgen.getxml(v_sql)) xml
from
(
select 'select '||colname||' col from transaction' v_sql
from control
)
);
COL
---
2
The results are based on this sample schema:
--Sample schema:
create table control
(
COLID number,
COLNAME varchar2(4000),
FLAG number
);
insert into control values(1,'NAME',1);
create table transaction
(
TXID number,
NAME varchar2(100),
DESCRIPTION varchar2(4000),
the_GROUP varchar2(100),
the_DATE date,
TYPE varchar2(100),
AMOUNT number
);
insert into transaction values(1,2,3,4,sysdate,6,7);
commit;
If you have more complicated query needs, for example if you need to return an unknown number of columns, you'll need to install something like my open source program Method4. That program allows dynamic SQL in SQL, but it requires installing some new objects first.
In practice, this level of dynamic SQL is rarely necessary. It's usually best to find a simpler way to solve the problem.

Oracle DB: any function to select all the database columns except for one (which is known)? [duplicate]

This question already has answers here:
Can you SELECT everything, but 1 or 2 fields, without writer's cramp?
(12 answers)
Closed 5 years ago.
I am using Oracle Database and I need to realize a query which retrieves all the values of a table record (for a specific WHERE condition), except for one which is known.
Imagine to have the following table:
Sample table
Where you do not want to retrieve the "Age" column, but where - in next releases of the software - the table could have more columns respect to the ones actually present.
Is there any command in Oracle which excludes a specific column (always known, as in the example "Age") and allows me to retrieve all the other values?
Thanks in advance!
You can make that particular column Invisible using following query:
alter table TABLE_NAME modify COLUMN_NAME INVISIBLE;
This will exclude that column from select * statement unless and until you specify that particular column in select clause like below:
select COLUMN_NAME from TABLE_NAME;
From Your sample data:
alter table SAMPLE_TABLE modify Age INVISIBLE;
select * FROM SAMPLE_TABLE will produce
select FirstName, LastName, Address, City, Age from SAMPLE_TABLE will produce:
There are several approaches
1)You can set column UNUSED.It won't be retrieved (and it wont be used) with the queries. This would be permanent. You can't get then column back, the only allowed op would be DROP UNUSED COLUMNS.
ALTER TABLE sample_table SET UNUSED(age);
2)You can set column INVISIBLE, this is temporary. It won't be retrieved, unless you explicitly reference it in SELECT query.
ALTER TABLE sample_table MODIFY age INVISIBLE;
// to change it back to VISIBLE
ALTER TABLE sample_table MODIFY age VISIBLE;
3)Create VIEW without age column and then query view instead of querying TABLE.
CREATE VIEW sample_table_view AS
SELECT first_name, last_name, address, city FROM sample_table;

Is it possible to CREATE TABLE with a column that is a combination of other columns in the same table?

I know that the question is very long and I understand if someone doesn't have the time to read it all, but I really wish there is a way to do this.
I am writing a program that will read the database schema from the database catalog tables and automatically build a basic application with the information extracted from the system catalogs.
Many tables in the database can be just a list of items of the form
CREATE TABLE tablename (id INTEGER PRIMARY KEY, description VARCHAR NOT NULL);
so when a table has a column that references the id of tablename I just resolve the descriptions by querying it from the tablename table, and I display a list in a combo box with the available options.
There are some tables however that cannot directly have a description column, because their description would be a combination of other columns, lets take as an example the most important of those tables in my first application
CREATE TABLE bankaccount (
bankid INTEGER NOT NULL REFERENCES bank,
officeid INTEGER NOT NULL REFERENCES bankoffice,
crc INTEGER NOT NULL,
number BIGINT NOT NULL
);
this as many would know, would be the full account number for a bank account, in my country it's composed as follows
[XXXX][XXXX][XX][XXXXXXXXXX]
^ ^ ^ ^
bank id | crc account number
|
|_ bank office id
so that's the reason of the way my bankaccount table is structured as is.
Now, I would like to have the complete bank account number in a description column so I can display it in the application without giving a special treatment to this situation, since there are some other tables with similar situation, something like
CREATE TABLE bankaccount (
bankid INTEGER NOT NULL REFERENCES bank,
officeid INTEGER NOT NULL REFERENCES bankoffice,
crc INTEGER NOT NULL,
number BIGINT NOT NULL,
description VARCHAR DEFAULT bankid || '-' || officeid || '-' || crc || '-' || number
);
Which of course doesn't work since the following error is raised1
ERROR: cannot use column references in default expression
If there is any different approach that someone can suggest, please feel free to suggest it as an answer.
1 This is the error message given by PostgreSQL.
What you want is to create a view on your table. I'm more familiar with MySQL and SQLite, so excuse the differences. But basically, if you have table 'AccountInfo' you can have a view 'AccountInfoView' which is sort of like a 'stored query' but can be used like a table. You would create it with something like
CREATE VIEW AccountInfoView AS
SELECT *, CONCATENATE(bankid,officeid,crc,number) AS FullAccountNumber
FROM AccountInfo
Another approach is to have an actual FullAccountNumber column in your original table, and create a trigger that sets it any time an insert or update is performed on your table. This is usually less efficient though, as it duplicates storage and takes the performance hit when data are written instead of retrieved. Sometimes that approach can make sense, though.
What actually works, and I believe it's a very elegant solution is to use a function like this one
CREATE FUNCTION description(bankaccount) RETURNS VARCHAR AS $$
SELECT
CONCAT(bankid, '-', officeid, '-', crc, '-', number)
FROM
bankaccount this
WHERE
$1.bankid = this.bankid AND
$1.officeid = this.officeid AND
$1.crc = this.crc AND
$1.number = this.number
$$ LANGUAGE SQL STABLE;
which would then be used like this
SELECT bankaccount.description FROM bankaccount;
and hence, my goal is achieved.
Note: this solution works with PostgreSQL only AFAIK.

PostgreSQL dynamic table access

I have a products schema and some tables there.
Each table in products schema has an id, and by this id I can get this table name, e.g.
products
\ product1
\ product2
\ product3
I need to select info from dynamic access to appropriate product, e.g.
SELECT * FROM 'products.'(SELECT id from categories WHERE id = 7);
Of course, this doesn't work...
How I can do something like that in PostgreSQL?
OK, I found a solution:
CREATE OR REPLACE FUNCTION getProductById(cid int) RETURNS RECORD AS $$
DECLARE
result RECORD;
BEGIN
EXECUTE 'SELECT * FROM ' || (SELECT ('products.' || (select category_name from category where category_id = cid) || '_view')::regclass) INTO result;
RETURN result;
END;
$$ LANGUAGE plpgsql;
and to select:
SELECT * FROM getProductById(7) AS b (category_id int, ... );
works for PostgreSQL 9.x
If you can change your database layout to use partitioning instead, that would probably be the way to go. Then you can just access the "master" table as if it were one table rather than multiple subtables.
You could create a view that combines the tables with an extra column corresponding to the table it's from. If all your queries specify a value for this extra column, the planner should be smart enough to skip scanning all the rest of the tables.
Or you could write a function in PL/pgSQL, using the EXECUTE command to construct the appropriate query after fetching the table name. The function can even return a set so it can be used in the FROM clause just as you would a table reference. Or you could just do the same query construction in your application logic.
To me, it sounds like you've a major schema design problem: shouldn't you only have one products table with a category_id in it?
Might you be maintaining the website mentioned in this article?
http://thedailywtf.com/Articles/Confessions-The-Shopping-Cart.aspx

Retrieve inserted row ID in SQL

How do I retrieve the ID of an inserted row in SQL?
Users Table:
Column | Type
--------|--------------------------------
ID | * Auto-incrementing primary key
Name |
Age |
Query Sample:
insert into users (Name, Age) values ('charuka',12)
In MySQL:
SELECT LAST_INSERT_ID();
In SQL Server:
SELECT SCOPE_IDENTITY();
In Oracle:
SELECT SEQNAME.CURRVAL FROM DUAL;
In PostgreSQL:
SELECT lastval();
(edited: lastval is any, currval requires a named sequence)
Note: lastval() returns the latest sequence value assigned by your session, independently of what is happening in other sessions.
In SQL Server, you can do (in addition to the other solutions already present):
INSERT INTO dbo.Users(Name, Age)
OUTPUT INSERTED.ID AS 'New User ID'
VALUES('charuka', 12)
The OUTPUT clause is very handy when doing inserts, updates, deletes, and you can return any of the columns - not just the auto-incremented ID column.
Read more about the OUTPUT clause in the SQL Server Books Online.
In Oracle and PostgreSQL you can do this:
INSERT INTO some_table (name, age)
VALUES
('charuka', 12)
RETURNING ID
When doing this through JDBC you can also do that in a cross-DBMS manner (without the need for RETURNING) by calling getGeneratedKeys() after running the INSERT
I had the same need and found this answer ..
This creates a record in the company table (comp), it the grabs the auto ID created on the company table and drops that into a Staff table (staff) so the 2 tables can be linked, MANY staff to ONE company. It works on my SQL 2008 DB, should work on SQL 2005 and above.
===========================
CREATE PROCEDURE [dbo].[InsertNewCompanyAndStaffDetails]
#comp_name varchar(55) = 'Big Company',
#comp_regno nchar(8) = '12345678',
#comp_email nvarchar(50) = 'no1#home.com',
#recID INT OUTPUT
-- The '#recID' is used to hold the Company auto generated ID number that we are about to grab
AS
Begin
SET NOCOUNT ON
DECLARE #tableVar TABLE (tempID INT)
-- The line above is used to create a tempory table to hold the auto generated ID number for later use. It has only one field 'tempID' and its type INT is the same as the '#recID'.
INSERT INTO comp(comp_name, comp_regno, comp_email)
OUTPUT inserted.comp_id INTO #tableVar
-- The 'OUTPUT inserted.' line above is used to grab data out of any field in the record it is creating right now. This data we want is the ID autonumber. So make sure it says the correct field name for your table, mine is 'comp_id'. This is then dropped into the tempory table we created earlier.
VALUES (#comp_name, #comp_regno, #comp_email)
SET #recID = (SELECT tempID FROM #tableVar)
-- The line above is used to search the tempory table we created earlier where the ID we need is saved. Since there is only one record in this tempory table, and only one field, it will only select the ID number you need and drop it into '#recID'. '#recID' now has the ID number you want and you can use it how you want like i have used it below.
INSERT INTO staff(Staff_comp_id)
VALUES (#recID)
End
-- So there you go. I was looking for something like this for ages, with this detailed break down, I hope this helps.