Left join get all data in both table - sql-server

I have two tables Q and A,
records are A are
QID UserID Value
1 100 A
2 100 B
3 100 C
1 101 AA
2 101 BB
3 101 CC
1 102 AAA
2 102 BBB
As you can see, there is no record for user 102 for QID 3. There is this another table Q.
QID Value
1 Name
2 Email
3 Site
What I want is, for each user, weather they have answered a question or not (that is, weather a entry exits in A table or not) I want all questions for all users and their answers. Something like this.
QID QValue UserID Value
1 Name 100 A
2 Email 100 B
3 Site 100 C
1 Name 101 AA
2 Email 101 BB
3 Site 101 CC
1 Name 102 AAA
2 Email 102 BBB
What the problem is one row is missing from the desired output, and that is
3 Site 102 NULL
Because for user 102 there is no entry in A table. I tried LEFT JOIN, but obviously it won't give the desired result as all the left table are already there. And INNER JOIN doesn't works either.
It is also complete possible for answers table (table A) to have data like this
QID QValue UserID Value
1 Name 100 A
2 Email 101 BB
3 Site 102 CCC
Say, all users just have filled in one record, in this case desired output is something like this
QID QValue UserID Value
1 Name 100 A
2 Email 100 NULL
3 Site 100 NULL
1 Name 101 NULL
2 Email 101 BB
3 Site 101 NULL
1 Name 102 NULL
2 Email 102 NULL
3 Email 102 CCC
If I do a LEFT JOIN on QID it doesn't works. Please suggest what should be done.

Try this:
declare #A table(QID int, UserID int, Value varchar(10))
declare #Q table(QID int, Value varchar(10))
insert into #A values (1, 100, 'A')
insert into #A values (2, 100, 'B')
insert into #A values (3, 100, 'C')
insert into #A values (1, 101, 'AA')
insert into #A values (2, 101, 'BB')
insert into #A values (3, 101, 'CC')
insert into #A values (1, 102, 'AAA')
insert into #A values (2, 102, 'BBB')
insert into #Q values (1, 'Name')
insert into #Q values (2, 'Email')
insert into #Q values (3, 'Site')
select
U.UserID,
Q.QID,
Q.Value as QValue,
A.Value
from
(select distinct UserID from #A) U -- all Users
cross join #Q Q -- all Questions
left outer join #A A on A.UserID = U.UserID and A.QID = Q.QID
So basically you do a cross join between all questions and all users first to get all combinations. Then you take this result and do a left join with all the answers. Missing answers will have NULL values in the Value (the real answer) field.

Related

How to fill ParentID column in one update statement?

My table is like this:
ID Code ParentID
-------------------
1 A01 NULL
2 B83 NULL
3 H92 NULL
15 A013 NULL
23 A018 NULL
33 A01899 NULL
44 B8329 NULL
67 B83293 NULL
What I want is to update ParentID to match the ID of the parent code.
A01 is the parent for A013
A01 is the parent for A018
A018 is the parent for A01899
and so on.
You can see the length of A01 is 3 while the child A013 length is 4, the length of A018 is 4 and the child A01899 length is 6.
I can do that with multiple update statements and repeat that for each case.
UPDATE A
SET ParentID = B.ID
FROM Table A
INNER JOIN Table B ON A.Code like B.Code + '%'
WHERE LEN(A.Code) = 4 AND LEN(B.Code) = 3
But the question is how to do that in a single update statement?
You can first find all the relevant matches - similar to what you have - but also the length of the code it matched to. Then find the one with the longest matched code.
CREATE TABLE #TableA (ID int, Code varchar(10), ParentID int);
INSERT INTO #TableA (ID, Code, ParentID)
VALUES
(1 , 'A01' , NULL),
(2 , 'B83' , NULL),
(3 , 'H92' , NULL),
(15, 'A013' , NULL),
(23, 'A018' , NULL),
(33, 'A01899', NULL),
(44, 'B8329' , NULL),
(67, 'B83293', NULL);
WITH A AS
(SELECT TA.ID, TA.ParentID, TB.ID AS TB_ID,
ROW_NUMBER() OVER (PARTITION BY TA.ID ORDER BY TB.Len_Code DESC) AS rn
FROM #TableA TA
INNER JOIN
(SELECT ID, Code, LEN(CODE) AS Len_Code
FROM #TableA
) TB ON TA.Code LIKE TB.Code + '%'
WHERE TA.ID <> TB.ID
)
UPDATE A
SET A.ParentId = A.TB_ID
WHERE A.rn = 1;
Result
ID Code ParentID
1 A01 NULL
2 B83 NULL
3 H92 NULL
15 A013 1
23 A018 1
33 A01899 23
44 B8329 2
67 B83293 44
Just another option.
Significant Digits can be a risky business in the long run. It has been my experience that they tend to have a rather short shelf-life until an exception needs to be made.
In the example below, we allow for up to three characters in distance. We apply the closest first via the coalesce()
Just to be clear, Left Join D may not be necessary if Parents are within 1 or 2 characters. Conversely, this could be expanded if needed Left Join D ...
Example
Declare #YourTable Table ([ID] int,[Code] varchar(50),[ParentID] int)
Insert Into #YourTable Values
(1,'A01',NULL)
,(2,'B83',NULL)
,(3,'H92',NULL)
,(15,'A013',NULL)
,(23,'A018',NULL)
,(33,'A01899',NULL)
,(44,'B8329',NULL)
,(67,'B83293',NULL)
;with cte as (
Select A.*
,PtNr = coalesce(B.ID,C.ID,D.ID)
From #YourTable A
Left Join #YourTable B on left(A.[Code],len(A.[Code])-1)=B.[Code]
Left Join #YourTable C on left(A.[Code],len(A.[Code])-2)=C.[Code]
Left Join #YourTable D on left(A.[Code],len(A.[Code])-3)=D.[Code]
)
Update cte set ParentID=PtNr
Select * From #YourTable
The Update Table
ID Code ParentID
1 A01 NULL
2 B83 NULL
3 H92 NULL
15 A013 1
23 A018 1
33 A01899 23
44 B8329 2
67 B83293 44

SQL Server group by 2 columns - generate sequence number

I have table like below,
create table #temp
(
Name varchar(100),
Id int,
)
Insert into #temp values ('AAA',101)
Insert into #temp values ('AAA',102)
Insert into #temp values ('AAA',102)
Insert into #temp values ('AAA',103)
Insert into #temp values ('AAA',103)
Insert into #temp values ('BBB',201)
Insert into #temp values ('BBB',201)
Insert into #temp values ('BBB',202)
Insert into #temp values ('BBB',203)
Expected output:
Name Id Seq
-------------------
AAA 101 1
AAA 102 2
AAA 102 2
AAA 103 3
AAA 103 3
BBB 201 1
BBB 201 1
BBB 202 2
BBB 203 3
I want to generate sequence number based on name and Id. If Id is same, same sequence number needs to be assign.
Use DENSE_RANK:
SELECT
Name,
Id,
DENSE_RANK() OVER (PARTITION BY Name ORDER BY Id) Seq
FROM #temp;
Demo

Concatenate duplicates with delimiter

There is several examples that concatenate string dealing with one single table. In my case I have two tables to take into account.
Table A
requestid int PK
Table B
requestid int
documentname varchar(50)
Table A requestid is of course unique, where table B requestid may have multiple lines. Table B can contain multiple relationship with the same requestid from table A. Also some of the Table A requestid might not have any associated records in table B.
I need to extract and join the two tables. Table A contains ~300k lines and table B contains ~140k lines. See the data below to illustrate what I need to achieve.
Table A sample
requestid FieldA FieldB FieldC
1 33 44 22
2 15 23 73
3 26 73 34
Table B sample
requestid documentname
1 firstdoc.doc
1 seconddoc.doc
1 thirddoc.doc
3 onedoc.doc
3 lastdoc.doc
Expected result:
requestid FieldA FieldB FieldC documentname
1 33 44 22 firstdoc.doc, seconddoc.doc, thirddoc.doc
2 15 23 73 NULL
3 26 73 24 onedoc.doc, lastdoc.doc
In my solution it is very important that requestid with no document associated to it are still in the result.
Hope my question is clear, thank you in advance.
This can be easily done with a stuff
See the example below
declare #tableA table (requestid int, fieldA int, fieldB int, fieldC int)
declare #tableB table (requestid int, documentname varchar(50))
insert into #tableA values (1, 33, 44, 22), (2, 15, 23, 73), (3, 26, 73, 34)
insert into #tableB values (1, 'firstdoc.doc'), (1, 'seconddoc.doc'), (1, 'thirddoc.doc'), (3, 'onedoc.doc'), (3, 'lastdoc.doc')
select a.requestid, a.fieldA, a.fieldB, a.fieldC,
stuff(( ( select ',' + b.documentname
from #tableB b
where b.requestid = a.requestid
order by 1
For XML PATH (''))
), 1, 1, '') as docname
from #tableA a
the result is
requestid fieldA fieldB fieldC docname
--------- ------ ------ ------ --------
1 33 44 22 firstdoc.doc,seconddoc.doc,thirddoc.doc
2 15 23 73
3 26 73 34 lastdoc.doc,onedoc.doc

How to insert master detail records in T-SQL?

I need to copy some master-detail records, along the lines of:
INSERT INTO Order
(
SupplierId
,DateOrdered
)
SELECT
SupplierID
,DateOrdered
FROM Order
WHERE SupplierId = 10
DECLARE #OrderId int;
Select #OrderId = Scope_Identity;
INSERT INTO OrderItem
(
Quantity
,ProductCode
,Price
,FkOrderId
)
SELECT
Quantity
,ProductCode
,Price
,FkOrderId
FROM OrderItem
WHERE FkOrderId = #OrderId
This will not work. The reason is that there are multiple Orders for Supplier = 10. So what is the best way to iterate through each Order where Supplier = 10, Add the order, and then add the relevant child OrderItem BEFORE going onto the next Order Record where supplier=10. I think I am talking about batching, possibly cursors, but I am a newbie to T-SQL / Store Procedures.
I would appreciate advice on the above.
Thanks.
EDIT
Some more information which I hope will clarify by virtue of some sample data.
Original Order Table
Id SupplierId DateOrdered
1 10 01/01/2000
2 10 01/01/2000
Original OrderItem Table
Id Quantity ProductCode Price FkOrderId
1 20 X1 100 1
2 10 Y1 50 1
3 30 X1 100 2
4 20 Y1 50 2
Final Order Table
Id SupplierId DateOrdered
1 10 01/01/2000
2 10 01/01/2000
3 10 01/01/2000 (Clone of 1)
4 10 01/01/2000 (Clone of 2)
Final OrderItem Table
Id Quantity ProductCode Price FkOrderId
1 20 X1 100 1
2 10 Y1 50 1
3 30 X1 100 2
4 20 Y1 50 2
5 20 X1 100 3 (Clone of 1, linked to clone Order=3)
6 10 Y1 50 3 (Clone of 2, linked to clone Order=3)
7 30 X1 100 4 (Clone of 3, linked to clone Order=4)
8 20 Y1 50 5 (Clone of 4, linked to clone Order=4)
So I need some help with the code can do this cloning of Order and OrderItem to achieve the "final" table records.
It seems I need to do something like:
For each matching record in "Order"
Clone Order Record
Clone OrderItem Record where FkOrderId = OldOrderId
Next
This answers your question (no cursors either)
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE [Order]
(
Id Int Primary Key Identity,
SupplierId Int,
DateOrdered Date
)
SET IDENTITY_INSERT [Order] ON
INSERT INTO [Order] (Id, SupplierId, DateOrdered)
VALUES
(1, 10, '01/01/2000'),
(2, 10, '01/01/2000')
SET IDENTITY_INSERT [Order] OFF
CREATE TABLE [OrderItem]
(
ID INT Primary Key Identity,
Quantity Int,
ProductCode CHAR(2),
Price Int,
FKOrderId Int
)
SET IDENTITY_INSERT [OrderItem] ON
INSERT INTO [OrderItem] (Id, Quantity, ProductCode, Price, FKOrderId)
VALUES
(1, 20, 'X1', 100, 1),
(2, 10, 'Y1', 50, 1),
(3, 30, 'X1', 100, 2),
(4, 20, 'Y1', 50, 2)
SET IDENTITY_INSERT [OrderItem] OFF
Query 1:
DECLARE #NewEntries TABLE (ID Int, OldId Int);
MERGE INTO [Order]
USING [Order] AS cf
ON 1 = 0 -- Ensure never match - therefore an Insert
WHEN NOT MATCHED AND cf.SupplierId = 10 THEN
INSERT(SupplierId, DateOrdered) Values(cf.SupplierId, cf.DateOrdered)
Output inserted.Id, cf.Id INTO
#NewEntries(Id, OldId);
INSERT INTO [OrderItem]
(
Quantity
,ProductCode
,Price
,FkOrderId
)
SELECT
Quantity
,ProductCode
,Price
,NE.ID
FROM [OrderItem] OI
INNER JOIN #NewEntries NE
ON OI.FKOrderId = NE.OldId ;
SELECT *
FROM [OrderItem];
Results:
| ID | QUANTITY | PRODUCTCODE | PRICE | FKORDERID |
|----|----------|-------------|-------|-----------|
| 1 | 20 | X1 | 100 | 1 |
| 2 | 10 | Y1 | 50 | 1 |
| 3 | 30 | X1 | 100 | 2 |
| 4 | 20 | Y1 | 50 | 2 |
| 5 | 20 | X1 | 100 | 3 |
| 6 | 10 | Y1 | 50 | 3 |
| 7 | 30 | X1 | 100 | 4 |
| 8 | 20 | Y1 | 50 | 4 |
Add an additional column to the Order table called OriginalOrderId. Make it nullable, FK'd back to OrderId, and put an index on it. Use "INSERT INTO [Order]... SELECT ... OUTPUT INSERTED.* INTO #ClonedOrders From ...". Add an index on #ClonedOrders.OriginalOrderId. Then you can do "INSERT INTO OrderItem ... SELECT co.OrderId, ... FROM #ClonedOrders co INNER JOIN OrderItem oi ON oi.OrderId = co.OriginalOrderId". This will get you the functionality that you're looking for, along with the performance benefits of set based statements. It will also leave you evidence of the original source of the orders and a field that you can use to differentiate cloned orders from non-cloned orders.
in this case you have to use output clause.. let me give you one sample script that will help you to relate with your requirement
Declare #Order AS Table(id int identity(1,1),SupplierID INT)
DECLARE #outputOrder AS TABLE
(Orderid INT)
INSERT INTO #Order (SupplierID)
Output inserted.id into #outputOrder
Values (102),(202),(303)
select * from #outputOrder
next step for your case would be use newly generated orderid from outputorder table & join to get orderitems from input table
This will handle your first table.
PS: Supply your questions in this state and they will be answered faster.
IF OBJECT_ID('Orders') IS NOT NULL DROP TABLE Orders
IF OBJECT_ID('OrderItem') IS NOT NULL DROP TABLE OrderItem
IF OBJECT_ID('tempdb..#FinalOrders') IS NOT NULL DROP TABLE #FinalOrders
CREATE TABLE Orders (OrdersID INT, SupplierID INT, DateOrdered DATETIME)
CREATE TABLE OrderItem (OrderItemID INT, Quantity INT, FkOrderId INT)
INSERT INTO Orders VALUES (1,20,'01/01/2000'),(2,20,'01/01/2000')
INSERT INTO OrderItem VALUES
(1,20,1),
(2,10,1),
(3,30,2),
(4,20,2)
SELECT
a.OrderItemID,
b.SupplierID,
b.DateOrdered
INTO #FinalOrders
FROM OrderItem as a
INNER JOIN Orders as b
ON a.FkOrderId = b.OrdersID
SELECT * FROM #FinalOrders
This can be achieved with a cursor. But please note that cursors will pose significant performance drawbacks.
DECLARE #SupplierID AS INT
DECLARE #OrderId AS INT
DECLARE #DateOrdered AS DATE
DECLARE #OrderIdNew AS INT
Declare #Order AS Table(OrderId INT,SupplierID INT,DateOrdered Date)
INSERT INTO #Order
SELECT
ID
,SupplierID
,DateOrdered
FROM [Order]
WHERE SupplierId = 10
DECLARE CUR CURSOR FAST_FORWARD FOR
SELECT
OrderId
,SupplierID
,DateOrdered
FROM #Order
OPEN CUR
FETCH NEXT FROM CUR INTO #OrderId, #SupplierID, #DateOrdered
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO [Order]
(
SupplierId
,DateOrdered
)
VALUES
(#SupplierID,#DateOrdered)
Select #OrderIdNew=##IDENTITY
INSERT INTO [OrderItem]
([Quantity]
,[ProductCode]
,[Price]
,[FkOrderId])
SELECT [Quantity]
,[ProductCode]
,[Price]
,#OrderIdNew
FROM [OrderItem]
WHERE [FkOrderId]=#OrderId
FETCH NEXT FROM CUR INTO #OrderId, #SupplierID, #DateOrdered
END
CLOSE CUR;
DEALLOCATE CUR;
You could try doing and inner join between Order and OrderItems where the clause of the inner join is SupplierId = 10,
or just modify your where to achieve the same result.
Try doing something along the lines of:
INSERT INTO OrderItem
(
Quantity
,ProductCode
,Price
,FkOrderId
)
SELECT
Quantity
,ProductCode
,Price
,FkOrderId
FROM OrderItem
where FkOrderId in (Select Id FROM Order WHERE SupplierId = 10)

Update rows by separating values from one column and inserting into 2 diff. columns

For eg. I have table T1. There are 4 columns in it c1 c2 c3 and c4. I have c1 as id, c2 contains combine name and address. c3 and c4 are empty. There are multiple rows for given id. Let's say there are 10 rows for id=10.
What I want is for all the rows with id=10, I want to read c2, separate values in c2 as name and address and store name in c3 and address in c4.
How can I do this in SQL server 2005/2008?
Thanks
Try:
UPDATE YourTable
SET c3=LEFT(c2,CHARINDEX(' ',c2))
,c4=RIGHT(c2,LEN(c2)-CHARINDEX(' ',c2))
WHERE c1=#YourIdValue
In the question, the method to separate values in c2 as name and address is not described, so I just split column c2 based on the first space found. c2='abcd efgh' becomes: c3='abcd', c4='efgh'.
Working sample:
DECLARE #YourTable table (c1 int,c2 varchar(10),c3 varchar(10),c4 varchar(10))
INSERT #YourTable VALUES (1,'aaaa bbbb',null,null)
INSERT #YourTable VALUES (1,'aaa bbb' ,null,null)
INSERT #YourTable VALUES (1,'aa bb' ,null,null)
INSERT #YourTable VALUES (1,'a b' ,null,null)
INSERT #YourTable VALUES (2,'222 333' ,null,null)
INSERT #YourTable VALUES (2,'aaa bbb' ,null,null)
INSERT #YourTable VALUES (3,'a b' ,null,null)
DECLARE #YourIdValue int
SET #YourIdValue=1
UPDATE #YourTable
SET c3=LEFT(c2,CHARINDEX(' ',c2))
,c4=RIGHT(c2,LEN(c2)-CHARINDEX(' ',c2))
WHERE c1=#YourIdValue
SELECT * FROM #YourTable
OUTPUT:
c1 c2 c3 c4
----------- ---------- ---------- ----------
1 aaaa bbbb aaaa bbbb
1 aaa bbb aaa bbb
1 aa bb aa bb
1 a b a b
2 222 333 NULL NULL
2 aaa bbb NULL NULL
3 a b NULL NULL
(7 row(s) affected)

Resources