Find and insert dummy rows - possible scenario for OUTER APPLY - sql

This is a mock up of the situation we've got:
IF OBJECT_ID('TEMPDB..#People') IS NOT NULL BEGIN DROP TABLE #People END;
CREATE TABLE #People
(
Name VARCHAR(100),
Category VARCHAR(20),
ID INT
);
INSERT INTO #People
values
('x','Bronze',1),
('y','Bronze',2),
('z','Silver',3),
('j','Gold',4),
('q','Bronze',5),
('x','Silver',1);
IF OBJECT_ID('TEMPDB..#Category') IS NOT NULL BEGIN DROP TABLE #Category END;
CREATE TABLE #Category
(
Category VARCHAR(100)
);
INSERT INTO #Category
values
('Gold'),
('Silver'),
('Bronze');
If a name does not have a Category e.g. x does not have Gold then I'd like a row creating and adding into the table #People with an ID of -1.
Current solution I have is this:
WITH x AS
(
SELECT DISTINCT
x.Name,
s.Category
FROM #People x
CROSS JOIN #Category s
)
INSERT INTO #People
SELECT J.Name,
J.Category,
ID = -1
FROM x J
WHERE NOT EXISTS
(
SELECT 1
FROM #People Q
WHERE J.Name = Q.Name
AND J.Category = Q.Category
);
See it works!...:
SELECT *
FROM #People;
I have a feeling CROSS APPLY might be a good operator to use in order to simplify the above - What is the simplest way to find, create and insert these rows?

insert into People(Name, Category, Id)
(
select distinct
p.Name,
c.Category,
p.Id
from
people p
cross join category c
where
Concat(p.id, c.Category) not in (select Concat(id, Category) from people)
);
http://www.sqlfiddle.com/#!6/92cd5/14

Related

Query different type data in to a single row with the master record

I just created the following sample data to demonstrate what I am after.
I need to query the above table to get my information as 1 single row
i.e. like the following
I was planning to create two temp tables and insert the two different types of addresses seperately. Then, inner join them with the main company table. I am not sure, this is a good solution. I appreaciate if anyone share their thoughts or code to my problem.
Try this..
Select companyId,CompanyName,homesddress1
,homeaddress2,HomePostCode,OfficeAddress1,OfficeAddress2,OfficePostCode
From tblCompany a
Outer apply ( select address1 homesddress1, address2 homeaddress2,postcode HomePostCode
From tblAddress t
Where AddressType='home' and t.companyid=a.companyid)
Outer apply (select address1 OfficeAddress1, address2 Officeaddress2,postcode OfficePostCode
From tblAddress t2
Where AddressType='Office ' and t2.companyid=a.companyid)
You can do it with a simple select using two outer joins. Note that you need the joins to be outer because for some companies you may only have one address.
DECLARE #company TABLE (
CompanyId int,
CompanyName varchar(50)
)
DECLARE #companyAddress TABLE (
Id int,
AddressType varchar(10),
Address1 varchar(50),
Address2 varchar(50),
Postcode varchar(10),
CompanyId int
)
INSERT INTO #company VALUES (1, 'Test Company')
INSERT INTO #companyAddress VALUES (1, 'Home', '25 Street', 'City 1', 'BA3 1PE', 1)
INSERT INTO #companyAddress VALUES (2, 'Office', '25 Street', 'City 2', 'NA1 4TW', 1)
SELECT c.CompanyId, c.CompanyName,
h.Address1 AS HomeAddress1, h.Address2 AS HomeAddress2, h.Postcode AS HomePostcode,
o.Address1 AS OfficeAddress1, o.Address2 AS OfficeAddress2, o.Postcode AS OfficePostcode
FROM #company c
LEFT JOIN
#companyAddress h ON h.CompanyId = c.CompanyId AND h.AddressType = 'Home'
LEFT JOIN
#companyAddress o ON o.CompanyId = c.CompanyId AND o.AddressType = 'Office'
Here is an Almost Dynamic version. You just have to specify the field list in the final pivot
Declare #YourTable table (ID int,AddressType varchar(25),Address1 varchar(50),Address2 varchar(50),CompanyID int)
Insert Into #YourTable values
(1,'Home' ,'25 Street','City 1',1),
(2,'Office','10 Avenue','City 2',1)
Declare #XML xml = (Select * from #YourTable for XML RAW) --<<< Initial Query
;with cteBase as (
Select ID = R.value('#CompanyID','int') --<<< Key ID
,AddressType = R.value('#AddressType','varchar(50)')
,Item = R.value('#AddressType','varchar(50)')+Attr.value('local-name(.)','varchar(100)')
,Value = Attr.value('.','varchar(max)')
From #XML.nodes('/row') as A(R)
Cross Apply A.r.nodes('./#*[local-name(.)!="CompanyID"]') as B(Attr) --<<< Key ID
),cteDist as (Select Distinct ID,Item from cteBase
),cteComp as (
Select A.*,B.Value
From cteDist A
Cross Apply (Select Value=Stuff((Select Distinct ',' + Value
From cteBase
Where ID=A.ID
and Item=A.Item
For XML Path ('')),1,1,'') ) B
)
Select *
From (Select * From cteComp) as s
Pivot (max(value)
For Item in (HomeID,HomeAddressType,HomeAddress1,HomeAddress2,OfficeID,OfficeAddressType,OfficeAddress1,OfficeAddress2)) as pvt
Returns
ID HomeID HomeAddressType HomeAddress1 HomeAddress2 OfficeID OfficeAddressType OfficeAddress1 OfficeAddress2
1 1 Home 25 Street City 1 2 Office 10 Avenue City 2

Join with recursive query

Setup
I have the following tables (simplyfied):
CREATE TABLE Category(
CategoryId int NOT NULL PRIMARY KEY,
ParentCategoryId int NULL,
Name nvarchar(255) NOT NULL,
FOREIGN KEY (ParentCategoryId) REFERENCES Category(CategoryId) ON UPDATE NO ACTION ON DELETE NO ACTION);
CREATE TABLE TimeSlot(
TimeSlotId int NOT NULL PRIMARY KEY,
CategoryId int NOT NULL,
FOREIGN KEY (CategoryId) REFERENCES Category(CategoryId) ON UPDATE NO ACTION ON DELETE NO ACTION);
CREATE TABLE PersonTimeSlotAssignment(
PersonId int NOT NULL,
TimeSlotId int NOT NULL,
PRIMARY KEY (PersonId, TimeSlotId),
FOREIGN KEY (TimeSlotId) REFERENCES TimeSlot(TimeSlotId) ON UPDATE NO ACTION ON DELETE NO ACTION);
and here is some test data:
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (100, NULL, 'cat 1');
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (110, 100, 'cat 1.1');
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (111, 110, 'cat 1.1.1');
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (120, 100, 'cat 1.2');
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (200, NULL, 'cat 2');
INSERT INTO TimeSlot(TimeSlotId, CategoryId) VALUES (301, 111);
INSERT INTO TimeSlot(TimeSlotId, CategoryId) VALUES (302, 120);
INSERT INTO TimeSlot(TimeSlotId, CategoryId) VALUES (303, 200);
INSERT INTO PersonTimeSlotAssignment(PersonId, TimeSlotId) VALUES (401, 301);
INSERT INTO PersonTimeSlotAssignment(PersonId, TimeSlotId) VALUES (401, 302);
INSERT INTO PersonTimeSlotAssignment(PersonId, TimeSlotId) VALUES (402, 302);
INSERT INTO PersonTimeSlotAssignment(PersonId, TimeSlotId) VALUES (402, 303);
What I can do
SELECT ts.TimeSlotId, pc.Name
FROM PersonTimeSlotAssignment
JOIN TimeSlot AS ts ON PersonTimeSlotAssignment.TimeSlotId = ts.TimeSlotId
JOIN Category AS pc ON ts.CategoryId = pc.CategoryId
WHERE PersonTimeSlotAssignment.PersonId = #PERSON_ID;
This gives me for some person a list of all TimeSlots to which this person is assigned and the name of the leaf category which the TimeSlot belongs to. For example for person with ID 401 it gives:
TimeSlotId Name
---------------------
301 cat 1.1.1
302 cat 1.2
With the following recursive query I can also get from some category all the ancestors up to the root category:
;WITH Parents AS (
SELECT * FROM Category
WHERE CategoryId=#CATEGORY_ID
UNION ALL SELECT c.* FROM Category c JOIN Parents p ON p.ParentCategoryId=c.CategoryId
)
SELECT Name FROM Parents;
For example for category with ID 111 I get:
Name
---------
cat 1.1.1
cat 1.1
cat 1
What I want to do
What I need is a list of TimeSlots a person is assigned with, joined with the category names for that TimeSlot up to the root category. So for person with ID 401 the result should look like this:
TimeSlotId Name
---------------------
301 cat 1.1.1
301 cat 1.1
301 cat 1
302 cat 1.2
302 cat 1
I was not able to figure out how to combine the above two queries so that I get the expected result.
What I tried
I was hoping that something along these lines could work:
;WITH Parents AS (
SELECT * FROM Category
WHERE CategoryId=<<'How to get CategoryId for each assigned TimeSlot here?'>>
UNION ALL SELECT c.* FROM Category c JOIN Parents p ON p.ParentCategoryId=c.CategoryId
)
SELECT ts.TimeSlotId, pc.Name
FROM PersonTimeSlotAssignment
JOIN TimeSlot AS ts ON PersonTimeSlotAssignment.TimeSlotId = ts.TimeSlotId
JOIN Parents AS pc ON <<'How should this look like?'>>
WHERE PersonTimeSlotAssignment.PersonId = #PERSON_ID;
User defined function and cross apply is very useful in this case.
--1. Create function
create function fn_Category(#id int)
returns table
as
return
with tbl as (
--anckor query
select CategoryId, ParentCategoryId,Name, 1 lvl
from Category where CategoryId = #id
union all
--recursive query
select c.CategoryId, c.ParentCategoryId,c.Name, lvl+1
from Category c
inner join tbl on tbl.ParentCategoryId=c.CategoryId--go up the tree
)
select * from tbl
go
--end of function
--2. and now we can use it
declare #PERSON_ID int = 401
SELECT ts.TimeSlotId, pc.Name
FROM PersonTimeSlotAssignment
JOIN TimeSlot AS ts ON PersonTimeSlotAssignment.TimeSlotId = ts.TimeSlotId
--JOIN Category AS pc ON ts.CategoryId = pc.CategoryId
--use cross apply instead
cross apply fn_Category(ts.CategoryId) pc
WHERE PersonTimeSlotAssignment.PersonId = #PERSON_ID;
This will handle recursing the categories and providing all the data based on a PersonId or TimeSlotId:
WITH Categories (PersonId, CategoryId, ParentCategoryId, TimeSlotId, Name, BASE)
AS
(
SELECT PersonId, c.CategoryId, c.ParentCategoryId, pts.TimeSlotId, c.Name, 0 AS BASE
FROM Category c
INNER JOIN TimeSlot ts ON c.CategoryId = ts.CategoryId
INNER JOIN PersonTimeSlotAssignment pts ON ts.TimeSlotId = pts.TimeSlotId
UNION ALL
SELECT PersonId, pc.CategoryId, pc.ParentCategoryId, TimeSlotId, pc.Name, BASE + 1
FROM Category pc
INNER JOIN Categories cs ON cs.ParentCategoryId = pc.CategoryId
)
SELECT * FROM Categories
WHERE PersonId = 401
--WHERE TimeSlotId = 301
There may be a better way to write this, but does what you've asked and should get you where you need to go. The 'BASE' does not serve its original purpose, but does still show the correlation between your Person and Category, e.g. BASE 0 means the category from that record is assigned directly to the person. So, I left it for that. Thanks.
Hope this helps you :)
DECLARE #PersonId INT= 401;
WITH CTE AS
(
SELECT
t.*,
c.CategoryId AS CategoryId_c,
c.ParentCategoryId as ParentCategoryId_c,
c.Name AS Name_c,
c1.CategoryId AS CategoryId_c2,
c1.ParentCategoryId AS ParentCategoryId_c2,
c1.Name AS Name_c2,
c2.CategoryId as CategoryId_c3,
c2.ParentCategoryId AS ParentCategoryId_c3,
c2.Name AS Name_c3
FROM
PersonTimeSlotAssignment p
INNER JOIN TimeSlot t ON t.TimeSlotId=p.TimeSlotId
INNER JOIN Category c ON t.CategoryId=c.CategoryId
LEFT JOIN Category c1 ON c1.CategoryId=c.ParentCategoryId
LEFT JOIN Category c2 ON c2.CategoryId=c1.ParentCategoryId
WHERE p.PersonId=#PersonId
)
SELECT * FROM (
SELECT TimeSlotId,Name_c FROM CTE
UNION
SELECT TimeSlotId,Name_c2 FROM CTE
UNION
SELECT TimeSlotId,Name_c3 FROM CTE
)a WHERE Name_c IS NOT NULL
DECLARE #PersonId int =401;
IF OBJECT_ID('tempdb.dbo.#TimeSlot') is not null
DROP TABLE #TimeSlot
SELECT DISTINCT DENSE_RANK() OVER(ORDER BY t.TimeSlotId ) ID, t.TimeSlotId,c.CategoryId
INTO #TimeSlot
FROM PersonTimeSlotAssignment p
INNER JOIN TimeSlot t
ON p.TimeSlotId=t.TimeSlotId
INNER JOIN Category c
ON c.CategoryId=t.CategoryId
WHERE PersonId=#PersonId
IF OBJECT_ID('tempdb.dbo.#Output') is not null
DROP TABLE #Output
CREATE TABLE #Output(timeSlotId INT,CategoryId INT)
DECLARE #id INT=1 DECLARE #timeSlotId AS INT DECLARE #CategoryId INT
WHILE( SELECT id FROM #TimeSlot WHERE id=#id) IS NOT NULL
BEGIN
SELECT #timeSlotId=TimeSlotId,#CategoryId=CategoryId FROM #TimeSlot WHERE ID=#id
INSERT INTO #Output SELECT #timeSlotId,#CategoryId
WHILE( SELECT ParentCategoryId FROM Category WHERE CategoryId=#CategoryId) is not null
BEGIN
SELECT #CategoryId=ParentCategoryId FROM Category WHERE CategoryId=#CategoryId
INSERT INTO #Output SELECT #timeSlotId,#CategoryId
END
SET #id=#id+1
END
SELECT a.timeSlotId,c.Name FROM #Output a INNER JOIN Category c ON a.CategoryId=c.CategoryId

Check if entry of one table is in another one

I have two SQL Server tables.
First is filled with languagecodes (e.g. en, de, fr,..).
Second looks like:
ID | Lang | Text
----------------
..1..|..en..|..one..
..1..|..de..|..eins..
..1..|..fr..|..une..
..2..|..en..|..two..
..2..|..de..|..zwei..
Now what I want is to go trough the seconde table over every ID, check if there is a row for every language and if not add a empty row. So for the example above the second table will have an additional row:
..2..|..fr..|.. ..
Is there any way to realize this in SQL Server?
This whould be basic join question if you have ID at first table so the code is:
declare #tabLanguages table (id int,Lang char(2))
insert into #tabLanguages
values (1,'en'),(2,'de'),(3,'fr')
declare #Translation table (id int, Lang char(2), Text nvarchar(200))
insert into #Translation
values
(1,'en','one'),
(1,'de','eins'),
(1,'fr','.une'),
(2,'en','two'),
(2,'de','zwei')
select isnull(t.id,l.Id) as ID,
isnull(t.Lang,l.Lang) as Lang,
t.Text
from #tabLanguages l
left outer join #Translation t on l.id = t.Id
order by Id
But if you don't have Id in the first table then you can use following:
declare #tabLanguages table (Lang char(2))
insert into #tabLanguages
values ('en'),('de'),('fr')
declare #Translation table (id int, Lang char(2), Text nvarchar(200))
insert into #Translation
values
(1,'en','one'),
(1,'de','eins'),
(1,'fr','.une'),
(2,'en','two'),
(2,'de','zwei')
select isnull(t.id,l.Id) as ID,
isnull(t.Lang,l.Lang) as Lang,
t.Text
from (
select *, row_number() over (order by Lang) as Id
from #tabLanguages t
) l
left outer join #Translation t on l.id = t.Id
order by l.Id

"Invalid Column" when using column from table variable

I'm trying to declare a table variable and then join it to a table I created in the database. Every time I try to insert my "NAME" field into my table, I get the error 'Invalid Column Name "NAME"', even though the GNAME field works fine. What am I doing wrong, and how can I join me NAME column?
DECLARE #Names TABLE
(
ID INT,
NAME VARCHAR(100),
GNAME VARCHAR(100)
)
INSERT INTO #Names
(
ID,
NAME,
GNAME
)
SELECT
CName.ID,
Ref.NAME,
Ref.GNAME
FROM
#CurrentPositions AS CName
LEFT OUTER JOIN
dbo.NameField AS Ref
ON
CName.ID = Ref.ID
IF ( OBJECT_ID('dbo.ReportTable', 'U') IS NOT NULL)
DROP TABLE dbo.ReportTable
CREATE TABLE [dbo].[ReportTable]
(
[ID_NUMBER] [INT],
[NAME] [VARCHAR](150)
[GNAME] [VARCHAR](150)
)
INSERT INTO [dbo].[ReportTable]
(
ID_NUMBER,
NAME,
GNAME
)
SELECT
C.ID_NUMBER,
N.NAME,
N.GNAME
FROM
#Names AS N
INNER JOIN
#CurrentPositions AS C
ON N.ID_NUMBER = C.ID_NUMBER
Try using a Temp table :
CREATE TABLE #Names
(
ID INT,
NAME VARCHAR(100),
GNAME VARCHAR(100)
)
INSERT INTO #Names
(
ID,
NAME,
GNAME
)
SELECT
CName.ID,
Ref.NAME,
Ref.GNAME
FROM
#CurrentPositions AS CName
LEFT OUTER JOIN
dbo.NameField AS Ref
ON
CName.ID = Ref.ID
IF ( OBJECT_ID('dbo.ReportTable', 'U') IS NOT NULL)
DROP TABLE dbo.ReportTable
CREATE TABLE [dbo].[ReportTable]
(
[ID_NUMBER] [INT],
[NAME] [VARCHAR](150)
[GNAME] [VARCHAR](150)
)
INSERT INTO [dbo].[ReportTable]
(
ID_NUMBER,
NAME,
GNAME
)
SELECT
C.ID_NUMBER,
N.NAME,
N.GNAME
FROM
#Names AS N
INNER JOIN
#CurrentPositions AS C
ON N.ID_NUMBER = C.ID_NUMBER
I've assumed that you will also change the table variable #CurrentPositions to a temp table
Just remember to drop the tables after you use them.
It is quite possible that all you need to do is wrap your field names in square brackets, e.g.
INSERT INTO #Names
(
[ID],
[NAME],
[GNAME]
)
SELECT
CName.[ID],
Ref.[NAME],
Ref.[GNAME]
FROM
#CurrentPositions AS CName
LEFT OUTER JOIN
dbo.NameField AS Ref
ON
CName.[ID] = Ref.[ID]
If that doesn't fix it, please post the schema of your #CurrentPositions and dbo.NameField tables.

SQL select -one to many joins want to have the manys

I have two tables, TBL_PARENT (parentID, ParentName) and TBL_CHILDREN (ParentID,Child_Name)
A Parent can have 0 to many children
What I want is a query to give me a list of parent and their children in single row per parent.
For example
Parent1 John,Mary
Parent2 jane,steve,jana
And the number of rows to be the total number of parents
try this query :
I have created 3 table 2 of them are already created on your database #parant, #ch
and the third one is a temp table to put the result in.
create table #parant (id int , name varchar(10))
create table #ch (id int , name varchar(10), pid int)
insert into #parant select 1,'PA'
insert into #parant select 2,'PB'
insert into #parant select 3,'PC'
insert into #ch select 1,'Ca',1
insert into #ch select 1,'Cb',1
insert into #ch select 1,'Cc',1
insert into #ch select 1,'Cd',3
insert into #ch select 1,'Cf',3
insert into #ch select 1,'Ch',1
create table #testTable (id int, name varchar(10),chid int, chname varchar(10), cpid int)
insert into #testTable
select x.id , x.name ,isnull( y.id ,0), isnull(y.name,'') ,isnull(y.pid ,0)
from #parant as x
left outer join #ch as y
on x .id = y .pid
SELECT t.ID, t.name , STUFF(
(SELECT ',' + s.chname
FROM #TestTable s
WHERE s.ID = t.ID
FOR XML PATH('')),1,1,'') AS CSV
FROM #TestTable AS t
GROUP BY t.ID, t.name
GO
drop table #testTable
drop table #ch
drop table #parant
for the above data i got the following result
1 PA Ca,Cb,Cc,Ch
2 PB
3 PC Cd,Cf
SELECT COUNT(P.parentID),
P.ParentName,
C.Child_Name
FROM TBL_PARENT as P
INNER JOIN TBL_CHILDREN as C
WHERE P.parentID == c.ParentID
GROUP BY P.ParentName;
The line P.parentID == c.ParentID is doing the Join, and the line count(P.parentID) is doing the count of all the parents and the line GROUP BY P.ParentName is grouping all the rows by the name of the parent so you can display all the children of every single parent.