Please check the SQL schema and query on SQL Fiddle
I'm getting repetitive records with NULL values, if anyone can rectify the problem.
Regards
This is what I am getting:
| MEM_ID | MEM_EMAIL | GENDER | EDUCATION | PROFESSION |
|--------|----------------|--------|-----------|-------------|
| 1 | it#email.com | Male | (null) | (null) |
| 1 | it#email.com | (null) | Graduate | (null) |
| 1 | it#email.com | (null) | (null) | Engineer |
| 2 | info#email.com | Female | (null) | (null) |
| 2 | info#email.com | (null) | Graduate | (null) |
| 2 | info#email.com | (null) | (null) | Not Working |
but I need
| MEM_ID | MEM_EMAIL | GENDER | EDUCATION | PROFESSION |
|--------|----------------|--------|-----------|-------------|
| 1 | it#email.com | Male | Graduate | Engineer |
| 2 | info#email.com | Female | Graduate | Not Working |
|
Ah yes, the famous Inner-Platform effect, where you try to implement relations by creating "attribute-value" tables and assigning magic strings for data types and values, then try to retrieve values with massive self-joins at runtime.
Only madness lies down this road. SQL already includes features for enforcing key values and referential integrity; don't try to implement this yourself. It's especially frustrating because your schema is actually quite simple:
CREATE TABLE [dbo].Member(
ID INT PRIMARY KEY,
Email Varchar(50) NOT NULL,
GenderID INT NOT NULL,
EducationID INT,
ProfessionID INT
)
CREATE TABLE [dbo].Gender(
GenderID INT PRIMARY KEY,
GenderName Varchar(50) NOT NULL
)
CREATE TABLE [dbo].Education(
EducationID INT PRIMARY KEY,
EducationName Varchar(50) NOT NULL
)
CREATE TABLE [dbo].Profession(
ProfessionID INT PRIMARY KEY,
ProfessionName Varchar(50) NOT NULL
)
Assign your magic values to Gender, Education, and Profession rows and assign their IDs to Member. You can perform full lookups with a simple:
SELECT ID, Email, GenderName, EducationName, ProfessionName
FROM Member m
JOIN Gender g ON g.GenderID=m.GenderID
LEFT JOIN Education e ON e.EducationID=m.EducationID
LEFT JOIN Profession p ON p.ProfessionID=m.ProfessionID
WHERE ...
You want to enforce values? Make the Member columns NOT NULL. Want to allow, say, only a single instance of each Education row per member? Foreign-key constraints already support this, no need to invent your own query language.
I think you are basically trying to do a pivot on your data. This is one way to accomplish that.
SELECT M.mem_Id,
M.mem_email,
[Gender] = (select max( A.att_value)
from tbl_attributes A
inner join tbl_mem_att_values MAV
on MAV.att_id = A.att_id
inner join tbl_types T
on T.type_id = A.type_id
where T.type_name = 'Gender'
and MAV.mem_Id = M.mem_Id),
[Education] = (select max( A.att_value)
from tbl_attributes A
inner join tbl_mem_att_values MAV
on MAV.att_id = A.att_id
inner join tbl_types T
on T.type_id = A.type_id
where T.type_name = 'Education'
and MAV.mem_Id = M.mem_Id),
[Profession] = (select max( A.att_value)
from tbl_attributes A
inner join tbl_mem_att_values MAV
on MAV.att_id = A.att_id
inner join tbl_types T
on T.type_id = A.type_id
where T.type_name = 'Profession'
and MAV.mem_Id = M.mem_Id)
FROM tbl_members M
The result looks like this
EM_ID MEM_EMAIL GENDER EDUCATION PROFESSION
1 it#email.com Male Graduate Engineer
2 info#email.com Female Graduate Not Working
Here's what you need...
SQL Fiddle
SELECT M.mem_Id,
M.mem_email,
( SELECT AA.att_value
FROM tbl_mem_att_values mv
JOIN tbl_attributes AA ON AA.att_id = mv.att_id
JOIN tbl_types TG ON TG.type_name = 'Gender' AND TG.type_id = aa.type_id
WHERE mv.mem_id = M.mem_Id) AS Gender,
( SELECT AA.att_value
FROM tbl_mem_att_values mv
JOIN tbl_attributes AA ON AA.att_id = mv.att_id
JOIN tbl_types TG ON TG.type_name = 'Education' AND TG.type_id = aa.type_id
WHERE mv.mem_id = M.mem_Id) AS Education,
( SELECT AA.att_value
FROM tbl_mem_att_values mv
JOIN tbl_attributes AA ON AA.att_id = mv.att_id
JOIN tbl_types TG ON TG.type_name = 'Profession' AND TG.type_id = aa.type_id
WHERE mv.mem_id = M.mem_Id) AS Profession
FROM tbl_members M
Brad beat me by 6 seconds
Just as an alternative is you want to stick to the joins:
SELECT M.mem_Id,
M.mem_email,
AA.att_value AS Gender,
AB.att_value AS Education,
AC.att_value AS Profession
FROM tbl_members M
JOIN tbl_mem_att_values mavA ON M.mem_Id = mavA.mem_id
JOIN tbl_mem_att_values mavB ON M.mem_Id = mavB.mem_id
JOIN tbl_mem_att_values mavC ON M.mem_Id = mavC.mem_id
JOIN tbl_types TA ON TA.type_name = 'Gender'
JOIN tbl_types TB ON TB.type_name = 'Education'
JOIN tbl_types TC ON TC.type_name = 'Profession'
LEFT JOIN tbl_attributes AA ON mavA.att_id = AA.att_id AND TA.type_id = AA.type_id
LEFT JOIN tbl_attributes AB ON mavB.att_id = AB.att_id AND TB.type_id = AB.type_id
LEFT JOIN tbl_attributes AC ON mavC.att_id = AC.att_id AND TC.type_id = AC.type_id
WHERE AA.type_id IN (TA.type_id, TB.type_id, TC.type_id)
AND AB.type_id IN (TA.type_id, TB.type_id, TC.type_id)
AND AC.type_id IN (TA.type_id, TB.type_id, TC.type_id)
Related
I'm having problem with I would think simple query.
I'm trying to group users, by given name so in result I get something like this:
| name_1 | 0 |
| name_2 | 0 |
| name_3 | 2 |
| name_4 | 0 |
| name_5 | 0 |
And I achieve this with this query:
SELECT r.name, count(ur.user_id)
FROM public.user_roles as ur
RIGHT JOIN roles as r
ON ur.role_id = r.id and r.company_id = 1
GROUP BY r.name
Now I want to add another condition, and another joined table. UserRole contains reference to Role and User, so my query looks like this:
SELECT r.name, count(ur.user_id)
FROM public.user_roles as ur
RIGHT JOIN roles as r
ON ur.role_id = r.id and r.company_id = 1
LEFT JOIN users as u
ON ur.user_id = u.id
WHERE u.status != 'deleted'
GROUP BY r.name, u.status
But instead of getting table as on the beginning. I get something like this(according to if I look for deleted or active user)
| name_3 | 2 | <- for active
[] <- for deleted
And I want to achieve something like first table from the post. Any idea?
EDIT
Expected result would be:
| name_1 | 0 |
| name_2 | 0 |
| name_3 | 0 |
| name_4 | 0 |
| name_5 | 0 |
You have to move u.status in the join like this. If you don't, the left join is considered as an inner join
SELECT r.name, count(ur.user_id)
FROM public.user_roles as ur
RIGHT JOIN roles as r
ON ur.role_id = r.id and r.company_id = 1
LEFT JOIN users as u
ON ur.user_id = u.id and u.status != 'deleted'
GROUP BY r.name, u.status
I'm having an issue where I went all records in Table B and any non matching records in Table A but it's bringing back the matching records in Table A. There is another left join to an additional table which is brought in for reference only.
I'm using SSMS v18.
So ID will be on Table A and Table B. There will be multiple records of this ID on A and B but I don't want the duplicate records if date/time and ID is the same in Table A and in Table B.
e.g. - I've simplified the query I'm using below.
Select
a.id
a.datetime
a.emp_id
c.team_id
From
table_a as a
Left Join
table_b as b On a.id = b.id
And a.datetime <> b.datetime
Left Join
table_c On a.emp_id = c.emp_id
As there isn't NULLs I don't think I can use that. I don't believe a full outer join will return what I need.
Is there a method is solve this? A union query solution will not work as Table A and Table B do not have the same columns/column names.
Please let me know if more information is required.
EDIT - Additional
Apologies but now there's been a change of requirement where I now need to remove the matching records rather than remove just the duplicates. Is there a way around this?
Additional - Data Examples
Table A:
+----+------------------+--------+
| Id | Datetime | emp_id |
+----+------------------+--------+
| 1 | 20/04/2021 10:30 | a |
| 1 | 20/04/2021 11:15 | a |
| 2 | 21/04/2021 12:10 | b |
| 2 | 21/04/2021 13:20 | b |
| 2 | 22/04/2021 15:30 | c |
| 3 | 23/04/2021 09:45 | d |
| 4 | 23/04/2021 14:35 | e |
+----+------------------+--------+
Table B:
+----+------------------+-------------+
| Id | Datetime | other_field |
+----+------------------+-------------+
| 1 | 20/04/2021 10:30 | x |
| 2 | 21/04/2021 13:20 | y |
| 4 | 23/04/2021 14:35 | z |
+----+------------------+-------------+
Desired Output:
+----+------------------+--------+---------+
| Id | Datetime | emp_id | team_id |
+----+------------------+--------+---------+
| 1 | 20/04/2021 11:15 | a | team_01 |
| 2 | 21/04/2021 12:10 | b | team_02 |
| 2 | 22/04/2021 15:30 | c | team_01 |
| 3 | 23/04/2021 09:45 | d | team_02 |
+----+------------------+--------+---------+
So the duplicate ID & Datetime in Table B does not show in final output (regardless of any other fields)
You seem to need a right join instead of a left join. A left join will bring back all rows in table A, and all rows in table B which match the condition which you provided. You seem to want all in table B, which requires a right join.
I know some developers who have an aversion to right joins, if you feel that way, you can simply switch the order of the tables in your query to have table B listed first, left join to table A. I feel that the first solution is the easier one, though you need to be comfortable with it.
Here are my solutions, listed in the order in which I mentioned above.
Select
a.id
,a.datetime
,a.emp_id
,c.team_id
From
table_a as a
RIGHT Join -- here is my change
table_b as b On a.id = b.id
And a.datetime <> b.datetime
Left Join
table_c On a.emp_id = c.emp_id;
/*solution II*/
Select
a.id
,a.datetime
,a.emp_id
,c.team_id
From
table_b as b
Left Join
table_a as a On a.id = b.id
And a.datetime <> b.datetime
Left Join
table_c On a.emp_id = c.emp_id;
/*Updated solution, based on the comments (requirements seem to have changed)*/
Select
a.id
,a.datetime
,a.emp_id
,c.team_id
From
table_b as b
Left Join
table_a as a On a.id = b.id
Left Join
table_c On a.emp_id = c.emp_id
WHERE (a.datetime <> b.datetime OR b.datetime IS NULL);
Explanation of the updated solution: there was nothing to take into account the rows which would not match, hence the OR in the join
Please see Microsoft documentation on joins below.
https://learn.microsoft.com/en-us/sql/relational-databases/performance/joins?view=sql-server-ver15#:~:text=Joins%20indicate%20how%20SQL%20Server,be%20used%20for%20the%20join.
I have several tables that I have joined like this:
SELECT *
FROM tableA AS A
FULL OUTER JOIN tableB AS B
ON B.KEY = A.KEY
FULL OUTER JOIN tableC AS C
ON C.KEY = A.KEY
FULL OUTER JOIN tableD AS D
ON D.KEY = A.KEY
This works fine, but i sometime get separate rows where the key does not exist in all tables.
Example
WITH tableA([KEY], payloadColumn) AS
(
SELECT 1, 'var_A1'
), tableB([KEY], payloadColumn) AS
(
SELECT 2, 'var_B2'
), tableC([KEY], payloadColumn) AS
(
SELECT 1, 'var_C1'
), tableD([KEY], payloadColumn) AS
(
SELECT 2, 'var_D2'
)
SELECT * FROM tableA as A
FULL OUTER JOIN tableB as B ON B.[KEY] = A.[KEY]
FULL OUTER JOIN tableC as C ON C.[KEY] = A.[KEY]
FULL OUTER JOIN tableD as D ON D.[KEY] = A.[KEY]
Returns
+------+---------------+------+---------------+------+---------------+------+---------------+
| KEY | payloadColumn | KEY | payloadColumn | KEY | payloadColumn | KEY | payloadColumn |
+------+---------------+------+---------------+------+---------------+------+---------------+
| 1 | var_A1 | NULL | NULL | 1 | var_C1 | NULL | NULL |
| NULL | NULL | 2 | var_B2 | NULL | NULL | NULL | NULL |
| NULL | NULL | NULL | NULL | NULL | NULL | 2 | var_D2 |
+------+---------------+------+---------------+------+---------------+------+---------------+
But i need them all to be in the same row in the result (not two rows for 2). How can I do this?
That's my proposed solution based on the draft in a previous version of OP, and their last edit:
CREATE VIEW ABCD
AS
SELECT [KEY] FROM tableA
UNION
SELECT [KEY] FROM tableB
UNION
SELECT [KEY] FROM tableC
UNION
SELECT [KEY] FROM tableD
Creating a view may not be an option; in this case rewrite as a CTE.
At least in this way, the execution plan will continue to use indexes (assuming [KEY] is a PK or indexed).
SELECT
K.[KEY] as sameKey,
A.payloadColumn,
B.payloadColumn,
C.payloadColumn,
D.payloadColumn
FROM ABCD K
LEFT JOIN tableA A ON A.[KEY]=K.[KEY]
LEFT JOIN tableB B ON B.[KEY]=K.[KEY]
LEFT JOIN tableC C ON C.[KEY]=K.[KEY]
LEFT JOIN tableD D ON D.[KEY]=K.[KEY]
If this got your idea wrong, please elaborate.
You can use COALESCE
WITH tableA([KEY], payloadColumn)
AS (SELECT 1,
'var_A1'),
tableB([KEY], payloadColumn)
AS (SELECT 2,
'var_B2'),
tableC([KEY], payloadColumn)
AS (SELECT 1,
'var_C1'),
tableD([KEY], payloadColumn)
AS (SELECT 2,
'var_D2')
SELECT COALESCE(A.[KEY], B.[KEY], C.[KEY], D.[KEY]) AS [KEY],
A.payloadColumn,
B.payloadColumn,
C.payloadColumn,
D.payloadColumn
FROM tableA AS A
FULL OUTER JOIN tableB AS B
ON B.[KEY] = A.[KEY]
FULL OUTER JOIN tableC AS C
ON C.[KEY] = COALESCE(A.[KEY], B.[KEY])
FULL OUTER JOIN tableD AS D
ON D.[KEY] = COALESCE(A.[KEY], B.[KEY], C.[KEY]);
Returns
+-----+---------------+---------------+---------------+---------------+
| KEY | payloadColumn | payloadColumn | payloadColumn | payloadColumn |
+-----+---------------+---------------+---------------+---------------+
| 1 | var_A1 | NULL | var_C1 | NULL |
| 2 | NULL | var_B2 | NULL | var_D2 |
+-----+---------------+---------------+---------------+---------------+
If all the tables have a covering index on [KEY] INCLUDE (payloadColumn) the execution plan for this can just chain together merge joins.
I have a table with three columns with double values, but no double rows. Now I want to split this table in two table with unique values and a link table. I think the Problem gets clearer when I Show you example tables:
Original:
| ID | Column_1 | Column_2 | Column_3 |
|----|----------|----------|----------|
| 1 | A | 123 | A1 |
| 2 | A | 123 | A2 |
| 3 | B | 234 | A2 |
| 4 | C | 456 | A1 |
Table_1
| ID | Column_1 | Column_2 |
|----|----------|----------|
| 1 | A | 123 |
| 2 | B | 234 |
| 3 | C | 456 |
Table_2
| ID | Column_3 |
|----|----------|
| 1 | A1 |
| 2 | A2 |
Link-Table
| ID | fk1 | fk2 |
|----|-----|-----|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 2 |
| 4 | 3 | 1 |
Table_1 I created like this:
INSERT INTO Table_1(Column_1, Column_2)
SELECT DISTINCT Column_1, Column_2 FROM Original
WHERE Original.Column_1 NOT IN (SELECT Column_1 FROM Table_1)
Table_2 I created in the same way.
The question now is, how to create the Link-Table?
The original table does grow continuesly, so only new entries should be added.
Do I have to use a Cursor, or is there a better way?
SOLUTION:
MERGE Link_Table AS LT
USING (SELECT DISTINCT T1.ID AS T1ID, T2.ID AS T2ID FROM Original AS O
INNER JOIN Table_1 AS T1 ON T1.Column_1 = O.Column_1
INNER JOIN Table_2 AS T2 ON T2.Column_3 = O.Column_3) AS U
ON LT.fk1 = U.T1ID
WHEN NOT MATCHED THEN
INSERT (fk1, fk2)
VALUES (U.T1ID, U.T2ID);
You can JOIN all 3 tables to get proper data for link table:
--INSERT INTO [Link-Table]
SELECT t1.ID,
t2.ID
FROM Original o
INNER JOIN Table_1 t1
ON t1.Column_1 = o.Column_1
INNER JOIN Table_2 t2
ON t2.Column_3 = o.Column_3
If your original table will grow, then you need to use MERGE to update/insert new data.
You have to inner join your Original,Table_1 and Table_2 to get the desired result.
Try like this, Its similar to gofr1 post.
DECLARE #orginal TABLE (
ID INT
,Column_1 VARCHAR(10)
,Column_2 INT
,Column_3 VARCHAR(10)
)
DECLARE #Table_1 TABLE (
ID INT
,Column_1 VARCHAR(10)
,Column_2 INT
)
DECLARE #Table_2 TABLE (
ID INT
,Column_3 VARCHAR(10)
)
Insert into #orginal values
(1,'A',123,'A1')
,(2,'A',123,'A2')
,(3,'B',234,'A2')
,(4,'C',456,'A1')
Insert into #Table_1 values
(1,'A',123)
,(2,'B',234)
,(3,'C',456)
Insert into #Table_2 values
(1,'A1')
,(2,'A2')
SELECT O.ID
,T1.ID
,T2.ID
FROM #orginal O
INNER JOIN #Table_1 T1 ON T1.Column_1 = O.Column_1
INNER JOIN #Table_2 T2 ON T2.Column_3 = O.Column_3
Considering the following tables:
Users
----------------------
Id | 1 | 2
Name| John| Jack
Cars
----------------------
Id | 14 | 26
Name| Mercedes| BMW
Import
-----------------------
Id | 12 | 34
UserName | John | Daniel
UserId | NULL | NULL
CarName | BMW | Mercedes
CarId | NULL | NULL
SomeOtherId| 45 | 45
I basically want to find UserId and CarId in the other tables, If they are not find they remain NULL,
I tried something like
UPDATE Import i
SET UserId = ( SELECT Id FROM Users WHERE Name=i.UserName ),
CarId = ( SELECT Id FROM Cars WHERE Name= i.CarName )
WHERE SomeOtherId=45;
In this case i am getting an incorrect syntax.
How can i fix it?
Is it a way to use MERGE in this case? (merging more than 2 tables and using a WHERE clause)?
Thanks.
You might use from clause in update itself:
UPDATE i
SET UserId = Users.Id,
CarId = Cars.Id
FROM Import i
LEFT JOIN Users
ON i.UserName = Users.Name
LEFT JOIN Cars
ON i.CarName = Cars.Name
WHERE i.SomeOtherId=45;
Hope the following will solve your problem
UPDATE I
SET UserId = U.Id
FROM IMPORT I
INNER JOIN Users U
ON I.UserName = U.Name
WHERE UserId is Null
UPDATE I
SET CarId = C.Id
FROM IMPORT I
INNER JOIN Cars C
ON I.CarName = C.Name
WHERE CarId is Null