Optimize EAV based SQL Query - sql

I have a code that will return about 1 million records in about 15 seconds on a normal PC.
select joined.* , eset.PageNo from (select * from
(
select e.EntityTypeID , a.EntityID , a.AttributeValue , ea.AttributeName
from
(SELECT T1.EntityID,
T1.AttributeID,
STUFF(( SELECT ', ' + T22.OptionText
FROM [POC].[dbo].[EntityAttributeOptionValues] t2
inner join EntityAttributeOptions t22 on
t22.ID = t2.OptionValueID
WHERE T2.AttributeID = T1.AttributeID
AND T2.EntityID = T1.EntityID
-- ANd t2.EntityID between 5060 and 56598
FOR XML PATH('')
), 1, 2, '') [AttributeValue]
FROM [POC].[dbo].[EntityAttributeOptionValues] t1
inner join EntityAttributeOptions t11 on
t11.ID = t1.OptionValueID
--where t1.EntityID between 5060 and 56598
GROUP BY T1.EntityID, T1.AttributeID
) a
inner join Entities e on e.ID = a.EntityID
inner join EntityAttributes ea on ea.ID = a.AttributeID
--where e.EntityTypeID = 9
) options
union all
(select EntityAttributes.EntityTypeID, EntityAttributeValues.EntityID as 'EntityID' , EntityAttributeValues.AttributeValue,EntityAttributes.attributeName from EntityAttributeValues
inner join EntityAttributes on EntityAttributes.ID = EntityAttributeValues.AttributeID
)) joined
inner join Entity_Paged_View_With_Set eset on eset.ID = joined.EntityID
This query will return five columns called EntityTypeID, EntityID, AttributeValue, AttributeName, PageNo.
EntityTypeID EntityID AttributeValue AttributeName PageNo
1 5790 Musics, Paintings Hobbies 286
1 11673 Musics, Paintings Hobbies 580
1 30595 Males Gender 1527
1 31860 No Want Bus Facility 1590
1 39755 Males Gender 1985
1 41020 No Want Bus Facility 2048
1 47599 Males Gender 2377
1 48864 No Want Bus Facility 2440
Now when I just try to get about 100 rows it will take about 3 seconds. I want to filter this query by like
pageNo = 1 and EntityTypeID = 1
or
pageNo = 1 and EntityTypeID = 3
or
pageNo = 5632 and EntityTypeID = 1
and so on.
Can you please specify where should I apply where condition. Even order by clause drastically reduce performance.
The upper query before union all is slow. but the later is fast.
Where should I apply where conditions to optimize the query such that it will take less than a second to execute

Related

UNION does not remove duplicates in result

I have 2 queries that i need the result to be combined (no duplicates), i thought UNION would work
but the result is as if i used UNION ALL.
SELECT Cams_Global.dbo.A960.OmrådesNr, Cams_Global.dbo.A960.OmrådesBenämning, 0 AS Antal
FROM A406 INNER JOIN
Cams_Global.dbo.A960 ON A406.OmrådesNr = Cams_Global.dbo.A960.OmrådesNr
WHERE (A406.Kund IN (5566321537))
UNION
SELECT A960_1.OmrådesNr, A960_1.OmrådesBenämning, COUNT(A806.Aordernr) AS Antal
FROM A806 INNER JOIN
A406 AS A406_1 ON A806.Ställeid = A406_1.Ställeid INNER JOIN
A400 ON A806.Objektid = A400.Objektid INNER JOIN
A402 ON A400.ObjGrupp = A402.Objgrupp INNER JOIN
Cams_Global.dbo.A957 ON A806.LevId = Cams_Global.dbo.A957.LevId RIGHT OUTER JOIN
Cams_Global.dbo.A960 AS A960_1 ON A406_1.OmrådesNr = A960_1.OmrådesNr
WHERE (A806.Beställning = 0) AND (Cams_Global.dbo.A957.LevIdGrupp IN (1001, 1000)) AND
(A806.ProtokollSparad = 0) AND (A406_1.Kund = 5566321537)
GROUP BY A960_1.OmrådesBenämning, A960_1.OmrådesNr
ORDER BY Cams_Global.dbo.A960.OmrådesBenämning
What i get now
1031 Fagersanna 0
1031 Fagersanna 19
1046 Sånna 0
1032 Tibro 0
1032 Tibro 40
And what i want
1031 Fagersanna 19
1046 Sånna 0
1032 Tibro 40
You can try the below way - using aggregation on top of your query
select OmrådesNr,OmrådesBenämning,max(Antal) as Antal
from
(
SELECT Cams_Global.dbo.A960.OmrådesNr, Cams_Global.dbo.A960.OmrådesBenämning, 0 AS Antal
FROM A406 INNER JOIN
Cams_Global.dbo.A960 ON A406.OmrådesNr = Cams_Global.dbo.A960.OmrådesNr
WHERE (A406.Kund IN (5566321537))
UNION
SELECT A960_1.OmrådesNr, A960_1.OmrådesBenämning, COUNT(A806.Aordernr) AS Antal
FROM A806 INNER JOIN
A406 AS A406_1 ON A806.Ställeid = A406_1.Ställeid INNER JOIN
A400 ON A806.Objektid = A400.Objektid INNER JOIN
A402 ON A400.ObjGrupp = A402.Objgrupp INNER JOIN
Cams_Global.dbo.A957 ON A806.LevId = Cams_Global.dbo.A957.LevId RIGHT OUTER JOIN
Cams_Global.dbo.A960 AS A960_1 ON A406_1.OmrådesNr = A960_1.OmrådesNr
WHERE (A806.Beställning = 0) AND (Cams_Global.dbo.A957.LevIdGrupp IN (1001, 1000)) AND
(A806.ProtokollSparad = 0) AND (A406_1.Kund = 5566321537)
GROUP BY A960_1.OmrådesBenämning, A960_1.OmrådesNr
)A group by OmrådesNr,OmrådesBenämning

SQL correct query or not

given these relationships, how could you query the following:
The tourists (name and email) that booked at least a pension whose rating is greater than 9, but didn't book any 3 star hotel with a rating less than 9.
Is the following correct?
SELECT Tourists.name, Tourists.email
FROM Tourists
WHERE EXISTS (
SELECT id FROM Bookings
INNER JOIN Tourists ON Bookings.touristId=Tourists.id
INNER JOIN AccomodationEstablishments ON Bookings.accEstId=AccomodationEstablishments.id
INNER JOIN AccomodationTypes ON AccomodationEstablishments.accType=AccomodationTypes.id
WHERE AccomodationTypes.name = 'Pension' AND
AccomodationEstablishments.rating > 9
) AND NOT EXISTS (
SELECT id FROM Bookings
INNER JOIN Tourists ON Bookings.touristId=Tourists.id
INNER JOIN AccomodationEstablishments ON Bookings.accEstId=AccomodationEstablishments.id
INNER JOIN AccomodationTypes ON AccomodationEstablishments.accType=AccomodationTypes.id
WHERE AccomodationTypes.name = 'Hotel' AND
AccomodationEstablishments.noOfStars = 3 AND
AccomodationEstablishments.rating < 9
)
I would do this using aggregation and having:
SELECT t.name, t.email
FROM Bookings b INNER JOIN
Tourists t
ON b.touristId = t.id INNER JOIN
AccomodationEstablishments ae
ON b.accEstId = ae.id INNER JOIN
AccomodationTypes a
ON ae.accType = a.id
GROUP BY t.name, t.email
HAVING SUM(CASE WHEN a.name = 'Pension' AND ae.rating > 9 THEN 1 ELSE 0 END) > 0 AND
SUM(a.name = 'Hotel' AND ae.noOfStars = 3 AND ae.rating < 9 THEN 1 ELSE 0 END)= 0;
Your method also works, but you probably need t.id in the subqueries.

count after join on multiple tables and count of multiple column values

Please help me with below problem.
table 1 employee details
emp name empno.
---------------------------------
John 1234
Joe 6789
table 2 employee assignment
empno assignmentstartdate assignmentenddate assignmentID empassignmentID
-----------------------------------------------------------------------------
1234 01JAN2017 02JAN2017 A1 X1
6789 01jan2017 02JAN2017 B1 Z1
table 3 employee assignment property
empassignmentID assignmentID propertyname propertyvalue
-------------------------------------------------------------------
X1 A1 COMPLETED true
X1 A1 STARTED true
Z1 B1 STARTED true
Z1 B1 COMPLETED false
Result wanted: (count of completed and started for each employee)
emp name emp no. COMPLETED STARTED
------------------------------------------
John 1234 1 1
Joe 6789 0 1
Currently with my query it is not putting count correctly for propertyvalue if I run for one employee it works correctly but not for multiple employees.
Please help.
SELECT empno ,
empname ,
(SELECT COUNT(A.propertyvalue)
FROM employeedetails C ,
employees_ASSIGNMENT RCA,
employee_assignment_property A
WHERE TRUNC(startdate) >= '14jun2017'
AND TRUNC(endate) <= '20jun2017'
AND RCA.empno = C.empno
AND RCA.empassignmetid = A.empassignmetid
AND rca.EMPNO IN ('1234','6789')
AND RCA.assignmentid = A.assignmentid
AND A.Name = 'COMPLETED'
AND A.propertyvalue = 'true') ,
(SELECT COUNT(A.propertyvalue)
FROM employeedetails C ,
employees_ASSIGNMENT RCA,
employee_assignment_property A
WHERE TRUNC(startdate) >= '14jun2017'
AND TRUNC(endate) <= '20jun2017'
AND RCA.empno = C.empno
AND RCA.empassignmetid = A.empassignmetid
AND rca.EMPNO IN ('1234','6789')
AND RCA.assignmentid = A.assignmentid
AND A.Name = 'STARTED'
AND A.propertyvalue = 'true')FROM employeedetails WHERE EMPNO IN
('1234','6789') GROUP BY C.empno ,
C.EMPNAME
I think you are simply looking for this:
SELECT DET.empname
, COUNT(CASE WHEN PROP.propertyname = 'COMPLETED' THEN 1 END) COMP_COUNT
, COUNT(CASE WHEN PROP.propertyname = 'STARTED' THEN 1 END) START_COUNT
FROM employeedetails DET
INNER JOIN employees_ASSIGNMENT ASS
ON ASS.empno = DET.empno
INNER JOIN employee_assignment_property PROP
ON PROP.empassignmentID = ASS.empassignmentID
AND PROP.assignmentID = ASS.assignmentID
GROUP BY DET.empname
Just add a WHERE clause if you need one.
if you want you result as a query without CTEs this should work:
select empName,
empNo,
(select employee_details.empNo, count(employee_assignment.assId)
from employee_details as t1
join employee_assignment on (t1.empno = employee_assignment.empno)
join employee_assignment_property on (employee_assignment.assId = employee_assignment_property.assId)
where employee_assignment.ptop = 'COMPLETED'
and t.empNo = t1.empNo
group by t1.empNo ) as [COMPLETED],
(select employee_details.empNo, count(employee_assignment.assId)
from employee_details as t1
join employee_assignment on (t1.empno = employee_assignment.empno)
join employee_assignment_property on (employee_assignment.assId = employee_assignment_property.assId)
where employee_assignment.ptop = 'STARTED'
and t.empNo = t1.empNo
group by t1.empNo ) as [STARTED],
from employee_details as t
If you don't want to do a dirty query composed of subqueries, you can try creating a view (if your database permits it).
What does it mean : I'll be useless in front of this. In summary, a view is a temporary table.
Hope this helps
this should work using CTEs:
Using Common Table Expressions
with numComplet()
as
(
select tbl1.empNo, count(tbl2.assId)
from tbl1
join tbl2 on (tbl1.empno = tbl2.empno)
join tbl3 on (tbl2.assId = tbl3.assId)
where tbl2.ptop = 'COMPLETED'
group by tbl1.empNo
),
with numStarted()
as
(
select tbl1.empNo, count(tbl2.assId)
from tbl1
join tbl2 on (tbl1.empno = tbl2.empno)
join tbl3 on (tbl2.assId = tbl3.assId)
where tbl2.ptop = 'STARTED'
group by tbl1.empNo
)
select *
from tbl1
join numComplet on (tbl1.empNo = numComplet.empNo)
join numStarted on (tbl1.empNo = numStarted.empNo)
I put down table names as tbl[1|2|3]

SQL combine relationship table as separate columns

I have this 2 tables
Items
ID Type ClientID
========================
1 0 123
2 0 123
Texts
ItemID Language Text
================================
1 Eng Hi there!
1 Spa Holla!
2 Eng bye!
2 Spa bye in Spanish!
In my final result I wat the SQL to return me this table
ID Type Eng Spa
================================
1 0 Hi there! Holla!
2 0 Bye! bye in Spanish!
I tried to create this statement:
SELECT DISTINCT I.ID ,I.Type,
(SELECT T.Text WHERE D.Language='Eng') AS 'Eng',
(SELECT T.Text WHERE D.Language='Spa') AS 'Spa'
FROM Items I
INNER JOIN Texts T ON I.ID=T.ItemID
but i get this result:
ID Type Eng Spa
================================
1 0 Hi there! NULL
1 0 NULL Holla!
2 0 Bye! NULL
2 0 NULL bye in Spanish!
I don't see why a join is necessary. You can just use conditional aggregation:
select t.itemid,
max(case when t.language = 'Eng' then t.text end) as Eng,
max(case when t.language = 'Spa' then t.text end) as Spa
from texts t
group by t.itemid;
Use nesting to make two joins on same table with a filter (where clause) .
Below i have tested on MySQL
SELECT
i.id, eng_table.text AS eng, spa_table.text AS spa
FROM
i
LEFT OUTER JOIN
(SELECT
ItemID AS ID, Text
FROM
t
WHERE
Language = 'ENG') AS eng_table ON i.id = eng_table.id
LEFT OUTER JOIN
(SELECT
ItemID AS ID, Text
FROM
t
WHERE
Language = 'SPA') AS spa_table ON i.id = spa_table.id
Regards,
Bikxs
A solution using joins would look something like....
Select i.ID
,i.[Type]
,t1.[Text] AS [Eng]
,t2.[Text] AS [Spa]
FROM Items i
INNER JOIN Texts t1 ON i.ID = t1.ItemID AND t1.[Language] = 'Eng'
INNER JOIN Texts t2 ON i.ID = t2.ItemID AND t2.[Language] = 'Spa'
WITH Eng
AS
(
SELECT * FROM Texts t WHERE t.Language = 'Eng'
),
Spa
AS
(
SELECT * FROM Texts t WHERE t.Language = 'Spa'
)
SELECT i.ID, i.Type, e.Text AS Eng, s.Text AS Spa
FROM Items i
LEFT JOIN Eng e ON i.ID = e.ItemID
LEFT JOIN Spa s ON i.ID = s.ItemID
Even you can use a dynamic sql query.
Query
DECLARE #query VARCHAR(MAX)
SELECT #query = 'SELECT t1.itemid, MAX(t2.[Type]) AS [Type], ' +
STUFF
(
(
SELECT DISTINCT ',MAX(CASE WHEN t1.[language] = '''+ [language]
+ ''' THEN t1.[Text] END) AS ' + [Language]
FROM Texts
FOR XML PATH('')
),
1,1,'');
SELECT #query += ' FROM texts t1 JOIN items t2 ON t1.ItemId = t2.ID GROUP BY t1.itemid;';
EXECUTE(#query);

Join table on conditions, count on conditions

SELECT *, null AS score,
'0' AS SortOrder
FROM products
WHERE datelive = -1
AND hidden = 0
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'1' AS SortOrder
FROM products e
LEFT JOIN reviews r
ON r.productID = e.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) >= 5
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'2' AS SortOrder
FROM products e
LEFT JOIN reviews r
ON r.productID = e.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) < 5
ORDER BY SortOrder ASC, score DESC
This creates an SQL object for displaying products on a page. The first request grabs items of type datelive = -1, the second of type datelive != -1 but r.count(*) >= 5, and the third of type datelive != -1 and r.count(*) < 5. The reviews table is structured similar to the below:
reviewID | productID | a | b | c | d | approved
-------------------------------------------------
1 1 5 4 5 5 1
2 5 3 2 5 5 0
3 2 5 5 4 3 1
... ... ... ... ... ... ...
I'm trying to work it such that r.count(*) only cares for rows of type approved = 1, since tallying data based on unapproved reviews isn't ideal. How can I join these tables such that the summations of scores and the number of rows is dependent only on approved = 1?
I've tried adding in AND r.approved = 1 in the WHERE conditional for the joins and it doesn't do what I'd like. It does sort it properly, but then it no longer includes items with zero reviews.
You seem to be nearly there.
In your question you talked about adding the AND r.approved = 1 to the join criteria but by the sounds of it you are actually adding it to the WHERE clause.
If you instead properly add it to the join criteria like below then it should work fine:
SELECT *, null AS score,
'0' AS SortOrder
FROM products
WHERE datelive = -1
AND hidden = 0
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'1' AS SortOrder
FROM products e
LEFT JOIN reviews r ON r.productID = e.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) >= 5
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'2' AS SortOrder
FROM products e
LEFT JOIN reviews r ON r.productID = e.productID AND r.approved = 1
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) < 5
ORDER BY SortOrder ASC, score DESC
SQL Fiddle here.
Notice again how I have simply put the AND r.approved = 1 directly after LEFT JOIN reviews r ON r.productID = e.productID which adds an extra criteria to the join.
As I mentioned in my comment, the WHERE clause will filter rows out of the combined record set after the join has been made. In some cases the RDBMS may optimise it out and put it into the join criteria but only where that would make no difference to the result set.
Calculating the non-zero sums and joining it to your result may solve it;
fiddle
SELECT a.productID,
NULL AS score,
'0' AS SortOrder
FROM products a
WHERE datelive = -1
AND hidden = 0
UNION
SELECT e.productID,
(min(x.a)/(min(x.cnt)*1.0)+ min(x.b)/(min(x.cnt)*1.0)+ min(x.c)/(min(x.cnt)*1.0)+ min(x.d)/(min(x.cnt)*1.0))/4 AS score,
'1' AS SortOrder
FROM products e
JOIN reviews r ON r.productID = e.productID
LEFT JOIN
(SELECT ee.productID,
sum(rr.a) AS a,
sum(rr.b) AS b,
sum(rr.c) AS c,
sum(rr.d) AS d,
count(*) AS cnt
FROM products ee
LEFT JOIN reviews rr ON ee.productID = rr.productID
GROUP BY ee.productID) x ON e.productID = x.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID HAVING COUNT(*) >= 5
UNION
SELECT e.productID,
(min(x.a)/(min(x.cnt)*1.0)+ min(x.b)/(min(x.cnt)*1.0)+ min(x.c)/(min(x.cnt)*1.0)+ min(x.d)/(min(x.cnt)*1.0))/4 AS score,
'2' AS SortOrder
FROM products e
LEFT JOIN reviews r ON r.productID = e.productID
LEFT JOIN
(SELECT ee.productID,
sum(rr.a) AS a,
sum(rr.b) AS b,
sum(rr.c) AS c,
sum(rr.d) AS d,
count(*) AS cnt
FROM products ee
LEFT JOIN reviews rr ON ee.productID = rr.productID
GROUP BY ee.productID) x ON e.productID = x.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID HAVING COUNT(*) < 5
ORDER BY SortOrder ASC,
score DESC
You could create a temp table that only contains rows where approved = 1, and then join on the temp table instead of reviews.
create table tt_reviews like reviews;
insert into tt_reviews
select * from reviews
where approved = 1;
alter table tt_reviews add index(productID);
Then replace reviews with tt_reviews in your above query.