select columns in select query from another mapping table - postgresql

I am looking for select query with dynamic column names that can be derived from another table. Below the sample data and query I am looking to get.
create table mapping_tmp (
string_number varchar,
mapping_name varchar
)
create table fact_tmp (
product varchar,
product_family varchar,
string1 varchar,
string2 varchar,
string3 varchar,
string4 varchar,
string5 varchar
)
insert into mapping_tmp values ('string1','commodity');
insert into mapping_tmp values ('string2','real commodity');
insert into mapping_tmp values ('string3','country');
insert into mapping_tmp values ('string4','region');
insert into mapping_tmp values ('string5','area');
insert into fact_tmp values ('P1','PF1','ABC1','DEF1','GHI1','JKL1','MNO1');
insert into fact_tmp values ('P2','PF2','ABC2','DEF2','GHI2','JKL2','MNO2');
insert into fact_tmp values ('P3','PF3','ABC3','DEF3','GHI3','JKL3','MNO3');
insert into fact_tmp values ('P4','PF4','ABC4','DEF4','GHI4','JKL4','MNO4');
insert into fact_tmp values ('P5','PF5','ABC5','DEF5','GHI5','JKL5','MNO5');
insert into fact_tmp values ('P6','PF6','ABC6','DEF6','GHI6','JKL6','MNO6');
Expected output, select fields should be taken from mapping_tmp and those fields data should be displayed in select result.
select product,
(select string_number from mapping_tmp where mapping_name = 'country') as country,
(select string_number from mapping_tmp where mapping_name = 'area') as area
from fact_tmp;
The actual query is
select product, string3 as country, string5 as area from fact_tmp;
and the output:
product country area
1 P1 GHI1 MNO1
2 P2 GHI2 MNO2
3 P3 GHI3 MNO3
4 P4 GHI4 MNO4
5 P5 GHI5 MNO5
6 P6 GHI6 MNO6
I am looking for simple sql query, I cannot use stored procedure or function in application.

Related

Seperate Input/Output type for a Table in PostgreSQL

I was wondering if it is possible to have the input for a column be one string, with the output being a different string through some dictionary in PostgreSQL. I do know how to use CASE to convert numbers to strings using a SELECT statement, however, I was hoping to create a table such that inputs only require numbers but outputs always give strings.
As an example, for currency USD, CDN and GBP, where 1 = USD, 2 = CDN and 3 = GBP, an example would be:
CREATE TABLE test_table (
currency CHAR (1) CHECK (currency IN ('1','2','3'))
)
Where I could do this:
INSERT INTO test_table (currency)
VALUES ('1')
INSERT INTO test_table (currency)
VALUES ('1')
INSERT INTO test_table (currency)
VALUES ('2')
INSERT INTO test_table (currency)
VALUES ('3')
INSERT INTO test_table (currency)
VALUES ('3')
and the output would look like this:
You can use a CASE expression:
select case currency
when '1' then 'USD'
when '2' then 'CDN'
when '3' then 'GBP'
when '4' then 'EUR'
end as currency
from test_table;
But a better solution would be to create a currency table:
create table currency
(
id integer primary key,
currency_code varchar(3)
);
Then create a foreign key from your base table to the lookup table:
create table test_table
(
...
currency_id integer not null references currency,
...
);
Then use a join to display the code:
select c.code
from test_table t
join currency c on c.id = t.currency_id;

SQL Stored Procedure with Table Valued Parameters and Hierarchical data [duplicate]

Code:
CREATE TYPE dbo.tEmployeeData AS TABLE
(
FirstName NVARCHAR(50),
LastName NVARCHAR(50),
DepartmentType NVARCHAR(10),
DepartmentBuilding NVARCHAR(50),
DepartmentEmployeeLevel NVARCHAR(10),
DepartmentTypeAMetadata NVARCHAR(100),
DepartmentTypeBMetadata NVARCHAR(100)
)
GO
CREATE PROC dbo.EmployeeImport
(#tEmployeeData tEmployeeData READONLY)
AS
BEGIN
DECLARE #MainEmployee TABLE
(EmployeeID INT IDENTITY(1,1),
FirstName NVARCHAR(50),
LastName NVARCHAR(50))
DECLARE #ParentEmployeeDepartment TABLE
(EmployeeID INT,
ParentEmployeeDepartmentID INT IDENTITY(1,1),
DepartmentType NVARCHAR(10))
DECLARE #ChildEmployeeDepartmentTypeA TABLE
(ParentEmployeeDepartmentID INT,
DepartmentBuilding NVARCHAR(50),
DepartmentEmployeeLevel NVARCHAR(10),
DepartmentTypeAMetadata NVARCHAR(100))
DECLARE #ChildEmployeeDepartmentTypeB TABLE
(ParentEmployeeDepartmentID INT,
DepartmentBuilding NVARCHAR(50),
DepartmentEmployeeLevel NVARCHAR(10),
DepartmentTypeBMetadata NVARCHAR(100))
-- INSERT CODE GOES HERE
SELECT * FROM #MainEmployee
SELECT * FROM #ParentEmployeeDepartment
SELECT * FROM #ChildEmployeeDepartmentTypeA
SELECT * FROM #ChildEmployeeDepartmentTypeB
END
GO
DECLARE #tEmployeeData tEmployeeData
INSERT INTO #tEmployeeData (FirstName, LastName, DepartmentType,
DepartmentBuilding, DepartmentEmployeeLevel,
DepartmentTypeAMetadata, DepartmentTypeBMetadata)
SELECT
N'Tom_FN', N'Tom_LN', N'A',
N'101', N'IV', N'Tech/IT', NULL
UNION
SELECT
N'Mike_FN', N'Mike_LN', N'B',
N'OpenH', N'XII', NULL, N'Med'
UNION
SELECT
N'Joe_FN', N'Joe_LN', N'A',
N'101', N'IV', N'Tech/IT', NULL
UNION
SELECT
N'Dave_FN', N'Dave_LN', N'B',
N'OpenC', N'XII', NULL, N'Lab'
EXEC EmployeeImport #tEmployeeData
GO
DROP PROC dbo.EmployeeImport
DROP TYPE dbo.tEmployeeData
Notes:
The table variables are replaced by real tables in live environment.
EmployeeID and ParentEmployeeDepartmentID columns' values don't always match each other. Live environment has more records in the udt (tEmployeeData) than just 4
Goal:
The udt (tEmployeeData) will be passed into the procedure
The procedure should first insert the data into the #MainEmployee table (and get the EmployeeIDs)
Next, the procedure should insert the data into the #ParentEmployeeDepartment table (and get the ParentEmployeeDepartmentID) - note EmployeeID is coming from the previous output.
Then, the procedure should split the child level data based on the DepartmentType ("A" = insert into #ChildEmployeeDepartmentTypeA and "B" = insert into #ChildEmployeeDepartmentTypeB).
ParentEmployeeDepartmentID from #ParentEmployeeDepartment should be used when inserting data into either #ChildEmployeeDepartmentTypeA or #ChildEmployeeDepartmentTypeB
The procedure should should run fast (need to avoid row by row operation)
Output:
#MainEmployee:
EmployeeID FirstName LastName
---------------------------------
1 Tom_FN Tom_LN
2 Mike_FN Mike_LN
3 Joe_FN Joe_LN
4 Dave_FN Dave_LN
#ParentEmployeeDepartment:
EmployeeID ParentEmployeeDepartmentID DepartmentType
-------------------------------------------------------
1 1 A
2 2 B
3 3 A
4 4 B
#ChildEmployeeDepartmentTypeA:
ParentEmployeeDepartmentID DepartmentBuilding DepartmentEmployeeLevel DepartmentTypeAMetadata
---------------------------------------------------------------------------------------------------------
1 101 IV Tech/IT
3 101 IV Tech/IT
#ChildEmployeeDepartmentTypeB:
ParentEmployeeDepartmentID DepartmentBuilding DepartmentEmployeeLevel DepartmentTypeAMetadata
----------------------------------------------------------------------------------------------------------
2 OpenH XII Med
4 OpenC XII Lab
I know I can use the OUTPUT clause after the insert and get EmployeeID and ParentEmployeeDepartmentID, but I'm not sure how to insert the right child records into right tables with right mapping to the parent table. Any help would be appreciated.
Here is my solution (based on the same answer I've linked to in the comments):
First, you must add another column to your UDT, to hold a temporary ID for the employee:
CREATE TYPE dbo.tEmployeeData AS TABLE
(
FirstName NVARCHAR(50),
LastName NVARCHAR(50),
DepartmentType NVARCHAR(10),
DepartmentBuilding NVARCHAR(50),
DepartmentEmployeeLevel NVARCHAR(10),
DepartmentTypeAMetadata NVARCHAR(100),
DepartmentTypeBMetadata NVARCHAR(100),
EmployeeId int
)
GO
Populating it with that new employeeId column:
DECLARE #tEmployeeData tEmployeeData
INSERT INTO #tEmployeeData (FirstName, LastName, DepartmentType,
DepartmentBuilding, DepartmentEmployeeLevel,
DepartmentTypeAMetadata, DepartmentTypeBMetadata, EmployeeId)
SELECT
N'Tom_FN', N'Tom_LN', N'A',
N'101', N'IV', N'Tech/IT', NULL, 5
UNION
SELECT
N'Mike_FN', N'Mike_LN', N'B',
N'OpenH', N'XII', NULL, N'Med', 6
UNION
SELECT
N'Joe_FN', N'Joe_LN', N'A',
N'101', N'IV', N'Tech/IT', NULL, 7
UNION
SELECT
N'Dave_FN', N'Dave_LN', N'B',
N'OpenC', N'XII', NULL, N'Lab', 8
Insert part goes here
Then, you use a table variable to map the inserted value from the employee table to the temp employee id in the data you sent to the procedure:
DECLARE #EmployeeidMap TABLE
(
temp_id int,
id int
)
Now, the trick is to populate the employee table with the MERGE statement instead of an INSERT...SELECT because you have to use values from both inserted and source data in the output clause:
MERGE INTO #MainEmployee USING #tEmployeeData AS sourceData ON 1 = 0 -- Always not matched
WHEN NOT MATCHED THEN
INSERT (FirstName, LastName)
VALUES (sourceData.FirstName, sourceData.LastName)
OUTPUT sourceData.EmployeeId, inserted.EmployeeID
INTO #EmployeeidMap (temp_id, id); -- populate the map table
From that point on it's simple, you need to join the data you sent to the #EmployeeidMap to get the actual employeeId:
INSERT INTO #ParentEmployeeDepartment (EmployeeID, DepartmentType)
SELECT Id, DepartmentType
FROM #tEmployeeData
INNER JOIN #EmployeeidMap ON EmployeeID = temp_id
Now you can use the data in #ParentEmployeeDepartment to map the actual values in ParentEmployeeDepartmentID to the data you sent:
Testing the inserts so far
SELECT FirstName,
LastName,
SentData.DepartmentType As [Dept. Type],
DepartmentBuilding As Building,
DepartmentEmployeeLevel As [Emp. Level],
DepartmentTypeAMetadata As [A Meta],
DepartmentTypeBMetadata As [B Meta],
SentData.EmployeeId As TempId, EmpMap.id As [Emp. Id], DeptMap.ParentEmployeeDepartmentID As [Dept. Id]
FROM #tEmployeeData SentData
INNER JOIN #EmployeeidMap EmpMap ON SentData.EmployeeId = temp_id
INNER JOIN #ParentEmployeeDepartment DeptMap ON EmpMap.id = DeptMap.EmployeeID
results:
FirstName LastName Dept. Type Building Emp. Level A Meta B Meta TempId Emp. Id Dept. Id
--------- -------- ---------- -------- ---------- ------ ------ ------ ----------- -----------
Dave_FN Dave_LN B OpenC XII NULL Lab 8 1 1
Joe_FN Joe_LN A 101 IV Tech/IT NULL 7 2 2
Mike_FN Mike_LN B OpenH XII NULL Med 6 3 3
Tom_FN Tom_LN A 101 IV Tech/IT NULL 5 4 4
I'm sure that from this point you can easily figure out the last 2 inserts yourself.

Inner join request to get the items available for the user

I have four tables:
CREATE TABLE t_users (
user_id varchar PRIMARY KEY,
user_email varchar
);
CREATE TABLE t_items (
item_id varchar PRIMARY KEY,
owner_id varchar not null references t_users(user_id),
title varchar
);
CREATE TABLE t_access_gropes (
access_group_id varchar PRIMARY KEY,
user_id varchar not null references t_users(user_id)
);
CREATE TABLE t_access_sets (
access_set_id varchar PRIMARY KEY,
item_id varchar not null references t_items(item_id),
access_group_id varchar not null references t_access_gropes(access_group_id)
);
With data:
INSERT INTO t_users VALUES ('us123', 'us123#email.com');
INSERT INTO t_users VALUES ('us456', 'us456#email.com');
INSERT INTO t_users VALUES ('us789', 'us789#email.com');
INSERT INTO t_items VALUES ('it123', 'us123', 'title1');
INSERT INTO t_items VALUES ('it456', 'us456', 'title2');
INSERT INTO t_items VALUES ('it678', 'us789', 'title3');
INSERT INTO t_items VALUES ('it323', 'us123', 'title4');
INSERT INTO t_items VALUES ('it764', 'us456', 'title5');
INSERT INTO t_items VALUES ('it826', 'us789', 'title6');
INSERT INTO t_items VALUES ('it568', 'us123', 'title7');
INSERT INTO t_items VALUES ('it038', 'us456', 'title8');
INSERT INTO t_items VALUES ('it728', 'us789', 'title9');
INSERT INTO t_access_gropes VALUES ('ag123', 'us123');
INSERT INTO t_access_gropes VALUES ('ag456', 'us456');
INSERT INTO t_access_gropes VALUES ('ag789', 'us789');
INSERT INTO t_access_sets VALUES ('as123', 'it123', 'ag123');
INSERT INTO t_access_sets VALUES ('as456', 'it456', 'ag123');
The t_access_gropes forms groups of users.
The t_access_sets forms security kits.
How to make a request to get the all items available for the user. Something like:
select *
from t_items
inner join t_users on t_items.owner_id = t_users.user_id
inner join t_access_gropes on t_users.user_id = t_access_gropes.user_id
inner join t_access_sets on t_items.item_id = t_access_sets.item_id
where t_access_gropes.user_id = 'us123';
Thank you.
select u.user_id, u.email, i.item_id, i.title
from t_users u
join t_items i on i.owner_id = u.user_id
where u.user_id = 'us123'
I believe this is what you want for your exact request!
Otherwise what you wrote is fine however I dont see the relevance in the tables you joined together as you directly connected your users table and items table together, which is why you wouldnt need to join the other two tables (groups & sets). Usually in some cases you find a table of user_items inbetween the users and items table.

aggregate multiple columns over dynamic pivot in sql

I'm creating a stored procedure that would allow the user to retrieve data from 2 tables by providing the PersonID number as a parameter.
I thought of using the pivot function to pivot the Data table dynamically by non-aggregating over multiple columns and retrieving data from ONE column in a different table. The 2 tables below are just sample data as I have over 100 columns for the data table, hence the dynamic part. The 2 tables doesn't have a common ID column but just a common column_name.
Here are the 2 tables:
Mapping Table:
CREATE table #table (
ID varchar(10) NOT NULL,
Column_Name varchar (255) NOT NULL,
Page_Num varchar(10) NOT NULL,
Line_Num varchar(10) NOT NULL,
Element_Num varchar(10) NOT NULL
)
INSERT INTO #table (ID,Column_Name,Page_Num,Line_Num,Element_Num) VALUES ('1','Name', 'DT-01', '200','20')
INSERT INTO #table (ID,Column_Name,Page_Num,Line_Num,Element_Num) VALUES ('2','SSN', 'DT-02', '220','10')
INSERT INTO #table (ID,Column_Name,Page_Num,Line_Num,Element_Num) VALUES ('3','City', 'DT-03', '300','11')
INSERT INTO #table (ID,Column_Name,Page_Num,Line_Num,Element_Num) VALUES ('4','StreetName', 'DT-04', '350','33')
INSERT INTO #table (ID,Column_Name,Page_Num,Line_Num,Element_Num) VALUES ('5','Sex', 'DT-05', '310','51')
Creates:
ID Column_Name Page_Num Line_Num Element_Num
_________________________________________________________________
1 Name DT-01 200 20
2 SSN DT-02 220 10
3 City DT-03 300 11
4 StreetName DT-04 350 33
5 Sex DT-05 310 51
Data table:
CREATE table #temp (
PersonID varchar (100) NOT NULL,
Name varchar(100) NOT NULL,
SSN varchar (255) NOT NULL,
City varchar(100) NOT NULL,
StreetName varchar(100) NOT NULL,
Sex varchar(100) NOT NULL
)
INSERT INTO #temp (PersonID,Name,SSN,City,StreetName,Sex) VALUES ('112','Joe','945890189', 'Lookesville', 'Broad st','Male')
INSERT INTO #temp (PersonID,Name,SSN,City,StreetName,Sex) VALUES ('140','Santana','514819926', 'Falls Church', 'Gane Rd', 'Female')
INSERT INTO #temp (PersonID,Name,SSN,City,StreetName,Sex) VALUES ('481','Wyatt','014523548','Gainesville', 'Westfield blvd', 'Male')
INSERT INTO #temp (PersonID,Name,SSN,City,StreetName,Sex) VALUES ('724','Brittany','551489230','Aldi', 'Ostrich rd', 'Female')
INSERT INTO #temp (PersonID,Name,SSN,City,StreetName,Sex) VALUES ('100','Giovanni','774451362','Paige', 'Company ln', 'Male')
Creates:
PersonID Name SSN City StreetName Sex
_______________________________________________________________________
112 Joe 945890189 Lookesville Broad st Male
140 Santana 514819926 Falls Church Gane Rd Female
481 Wyatt 014523548 Gainesville Westfield rd Male
724 Brittany 551489230 Aldi Ostrich rd Female
100 Giovanni 774451362 Paige Company ln Male
The end result should be:
Example: User enters parameter PersonID = 140
Column_name Page_Num Line_Num Element_Num Data
_____________________________________________________________________________
Name DT-01 200 20 Santana
SSN DT-02 220 10 514819926
City DT-03 300 11 Falls Church
StreetName DT-04 350 33 Gane Rd
Sex DT-05 310 51 Female
... ... ... ... ...
and so on..
The following will dynamically unpivot a data row, and then perform a join on the field name with the def data.
If you want to run this query without a filter, I would suggest adding A.PersonID to the top SELECT and remove the WHERE
I should add, UNPIVOT would be more performant, but with this approach, there is no need to define and/or recast values. That said, the performance is still very respectable.
Example
Select D.*
,Data=C.Value
From #Temp A
Cross Apply (Select XMLData = cast((Select A.* For XML Raw) as xml)) B
Cross Apply (
Select Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From B.XMLData.nodes('/row') as X(r)
Cross Apply X.r.nodes('./#*') AS N(attr)
) C
Join #Table D on (C.Item=D.Column_Name)
Where PersonID=140
Returns
If it Helps with the Visualization, the CROSS APPLY C generates the following:
EDIT - As a Stored Procedure
CREATE PROCEDURE [dbo].[YourProcedureName](#PersonID int)
As
Begin
Set NoCount On;
Select D.*
,Data=C.Value
From YourPersonTableName A
Cross Apply (Select XMLData = cast((Select A.* For XML Raw) as xml)) B
Cross Apply (
Select Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From B.XMLData.nodes('/row') as X(r)
Cross Apply X.r.nodes('./#*') AS N(attr)
) C
Join YourObjectTableName D on (C.Item=D.Column_Name)
Where PersonID=#PersonID
End

What's wrong with this T-SQL MERGE statement?

I am new to MERGE, and I'm sure I have some error in my code.
This code will run and create my scenario:
I have two tables, one that is called TempUpsert that fills from a SqlBulkCopy operation (100s of millions of records) and a Sales table that holds the production data which is to be indexed and used.
I wish to merge the TempUpsert table with the Sales one
I am obviously doing something wrong as it fails with even the smallest example
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TempUpsert]') )
drop table TempUpsert;
CREATE TABLE [dbo].[TempUpsert](
[FirstName] [varchar](200) NOT NULL,
[LastName] [varchar](200) NOT NULL,
[Score] [int] NOT NULL
) ON [PRIMARY] ;
CREATE TABLE [dbo].[Sales](
[FullName] [varchar](200) NOT NULL,
[LastName] [varchar](200) NOT NULL,
[FirstName] [varchar](200) NOT NULL,
[lastUpdated] [date] NOT NULL,
CONSTRAINT [PK_Sales] PRIMARY KEY CLUSTERED
(
[FullName] ASC
)
---- PROC
CREATE PROCEDURE [dbo].[sp_MoveFromTempUpsert_to_Sales]
(#HashMod int)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
MERGE Sales AS trget
USING (
SELECT
--- Edit: Thanks to Mikal added DISTINCT
DISTINCT
FirstName, LastName , [Score], LastName+'.'+FirstName AS FullName
FROM TempUpsert AS ups) AS src (FirstName, LastName, [Score], FullName)
ON
(
src.[Score] = #hashMod
AND
trget.FullName=src.FullName
)
WHEN MATCHED
THEN
UPDATE SET trget.lastUpdated = GetDate()
WHEN NOT MATCHED
THEN INSERT ([FullName], [LastName], [FirstName], [lastUpdated])
VALUES (FullName, src.LastName, src.FirstName, GetDate())
OUTPUT $action, Inserted.*, Deleted.* ;
--print ##rowcount
END
GO
--- Insert dummie data
INSERT INTO TempUpsert (FirstName, LastName, Score)
VALUES ('John','Smith',2);
INSERT INTO TempUpsert (FirstName, LastName, Score)
VALUES ('John','Block',2);
INSERT INTO TempUpsert (FirstName, LastName, Score)
VALUES ('John','Smith',2); --make multiple on purpose
----- EXECUTE PROC
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[sp_MoveFromTempUpsert_to_Sales]
#HashMod = 2
SELECT 'Return Value' = #return_value
GO
This returns:
(1 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
Msg 2627, Level 14, State 1, Procedure sp_MoveFromTempUpsert_to_Sales, Line 12
Violation of PRIMARY KEY constraint 'PK_Sales'. Cannot insert duplicate key in object
'dbo.Sales'. The statement has been terminated.
(1 row(s) affected)
What am I doing wrong please?
Greatly appreciated
The first two rows in your staging table will give you the duplicate PK. violation. Conc is the PK and you insert tmain+dmain with the same value twice.
In Summation
MERGE requires its input (Using) to be duplicates free
the Using is a regular SQL statement, so you can use Group By, distinct and having as well as Where clauses.
My final Merge looks like so :
MERGE Sales AS trget
USING (
SELECT FirstName, LastName, Score, LastName + '.' + FirstName AS FullName
FROM TempUpsert AS ups
WHERE Score = #hashMod
GROUP BY FirstName, LastName, Score, LastName + '.' + FirstName
) AS src (FirstName, LastName, [Score], FullName)
ON
(
-- src.[Score] = #hashMod
--AND
trget.FullName=src.FullName
)
WHEN MATCHED
THEN
UPDATE SET trget.lastUpdated = GetDate()
WHEN NOT MATCHED
THEN INSERT ([FullName], [LastName], [FirstName], [lastUpdated])
VALUES (FullName, src.LastName, src.FirstName, GetDate())
OUTPUT $action, Inserted.*, Deleted.* ;
--print ##rowcount
END
And it works!
Thanks to you all :)
Without DISTINCT or proper AGGREGATE function in subquery used in USING part of MERGE there will be two rows which suits criteria used in ON part of MERGE, which is not allowed. (Two John.Smith)
AND
Move the condition src.[Score] = #hashMod inside the subquery,
instead if ON clause not succeed, for example John.Smith have score of 2, and #HashMod = 1 - then if you already have the row with John.Smith in target table - you'll get an error with Primary Key Constraint