Insert on a child table and update FK on parent - sql

I have a parent table with the following structure and data:
---------------------------------------------
| Id | TranslationId | Name |
---------------------------------------------
| 1 | NULL | Image1.jpg |
| 2 | NULL | Image7.jpg |
| 3 | NULL | Picture_Test.png |
---------------------------------------------
And the empty child table which holds the translated images:
-------------------------------------------------------------------------
| Id | De | Fr | En |
-------------------------------------------------------------------------
| | | | |
-------------------------------------------------------------------------
Now I'm looking for a single query statement or at least few queries which I can run without any further programming. Doing this job with scripting or programming would be easy but I have often situations where I need this kind of insert / update. And developing each time a small console app is not feasible.
At the end the two tables should look like this:
---------------------------------------------
| Id | TranslationId | Name |
---------------------------------------------
| 1 | 28 | NULL |
| 2 | 29 | NULL |
| 3 | 30 | NULL |
---------------------------------------------
-------------------------------------------------------------------------
| Id | De | Fr | En |
-------------------------------------------------------------------------
| 28 | Image1.jpg | NULL | NULL |
| 29 | Image7.jpg | NULL | NULL |
| 30 | Picture_Test.png | NULL | NULL |
-------------------------------------------------------------------------
Thank you for any advice.

You could do it something like the below :
INSERT INTO Child
(
Id
,De
,Fr
,En
)
OUTPUT Inserted.Id INTO #Temp
SELECT Id
,De
,Fr
,En
FROM #Values --If you are using a table type to insert into the Child table as a set based approach
;WITH CTE
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY Id) AS Rnk
,Id
FROM #Temp
)
,CTE1 AS
(
SELECT ROW_NUMBER() OVER(ORDER BY Id) AS Rnk
,*
FROM Parent
)
UPDATE cte1
SET TranslationId = cte.Id
FROM CTE1 cte1
JOIN CTE cte ON cte.Rnk = cte1.Rnk

Demo, assuming Name is unique in the first table
create table tab1 (
id int identity
,TranslationId int null
,Name nvarchar(max) null
);
insert tab1 (Name)
values
('Image1.jpg')
,('Image7.jpg')
,('Picture_Test.png')
,(null)
create table tab2 (
id int identity (100,1)
,De nvarchar(max) null
,Fr nvarchar(max) null
,En nvarchar(max) null
);
-- Update them
declare #map table(
name nvarchar(max)
,ref int
);
insert tab2 (de)
output inserted.De, inserted.id
into #map(Name, ref)
select Name
from tab1 src
where Name is not null and not exists (select 1 from tab2 t2 where t2.De = src.Name);
update t1 set TranslationId = ref, Name = null
from tab1 t1
join #map m on t1.Name = m.Name;
select * from tab1;
select * from tab2;

I figured out in the meantime how to do it. Applied on the database, the query looks like this:
DECLARE #Temp TABLE (ImageId INT, Id INT)
MERGE INTO Translation USING
(
SELECT Image.Name AS Name, Image.Id AS ImageId
FROM Candidate
INNER JOIN Candidacy ON Candidate.Id = Candidacy.CandidateId
INNER JOIN Election ON Candidacy.ElectionId = Election.Id
INNER JOIN SmartVoteCandidate ON Candidate.Id = SmartVoteCandidate.CandidateId
INNER JOIN Image ON SmartVoteCandidate.SpiderImageId = Image.Id
WHERE Election.Id = 1575) AS temp ON 1 = 0
WHEN NOT MATCHED THEN
INSERT (De)
VALUES (temp.Name)
OUTPUT temp.ImageId, INSERTED.Id
INTO #Temp (ImageId, Id);
UPDATE Image
SET Image.TranslationId = t.Id, Name = NULL
FROM #Temp t
WHERE Image.Id = t.ImageId
The solution is heavily inspired by
Is it possible to for SQL Output clause to return a column not being inserted?
Using a join in a merge statement

Related

Join on different tables depending on column associated with value being joined on?

I am trying to create a view that provides different tables depending on a column associated with the value being joined on.
Example:
SomeTable
+-----+------+
| ID | Type |
+-----+------+
| 123 | 1 |
| 124 | 2 |
| 125 | 1 |
| 126 | 2 |
+-----+------+
TableA
+---------+---------------+-----+------------+
| Item_ID | Serial_Number | ID | LocationID |
+---------+---------------+-----+------------+
| 1 | 19-001 | 124 | 4 |
| 2 | 19-002 | 126 | 17 |
+---------+---------------+-----+------------+
TableB
+-----+------------+----------+
| ID | LocationID | Quantity |
+-----+------------+----------+
| 123 | 7 | 15 |
| 125 | 12 | 10 |
+-----+------------+----------+
SELECT t.ID, v.LocationID
FROM SomeTable t
FULL JOIN NewView v ON t.ID = v.ID
WHERE t.ID = 123
If the ID the view is being joined on is Type 1 then the View selects Table A. If the ID is Type 2, then select Table B.
The expected result for ID 123 would be:
+-----+------------+
| ID | LocationID |
+-----+------------+
| 123 | 7 |
+-----+------------+
And the expected result for ID 124 would be:
+-----+------------+
| ID | LocationID |
+-----+------------+
| 124 | 4 |
+-----+------------+
I know I could do this by using a function and passing a parameter like this:
SELECT t.ID, f.LocationID
FROM SomeTable t
FULL JOIN NewFunction(123) f ON t.ID = f.ID
WHERE t.ID = 123
And here is the function:
CREATE FUNCTION NewFunction (#ID)
RETURNS #ReturnTable TABLE (ID INT, LocationID INT)
BEGIN
DECLARE #Type INT
SET #Type = (SELECT Type FROM SomeTable WHERE ID = #ID)
IF #Type = 1
INSERT INTO #ReturnTable (ID, LocationID)
SELECT t.ID, a.LocationID
FROM SomeTable t
FULL JOIN TableA a
WHERE t.ID = #ID
ELSE
INSERT INTO #ReturnTable (ID, LocationID)
SELECT t.ID, b.LocationID
FROM SomeTable t
FULL JOIN TableB b
WHERE t.ID = #ID
RETURN
END
The problem is that passing a parameter to a function like this will require making changes to the application. I would prefer not to have to implement these changes, so it would be ideal if I could recreate the functionality in the example above either with a view or a function that does not require a parameter. Any ideas?
Why not create a view for all three tables?
select t.id, coalesce(a.locationid, b.locationid) as locationid
from sometable t left join
a
on t.id = a.id and t.type = 1 left join
b
on t.id = b.id and t.type = 2;
You need the type to get the right table, so it seems that all three tables should be used together.
You can't send a parameter into a VIEW, so it isn't possible to make it do different things depending on the content of the select query.
What you could do, is to use a UNION query assuming that Table A and Table B have the same columns, then use the Type column in your WHERE clause

create sql function ( Column typeX)

create sql function ( Column typeX)
hi
i have this Table:
autoID | id | name | age | Tel
------------------------------------------
1 | 1 | Frank | 40 | null
2 | 1 | null | 50 | 7834xx
3 | 1 | Alex | null | null
4 | 1 | null | 20 | null
5 | 2 | James | null | 4100xx
6 | 3 | jan | 24 | null
7 | 3 | null | null | 4100xx
my query for select :
SELECT TOP 10
(SELECT top(1) name FROM test1 where id=1 and name is not null order by autoID desc) as name ,(SELECT top(1) age FROM test1 where id=1 and age is not null order by autoID desc) as Age ,(SELECT top(1) Tel
FROM test1
where id=1 and Tel
is not null order by autoID desc) as Telephon FROM [dbo].[test1] group by id
Result:
autoID | id | name | age | Tel
------------------------------------------
1 | 1 | Alex | 20 | 7834xx
I need create function like this:
CREATE FUNCTION TestSchema.MyfunctinX(#ColumnX #ColumnX.type)
RETURNS #ColumnX.type
AS
BEGIN
DECLARE #value #ColumnX.type
SELECT top 1 #value from #ColumnX where #value is not null order by #autoID desc
RETURN #value
END;
GO
for my select query get Short like:
Select Id, MyfunctinX(name) as [Name], MyfunctinX(age) as Age, MyfunctinX(Tel)
as Tel from yourtable Group by Id
Is there a way to do this?
Your structure is not very efficient. You may be better served with an EAV Structure (Entity Attribute Value)
Example
Select A.ID
,Name = [dbo].[MyFunction] (A.ID,'Name')
,Age = [dbo].[MyFunction] (A.ID,'Age')
,Tel = [dbo].[MyFunction] (A.ID,'Tel')
From (Select Distinct ID From YourTable ) A
Returns
ID Name Age Tel
1 Alex 20 7834xx
2 James NULL 4100xx
3 jan 24 4100xx
The UDF
CREATE FUNCTION [dbo].[MyFunction] (#ID int,#Field varchar(100))
Returns varchar(max)
As
Begin
Return (
Select Top 1 with ties Value
From (Select XMLData = cast((Select * from YourTable where ID=#ID For XML RAW) as xml)) A
Cross Apply (
Select autoID = r.value('#autoID','int')
,Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From A.XMLData.nodes('/row') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
Where attr.value('local-name(.)','varchar(100)') not in ('autoID')
and attr.value('local-name(.)','varchar(100)')= #Field
) B
Order By Row_Number() over (Partition By Item Order By autoID Desc)
)
End

Get nth level on self-referencing table

I have a self-referencing table which has at max 5 levels
groupid | parentid | detail
--------- | --------- | ---------
Group A | Highest | nope
Group B | Group A | i need this
Highest | NULL | nope
Group C | Group B | nope
Group D | Group C | nope
I have a transaction table which lookups to the groupid on the table above to retrieve the detail value where groupid = Group B. The values of the groupid on the transaction table is only between Group B to D and will never go any higher.
txnid | groupid | desired | desired
--------- | --------- | --------- | ---------
1 | Group D | Group B | i need this
2 | Group B | Group B | i need this
3 | Group C | Group B | i need this
4 | Group B | Group B | i need this
How should my T-SQL script be like to attain the desired column? I can left join to the self referencing table multiple times to get until group B it's not consistent on how many time I need to join back.
Greatly appreciate any thoughts!
Still not clear to me how do you know which is the GROUP B, I suppose it's the record where the parent of it parent is null.
create table org(groupid char(1), parentid char(1), details varchar(20));
insert into org values
('a', null, 'nope'),('b', 'a', 'I need this'),('c', 'b', 'nope'),('d', 'c', 'nope'),('e', 'd', 'nope');
create table trans(id int, groupid char(1));
insert into trans values
(1, 'b'),(2, 'c'),(3, 'c'),(4, 'd'),(5, 'e');
GO
10 rows affected
with all_levels as
(
select ob.groupid groupid_b, oc.groupid groupid_c,
od.groupid groupid_d, oe.groupid groupid_e,
ob.details
from org ob
inner join org oc
on oc.parentid = ob.groupid
inner join org od
on od.parentid = oc.groupid
inner join org oe
on oe.parentid = od.groupid
where ob.parentid is not null
) select * from all_levels;
GO
groupid_b | groupid_c | groupid_d | groupid_e | details
:-------- | :-------- | :-------- | :-------- | :----------
b | c | d | e | I need this
--= build a 4 levels row
with all_levels as
(
select ob.groupid groupid_b, oc.groupid groupid_c,
od.groupid groupid_d, oe.groupid groupid_e,
ob.details
from org ob
inner join org oc
on oc.parentid = ob.groupid
inner join org od
on od.parentid = oc.groupid
inner join org oe
on oe.parentid = od.groupid
where ob.parentid is not null
)
--= no matter what groupid returns b group details
, only_b as
(
select groupid_b as groupid, groupid_b, details from all_levels
union all
select groupid_c as groupid, groupid_b, details from all_levels
union all
select groupid_d as groupid, groupid_b, details from all_levels
union all
select groupid_e as groupid, groupid_b, details from all_levels
)
--= join with transactions table
select id, t.groupid, groupid_b, ob.details
from trans t
inner join only_b ob
on ob.groupid = t.groupid;
GO
id | groupid | groupid_b | details
-: | :------ | :-------- | :----------
1 | b | b | I need this
2 | c | b | I need this
3 | c | b | I need this
4 | d | b | I need this
5 | e | b | I need this
dbfiddle here
You can deal with a recursive function too, but I don't believe it can be better on terms of performance.
create function findDetails(#groupid char(1))
returns varchar(100)
as
begin
declare #parentid char(1) = '1';
declare #next_parentid char(1) = '1';
declare #details varchar(100) = '';
while #next_parentid is not null
begin
select #details = org.details, #parentid = org.parentid, #next_parentid = op.parentid
from org
inner join org op
on op.groupid = org.parentid
where org.groupid = #groupid
set #groupid = #parentid;
end
return #details;
end
GO
✓
select id, groupid, dbo.findDetails(groupid) as details_b
from trans;
GO
id | groupid | details_b
-: | :------ | :----------
1 | b | I need this
2 | c | I need this
3 | c | I need this
4 | d | I need this
5 | e | I need this
dbfiddle here

Join Vertical & Horizontal table in SQL Server using Pivot

I want to join two tables and combine into one but problem is one table is in Horizontal Format and other in Vertical format.
below is the table structure and join will be on Employeeid:
Table 1 : EmpDetail
ID | CODE | Name
-- |--------| ---
1 | 1008M | ABC
2 | 1039E | XYZ
3 | 1040E | TYS
Table 2 : EmpCustomeDetail
EmpID | FiledName | FieldValue
-- |-------- | ---
1 | FlD1 | temp1
1 | FlD2 | temp2
1 | FlD3 | temp3
2 | FlD1 | temp1
3 | FLD4 | temp6
Desired Output Required :
EmpID | Code | Name | Fld1 | Fld2 | Fld3 | Fld4
-- |---- | ------| --- | ---- |---- |----
1 | 1008M | ABC | temp1 | temp2 | temp3 | null
2 | 1039E | XYZ | temp1 | null | null | null
3 | 1040E | TYS | null | null | null | temp6
I had tried using Pivot Query but it is not giving exact output which i requried
Below is the query so far i have tried
SELECT A.*
FROM (
SELECT
e.Id,
e.code,
e.Fname,
FROM EmpDetail e
LEFT JOIN (
SELECT *
FROM (
SELECT
d.CustomeFieldName
, c.ComboValue
, d.EmployeeId
FROM EmpCustomeDetail d
) src
PIVOT (
MAX(FieldValue)
) src2
) c ON e.Id = c.EmployeeId
) A
Here are two statements:
The first is a simple PIVOT. You can use it, in case you know all Fieldnames (btw: there's a typo in your sample) in advance.
The second is roughly the same statement, but the column names are taken dynamically. This will work with (almost) any count and with different namings.
First a mock-up-test-scenraio
CREATE TABLE DummyEmpDetail (ID INT,CODE VARCHAR(10),Name VARCHAR(100));
INSERT INTO DummyEmpDetail VALUES
(1,'1008M','ABC')
,(2,'1039E','XYZ')
,(3,'1040E','TYS');
CREATE TABLE DummyEmpCustomeDetail (EmpID INT,FiledName VARCHAR(100),FieldValue VARCHAR(100));
INSERT INTO DummyEmpCustomeDetail VALUES
(1,'FlD1','temp1')
,(1,'FlD2','temp2')
,(1,'FlD3','temp3')
,(2,'FlD1','temp1')
,(3,'FLD4','temp6');
--The static PIVOT statement
SELECT p.EmpID
,p.Name
,p.CODE
,p.Fld1
,p.Fld2
,p.Fld3
p,Fld4
FROM
(
SELECT e.CODE,e.Name,ec.*
FROM DummyEmpDetail AS e
INNER JOIN DummyEmpCustomeDetail AS ec ON e.ID=ec.EmpID
) AS tbl
PIVOT
(
MAX(FieldValue) FOR FiledName IN(Fld1,Fld2,Fld3,Fld4)
) AS p;
--The dynamic PIVOT statement
DECLARE #colNames VARCHAR(MAX)=
(
STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(FiledName) FROM DummyEmpCustomeDetail
FOR XML PATH('')
),1,1,''
)
);
DECLARE #command VARCHAR(MAX)=
'SELECT p.EmpID
,p.Name
,p.CODE
,' + #colNames +
' FROM
(
SELECT e.CODE,e.Name,ec.*
FROM DummyEmpDetail AS e
INNER JOIN DummyEmpCustomeDetail AS ec ON e.ID=ec.EmpID
) AS tbl
PIVOT
(
MAX(FieldValue) FOR FiledName IN(' + #colnames + ')
) AS p;';
EXEC (#command);
GO
DROP TABLE DummyEmpCustomeDetail;
DROP TABLE DummyEmpDetail;
Both lead to the same result...
Try like below. If values of fieldname will not be static then you should use dynamic sql.
SELECT EMPID,
CODE,
NAME,
FLD1,
FLD2,
FLD3,
FLD4
FROM EmpDetail C
JOIN (SELECT A.*
FROM EmpCustomeDetail
PIVOT ( MIN([FIELDVALUE])
FOR [FILEDNAME] IN([FLD1],
[FLD3],
[FLD2],
FLD4) )A)B
ON C.ID = B.[EMPID]
No need for sub-queries.
select e.*,FlD1,FlD2,FlD3,FlD4
from EmpDetail e
left join EmpCustomeDetail
pivot (max(FieldValue) for FiledName in (FlD1,FlD2,FlD3,FlD4)) ecd
on ecd.EmpID = e.ID
+----+-------+------+-------+-------+-------+-------+
| ID | CODE | Name | FlD1 | FlD2 | FlD3 | FlD4 |
+----+-------+------+-------+-------+-------+-------+
| 1 | 1008M | ABC | temp1 | temp2 | temp3 | NULL |
+----+-------+------+-------+-------+-------+-------+
| 2 | 1039E | XYZ | temp1 | NULL | NULL | NULL |
+----+-------+------+-------+-------+-------+-------+
| 3 | 1040E | TYS | NULL | NULL | NULL | temp6 |
+----+-------+------+-------+-------+-------+-------+

Create New Table From Other Table After Grouping

How can I insert to a table a value from "grouping" other table?
That means I have 2 table with different structure.
The table ORDRE with existed DATA
Table ORDRE:
ORDRE ID | CODE_DEST |
-------------------------
1 | a |
2 | b |
3 | c |
4 | a |
5 | a |
6 | b |
7 | g |
I want to INSERT the value FROM Table ORDRE INTO TABLE VOIT:
ID_VOIT | ORDRE ID | CODE_DEST |
---------------------------------------
1 | 1 | a |
1 | 4 | a |
1 | 5 | a |
2 | 2 | b |
2 | 6 | b |
3 | 3 | c |
4 | 7 | g |
This is my best guess on what you need using only the info available.
declare #Ordre table
(
ordre_id int,
code_dest char(1)
)
declare #Voit table
(
id_voit int,
ordre_id int,
code_dest char(1)
)
insert into #Ordre values
(1,'a'),
(2,'b'),
(3,'c'),
(4,'a'),
(5,'a'),
(6,'b'),
(7,'g')
insert into #Voit
select id_voit, ordre_id, rsOrdre.code_dest
from #Ordre rsOrdre
inner join
(
select code_dest, ROW_NUMBER() over (order by code_dest) as id_voit
from #Ordre
group by code_dest
) rsVoit on rsVoit.code_dest = rsOrdre.code_dest
order by id_voit, ordre_id
select * from #Voit
Working Example.
For the specific data you give as an example, this works:
insert into VOIT
select
case code_dest
when 'a' then 1
when 'b' then 2
when 'c' then 3
when 'g' then 4
else 0
end, orderId, code_dest from ORDRE order by code_dest, orderId
But it kind of sucks because it requires hard-coding in a huge case statement.
Test is here - https://data.stackexchange.com/stackoverflow/q/119442/
What I like more is moving the VOIT ID / Code_Dest associations to a new table, so then you could do an inner join instead.
insert into VOIT
select voit_id, orderId, t.code_dest
from ORDRE t
join Voit_CodeDest t2 on t.code_dest = t2.code_dest
order by code_dest, orderId
Working example of that here - https://data.stackexchange.com/stackoverflow/q/119443/