How to join three tables with a NOT IN CLAUSE - sql

Scenario ... A STORE gets a LIST. The 'LIST' basically is a collection of SKUs that the store stocks that needs to be counted.
When a store starts counting the SKUs from a given List assigned to it, the information is saved in another table called 'StoreCycles' which records the ListId, StoreName and the Dates the counting started and completed.
These are my tables.
Table 1. Table with 'Lists' which have a primary key 'ListId'
ListId ListName
1 abc
2 def
3 ghi
Table 2. 'Stores' -- Each list from the above table ('Lists' Table) is assigned to one or more stores.
The ListId in the 'Stores' Table is the primary key of the 'Lists' tabel. The Listid and LineId together make up the foreign key.
ListId LineId StoreName
1 1 StoreA
1 2 StoreD
2 1 StoreB
2 2 StoreC
2 3 StoreA
3 1 StoreA
Table 3. 'StoreCycles' -- that saves when the list assigned to a store was started counting and when it was completed.
ListId StoreName StartDate CompleteDate
1 StoreA 2016-7-22 2016-7-22
2 StoreA 2016-7-22
2 StoreC 2016-7-22
At any time I want to pull up those list names that have not been completed , i.e they have a null complete date.
This is my query:
Select T0.ListId,
T0.ListNaame ,
T2.StartDate
From Lists T0
JOIN Stores T1 On T0.ListId = T1.ListId
LEFT JOIN StoreCycles T2 ON T0.ListId = T2.ListId
WHERE T1.StoreName = 'StoreA'
AND T0.ListId NOT IN (SELECT ListId FROM StoreCycles WHERE CompleteDate IS NOT NULL)
RESULT SHOULD BE >>
ListId ListName StartDate
2 def 2016-7-22
3 ghi NULL
BUT The result that I get is this
ListId ListName StartDate
2 def NULL
2 def NULL
3 ghi NULL

Just passing by and dumping SQL.
Go on, nothing to see.
There is no bug hunt going on here.
declare #Lists table (ListId int primary key, ListName varchar(20));
insert into #lists values (1,'abc'),(2,'def'),(3,'ghi');
declare #Stores table (ListId int, LineId int, StoreName varchar(20));
insert into #Stores values
(1,1,'StoreA'),
(1,2,'StoreD'),
(2,1,'StoreB'),
(2,2,'StoreC'),
(2,3,'StoreA'),
(3,1,'StoreA');
declare #StoreCycles table (ListId int, StoreName varchar(20), StartDate date, CompleteDate date);
insert into #StoreCycles values
(1,'StoreA','2016-7-22','2016-7-22'),
(2,'StoreA','2016-7-22',null),
(2,'StoreC','2016-7-22',null);
SELECT
L.ListId,
L.ListName,
SC.StartDate
FROM #Stores S
JOIN #Lists L On (S.ListId = L.ListId)
LEFT JOIN #StoreCycles SC ON (S.ListId = SC.ListId AND S.StoreName = SC.StoreName)
WHERE S.StoreName = 'StoreA'
AND SC.CompleteDate IS NULL;

Select
l.ListId
,l.ListNaame
,sc.StartDate
From
Lists l
JOIN Stores s
ON l.ListId = s.ListId
AND s.StoreName = 'StoreA'
LEFT JOIN StoreCycles sc
ON s.ListId = sc.ListId
AND s.LineId = sc.LineId
WHERE
sc.CompleteDate IS NULL
You are already doing joins if you build them correctly you will not need a select or not in in your where clause. Also your relationship for StoreCycles seems to be wrong because you are trying to go straight from Lists to StoreCycles but Stores is the intermediary table.
Also just a thought why not use table aliases that will help you know what table you are referring to rather than t0, t1, t2 such as l, s, sc....

Maybe another way to look at it is to return all rows where either the StartDate is NULL (no records) or the CompleteDate is NULL (incomplete records). Also, if the foreign key is using 2 columns you probably want to use both columns in the JOIN.
SELECT T0.ListId,
T0.ListName,
T2.StartDate
FROM Lists T0
JOIN Stores T1
ON T1.ListId = T0.ListId
LEFT JOIN StoreCycles T2
ON T2.ListId = T1.ListId
AND T2.LineId = T1.LineId
WHERE T1.StoreName = 'StoreA'
AND (T2.CompleteDate IS NULL OR T2.StartDate IS NULL)

Related

How to loop records table row by row in SQL Server?

Below are my tables :
Table:
ID Name category
--------------------
1 test 1
2 test 2
3 test1 3
4 test1 4
Category:
ID Name
------------
1 simple
2 complex
3 ordinory
4 ex-ordinory
In my table have 'Name' column. Each name has a category.
I need to fetch all names with categories wise.
select *
from table
inner join category ca on ca.id = table.category
where name = test
and category = 1
This query will return only name = test with category = 1 records
But I need to fetch all the names with their categories.
I am thinking to loop all the records row by row. Please suggest possible way to do this operation.
Is this what you're looking for?
CODE
CREATE TABLE TABLE_NAME (ID INT IDENTITY, NAME VARCHAR(25), CATEGORY INT)
INSERT INTO TABLE_NAME
VALUES ('test',1),('test',2),('test1',3),('test1',4)
CREATE TABLE CATEGORY (ID INT IDENTITY, NAME VARCHAR(25))
INSERT INTO CATEGORY
VALUES ('Simple'),('Complex'),('Ordinary'),('Ex-Ordinary')
SELECT DISTINCT
T.NAME
,C.NAME
FROM TABLE_NAME T
JOIN CATEGORY C
ON C.ID = T.CATEGORY
RESULT
TABLE_NAME CATEGORY_NAME
test Complex
test Simple
test1 Ex-Ordinary
test1 Ordinary

How can I modify a LEFT OUTER JOIN to add a filter for the RIGHT side table

I have three tables in my database.
AdminTest - Holds a list of tests that are available for users
AdminTestQuestion - Holds a list of questions
UserTest - Holds a list of tests that users have purchased. There's a UserId column in this table and rows in the table always have a value for this. When doing a select I need to be able to filter out the rows in this table by UserId
The data looks like this:
The database stores the results of three tests. test1, test2 and test3
The person with userId = 1 purchased test2
The person with userId = 2 purchased test3.
I am using the following SQL:
SELECT
AdminTest.AdminTestId,
AdminTest.Title,
COUNT(AdminTestQuestion.AdminTestQuestionId) Questions,
AdminTest.Price,
UserTest.PurchaseDate
FROM AdminTest
LEFT OUTER JOIN UserTest
ON AdminTest.AdminTestId = UserTest.AdminTestId
JOIN AdminTestQuestion
ON AdminTest.AdminTestId = AdminTestQuestion.AdminTestId
GROUP BY
AdminTest.AdminTestId,
AdminTest.Title,
UserTest.UserId
Which gives me a report like this:
AdminTestId Title Questions Price PurchaseDate
1 Test1 10 0
2 Test2 20 0 1/1/2011
3 Test3 10 10 2/2/2012
Can someone suggest how I could modify this so the SQL takes a parameter of UserId so it could correctly show the tests that have been purchased by a particular user:
This is what I would like to see when I provide a value of 1 for the UserId parameter:
AdminTestId Title Questions Price PurchaseDate
1 Test1 10 0
2 Test2 20 0 1/1/2011
3 Test3 10 10
This is what I would like to see when I provide a value of 2 for the UserId parameter:
AdminTestId Title Questions Price PurchaseDate
1 Test1 10 0
2 Test2 20 0
3 Test3 10 10 2/2/2012
What I have tried so far is adding WHERE clauses with the UserId to the AdminUser part of the select. But this does not seem to work. I hope someone can point me in the right direction.
For reference here's the DDL of the UserTest table that I want to filter out with UserId somehow:
CREATE TABLE [dbo].[UserTest] (
[UserTestId] INT IDENTITY (1, 1) NOT NULL,
[AdminTestId] INT NOT NULL,
[UserId] INT NOT NULL,
[PurchaseDate] DATETIME NOT NULL,
CONSTRAINT [PK_UserTest] PRIMARY KEY CLUSTERED ([UserTestId] ASC)
);
To expand on what #RichardHansell said...
You can filter a JOIN by adding things to the 'ON' clause of the script. The ON clause does not have to be only links between the two tables, you can add other filters in as well. Like so...
SELECT AdminTest.AdminTestId,
AdminTest.Title,
COUNT(AdminTestQuestion.AdminTestQuestionId) Questions,
AdminTest.Price,
UserTest.PurchaseDate
FROM AdminTest
LEFT OUTER JOIN UserTest
ON AdminTest.AdminTestId = UserTest.AdminTestId
AND UserTest.UserId = #FilteredUserId
JOIN AdminTestQuestion
ON AdminTest.AdminTestId = AdminTestQuestion.AdminTestId
GROUP BY AdminTest.AdminTestId, AdminTest.Title, UserTest.UserId
If you put the parameter test in the ON clause of the Left Outer Join, you should get the results you're after:
...
LEFT OUTER JOIN UserTest
ON AdminTest.AdminTestId = UserTest.AdminTestId
AND UserTest.UserId = #UserId
...

Create parent/child relationship from static rows

By connection to a webservice, I receive a list of data. Each record in the list contains three category fields, which I save in a product table with the following column markup:
CategoryName SubCategoryName SubSubCategoryName
-----------------------------------------------------
Men Clothing Jeans
Women Jewelry Bracelets
Women Clothing Hoodies
Men Clothing Hoodies
ProductTable: CategoryName | SubCategoryName | SubSubCategoryName
What I want to do, is to extract the categories from the product table and save them to a table with parent/child relationship.
Id ParentId CategoryName
-------------------------------
1 NULL Men
2 1 Clothing
3 2 Jeans
4 NULL Women
5 4 Jewelry
6 5 Bracelets
7 4 Clothing
8 7 Hoodies
9 2 Hoodies
What SQL query can I use to perform this action?
First, create a new table
create table NewCategories (
ID int IDENTITY(1,1) primary key,
ParentID int null,
Name nvarchar(max)
)
Now, Insert all rows into the new table (this will assign the ID's)
insert into NewCategories (Name)
select distinct CategoryName
from OldCategories
insert into NewCategories (Name)
select distinct SubCategoryName
from OldCategories
insert into NewCategories (Name)
select distinct SubSubCategoryName
from OldCategories
Update the NewCategories table, setting the ParentID column, once for the SubCategoryName, and once for the SubSubCategoryName:
update nc2
set ParentID = nc1.ID
from NewCategories nc1
inner join OldCategories oc on oc.CategoryName = nc1.Name
inner join NewCategories nc2 on oc.SubCategoryName = nc2.Name
update nc2
set ParentID = nc1.ID
from NewCategories nc1
inner join OldCategories oc on oc.SubCategoryName = nc1.Name
inner join NewCategories nc2 on oc.SubSubCategoryName = nc2.Name
This assumes that there are no *CategoryName duplicates in the original table.
SQL Fiddle
For duplicates, you can do (slightly more complex)
--insert all categories
insert into NewCategories (Name)
select distinct CategoryName
from OldCategories
--only categories in the "new" table now
insert into NewCategories (ParentID, Name)
select distinct n.ID, o.SubCategoryName
from OldCategories o
inner join NewCategories n on o.CategoryName = n.Name
--now subcategories are items with non-null parents,
-- so we need a double join
insert into NewCategories (ParentID, Name)
select distinct n1.ID, o.SubSubCategoryName
from OldCategories o
inner join NewCategories n1 on o.SubCategoryName = n1.Name
inner join NewCategories n2 on o.CategoryName = n2.Name and n2.ID=n1.ParentID
Here is a new fiddle, modified to handle duplicates

Querying for who worked on an item first and second

I have a table that looks like this:
Id (PK, int, not null)
ReviewedBy (nvarchar(255), not null)
ReviewDateTime(datetime, not null)
Decision_id (int, not null)
Item_id (FK, int, not null)
The business process with this table is that each Item (shown by Item_id foreign key) is to be worked on by 2 people.
How can I query this table to determine who (ReviewedBy) reviewed the item first and who reviewed it second.
I'm really struggling to figure this out because I neglected adding a Type column to my table that would determine which the user was acting as. :(
Edit
Given the following data
Id,ReviewedBy,ReviewedWhen,SomeOtherId,
16,111111,2011-12-14 22:06:54,1,
17,187935,2011-12-14 22:07:03,1,
18,187935,2011-12-14 22:07:18,2,
19,187935,2011-12-14 22:07:20,3,
20,111111,2011-12-14 22:07:23,2,
21,187935,2011-12-14 22:07:26,3,
22,123456,2011-12-14 22:27:50,4,
with schema
CREATE TABLE [Reviews] (
[Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[ReviewedBy] NVARCHAR(6) NOT NULL,
[ReviewedWhen] TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
[SomeOtherId] INTEGER NOT NULL
);
Executing the following to get a list of people who did second reviews will return rows where there is only one review for SomeOtherId.
select t1.*
from Reviews as t1
left outer join Reviews as t2
on (t1.SomeOtherId = t2.SomeOtherId and t1.ReviewedWhen < t2.ReviewedWhen)
where t2.SomeOtherId is null;
Solution
-- First checks
select t1.ReviewedBy, count(t1.Id)
from Reviews as t1
left outer join Reviews as t2
on (t2.SomeOtherId = t1.SomeOtherId and t1.ReviewedWhen > t2.ReviewedWhen)
where t2.SomeOtherID is null
group by t1.ReviewedBy;
-- Second checks
select t1.ReviewedBy, count(t1.Id)
from Reviews as t1
left outer join Reviews as t2
on (t2.SomeOtherId = t1.SomeOtherId and t1.ReviewedWhen < t2.ReviewedWhen)
where t2.SomeOtherID is null
and t1.Id not in (select Id from Reviews group by SomeOtherId having count(SomeOtherId) = 1)
group by t1.ReviewedBy;
Essentially, it was counting items where there was only one review as both a first and second check. All I had to do was ensure that when I'm counting second checks that I'm not including rows with only one review.
I thought I could achieve this in one query but guess not.
Try this:
select
t1.ReviewedBy FirstReviewer,
t2.ReviewedBy SecondReviewer
from
Table t1
left outer join Table t2 on t1.Item_Id = t2.Item_Id and t2.ReviewDateTime > t1.ReviewDateTime
If you want to only return rows that have been reviewed by two people, change the left outer join to an inner join.
If ReviewDateTime is never updated and Id is an identity column you can change the join to join on Id rather ReviewDateTime, which will be faster.

How to transform vertical table into horizontal table?

I have one table Person:
Id Name
1 Person1
2 Person2
3 Person3
And I have its child table Profile:
Id PersonId FieldName Value
1 1 Firstname Alex
2 1 Lastname Balmer
3 1 Email some_email#test.com
4 1 Phone +1 2 30004000
And I want to get data from these two tables in one row like this:
Id Name Firstname Lastname Email Phone
1 Person1 Alex Balmer some_email#test.com +1 2 30004000
What is the most optimized query to get these vertical (key, value) values in one row like this? Now I have a problem that I done four joins of child table to parent table because I need to get these four fields. Some optimization is for sure possible.
I would like to be able to modify this query in easy way when I add new field (key,value). What is the best way to do this? To create some stored procedure?
I would like to have strongly types in my DB layer (C#) and using LINQ (when programming) so it means when I add some new Key, Value pair in Profile table I would like to do minimal modifications in DB and C# if possible. Actually I am trying to get some best practices in this case.
Select
P.ID
, P.Name
, Case When C.FieldName = 'FirstName' Then C.Value Else NULL END AS FirstName
, Case When C.FieldName = 'LastName' Then C.Value Else NULL END AS LastName
, Case When C.FieldName = 'Email' Then C.Value Else NULL END AS Email
, Case When C.FieldName = 'Phone' Then C.Value Else NULL END AS Phone
From Person AS P
Inner JOIN Child AS C
ON P.ID = C.PersonID
You could use PIVOT; not sure which one would be the easiest for you to add a new column.
best optimized way with strongly typed fields, is to do it this way:
CREATE TABLE Persons
(PersonID int identity(1,1) primary key
,Firstname varchar(...)
,Lastname varchar(...)
,Email varchar(...)
,Phone varchar(...)
,....
)
then the most optimized query would be:
SELECT
PersonID,Firstname,Lastname,Email,Phone
FROM Persons
WHERE ...
Add all main columns into the persons table. if you need to specialize create additional tables:
--one person can play many instruments with this table
CREATE TABLE PersonMusicians
(PersonID int --pk fk to Persons.PersonID
,InstrumentCode char(1) --pk
,...
)
--only one row per person with this table
CREATE TABLE PersonTeachers
(PersonID int --pk fk to Persons.PersonID
,FavoriteSubjectCode char(1)
,SchoolName varchar(...)
)
if you have to have unlimited dynamic attribute fields, then I would create the above structure as fully as possible (as many common fields as possible) and then have an "AdditionalInfo" table where you store all the info like:
AdditionalInfoFields
FieldID int identity(1,1) primary key
FieldName varchar(...)
AdditionalInfo
AdditionalInfoID int identity(1,1) primary key
PersonID int fk to Persons.PersonID
FieldID int fk to AdditionalInfoFields.FieldID
FieldValue varchar(..) or you can look into sql_variant
have an index on AdditionalInfo.PersonID+FieldID and if you will search for all people that have attribute X, then also another like AdditionalInfo.FieldID+PersonID
short of any of the above, you will need to use the four left outer joins like you have mentioned in your option #1:
SELECT
P.ID, p.Name
, p1.Value AS Firstname
, p2.value AS Lastname
, p3.Value AS Email
, p4.Value AS Phone
FROM Persons p
LEFT OUTER JOIN Profile p1 ON p.PersonID=p1.PersonID AND p1.FieldName='Firstname'
LEFT OUTER JOIN Profile p1 ON p.PersonID=p1.PersonID AND p1.FieldName='Lastname'
LEFT OUTER JOIN Profile p1 ON p.PersonID=p1.PersonID AND p1.FieldName='Email'
LEFT OUTER JOIN Profile p1 ON p.PersonID=p1.PersonID AND p1.FieldName='Phone'
WHERE ....
you could always make a materialized view with an index out of this 4 left join query and have the data precalculated for you which should speed it up.