Building Dynamic queries Postgres - sql

I have table which stores details of each entity(employee, department). I want to build dynamic query from that table.
CREATE TABLE MyTable
(
Id int primary key,
EntityId int,
ColumnName varchar(100),
tablename varchar(100)
);
INSERT INTO MyTable (Id, EntityId, ColumnName, tableName)
VALUES (1,1,'name','employee');
INSERT INTO MyTable (Id, EntityId, ColumnName, tableName)
VALUES (2,1,'id','employee');
INSERT INTO MyTable (Id, EntityId, ColumnName, tableName)
VALUES (3,1,'salary','employee');
INSERT INTO MyTable (Id, EntityId, ColumnName, tableName)
VALUES (4,2,'name','departement');
INSERT INTO MyTable (Id, EntityId, ColumnName, tableName)
VALUES (5,2,'location','departement');
INSERT INTO MyTable (Id, EntityId, ColumnName, tableName)
VALUES (6,2,'id','departement');
Above is my table and insert scripts, How can I write a query which gives me output something like below.
SELECT id,name,salary from employee
SELECT id,location,name from departement
If i have multiple entity I should multiple select statements.

If despite the discouraging comments you still want to consider this approach, here is the query that constructs one query per entity:
SELECT entityid,
'SELECT ' ||
string_agg(columnname, ', ' ORDER BY id) ||
' FROM ' ||
tablename ||
';' AS query
FROM mytable
GROUP BY entityid, tablename;
Result with your example:
entityid | query
----------+---------------------------------------------
1 | SELECT name, id, salary FROM employee;
2 | SELECT name, location, id FROM departement;
(2 rows)

Related

SQL Dynamically update duplicate row values to be unique

The Problem
I need to update a table so that any duplicate rows are updated to have unique values.
The Catch
I need to dynamically ensure that the value I am updating the duplicate row to is also unique.
My Solution So Far (with test case)
CREATE TABLE #temp (name nvarchar(100), ID uniqueidentifier)
INSERT INTO #temp (Name, ID)
VALUES ('Duplicate', '32208C09-C0C3-408C-AB60-273811722194')
INSERT INTO #temp (Name, ID)
VALUES ('Duplicate', '32208C09-C0C3-408C-AB60-273811722194')
INSERT INTO #temp (Name, ID)
VALUES ('Duplicate (2)', '32208C09-C0C3-408C-AB60-273811722194')
;WITH cte AS (
SELECT Name
, ROW_NUMBER() OVER (PARTITION BY Name, ID ORDER BY Name) RowNum
FROM #temp
)
UPDATE cte
SET Name = CONCAT(Name, ' (', RowNum, ')')
WHERE RowNum > 1
SELECT * FROM #temp
DROP TABLE #temp
As you can tell, this will update the table so there is only one row with the name 'Duplicate' but two rows with the name 'Duplicate (2)'. How can I check and account for duplicates in the value I am updating to?
You could use another CTe which gets you the highest Number and then use that to generate the "next" number.
for 2 or more digits you need to adapt it
CREATE TABLE #temp (name nvarchar(100), AssetMakeID uniqueidentifier)
INSERT INTO #temp (Name, AssetMakeID)
VALUES ('Duplicate', '32208C09-C0C3-408C-AB60-273811722194')
INSERT INTO #temp (Name, AssetMakeID)
VALUES ('Duplicate', '32208C09-C0C3-408C-AB60-273811722194')
INSERT INTO #temp (Name, AssetMakeID)
VALUES ('Duplicate (2)', '32208C09-C0C3-408C-AB60-273811722194')
;WITH CTE1 AS (SELECT
MAX( COALESCE(REPLACE(REPLACE(SUBSTRING(name, PATINDEX('%([0-9])%', name), PATINDEX('%)%', name + 't') - PATINDEX('%(%',
name) + 1),'(','') ,')','') ,0)) hinum
,SUBSTRING(name,1, PATINDEX('% ([0-9])%', name) ) name
FROM #temp
WHERE SUBSTRING(name,1, PATINDEX('% ([0-9])%', name) ) IS NOT NULL
GROUP BY SUBSTRING(name,1, PATINDEX('% ([0-9])%', name) ) ),
cte AS (
SELECT #temp.Name
, CASE WHEN ROW_NUMBER() OVER (PARTITION BY #temp.Name, AssetMakeID ORDER BY #temp.Name) > 1 THEN
ROW_NUMBER() OVER (PARTITION BY #temp.Name, AssetMakeID ORDER BY #temp.Name) + hinum -1
ELSe ROW_NUMBER() OVER (PARTITION BY #temp.Name, AssetMakeID ORDER BY #temp.Name) END RowNum
FROM #temp LEFT JOIN CTE1 ON #temp.name = CTE1.name
)
UPDATE cte
SET Name = CONCAT(Name, ' (', RowNum, ')')
WHERE RowNum > 1
SELECT * FROM #temp
name
AssetMakeID
Duplicate
32208c09-c0c3-408c-ab60-273811722194
Duplicate (3)
32208c09-c0c3-408c-ab60-273811722194
Duplicate (2)
32208c09-c0c3-408c-ab60-273811722194
fiddle
Well the easy way is to use a unique string in the update so there is no way your update can cause a duplicate. The current timestamp (with milliseconds) works well. Like this:
UPDATE cte
SET Name = CONCAT(Name, ' (', RowNum, ') at ',convert(varchar(22),getdate(),126))
WHERE RowNum > 1
This will cope with one level of duplication e.g. 'Duplicate (2)' but not two e.g. 'Duplicate (2) (2)'.
Essentially just apply the same logic again in a second cte. In fact you should be able to do this using a recursive CTE to get it to work for all levels.
That said you could use a more unique method of de-duplicating names e.g. just add a guid and it will be unique.
WITH cte1 AS (
SELECT Name, Id
, ROW_NUMBER() OVER (PARTITION BY Name, ID ORDER BY Name) RowNum
-- You should already have one, but if not generate it
, ROW_NUMBER() OVER (ORDER BY Name) UniqueId
FROM #temp
), cte2 as (
SELECT NewName Name, RowNum, UniqueId
, ROW_NUMBER() OVER (PARTITION BY NewName, ID ORDER BY NewName) RowNum2
FROM cte1
CROSS APPLY (
VALUES (CASE WHEN RowNum = 1 THEN Name ELSE CONCAT(Name, ' (', RowNum, ')') END)
) n (NewName)
)
UPDATE c1 SET
Name = CASE WHEN RowNum2 = 1 THEN c2.Name ELSE CONCAT(c2.Name, ' (', RowNum2, ')') END
FROM cte1 c1
INNER JOIN cte2 c2 on c2.UniqueId = c1.UniqueId
WHERE c1.RowNum > 1 or RowNum2 > 1;
I am going to choose another answer as the correct answer since I personally prefer it, but I thought I'd post what I ended up doing myself.
DROP TABLE IF EXISTS #temp
CREATE TABLE #temp (name nvarchar(100), ID uniqueidentifier)
INSERT INTO #temp (Name, ID)
VALUES ('Duplicate', '32208C09-C0C3-408C-AB60-273811722194')
INSERT INTO #temp (Name, ID)
VALUES ('Duplicate', '32208C09-C0C3-408C-AB60-273811722194')
INSERT INTO #temp (Name, ID)
VALUES ('Duplicate (2)', '32208C09-C0C3-408C-AB60-273811722194')
DECLARE #doWhileTrueFlag bit = 1
WHILE (#doWhileTrueFlag = 1)
BEGIN
;WITH cte AS (
SELECT
Name,
ROW_NUMBER() OVER (PARTITION BY Name, ID ORDER BY Name) RowNum
FROM #temp
)
UPDATE cte
SET Name = CONCAT(Name, ' (', RowNum, ')')
WHERE RowNum > 1
SET #doWhileTrueFlag = CASE
WHEN ##ROWCOUNT > 0 THEN 1
ELSE 0
END
END
SELECT * FROM #temp
DROP TABLE #temp
This performs the update I was already doing in a loop until no more updates are done. A rather inelegant solution, but the names created are prettier for the clients.

Update Table 1 memo field with values from table 2 in a one-to-many relationship

I have 2 tables I would like to update one column in table 1 with values from table 2 where id=id. However table 2 has many rows matching table 1 and all rows of table 2 would need to be updated to 1 row in table 1
Table_A
id | all_names |
---+-----------------+
1 |AB CD FG HI |
2 | |
** Table_B **
id | name |
---+-------+
1 | |
2 | Jon |
2 | Mike |
After the update Table 1 should look like
id | all_names |
---+-----------------+
1 |AB CD FG HI |
2 |Jon Mike |
I tried
update a
set a.all_names = TRIM(a.all_names) + b.name + ' '
from table_a a, table_b b
where a.id = b.id
All I end up getting is an empty all_names in table_a
Any idea?
I can't really see any other way of doing this other than through a loop.
DECLARE #id int
DECLARE #name varchar(50)
SELECT * INTO #temp FROM TABLE_B
WHILE EXISTS (SELECT 1 FROM #temp)
BEGIN
SELECT #id = (SELECT TOP 1 id from #temp)
SELECT #name = (SELECT TOP 1 [name] from #temp where id = #id)
UPDATE A
SET all_names = LTRIM(RTRIM(all_names + CHAR(32) + #name))
FROM Table_A A
WHERE A.id = #id
DELETE FROM #temp WHERE id = #id and [name] = #name
END
DROP TABLE #temp
The query puts the contents of table B into a temporary table, and removes the row once it has used it. So essentially all names keeps the same value through the loop for its own ID, except a space + the next name gets added each time. I've added a trim to the update as well to prevent leading / trailing spaces.
I don't know if this helps, but this is an Oracle version strictly using SQL. You didn't mention it in your requirements, but the second merge prevents duplicate entries in the row:
Create tables and insert sample rows
DROP TABLE table_a;
DROP TABLE table_b;
CREATE TABLE table_a
(
id INTEGER
, all_names VARCHAR2 (128)
);
CREATE TABLE table_b
(
id INTEGER
, name VARCHAR2 (10)
);
INSERT INTO table_a (id, all_names)
VALUES (1, 'AB CD FG HI');
INSERT INTO table_a (id, all_names)
VALUES (2, NULL);
INSERT INTO table_b (id, name)
VALUES (1, NULL);
INSERT INTO table_b (id, name)
VALUES (2, 'Jon');
INSERT INTO table_b (id, name)
VALUES (2, 'Mike');
COMMIT;
Merge allowing duplicates
MERGE INTO table_a ta
USING (SELECT DISTINCT id, LISTAGG (name, ' ') WITHIN GROUP (ORDER BY name) OVER (PARTITION BY id) names
FROM table_b) tb
ON (ta.id = tb.id)
WHEN MATCHED
THEN
UPDATE SET all_names = all_names || tb.names
WHEN NOT MATCHED
THEN
INSERT (
ta.id, ta.all_names
)
VALUES (
tb.id, tb.names
);
SELECT *
FROM table_a;
ROLLBACK;
Merge eliminating duplicates
MERGE INTO table_a ta
USING (SELECT DISTINCT id, LISTAGG (name, ' ') WITHIN GROUP (ORDER BY name) OVER (PARTITION BY id) names
FROM (WITH
aset
AS
(SELECT id, TRIM (all_names) || ' ' AS all_names
FROM table_a),
bset (id, name, REMAINDER)
AS
(SELECT id
, SUBSTR (all_names, 1, INSTR (all_names, ' ') - 1) name
, SUBSTR (all_names, INSTR (all_names, ' ') + 1) REMAINDER
FROM aset
UNION ALL
SELECT id
, SUBSTR (REMAINDER, 1, INSTR (REMAINDER, ' ') - 1) name
, SUBSTR (REMAINDER, INSTR (REMAINDER, ' ') + 1) REMAINDER
FROM bset
WHERE name IS NOT NULL)
SELECT id, name
FROM bset
WHERE name IS NOT NULL
UNION
SELECT id, name
FROM table_b
WHERE name IS NOT NULL)) tb
ON (ta.id = tb.id)
WHEN MATCHED
THEN
UPDATE SET all_names = tb.names
WHEN NOT MATCHED
THEN
INSERT (ta.id, ta.all_names)
VALUES (tb.id, tb.names);
SELECT *
FROM table_a;
--ROLLBACK;
What I ended up doing
Declare #Crs cursor as select * from Table_B; //Temp Table
open #crs;
while fetch #crs do
update Table_A set all_names=ifnull(Table_B,'')+trim(#crs.name)+' ' where
id=#Crs.id;
end while;
close #crs;
This uses the least of lines and is elegant

Increment an ID within an INSERT INTO statement in AS400

Is there a way to insert a new record to a table that has an non-unique id and set this id to the next number in the same SQL statement?
Something like
INSERT INTO T1 (id, fname, lname)
VALUES ([last id + 1], 'Dan', 'Thomson');
this probably works
INSERT INTO T1 (id, fname, lname)
VALUES (ifnull((select max(id) from T1),0) + 1, 'Dan', 'Thomson')
DECLARE #COUNTER INT;
SET #COUNTER = 1;
WHILE(#COUNTER <= XXX)
BEGIN
INSERT INTO T1 (id, fname, lname)
VALUES (#COUNTER, #FNAME , #LNAME);
SET #COUNTER = #COUNTER + 1;
END
Built-in function MAX :
INSERT INTO T1 (id, fname, lname)
SELECT ifnull(MAX(id)+ 1,1), 'Dan', 'Thomson' FROM T1
Insert and set value with max()+1 problems
SELECT MAX(col) +1 is not safe -- it does not ensure that you aren't inserting more than one customer with the same customer_id value, regardless if selecting from the same table or any others.
For performance, add an index:
CREATE INDEX T1_INDEX1 ON T1 (id) DESC
References:
MAX()
CREATE INDEX
Index Advisor, Show Statements
Accelerated analytics - faster aggregations using the IBM DB2 for i encoded vector index (EVI) technology
Unit Test
-- Create Table
SET SCHEMA QTEMP;
CREAT TABLE testtbl (
id integer not null default,
fname char(10) not null default,
lname char(10) not null default)
;
-- Initialize Table with Data
insert into
testtbl ( id , fname, lname)
values
( 1, 'fname1', 'lname_1'),
( 2, 'fname2', 'lname_2'),
( 2, 'fname3', 'lname_3'),
( 3, 'fname4', 'lname_4')
;
-- Test Insert Statement
INSERT INTO
testtbl ( id, fname, lname )
SELECT ifnull(MAX( id ) + 1, 1), 'Dan', 'Thomson' FROM testtbl;
--Confirm Expectation
select * from testtbl;
ID FNAME LNAME
-------------------------------------
1 fname1 lname_1
2 fname2 lname_2
3 fname4 lname_4
4 Dan Thomson
if you want insert all content of table with unique id start with max +1
INSERT INTO T1 (id, fname, lname)
select
rownumber() over() + ifnull((select max(T1.id) from T1), 0),
T2.zone1, T2.zone2
from T2

Query to Append a Number to a Record it it finds a duplicate

I have an sql table with the following fields: Letter, Number, Result
Title Name Result
Mr Mark
Mr Mark
Mr Luke
Mr John
Mr John
I need to create an update query to have the result as
Title Name Result
Mr Mark MrMark
Mr Mark MrMark2
Mr Luke MrLuke
Mr John MrJohn
Mr John MrJohn2
Note that the second and the fifth record had a number 2 appended since it already found the same record (same Title and Name) previously.
Please help.
If it is MS SQL, try using ROW_NUMBER and PARTITION BY?
DECLARE #temp TABLE (Title NVARCHAR(200), Name NVARCHAR(200), Result NVARCHAR(200));
INSERT #temp
SELECT 'Mr', 'Mark', NULL
UNION ALL
SELECT 'Mr', 'Mark', NULL
UNION ALL
SELECT 'Mr', 'Luke', NULL
UNION ALL
SELECT 'Mr', 'John', NULL
UNION ALL
SELECT 'Mr', 'John', NULL
SELECT * FROM #temp
DECLARE #tempWithOrdering TABLE (RowNum INT, Title NVARCHAR(200), Name NVARCHAR(200), Result NVARCHAR(200));
INSERT #tempWithOrdering
SELECT ROW_NUMBER() OVER (PARTITION BY Name ORDER BY Title ), Title, Name, Result FROM #temp
SELECT * FROM #tempWithOrdering
SELECT
Title,
Name,
Result = (
SELECT TOP(1) Name +
CASE RowNum
WHEN t1.RowNum THEN ''
ELSE CAST(t1.RowNum AS NVARCHAR(12))
END
FROM #tempWithOrdering
WHERE Name = t1.Name
)
FROM #tempWithOrdering t1
Assuming you are using sql server and the duplicate is on the field 'Name' .Try this.
Use Analytic fn ROW_NUMBER() ;
If it is Oracle use || instead of +
WITH TEMP AS
(
SELECT Title , Name,
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY bill_period) AS RK
FROM TABLE1
)
SELECT Title , Name,Title + Name +RK FROM TEMP;

Query to display output horizontally

I need to display a query output in a horizontal manner. I have some example data
create table TestTable (id number, name varchar2(10))
insert into TestTable values (1, 'John')
insert into TestTable values (2, 'Mckensy')
insert into TestTable values (3, 'Valneech')
insert into TestTable values (4, 'Zeebra')
commit
select * from TestTable
This gets the output in a vertical view.
ID Name
==========
1 John
2 Mckensy
3 Valneech
4 Zeebra
However, I need to display it horizontally.
ID 1 2 3 4
Name John Mckensy Valneech Zeebra
How can one do this?
To pivot, you should use the pivot clause of the select statement:
select *
from testtable
pivot ( max(name)
for id in (1,2,3,4)
)
This is not particularly pretty to do in SQL, so you should consider carefully whether this is what you want to do. I normally use Oracle Base for pivoting examples but there are many out there.
Here's a little SQL Fiddle to demonstrate.
Maybe it will help you:
select 'id', LISTAGG(id, ' ') WITHIN GROUP (ORDER BY name)
from testtable
union
select 'name', LISTAGG(name, ' ') WITHIN GROUP (ORDER BY name)
from testtable
EDIT:
or with pivot:
create table TestTable2 (id varchar2(30), name varchar2(10));
insert into TestTable2 values ('id', 'name');
insert into TestTable2
select cast(id as varchar2(30)) as id , name
from testtable
select *
from testtable2
pivot ( max(name)
for id in ('id',1,2,3,4)
)
PIVOT operator is what you are looking for.