Problem creating a view containing lookup tables - sql

should be simple enough but it's causing me a couple of issues.
I have a data set similar to the following:
User
UserID
Name
Age
UserPropertyValues
UserID
PropertyCodeValueID
PropertyCodes
PropertyCodeID
PropertyCodeName
PropertyCodeValues
PropertyCodeValueID
PropertyCodeID
PropertValue
Now let's assume the tables contain the following data:
1 John 25
2 Sarah 34
1 2
1 3
2 1
2 3
1 FavColour
2 CarMake
3 PhoneType
1 1 Blue
2 1 Yellow
3 2 Ford
4 3 Mobile
5 3 Landline
Now from this I'm looking to create a view to return the User details, as well as the property values for Property code 1 and 2 like so:
John 25 Yellow Ford
Sarah 34 Blue Ford
The queries I have tried so far tend to return repeating rows of data :
John 25 Yellow
John 25 Ford
Sarah 34 Blue
Sarah 34 Ford
Any help is appreciated, thank you all in advance.

Input data:
DECLARE #User TABLE (UserID INT, Name VARCHAR(10), Age INT)
INSERT INTO #User
SELECT 1, 'John', 25 UNION
SELECT 2, 'Sarah', 34
DECLARE #UserPropertyValues TABLE(UserID INT, PropertyCodeValueID INT)
INSERT INTO #UserPropertyValues
SELECT 1, 2 UNION
SELECT 1, 3 UNION
SELECT 2, 1 UNION
SELECT 2, 3
DECLARE #PropertyCodes
TABLE (PropertyCodeID INT, PropertyCodeName VARCHAR(10))
INSERT INTO #PropertyCodes
SELECT 1, 'FavColour' UNION
SELECT 2, 'CarMake' UNION
SELECT 3, 'PhoneType'
DECLARE #PropertyCodeValues TABLE (PropertyCodeValueID INT,
PropertyCodeID INT, PropertValue VARCHAR(10))
INSERT INTO #PropertyCodeValues
SELECT 1, 1, 'Blue' UNION
SELECT 2, 1, 'Yellow' UNION
SELECT 3, 2, 'Ford' UNION
SELECT 4, 3, 'Mobile' UNION
SELECT 5, 3, 'Landline'
If two properties is all that you need in result, and each user have those properties, then try this:
SELECT U.Name, U.Age, PCVFC.PropertValue, PCVCM.PropertValue
FROM #User U
INNER JOIN #UserPropertyValues UPVFC ON U.UserID = UPVFC.UserID
INNER JOIN #PropertyCodeValues PCVFC
ON UPVFC.PropertyCodeValueID = PCVFC.PropertyCodeValueID
AND PCVFC.PropertyCodeID = 1
INNER JOIN #UserPropertyValues UPVCM ON U.UserID = UPVCM.UserID
INNER JOIN #PropertyCodeValues PCVCM
ON UPVCM.PropertyCodeValueID = PCVCM.PropertyCodeValueID
AND PCVCM.PropertyCodeID = 2
[edit] But to handle possible NULL values better use this:
SELECT U.Name, U.Age, FC.PropertValue, CM.PropertValue
FROM #User U
LEFT JOIN (
SELECT UserID, PropertValue FROM #UserPropertyValues UPV
INNER JOIN #PropertyCodeValues PCV
ON UPV.PropertyCodeValueID = PCV.PropertyCodeValueID
AND PCV.PropertyCodeID = 1
) FC ON U.UserID = FC.UserID
LEFT JOIN (
SELECT UserID, PropertValue FROM #UserPropertyValues UPV
INNER JOIN #PropertyCodeValues PCV
ON UPV.PropertyCodeValueID = PCV.PropertyCodeValueID
AND PCV.PropertyCodeID = 2
) CM ON U.UserID = CM.UserID

What you really need to is abandon this type of database design as soon as humanly possible. It will never be either effective of efficient. To get three types of values you have to join to the table three times. Once you have 30 or forty differnt types of information, you will need to join to the table that many times (and left joins at that). Further everytime you want any information you will need to join to this table. I see this as creating a major locking issue in your database. The people who originally designed one of the databases I work with did this and caused a huge performance issue when the company grew from having one or two customers to the largest in our industry.
If the properties are ones that will likely only have one realted records per person, put them into the user table. If they will have multiple records then create a separate table for each type of information (one for hones, one for email, one for cartype, etc.) Since the information you will eventually want to collect will usually be more than the simple value and differnt for each type of information they must be in separate tables. Then when you only need to see one value (say phone number but not email) you join to just that table and you aren't interfeing with people trying to access email but not phone number. And if you have a yellow ford or white Honda, it will be stored in only one record in the auto table rather than two property records in your design.

SELECT
u.[Name],
u.Age,
pcv1.PropertValue,
pcv2.PropertValue
FROM
Users u
LEFT JOIN
( UserPropertyValues upv1
JOIN PropertyCodeValues pcv1 ON
upv1.PropertyCodeValueID = pcv1.PropertyCodeValueID
AND pcv1.PropertyCodeID = 1
)
ON upv1.UserID = u.UserID
LEFT JOIN (
UserPropertyValues upv2
JOIN PropertyCodeValues pcv2 ON
upv2.PropertyCodeValueID = pcv2.PropertyCodeValueID
AND pcv2.PropertyCodeID = 2
)
ON upv2.UserID = u.UserID
Edit : I renamed user to users
Edit2 : Allow for null (not entered values)

Related

SQL Server: Select rows in table that share value with rows in another table

I have searched the questions but haven't been able to find an answer. Perhaps my wording may be wrong. My problem is as follows:
Given table Users:
ID code owner
1 777 James
2 432 George
3 111 Kale
And table Products:
ID product_name code
1 chair 777
2 table 777
3 fan 432
4 monitor 777
5 sofa 111
6 bed 111
I need a query that fetches N number of rows from table Users and then fetches all rows from table Products that have a code matching the code of Any row fetched previously.
Is this possible in SQL Server? Is it optimal?
For above example if i fetch first 2 rows (owners James and George) I should get all products with code 777 and 432.
To select the first n (here 2) users use TOP. Put that in a subquery and LEFT JOIN products on the common code to it.
SELECT *
FROM (SELECT TOP 2
*
FROM users
ORDER BY id) u
LEFT JOIN products p
ON p.code = u.code;
If you want to make users, that don't have at least one product, disappear you can replace the LEFT JOIN by an INNER JOIN.
Yes it is a common thing, called "Joins"
for example this would give you the answer:
Select products.product_name,users.owner FROM products LEFT JOIN users ON products.code=users.code
You can read more about JOINS here: https://www.w3schools.com/sql/sql_join.asp
Try this with Inner join :
Select products.product_name,users.owner FROM products inner JOIN users ON products.code=users.code
where users.code in (777,432)
Create table #Users
(
Id int Identity(1,1),
Code varchar(100),
Owner varchar(100)
)
Create table #Products
(
Id int Identity(1,1),
ProductName varchar(100),
Code varchar(100)
)
insert Into #Users(Code,Owner)
Select '777','James'
insert Into #Users(Code,Owner)
Select '432','George'
insert Into #Users(Code,Owner)
Select '111','Kale'
insert Into #Products(ProductName,Code)
Select 'Chair','777'
insert Into #Products(ProductName,Code)
Select 'Table','777'
insert Into #Products(ProductName,Code)
Select 'fan','432'
insert Into #Products(ProductName,Code)
Select 'monitor','777'
insert Into #Products(ProductName,Code)
Select 'Sofa','111'
insert Into #Products(ProductName,Code)
Select 'bed','111'
select * from #products
select * from #Products Left join #Users on #Users.Code=#Products.Code Where Owner in ('James','George')
This works even if code is not unique;
select * from Products where code in
(
select top 2 code from users order by id
)
Replace 2 with the relevant N

return rows only if a certain conditions match up with another table

I want an SQL query that returns a list of users, but only if they have a certain link and not another.
Table 1 links to table 2 users.agreementid = agreements.id, it can link multiple times as a user can be linked to different agreements
All the users in table 1 that only have a link to an agreement where the product is AggregationAgreement, if the user has a link to 2+ agreement ids and any of the links are to a ServiceAgreement then this should not be returned
So I have 2 tables:
TABLE 1- USERS
USER AGREEMENTID
--------------------
USER1 1
USER2 3
USER1 3
USER3 3
USER3 4
USER4 1
TABLE 2- AGREEMENTS
ID PRODUCT
-------------------------
1 ServiceAgreement
2 ServiceAgreement
3 AggregationAgreement
4 AggregationAgreement
5 ServiceAgreement
So for my results on the above example i only expect USER2 and USER3 to be returned.
USER 1 has two links but one of those is ServiceAgreement so it shouldn't be returned in the results.
USER 2 has link to just 1 aggregation agreement so this should be returned in the results.
USER 3 has two links but both are to AggregationAgreement so this should be returned in the results.
USER 4 has one link but it's to a ServiceAgreement so this should no be returned in the results.
Hope that all makes sense, as always appreciate any help.
This would return the users with the agreements, excluding ones that have a ServiceAgreement linked.
SELECT USERS.[UserName]
, AGREEMENTS.[AgreementId]
, AGREEMENTS.[Product]
FROM USERS
INNER JOIN AGREEMENTS
ON AGREEMENTS.[AgreementId] = USERS.[AgreementId]
WHERE USERS.[UserName] NOT IN
(
SELECT USERS.[UserName]
FROM USERS
INNER JOIN AGREEMENTS
ON AGREEMENTS.[AgreementId] = USERS.[AgreementId]
WHERE AGREEMENTS.[Product] = 'ServiceAgreement'
)
Try this:
select *
from users u
where exists
(
select 1
from agreements a
where u.agreementid=a.id and a.product='AggregationAgreement'
)
and not exists
(
select 1
from agreements a2
where u.agreementid=a2.id and a2.product<>'AggregationAgreement'
)
You can also use a query like below
select user
from
(
select
user=u.[user],
weight= case a.product ='ServiceAgreement' then -1 else 0 end -- negative weight for undesirable agreement
from
users u join agreements a
on u.AgreementId=a.AgreementId
and a.product in ('ServiceAgreement','AggregationAgreement') -- we only have two agreements of interest here
)t
group by [user]
having sum(weight)=0 -- should not be negative
Using EXCEPT:
declare #Users table ([User] nvarchar(10), [AGREEMENTID] int)
insert into #Users values
('USER1', 1)
, ('USER2', 3)
, ('USER1', 3)
, ('USER3', 3)
, ('USER3', 4)
, ('USER4', 1)
declare #Agreements table ([ID] int, [Product] nvarchar(100))
insert into #Agreements values
(1, 'ServiceAgreement')
, (2, 'ServiceAgreement')
, (3, 'AggregationAgreement')
, (4, 'AggregationAgreement')
, (5, 'ServiceAgreement')
select u.[User] from #Users u inner join #Agreements a on u.AGREEMENTID = a.ID and a.Product = 'AggregationAgreement'
except
select u.[User] from #Users u inner join #Agreements a on u.AGREEMENTID = a.ID and a.Product = 'ServiceAgreement'

Inserting into a join table if value does not already exist

I have 2 tables - user, region and a join/connecting table that is used to join both user and region.
I need to insert into the join table all the region values that the user does not already have and i am unsure how to go about this.
I have attempted this numerous ways but i am not entirely sure how to only place the values that do already exist within the table into the join table. Does anyone have any ideas or suggestions?
SELECT
CONVERT( CONCAT('INSERT INTO user_region VALUES(',
user.id,
',',
reg.id,
');') USING UTF8)
FROM
user user
JOIN
user_region user_reg ON user_reg.id = user.id
JOIN
region reg ON reg.id = user_reg.id
WHERE
(user.email_address LIKE '%gmail%'
OR user.email_address LIKE '%hotmail%');
User Table User Region Region
----------- ----------- ------
1 1 2 1
2 3 2 2
3 3 4 3
4 4 3 4
Kind of
INSERT INTO user_region (userID, regionID)
SELECT u.userID, r.regionID
FROM
(SELECT DISTINCT userId
FROM user
WHERE user.email_address LIKE '%gmail%'
OR user.email_address LIKE '%hotmail%') u
JOIN region r ON NOT EXISTS (
SELECT 1
FROM user_region ur
WHERE ur.userID = u.userID AND ur.regionID = r.regionID )

Table Join Distinct Field

The problem which I am having is joining two tables. One table contains all the generic data and the second table contains old fields which have been given specific values. The exemplary tables below should help to clarify my setup.
Table 1 (Generic Data)
ParaIndex ParaName ParaDefault
1 Cat 15
2 Fish 8
3 Dog 3
Table 2 (Specific Data)
Project ParaIndex ParaValue
John 1 6
John 2 7
Alan 3 9
The goal then is to join these tables to get a single table:
Table 3 (Table Join on ParaIndex for 'John')
ParaName ParaIndex ParaValue ParaDefault
Cat 1 6 15
Fish 2 7 8
Dog 3 3
So Table 3 would return all rows from Table 1 but would only show values for ParaValue where they are not empty. E What I have tried so far is a combination of changing the join type, distinct select, group by, select subqueries, and suggestions mentioned in other posts.
The end goal is then to use this query in vb.net as par of a form.
The closest which I have gotten is to return the requested data in table 3, but it would exclude rows which are not requested (e.g. if the query is for 'John' it would then exclude the parameters marked by 'Alan' which is 3-Dog in this case).
SELECT t1.ParaName, t1.ParaIndex, t2.ParaValue, t1.ParaDefault
FROM Table1 AS t1LEFT OUTER JOIN Table2 AS t2
ON t1.ParaIndex = t2.ParaIndex
WHERE (((t2.ParaIndex) is null) OR t2.Project = 'John')
You have to use a LEFT OUTER JOIN:
SELECT t1.ParaName,
t1.ParaIndex,
COALESCE(CAST(t2.ParaValue AS VARCHAR(10), '') AS ParaValue,
t1.ParaDefaule,
FROM dbo.Table1 t1
LEFT OUTER JOIN dbo.Table2 t2
ON t1.ParaIndex = t2.ParaIndex
WHERE t2.Project = 'John'
ORDER BY t1.ParaIndex

SQL Query to display heirarchical data

I have two tables - 'Users' and 'Supervision'
For this example, my users table is very simple:-
Users
=====
ID (PK)
UserName
Some users manage other users, so I've built a second table 'Supervision' to manage this:-
Supervision
===========
UserID
SuperID - this is the ID of the staff member that the user supervises.
This table is used to join the Users table to itself to identify a particular users supervisor. It might be that a user has more than one supervisor, so this table works perfectly to this end.
Here's my sample data in 'Users':-
userID userName
1 Bob
2 Margaret
3 Amy
4 Emma
5 Carol
6 Albert
7 Robert
8 Richard
9 Harry
10 Arthur
And my data in 'Supervision':-
userID superID
1 2
1 3
2 4
2 5
3 4
3 5
6 1
6 7
7 8
7 9
9 10
If I want to see who directly reports to Bob, writing an SQL query is straightforward, and tells me that Margaret and Amy are his direct reports.
What I want to do however is to write a query that shows everybody who comes under Bob, so it would need to look at Bobs direct reports, and then their direct reports, and so on - it would give Margaret, Amy, Emma and Carol as the result in this case.
I'm assuming this requires some kind of recursion but I'm completely stuck..
You should use recursive CTE:
WITH RCTE AS
(
SELECT * FROM dbo.Supervision WHERE UserID = 1
UNION ALL
SELECT s.* FROM dbo.Supervision s
INNER JOIN RCTE r ON s.userID = r.superID
)
SELECT DISTINCT u.userID, u.userName
FROM RCTE r
LEFT JOIN dbo.Users u ON r.superID = u.userID
SQLFiddle DEMO
Sounds to me like you need a Recursive CTE. This article serves as a primer, and includes a fairly similar example to the one you have:
http://blog.sqlauthority.com/2012/04/24/sql-server-introduction-to-hierarchical-query-using-a-recursive-cte-a-primer/
Hope it helps.
WITH MyCTE
AS (
-- ID's and Names
SELECT SuperID, ID
FROM Users
join dbo.Supervision
on ID = dbo.Supervision.UserID
WHERE UserID = 1
UNION ALL
--Who Manages who...
SELECT s.SuperID, ID
FROM Supervision s
INNER JOIN MyCTE ON s.UserID = MyCTE.SuperID
WHERE s.UserID IS NOT NULL
)
SELECT distinct MyCTE.ID, NAMES.UserName, '<------Reports to ' as Hierarchy, res_name.UserName
FROM MyCTE
join dbo.Users NAMES on
MyCTE.ID = NAMES.ID
join dbo.Users res_name
on res_name.ID = MyCTE.SuperID
order by MyCTE.ID, NAMES.UserName, res_name.UserName