Increase MS SQL transaction performance - sql

If you're missing information, I will attach them if requested.
Workspace
I have a database running on a MS SQL 2012 standard edition of this kind:
tables:
users(id, softId (not unique), birthdate)
rows: 10.5 million
indexes: all three columns, birthdate(clustered)
docs(docId, userId, creationDate, deleteDate, lastname, forename, classificationId)
rows: 23 million
indexes: lastname, forename, docId, creationDate, userID(clustered)
notice: in this specific case the names are related to the docs, not to the userId
classifications(id, description)
rows: 200
three tables "data"
rows: 10, 13 and 0.3 million
indexes: docIds
relations:
users to docs: 1 to n
classifications to docs: 1 to n
docs to data-tables: 1 to n
To select the complete records I am actually on following statements:
Server-Execution-Time 16 seconds
SELECT * FROM (
select * from docs
where userID in (
select distinct userID from users where softId like '...'
)
) as doc
LEFT JOIN users on users.userID = doc.userId
LEFT JOIN classifications on classifications.id = doc.classificationId
LEFT JOIN data1 on data1.docId = doc.docId
LEFT JOIN data2 on data2.docId = doc.docId
LEFT JOIN data3 on data3.docId = doc.docId;
Updated - now 15 seconds
SELECT
docID, calssificationId, classificationDescription,
userId, softId, forename, lastname, birthdate,
data1.id, data1.date, data2.id, data2.date, data3.id, data3.date,
FROM docs
JOIN users on users.userID = doc.userId AND softId like '...'
LEFT JOIN classifications on classifications.id = doc.classificationId
LEFT JOIN data1 on data1.docId = doc.docId
LEFT JOIN data2 on data2.docId = doc.docId
LEFT JOIN data3 on data3.docId = doc.docId;
execution plans
execution plan
Server-Execution-Time 17 seconds
DECLARE #userIDs table( id bigint );
DECLARE #docIDs table( id bigint );
insert into #userIDs select userID from users where softId like '...';
insert into #docIDs select docId from docs where userId in ( select id from #userIDs);
SELECT * FROM users where userID in ( select id from #userIDs);
SELECT * FROM docs where docID in (select id from #docIDs);
SELECT * FROM data1 where data1.docId in (select id from #docIDs);
SELECT * FROM data2 where data2.docId in (select id from #docIDs);
SELECT * FROM data3 where data3.docId in (select id from #docIDs);
GO
Updated - now 14 seconds
DECLARE #userIDs table( id bigint, softId varchar(12), birthdate varchar(8) );
DECLARE #docIDs table( id bigint, classification bigint, capture_date datetime, userId bigint, lastname varchar(50), forename varchar(50) );
INSERT INTO #userIDs select userID, softId, birthdate from users where softId like '...';
INSERT INTO #docIDs select docID, classification, capture_date, userID, lastname, forename from docs where userID in ( select id from #userIDs);
SELECT * FROM #userIDs;
SELECT * FROM #docIDs;
SELECT [only needed fields] FROM data1 where docID in (select id from #docIDs);
SELECT [only needed fields] FROM data2 where docID in (select id from #docIDs);
SELECT [only needed fields] FROM data3 where docID in (select id from #docIDs);
execution plans
execution plan userIds
execution plan docIds
execution plan userIds output
execution plan data1
General Updates
#AntonínLejsek suggested to define the docId of documents as a clustered index and the pkId as non-clustered. This changed the execution-time as follow:
Join-Statement: -1 second
Multi-Select-Statement: -5 seconds
I checked the indexes again and changed the included columns, now they have the execution-time:
Join-Statement: 4 seconds
Multi-Select-Statement: 6 seconds
The "simple" question
Do somebody have suggestions to reduce the executiontime?

I would phrase the logic as:
I would get rid of the first subquery and just do the necessary work on the users table:
SELECT *
FROM docs JOIN
users
ON users.userID = doc.userId AND softId LIKE '...' LEFT JOIN
. . .
The logic in the IN is unnecessary if you are doing a JOIN anyway.
Note: This might not help much, because your query appears to be returning lots of data, both in columns and rows.

I see two different databases in the plan, I would try to test it in one database first.
The database design is weird. You have clustered index on birthdate. As it is not unique, database has to make up another 4B number for making it unique. So You have 12B key in every nonclustered index, which is space and performance inefficient. You do not even have id included in the nonclustered index, so it has to be looked up, which is time wasting. In most cases You should cluster on primary key and that should be id.
--Deleted-- while softIds is almost unique, this paragraph became irelevant.

Add clustered indexes on your table variables by defining primary keys.
DECLARE #userIDs table( id bigint primary key, softId varchar(12), birthdate varchar(8) );
DECLARE #docIDs table( id bigint primary key, classification bigint, capture_date datetime, userId bigint, lastname varchar(50), forename varchar(50) );

Related

How to insert a column which sets unique id based on values in another column (SQL)?

I will create table where I will insert multiple values for different companies. Basically I have all values that are in the table below but I want to add a column IndicatorID which is linked to IndicatorName so that every indicator has a unique id. This will obviously not be a PrimaryKey.
I will insert the data with multiple selects:
CREATE TABLE abc
INSERT INTO abc
SELECT company_id, 'roe', roevalue, metricdate
FROM TABLE1
INSERT INTO abc
SELECT company_id, 'd/e', devalue, metricdate
FROM TABLE1
So, I don't know how to add the IndicatorID I mentioned above.
EDIT:
Here is how I populate my new table:
INSERT INTO table(IndicatorID, Indicator, Company, Value, Date)
SELECT [the ID that I need], 'NI_3y' as 'Indicator', t.Company, avg(t.ni) over (partition by t.Company order by t.reportdate rows between 2 preceding and current row) as 'ni_3y',
t.reportdate
FROM table t
LEFT JOIN IndicatorIDs i
ON i.Indicator = roe3 -- the part that is not working if I have separate indicatorID table
I am going to insert different indicators for the same companies. And I want indicatorID.
Your "indicator" is a proper entity in its own right. Create a table with all indicators:
create table indicators (
indicator_id int identity(1, 1) primary key,
indicator varchar(255)
);
Then, use the id only in this table. You can look up the value in the reference table.
Your inserts are then a little more complicated:
INSERT INTO indicators (indicator)
SELECT DISTINCT roevalue
FROM table1 t1
WHERE NOT EXISTS (SELECT 1 FROM indicators i2 WHERE i2.indicator = t1.roevalue);
Then:
INSERT INTO ABC (indicatorId, companyid, value, date)
SELECT i.indicatorId, t1.company, v.value, t1.metricdate
FROM table1 t1 CROSS APPLY
(VALUES ('roe', t1.roevalue), ('d/e', t1.devalue)
) v(indicator, value) JOIN
indicators i
ON i.indicator = v.indicator;
This process is called normalization and it is the typical way to store data in a database.
DDL and INSERT statement to create an indicators table with a unique constraint on indicator. Because the ind_id is intended to be a foreign key in the abc table it's created as a non-decomposable surrogate integer primary key using the IDENTITY property.
drop table if exists test_indicators;
go
create table test_indicators (
ind_id int identity(1, 1) primary key not null,
indicator varchar(20) unique not null);
go
insert into test_indicators(indicator) values
('NI'),
('ROE'),
('D/E');
The abc table depends on the ind_id column from indicators table as a foreign key reference. To populate the abc table company_id's are associated with ind_id's.
drop table if exists test_abc
go
create table test_abc(
a_id int identity(1, 1) primary key not null,
ind_id int not null references test_indicators(ind_id),
company_id int not null,
val varchar(20) null);
go
insert into test_abc(ind_id, company_id)
select ind_id, 102 from test_indicators where indicator='NI'
union all
select ind_id, 103 from test_indicators where indicator='ROE'
union all
select ind_id, 104 from test_indicators where indicator='D/E'
union all
select ind_id, 103 from test_indicators where indicator='NI'
union all
select ind_id, 105 from test_indicators where indicator='ROE'
union all
select ind_id, 102 from test_indicators where indicator='NI';
Query to get result
select i.ind_id, a.company_id, i.indicator, a.val
from test_abc a
join test_indicators i on a.ind_id=i.ind_id;
Output
ind_id company_id indicator val
1 102 NI NULL
2 103 ROE NULL
3 104 D/E NULL
1 103 NI NULL
2 105 ROE NULL
1 102 NI NULL
I was finally able to find the solution for my problem which seems to me very simple, although it took time and asking different people about it.
First I create my indicators table where I assign primary key for all indicators I have:
CREATE TABLE indicators (
indicator_id int identity(1, 1) primary key,
indicator varchar(255)
);
Then I populate easy without using any JOINs or CROSS APPLY. I don't know if this is optimal but it seems as the simplest choice:
INSERT INTO table(IndicatorID, Indicator, Company, Value, Date)
SELECT
(SELECT indicator_id from indicators i where i.indicator = 'NI_3y) as IndicatorID,
'NI_3y' as 'Indicator',
Company,
avg(ni) over (partition by Company order by reportdate rows between 2 preceding and current row) as ni_3y,
reportdate
FROM TABLE1

Needed helpful hand with a bit complicated query

I have a table 'Tasks' with the following structure
[TaskId],[CompanyId], [Year], [Month], [Value]
220,1,2018,1,50553.32
220,2,2018,2,222038.12
and another table where users have permissions to particular companies in table named 'UsersCopmpanies'
[UserId], [CompanyId]
1,1
and the thing is task no. 220 was moved between companies. In January task belonged to copmanyId=1 and than in February this task belonged to copmanyId = 2.
According to the table 'UsersCopmpanies' user does not have permision to compnayid = 2.
What I need to do is to get both rows from table 'Tasks' expect field Value, because user does not have persmission.
Expected result should be:
[TaskId], [CompanyId], [Year], [Month],[Value]
220,1,2018,1,50553.32
220,2,2018,2,(NULL or somenthing else for.example string 'lack of permission')
You can use a left join:
select t.TaskId, t.CompanyId, t.Year, t.Month,
(case when uc.CompanyId is not null then Value end) as Value
from tasks t left join
UsersCompanies uc
on uc.CompanyId = t.CompanyId and uc.UserId = 1;
I think this query using LEFT JOIN can be work at you expected :
CREATE TABLE #MyTasks
(TaskId int,
CompanyId int,
YearCol varchar(50),
MonthCol varchar(50),
SomeValue varchar(50)
);
GO
INSERT INTO #MyTasks
SELECT 220,1,2018,1,50553.32
UNION
SELECT 220,2,2018,2,222038.12
CREATE TABLE #MyUsersCopmpanies
(UserId int PRIMARY KEY,
CompanyId varchar(50)
);
GO
INSERT INTO #MyUsersCopmpanies
SELECT 1,1
DECLARE #MyUserParam INT = 1;
SELECT #MyTasks.TaskId, #MyTasks.CompanyId, #MyTasks.YearCol, #MyTasks.MonthCol,
CASE WHEN #MyUsersCopmpanies.UserId IS NOT NULL THEN #MyTasks.SomeValue ELSE 'lack of permission' END AS 'ValueTaskByPermissions'
FROM #MyTasks
LEFT JOIN #MyUsersCopmpanies ON #MyUsersCopmpanies.CompanyId = #MyTasks.CompanyId AND #MyUsersCopmpanies.UserId = #MyUserParam;
DROP TABLE #MyTasks
DROP TABLE #MyUsersCopmpanies
RESULT :
TaskId CompanyId YearCol MonthCol ValueTaskByPermissions
220 1 2018 1 50553.32
220 2 2018 2 lack of permission
Some code :
SELECT t.taskid,t.companyid,t.year,t.month,
(CASE WHEN u.companyid IS NOT NULL THEN t.value ELSE "lack of permission" end) AS ValueData
FROM `x_task` t LEFT JOIN x_userscopmpanies u ON u.companyid = t.companyid

SQL Query Optimization (After table structure change)

I am just wondering if anyone can see a better solution to this issue.
I previously had a flat (wide) table to work with, that contained multiple columns. This table has now been changed to a dynamic table containing just 2 columns (statistic_name and value).
I have amended my code to use sub queries to return the same results as before, however I am worried the performance is going to be terrible when using real live data. This is based on the exacution plan which shows a considerable difference between the 2 versions.
See below for a very simplified example of my issue -
CREATE TABLE dbo.TEST_FLAT
(
ID INT,
TEST1 INT,
TEST2 INT,
TEST3 INT,
TEST4 INT,
TEST5 INT,
TEST6 INT,
TEST7 INT,
TEST8 INT,
TEST9 INT,
TEST10 INT,
TEST11 INT,
TEST12 INT
)
CREATE TABLE dbo.TEST_DYNAMIC
(
ID INT,
STAT VARCHAR(6),
VALUE INT
)
CREATE TABLE dbo.TEST_URNS
(
ID INT
)
-- OLD QUERY
SELECT D.[ID], D.TEST1, D.TEST2, D.TEST3, D.TEST4, D.TEST5, D.TEST6, D.TEST7, D.TEST8, D.TEST9, D.TEST10, D.TEST11, D.TEST12
FROM [dbo].[TEST_URNS] U
INNER JOIN [dbo].[TEST_FLAT] D
ON D.ID = U.ID
-- NEW QUERY
SELECT U.[ID],
(SELECT VALUE FROM dbo.TEST_DYNAMIC WHERE ID = U.ID AND STAT = 'TEST1') AS TEST1,
(SELECT VALUE FROM dbo.TEST_DYNAMIC WHERE ID = U.ID AND STAT = 'TEST2') AS TEST2,
(SELECT VALUE FROM dbo.TEST_DYNAMIC WHERE ID = U.ID AND STAT = 'TEST3') AS TEST3,
(SELECT VALUE FROM dbo.TEST_DYNAMIC WHERE ID = U.ID AND STAT = 'TEST4') AS TEST4,
(SELECT VALUE FROM dbo.TEST_DYNAMIC WHERE ID = U.ID AND STAT = 'TEST5') AS TEST5,
(SELECT VALUE FROM dbo.TEST_DYNAMIC WHERE ID = U.ID AND STAT = 'TEST6') AS TEST6,
(SELECT VALUE FROM dbo.TEST_DYNAMIC WHERE ID = U.ID AND STAT = 'TEST7') AS TEST7,
(SELECT VALUE FROM dbo.TEST_DYNAMIC WHERE ID = U.ID AND STAT = 'TEST8') AS TEST8,
(SELECT VALUE FROM dbo.TEST_DYNAMIC WHERE ID = U.ID AND STAT = 'TEST9') AS TEST9,
(SELECT VALUE FROM dbo.TEST_DYNAMIC WHERE ID = U.ID AND STAT = 'TEST10') AS TEST10,
(SELECT VALUE FROM dbo.TEST_DYNAMIC WHERE ID = U.ID AND STAT = 'TEST11') AS TEST11,
(SELECT VALUE FROM dbo.TEST_DYNAMIC WHERE ID = U.ID AND STAT = 'TEST12') AS TEST12
FROM [dbo].[TEST_URNS] U
Note this is in SQL2008 R2 and this will be part of a stored procedure, the flat version of the table contained hundreds of thousands of records (900k or so at last count).
Thanks in advance.
Create an index on the STAT column of TEST_DYNAMIC, for quick lookups.
But first consider redesigning TEST_DYNAMIC changing STAT varchar(6) to STAT_ID int (referencing a lookup table)
Then on TEST_DYNAMIC, create an index on STAT_ID which will run quite a bit faster than an index on a text field.
Create your TEST_DYNAMIC and TEST_URNS tables like this:
CREATE TABLE [dbo].[TEST_DYNAMIC](
[ID] [int] IDENTITY(1,1) NOT NULL,
[STAT] [varchar](50) NOT NULL,
[VALUE] [int] IDENTITY(1,1) NOT NULL,
CONSTRAINT [PK_TEST_DYNAMIC] PRIMARY KEY CLUSTERED
(
[ID]
))
CREATE TABLE dbo.TEST_URNS
(
ID [int] IDENTITY(1,1) NOT NULL
)
CONSTRAINT [PK_TEST_URNS] PRIMARY KEY CLUSTERED
(
[ID]
))
If you notice after a period of time that performance becomes poor, then you can check the index fragmentation:
SELECT a.index_id, name, avg_fragmentation_in_percent
FROM sys.dm_db_index_physical_stats (DB_ID(), OBJECT_ID(dbo.TEST_DYNAMIC'),
NULL, NULL, NULL) AS a
JOIN sys.indexes AS b ON a.object_id = b.object_id AND a.index_id = b.index_id;
GO
Then you can rebuild the index like so:
ALTER INDEX PK_PK_TEST_DYNAMIC ON dbo.TEST_DYNAMIC
REBUILD;
GO
For details please see https://msdn.microsoft.com/en-us/library/ms189858.aspx
Also, I like #Brett Lalonde's suggestion to change STAT to an int.
The only way to really know is to try it out. In general, modern hardware should be able to support either query with little noticable impact in performance as long as you are indexing both tables correctly (you'll probably need an index on ID and STAT).
If you have 900K entities and 12 attributes, you have around 10 million rows; that should be fine on a decent serer. Eventually, you may run into performance problems if you add many records every month.
The bigger problem is that the example queries you paste are almost certainly not what you'll end up running in your real queries. If you have to filter and/or compare TEST5 with TEST6 on your derived table, you don't benefit from the additional indexing you could do if they were "real" columns.
You could then come full circle and implement your EAV table as an indexed view.

How to optimize this long running sqlite3 query for finding duplicates?

I've got this rather insane query for finding all but the FIRST record with a duplicate value. It takes a substantially long time to run on 38000 records; about 50 seconds.
UPDATE exr_exrresv
SET mh_duplicate = 1
WHERE exr_exrresv._id IN
(
SELECT F._id
FROM exr_exrresv AS F
WHERE Exists
(
SELECT PHONE_NUMBER,
Count(_id)
FROM exr_exrresv
WHERE exr_exrresv.PHONE_NUMBER = F.PHONE_NUMBER
AND exr_exrresv.PHONE_NUMBER != ''
AND mh_active = 1 AND mh_duplicate = 0
GROUP BY exr_exrresv.PHONE_NUMBER
HAVING Count(exr_exrresv._id) > 1)
)
AND exr_exrresv._id NOT IN
(
SELECT Min(_id)
FROM exr_exrresv AS F
WHERE Exists
(
SELECT PHONE_NUMBER,
Count(_id)
FROM exr_exrresv
WHERE exr_exrresv.PHONE_NUMBER = F.PHONE_NUMBER
AND exr_exrresv.PHONE_NUMBER != ''
AND mh_active = 1
AND mh_duplicate = 0
GROUP BY exr_exrresv.PHONE_NUMBER
HAVING Count(exr_exrresv._id) > 1
)
GROUP BY PHONE_NUMBER
);
Any tips on how to optimize it or how I should begin to go about it? I've checked out the query plan but I'm really not sure how to begin improving it. Temp tables? Better query?
Here is the explain query plan output:
0|0|0|SEARCH TABLE exr_exrresv USING INTEGER PRIMARY KEY (rowid=?) (~12 rows)
0|0|0|EXECUTE LIST SUBQUERY 0
0|0|0|SCAN TABLE exr_exrresv AS F (~500000 rows)
0|0|0|EXECUTE CORRELATED SCALAR SUBQUERY 1
1|0|0|SEARCH TABLE exr_exrresv USING AUTOMATIC COVERING INDEX (PHONE_NUMBER=? AND mh_active=? AND mh_duplicate=?) (~7 rows)
1|0|0|USE TEMP B-TREE FOR GROUP BY
0|0|0|EXECUTE LIST SUBQUERY 2
2|0|0|SCAN TABLE exr_exrresv AS F (~500000 rows)
2|0|0|EXECUTE CORRELATED SCALAR SUBQUERY 3
3|0|0|SEARCH TABLE exr_exrresv USING AUTOMATIC COVERING INDEX (PHONE_NUMBER=? AND mh_active=? AND mh_duplicate=?) (~7 rows)
3|0|0|USE TEMP B-TREE FOR GROUP BY
2|0|0|USE TEMP B-TREE FOR GROUP BY
Any tips would be much appreciated. :)
Also, I am using Ruby to make the sql query so if it makes more sense for the logic to leave SQL and be written in Ruby, that's possible.
The schema is as follows, and you can use sqlfiddle here: http://sqlfiddle.com/#!2/2c07e
_id INTEGER PRIMARY KEY
OPPORTUNITY_ID varchar(50)
CREATEDDATE varchar(50)
FIRSTNAME varchar(50)
LASTNAME varchar(50)
MAILINGSTREET varchar(50)
MAILINGCITY varchar(50)
MAILINGSTATE varchar(50)
MAILINGZIPPOSTALCODE varchar(50)
EMAIL varchar(50)
CONTACT_PHONE varchar(50)
PHONE_NUMBER varchar(50)
CallFromWeb varchar(50)
OPPORTUNITY_ORIGIN varchar(50)
PROJECTED_LTV varchar(50)
MOVE_IN_DATE varchar(50)
mh_processed_date varchar(50)
mh_control INTEGER
mh_active INTEGER
mh_duplicate INTEGER
Guessing from your post, it looks like you are trying to update the mh_duplicate column for any record that has the same phone number if it's not the first record with that phone number?
If that's correct, I think this should get you the id's to update (you may need to add back your appropriate where criteria) -- from there, the Update is straight-forward:
SELECT e._Id
FROM exr_exrresv e
JOIN
( SELECT t.Phone_Number
FROM exr_exrresv t
GROUP BY t.Phone_Number
HAVING COUNT (t.Phone_Number) > 1
) e2 ON e.Phone_Number = e2.Phone_Number
LEFT JOIN
( SELECT MIN(t2._Id) as KeepId
FROM exr_exrresv t2
GROUP BY t2.Phone_Number
) e3 ON e._Id = e3.KeepId
WHERE e3.KeepId is null
And the SQL Fiddle.
Good luck.
This considers a record duplicate if there exists an active record with a matching phone_number and with a lesser _id. (No grouping or counting needed.)
update exr_exrresv
set mh_duplicate = 1
where exr_exrresv._id in (
select target._id
from exr_exrresv as target
where target.phone_number != ''
and target.mh_active = 1
and exists (
select null from exr_exrresv as probe
where probe.phone_number = target.phone_number
and probe.mh_active = 1
and probe._id < target._id
)
)
This query will be greatly aided if there exists an index on phone_number, ideally on exr_exrresv (phone_number, _id)
SQLFiddle

SELECT command with a WHERE condition and an INNER JOIN

I'm sure I must be making a trivial mistake here, but I've been searching around for help with this problem and all I can find is information on conditional INNER JOINs.
< EDIT > The problem is that this stored procedure is not returning anything at all. If I type just:
SELECT TOP (6) UserID, Category, Title, SUBSTRING(Article, 0, 200) AS Summary, DatePosted
FROM ContribContent
WHERE (DateFeatured IS NOT NULL)
ORDER BY DateFeatured DESC
Into the console then I get values returned. So it must be something to do with the inner-join? < / EDIT >
The idea is to:
take the content which has been
featured (DateFeatured is NOT NULL)
and place it all into a temporary
table
get the user names and picture from the users table and match them to the values in the temporary table using the UserID value.
sort the temporary table in order of the date each post was featured.
select the top six entries from the table
Here's the code:
ALTER PROCEDURE [dbo].[admin_GetFeaturedContrib]
AS
BEGIN
DECLARE #FeaturedContrib TABLE (
UserID INT,
Category INT,
Title varchar(100),
Summary varchar(200),
DatePosted date,
FirstName varchar(50),
LastName varchar(50),
Picture varchar(100)
)
INSERT INTO #FeaturedContrib
SELECT TOP 6 ContribContent.UserID, ContribContent.Category, ContribContent.Title, SUBSTRING(ContribContent.Article, 0, 200) AS Summary, ContribContent.DatePosted, Users.FirstName, Users.LastName, Users.Picture
FROM ContribContent
INNER JOIN Users
ON ContribContent.UserID = Users.UserID
WHERE ContribContent.DateFeatured IS NOT NULL
ORDER BY ContribContent.DateFeatured DESC
SELECT * FROM #FeaturedContrib
END
There are two data tables involved:
Users - a table storing all of the users and their information.
UserID INT
FirstName varchar(50)
LastName varchar(50)
Picture varchar(50)
etc...
ContribContent
ContribContentID INT
UserID INT
Category INT
Title varchar(100)
Article varchar(MAX)
Picture varchar(50)
DatePosted date
DateFeatured date
Deleted bit
THANKS to anyone who can help out!
Run only -
SELECT TOP 6 ContribContent.UserID, ContribContent.Category, ContribContent.Title, SUBSTRING(ContribContent.Article, 0, 200) AS Summary, ContribContent.DatePosted, Users.FirstName, Users.LastName, Users.Picture
FROM ContribContent
INNER JOIN Users
ON ContribContent.UserID = Users.UserID
WHERE ContribContent.DateFeatured IS NOT NULL
ORDER BY ContribContent.DateFeatured DESC
See what you getting might be an issue with your Where or your join see carefully if you have any data in the first place being returned. My guess is join see if you have matching userids you are joining on...(Hint : Left join maybe your answer)