sql view as per other table - sql

Given the table Customers, example:
CustomerID FirstName MiddleInitial LastName
64 Abby A Garcia
65 Abby C Mehta
66 Abby E Chandra
67 Abby J Kapoor
68 Abby J Sanchez
69 Abby K Kovár
70 Abby L Sai
71 Abby M Lopez
72 Abby P Gonzalez
73 Abby P Rana
I would like to restrict the access to (table, column, row) as per definition in the other table, let's say m_Customers with definitions such as:
MetadataID Field Type Access
1 Customers table "Group1","Group2","Group3"
2 MiddleInitial column "Group1","Group3"
3 18 row "Group1","Group3"
Based on that, how do you formulate a query which ensures that if you are:
from *Group3" you can't see column MiddleInitials and row with CustomerId = 18
from "Group4" you can't see the data in Customers table
Any ideas?

It seems like your exclusions and inclusions are mixed. You have Group3 included with column Customers and row 18, but Group 4 not included with table Customers. Make your m_Customers table include only those that have access or only those that are restricted -- not both.
It would typically be faster and easier to manage to change your data structure for m_Customers to use a record for every group.
MetadataID Field Type Access
1 Customers table Group1
2 Customers table Group2
3 Customers table Group3
4 MiddleInitial column Group1
5 MiddleInitial column Group3
6 18 row Group1
7 18 row Group3
Here are two options; you can check for performance.
SELECT MAX(col.customerID) AS customerID, MAX(col.FirstName) AS FirstName, MAX(col.MiddleInitial) AS MiddleIntitial, MAX(col.LastName) AS LastName
FROM Customers AS c
INNER JOIN m_Customers AS mRow ON mRow.[Type] = 'row' AND mRow.CustomerID = c.Field AND mRow.Access = #group
LEFT JOIN (
SELECT
IIF(mCol.Field = 'customerID', c.customerID, NULL) AS customerID,
IIF(mCol.Field = 'Firstname', c.FirstName, NULL) AS FirstName,
IIF(mCol.Field = 'MiddleInitial', c.FirMiddleInitialstName, NULL) AS MiddleInitial,
IIF(mCol.Field = 'LastName', c.LastName, NULL) AS LastName
FROM Customers AS c
INNER JOIN m_Customers AS mCol ON mCol.[Type] = 'column' AND mCol.Field = 'MiddleInitial' AND mCol.Access = #group
) AS col ON col.customerID = c.CustomerID
GROUP BY col.customerID, col.FirstName, col.MiddleInitial, col.LastName
or
SELECT customerID.customerID, FirstName.FirstName, MiddleInitial.MiddleInitial, LastName.LastName
FROM Customers AS c
INNER JOIN m_Customers AS mRow ON mRow.[Type] = 'row' AND mRow.CustomerID = c.Field AND mRow.Access = #group
LEFT JOIN (
SELECT c.customerID
FROM Customers AS c
INNER JOIN m_Customers AS mCol ON mCol.[Type] = 'column' AND mCol.Field = 'customerID' AND mCol.Access = #group
) AS customerID ON customerID.customerID = c.customerID
LEFT JOIN (
SELECT c.customerID, c.FirstName
FROM Customers AS c
INNER JOIN m_Customers AS mCol ON mCol.[Type] = 'column' AND mCol.Field = 'FirstName' AND mCol.Access = #group
) AS FirstName ON FirstName.customerID = c.customerID
LEFT JOIN (
SELECT c.customerID, c.MiddleInitial
FROM Customers AS c
INNER JOIN m_Customers AS mCol ON mCol.[Type] = 'column' AND mCol.Field = 'MiddleInitial' AND mCol.Access = #group
) AS MiddleInitial ON MiddleInitial.customerID = c.customerID
LEFT JOIN (
SELECT c.customerID, c.LastName
FROM Customers AS c
INNER JOIN m_Customers AS mCol ON mCol.[Type] = 'column' AND mCol.Field = 'LastName' AND mCol.Access = #group
) AS LastName ON LastName.customerID = c.customerID
Consider changing the name of your column from Type to a non-reserved word.

Related

SQL query to full columns without defining it in Group By and optimize the Inner Join

one contain multiple columns and test two tables have one columns.
Table: Client
Name age Benefit Id code value
Tom 33 AA 0A 1 12
Tom 33 AB 0C 1 13
Tom 33 AA 0C 5 11
Sam 31 CC 0B 3 10
Rik 28 EE 0D 5 9
Table2: Sell
Code1
1
4
Table3: tip
Code2
5
6
I want output as Name,Age, Benefit, Id and Code, which code present in both table "sell" and "tip".
Name Age Benefit Id code Approved
Tom 33 AA 0A 1 7
Tom 33 AA 0C 5 11
Code I have written as
------Break code-----------
Create table #temp1 as
select c.* from Client c inner join Sell s on c.code1 = s.code
where Benifit= AA
Create table #temp2 as
select c.* from Client c inner join Sell s on c.code2 = s.code
where Benifit= AA
Create table #combine as
select ss.* ,
,case when ss.value > 5 then ss.value- 5 Else 0 approved
from #temp1 ss inner join #temp2 ff
On ss.Name= ff.Name
AND ss.Age= ff.Age
where Benifit= 'AA'
Group by ss.Name Age
------------------------------------------------
----Since 3 table are created with repeated logic, I have put Above code is put into one code, even thought it is not optimized ----------------
select ss.Name, ss.age
,case when ss.value > 5 then ss.value- 5 Else 0 approved
from
(select * from Client c inner join Sell s on c.code1 = s.code
where Benifit= AA) ss
inner join
(select * from Client cc inner join Tip t on cc.code2 = t.code
where Benifit= AA) ff
On ss.Name= ff.Name
AND ss.Age= ff.Age
Group by Name Age
So, I have two problem:
I want Benefit, Id, code and value beside Name and Age, since current query not allow to do that as 'Group By' is used
I am using two select multiple inner join as below
a. (select * from Client c inner join Sell s on c.code = s.code1
where Benifit= AA) ss
b. (select * from Client cc inner join Tip t on cc.code2 = t.code
where Benifit= AA) ff
but I don't want my code to repeatedly use the same code as above is repeated.
A new column Approved is created by subtracting 5 from Value column
Note: The output will Inner join based on "Name" and "Age" between
Inner join of Client & Sell
Inner join between Client & Tip
Please suggest.
Note I want Name, Age having code in both 'Sell' and 'Tip' where Benifit= AA Although output will be from "Sell".
I think you want a query like this -
SELECT C.Name, C.Age, C.Benefit, C.Id, C.Code, (C.Value - 5) Approved
FROM Client C
INNER JOIN Sell S ON C.Code = S.Code1
INNER JOIN Tip T ON S.Code1 = T.Code2
WHERE C.Benefit = 'AA'

SQL Sum double counting

My SQL is double counting 'Fund1Amount' and 'Fund2Amount' and 'TotalAllDonations' I have a record where the values of the FundAmount1 should = 10 and FundAmount2 should = 20 but they equal 20 & 40 and the total is double. I am using INNER JOINs twice on the same table abc_donationdetail which i am not sure if this is the problem.
SELECT
C.FirstName, C.LastName,
SUM(D.abc_totalamount) AS TotalAllDonations,
SUM(CASE WHEN DBU.abc_fundidname = 'Fund1' THEN DBU.abc_amount END) AS Fund1Amount,
SUM(CASE WHEN DBE.abc_fundidname = 'Fund2' THEN DBE.abc_amount END) AS Fund2Amount
FROM
Contact C
INNER JOIN
Account A ON C.parentcustomerid = A.accountid
INNER JOIN
Account PA ON A.parentaccountid = PA.accountid
INNER JOIN
abc_donation D ON D.abc_person = C.contactid
LEFT JOIN
abc_donationdetail DBU ON DBU.abc_donationid = D.abc_donationid
AND DBU.abc_fundidname= 'Fund1'
LEFT JOIN
abc_donationdetail DBE ON DBE.abc_donationid = D.abc_donationid
AND DBE.abc_fundidname = 'Fund2'
LEFT JOIN
abc_mmcs GD ON GD.abc_donor = C.contactid
LEFT JOIN
sab_item LIBU ON LIBU.sab_itemid = GD.abc_companyid
AND LIBU.sab_name = 'Fund1'
LEFT JOIN
sab_item LIBE ON LIBE.sab_itemid = GD.abc_companyid
AND LIBE.sab_name = 'Fund2'
where C.StateCode = 0 AND (GD.abc_enddate > GETDATE() or GD.abc_enddate IS NULL)
group by C.abc_memberid, C.FirstName, C.LastName , C.StateCode, A.name, A.parentaccountidname, A.dd_number, PA.dd_number, C.parentcustomeridname
order by C.lastname
Current
FN LN Total Fund1 Fund2
James Brown 70 40 30
Phillip Smith 160 60 100
Peter Jones 80 40 40
Vincent Limp 48 48 NULL
Michael Collins 60 60 NULL
Desired
FN LN Total Fund1 Fund2
James Brown 35 20 15
Phillip Smith 80 30 50
Peter Jones 40 20 20
Vincent Limp 24 24 NULL
Michael Collins 30 30 NULL
Any help would be great.
Thanks
If the problem is only in duplicates, than simply make a subquery:
Select fld, sum(value)
from (select distinct fld, value from tbl) as a
I can't say much here as there are lots of tables joined here and I don't know their schema. At most, I can suggest you to add group by with person's id and fund id as it'll remove the duplicates.
SELECT
C.FirstName, C.LastName,
SUM(D.abc_totalamount) AS TotalAllDonations,
SUM(CASE WHEN DBU.abc_fundidname = 'Fund1' THEN DBU.abc_amount END) AS Fund1Amount,
SUM(CASE WHEN DBU.abc_fundidname = 'Fund2' THEN DBU.abc_amount END) AS Fund2Amount
FROM
Contact C
INNER JOIN
Account A ON C.parentcustomerid = A.accountid
INNER JOIN
Account PA ON A.parentaccountid = PA.accountid
INNER JOIN
abc_donation D ON D.abc_person = C.contactid
LEFT JOIN
abc_donationdetail DBU ON DBU.abc_donationid = D.abc_donationid
AND (DBU.abc_fundidname= 'Fund1'
OR DBU.abc_fundidname = 'Fund2') //MERGED 2 JOINS
LEFT JOIN
abc_mmcs GD ON GD.abc_donor = C.contactid
LEFT JOIN
sab_item LIBU ON LIBU.sab_itemid = GD.abc_companyid
AND LIBU.sab_name = 'Fund1'
LEFT JOIN
sab_item LIBE ON LIBE.sab_itemid = GD.abc_companyid
AND LIBE.sab_name = 'Fund2'
where C.StateCode = 0 AND (GD.abc_enddate > GETDATE() or GD.abc_enddate IS NULL)
group by C.abc_memberid, C.FirstName, C.LastName , C.StateCode, A.name, A.parentaccountidname, A.dd_number, PA.dd_number, C.parentcustomeridname order by C.lastname
Hope it works!

show rows with no data in access

I have the following query:
SELECT PersonTotalHours.MA, PersonTotalHours.Year, PersonTotalHours.CalendarWeek,
PersonTotalHours.Hours, Person.Name, Person.Lastname
FROM PersonTotalHours
INNER JOIN Person
ON PersonTotalHours.MA = Person.MA;
Which results in the following table:
MA Year CalendarWeek Hours Name Lastname
aA 2000 5 53 aa AA
aA 2000 44 175 aa AA
... ... ... ...
aA 2001 4 226 aa AA
aA 2001 12 87 aa AA
... ... ... ...
bB 2000 1 189 bb BB
bB 2000 35 65 bb BB
... ... ... ...
as you can see, there is no data for some calendar weeks. Is there any way that I can have a row for all calendar weeks(1 to 53) and with hours=0 for the ones that don't exist now?
Edit
I have solve this temporarily by adding the missing row to the table. using a function that is called when the report opens. Still looking for a non-stupid solution.
Create a help table Calendar, storing all possible values. RIGHT JOIN it:
SELECT pth.MA, pth.Year, pth.CalendarWeek,
pth.Hours, p.Name, p.Lastname
FROM PersonTotalHours pth
INNER JOIN Person p
ON pth.MA = p.MA
RIGHT JOIN Calendar c
ON pth.Year = c.Year AND pth.CalendarWeek = c.CalendarWeek
(Using table aliases to spare some typing.)
EDIT: MS Access query attempt, version 1:
SELECT pth.MA, pth.Year, pth.CalendarWeek,
pth.Hours, p.Name, p.Lastname
FROM (PersonTotalHours pth
INNER JOIN Person p
ON pth.MA = p.MA)
RIGHT JOIN Calendar c
ON pth.Year = c.Year AND pth.CalendarWeek = c.CalendarWeek
EDIT: MS Access query attempt, version 2:
SELECT pth.MA, pth.Year, pth.CalendarWeek,
pth.Hours, p.Name, p.Lastname
FROM calendar
left join (PersonTotalHours pth
INNER JOIN Person p ON pth.MA = p.MA)
ON pth.Year = c.Year AND pth.CalendarWeek = c.CalendarWeek
Please check this query.
select PersonTotalHours.MA,PersonTotalHours.YEAR,b.CalendarWeek, PersonTotalHours.Hours, '' Name, '' LastName from PersonTotalHours
right join (
select '' MA, 0 Year, no CalendarWeek, 0 Hours, '' Name, '' Lastname from (
select row_number() over(order by object_id) no from sys.all_objects
) a where no <= 53) b on b.CalendarWeek = Table_1.CalendarWeek
I am not joined person table.

Complex sql server query

I need some help with a SQL query I'm working on. Here is a simplified version of the data I'm working with. I have 3 tables:
Contacts:
- ContactID
- ContactName
Submissions:
- SubmissionID
- ContactID
- SubmissionTypeID
SubmissionTypes:
- SubmissionTypeID
- SubmissionType
I need to return all of the Contacts (joined to Submissions on ContactID) where there are SubmissionTypeIDs that match up with a list of SubmissionTypeIDs. The tricky part is that I only want results where a Contact has a Submission record with a SubmissionTypeID that matches each of the values in the list. So, for instance, if I had this data:
Contacts
----------------
1 | Jim Johnson
2 | Sally Anderson
SubmissionTypes
----------------------
1 | Contact Form
2 | Request Form
3 | Generic Form
Submissions
----------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 1
If my SubmissionTypeID values are 1 and 2, I'd want to get the following results:
Jim Johnson | Contact Form
Jim Johnson | Request Form
I wouldn't want to see Sally Anderson because she doesn't have a record in Submissions for both values.
I'm guessing there are a few ways to do this. I'm excited to see your ideas.
Thank you!
Here's a convoluted way using double negation.
declare #list table (SubmissionTypeID int not null primary key);
insert into #list values (1), (2); -- values to search for.
with c as (
select
c.ContactID,
c.ContactName
from
Contacts c
where
not exists (
select
'x'
from
#list l
where
not exists (
select
'x'
from
Submissions s
where
s.ContactID = c.ContactID and
s.SubmissionTypeID = l.SubmissionTypeID
)
)
)
select
c.ContactName,
t.SubmissionType
from
c
inner join
Submissions s
on c.ContactId = s.ContactId
inner join
SubmissionTypes t
on s.SubmissionTypeID = t.SubmissionTypeID
inner join
#list l
on t.SubmissionTypeID = l.SubmissionTypeID;
Example SQLFiddle
One way is with INTERSECT:
select c.contactname, t.submissiontype
from contacts c
join submissions s
on c.contactid = s.contactid
join submissiontypes t
on s.submissiontypeid = t.submissiontypeid
join (select c.contactid
from contacts c
join submissions s
on c.contactid = s.contactid
where s.submissiontypeid = 1
intersect
select c.contactid
from contacts c
join submissions s
on c.contactid = s.contactid
where s.submissiontypeid = 2) v
on c.contactid = v.contactid
where s.submissiontypeid in (1, 2)
Fiddle: http://sqlfiddle.com/#!6/9ee4e/2/0
You can also COUNT where equal to 2 (you have 2 values you're checking for):
select c.contactname, t.submissiontype
from contacts c
join submissions s
on c.contactid = s.contactid
join submissiontypes t
on s.submissiontypeid = t.submissiontypeid
join (select c.contactid
from contacts c
join submissions s
on c.contactid = s.contactid
where s.submissiontypeid in (1, 2)
group by c.contactid
having count(distinct s.submissiontypeid) = 2) v
on c.contactid = v.contactid
where s.submissiontypeid in (1, 2)
Fiddle: http://sqlfiddle.com/#!6/9ee4e/1/0
Try this.. It works fine to me
DECLARE #list TABLE (SubmissionTypeID int not null primary key);
INSERT INTO #list VALUES(1); -- values to search for.
SELECT C.ContactName, ST.SubmissionTypeName
FROM
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY S.ContactID ORDER BY S.ContactID) AS RCount
FROM
Submission S
WHERE EXISTS
(SELECT 1 FROM #list l WHERE l.SubmissionTypeID = S.SubmissionTypeID)) AS Result
INNER JOIN Submission S1 ON S1.ContactID = Result.ContactID
INNER JOIN Contacts C ON C.ContactID = Result.ContactID
INNER JOIN SubmissionTypes ST ON ST.SubmissionTypeID = S1.SubmissionTypeID
WHERE RCOunt = (SELECT COUNT(DISTINCT SubmissionTypeID) FROM #list)
AND EXISTS
(SELECT 1 FROM #list l WHERE l.SubmissionTypeID = ST.SubmissionTypeID)
;

Using 'AND' in a many-to-many relationship

I have a Users table and a Groups table. Users can be in multiple groups via a 'UserInGroup' table and Groups can have a 'GroupTypeId'.
[User]
--------------
Id | Name
1 | Bob
2 | James
[UserInGroup]
-----------------
UserId | GroupId
1 1
1 2
[Group]
Id | Name | TypeId
------------------------
1 | Directors | 1
2 | IT | 1
3 | London | 2
I want to create a query to return for example users that are in both 'Directors' AND 'London' (rather than 'Directors' OR 'London'). However, I only want to AND groups of a different 'Type', I want to OR groups of the same type. I could do with having a separate table per group type but I can't as they are created dynamically.
Ideally I want to be able to query users who are in 'Directors' OR 'IT' AND 'London'.
What is the most efficient way of doing this?
This problem is commonly known as Relational Division.
SELECT a.Name
FROM [user] a
INNER JOIN UserInGroup b
ON a.ID = b.UserID
INNER JOIN [Group] c
ON b.groupID = c.TypeId
WHERE c.Name IN ('Directors','London')
GROUP BY a.Name
HAVING COUNT(*) = 2
SQLFiddle Demo
SQL of Relational Division
But if a UNIQUE constraint was not enforce on GROUP for every USER, DISTINCT keywords is needed to filter out unique groups:
SELECT a.Name
FROM [user] a
INNER JOIN UserInGroup b
ON a.ID = b.UserID
INNER JOIN [Group] c
ON b.groupID = c.TypeId
WHERE c.Name IN ('Directors','London')
GROUP BY a.Name
HAVING COUNT(DISTINCT c.Name) = 2
OUTPUT from both queries
╔══════╗
║ NAME ║
╠══════╣
║ Bob ║
╚══════╝
I arrived at the following solution (with help from J W and this article):
SELECT
u.Name UserName
FROM [User] u
INNER JOIN [UserInGroup] uig
ON uig.UserId = u.Id
INNER JOIN [Group] g
ON g.Id = uig.GroupId
WHERE
g.Id IN (1,2,3) -- these are the passed in groupids
GROUP BY
u.Name
having count(distinct g.TypeId)
= (select count(distinct g1.TypeId)
from [group] g1 where g1.Id IN (1,2,3))
This allows me to group the relational division by a discriminator field. An alternative would be this:
SELECT a.Name
FROM [User] a
INNER JOIN
(
SELECT b.UserID
FROM UserInGroup b
INNER JOIN [Group] c
ON b.groupID = c.Id
WHERE c.Name IN ('Directors','IT')
GROUP BY b.UserID
HAVING COUNT(DISTINCT c.Name) >= 1
) b ON a.ID = b.UserID
INNER JOIN
(
SELECT DISTINCT b.UserID
FROM UserInGroup b
INNER JOIN [Group] c
ON b.groupID = c.Id
WHERE c.Name = 'London'
) c ON a.ID = c.UserID
With an extra join for each GroupTypeId. Execution plans look similar, so I went with the first option.