withCount() - get difference of 2 counts - mysql

I have a table that has a column is_up_vote = true|false
I am trying to write a query using withCount() so it returns me the sum of true values and false values.
select COUNT(is_up_vote) from comment_votes WHERE is_up_vote = true returns 17
select COUNT(is_up_vote) from comment_votes WHERE is_up_vote = false returns 15
However I couldn't figure out how to get different of them, so the total returns 2.
What I tried is:
Model::withCount(['votes' => function($q) {
$q->selectRaw(
'(SUM (COUNT(is_up_vote) WHERE is_up_vote = true) - (COUNT(is_up_vote) WHERE is_up_vote = false) )'
);
}]);
But this returns 17+15 = 32
without SUM(), it's also returning 32.
$q->selectRaw(
'( (COUNT(is_up_vote) WHERE is_up_vote = true) - (COUNT(is_up_vote) WHERE is_up_vote = false) )'
);
What am I doing wrong?
Edit:
If I try one side, it ignores the where and still returns 32, so the where is not getting called (where it does in sql)
return $query->withCount(['votes' => function($q) {
$q->selectRaw('(COUNT(is_up_vote) WHERE is_up_vote = true)');
}]);
maunal sequel query returns 17:
select COUNT(is_up_vote) from comment_votes WHERE is_up_vote = true
Edit 2:
$query->withCount([
'votes as up_votes_count' => function($q) {
$q->where('is_up_vote', true);
},
'votes as down_votes_count' => function($q) {
$q->where('is_up_vote', false);
},
]);
The only thing that I could make work is this, but it'd need extra step for getting the total, so I didn't really like this approach. I'm sure someone more proficient with queries can come up with something with direct query.

withCount may not be able to handle this case. what about using addSelect to calculate the votes count.
return Model::addSelect(['votes_count' => Vote::selectRaw('( (COUNT(is_up_vote) WHERE is_up_vote = true) - (COUNT(is_up_vote) WHERE is_up_vote = false) )')
->whereColumn('parent_id', 'parents.id')
])->get();
I don't know whether the sub-query is gonna work. But you know the idea.

According to the instructions commented by #Jefferson Pessanha, just using Model::selectRaw('(abs(2 * sum(is_up_vote) - count(*)))')->get(); would be enough

create table comment_votes
(
is_up_vote tinyint(1) default 1 null
);
insert into comment_votes values (true),(true),(true),(true),(true),(true),(true),(true),(true),(true),(true),(true),(true),(true),(true),(true),(true);
insert into comment_votes values (false),(false),(false),(false),(false),(false),(false),(false),(false),(false),(false),(false),(false),(false),(false);
select COUNT(is_up_vote) from comment_votes WHERE is_up_vote = true;
returns 17
select COUNT(is_up_vote) from comment_votes WHERE is_up_vote = false;
returns 15
select (abs(2 * sum(is_up_vote) - count(*))) from comment_votes;
returns 2
Explain:
trueValues + falseValues = total
falseValues = total - trueValues
trueValues - falseValues = x
trueValues - (total - trueValues) = x
2 * trueValues - total = x
trueValues = sum(is_up_vote) [only trues stored as 1 are counted]
total = count(*)
Abs function is to convert the result to positive in case of negative
Isn't it correct?

Try this.
select
sum( if(is_up_vote= true, 1, 0) ) as uptrue,
sum( if(is_up_vote= false, 1, 0) ) as upfalse,
sum( if(is_up_vote= true, 1, -1) ) as updiff
from comment_votes
Edit: 1
Instead of checking if it is true or false, try this.
select
sum( if(is_up_vote, 1, 0) ) as uptrue,
sum( if(is_up_vote, 0, 1) ) as upfalse,
sum( if(is_up_vote, 1, -1) ) as updiff
from comment_votes

Related

How to query in EF core with OrderByDescending, Take, Select and FirstOrDefault

So I've got a table named Summaries, it looks like this
I need to get to sum the latest entries of TotalPieces based on CoveredDate and should be grouped by ServiceCode and queried by month
for example, ServiceCode 'A' has entries on 2020-01-01, 2020-01-02, 2020-01-03, 2020-01-31, 2020-02-01, 2020-02-28, 2020-02-29
and ServiceCode 'B' has entries on 2020-01-01, 2020-01-02, 2020-01-31, 2020-02-20, 2020-02-21,
i need to get the sum based on month, lastest entry on 'A' on January is on 2020-01-31, and 'B' has latest entry on 2020-01-31, I need to sum their 'TotalPieces', so I should get 25 + 25 = 50.
basically i need to do is
Get all the lastest entries based on CoveredDate and month/year
Sum the TotalPieces by ServiceCode
i got a working query, but this is just a workaround because i can't get it right on query.
int sum_totalpieces = 0;
foreach (var serviceCode in service_codes)
{
var totalpieces = _DbContext.ActiveSummaries.Where(acs =>
acs.CoveredDate.Date.Month == query_month
&& acs.CoveredDate.Date.Year == query_year
&& acs.service_codes == serviceCode
)
.OrderByDescending(obd => obd.CoveredDate)
.Take(1)
.Select(s => s.TotalPieces)
.ToList()
.FirstOrDefault();
sum_totalpieces += totalpieces;
}
the service_codes is just a List of string
If you guys could just get rid of the foreach block their and make it services_codes.Contains() on query, or another workaround to make the result faster that would be great. Thanks a lot.
This will do it, but I don't think it will translate to SQL and run at the server:
_DbContext.ActiveSummaries
.Where(b =>
b.CoveredDate >= new DateTime(2020,1,1) &&
b.CoveredDate < new DateTime(2020,2,1) &&
new [] { "A", "B" }.Contains(b.ServiceCode)
)
.GroupBy(g => g.ServiceCode)
.Sum(g => g.OrderByDescending(gb=> gb.CoveredDate).First().TotalPieces);
If you want to do it as a raw SQL for best performance it would look like:
SELECT SUM(totalpieces)
FROM
x
INNER JOIN
(
SELECT servicecode, MAX(covereddate) cd
FROM x
WHERE x.servicecode IN ('A','B') AND covereddate BETWEEN '2020-01-01' AND '2020-01-31'
)y ON x.servicecode=y.servicecode and x.covereddate = y.cd

Evaluation of nested IF in MySQL

I found this code in a WordPress plugin, but I cannot understand what does it mean or how it can be read.
Can somebody to help me underdstaint this code :?
IF(agr_sam_ads.ad_users = 0, TRUE, IF(agr_sam_ads.ad_users_reg = 1, IF(agr_sam_ads.x_ad_users = 1, NOT FIND_IN_SET("admin", agr_sam_ads.x_view_users), TRUE ....
I have not paste the whole query because it is huge. In what I am interested to be helped is the part of the Query that looks like that:
IF(expression, value, IF(expression, IF(expression, SQL Logical Query, value ...
I have not see this syntax, and I don't know where to search for that. In MySQL documentation the IF statement syntax is like that : http://dev.mysql.com/doc/refman/5.0/en/if.html and it is not looks like the one I have paste above.
Just for the users are interested in the full code, the code is here:
SELECT
agr_sam_places.id,
agr_sam_places.name,
agr_sam_places.description,
agr_sam_places.code_before,
agr_sam_places.code_after,
agr_sam_places.place_size,
agr_sam_places.place_custom_width,
agr_sam_places.place_custom_height,
agr_sam_places.patch_img,
agr_sam_places.patch_link,
agr_sam_places.patch_code,
agr_sam_places.patch_adserver,
agr_sam_places.patch_dfp,
agr_sam_places.patch_source,
agr_sam_places.trash,
(
SELECT
COUNT(*)
FROM
agr_sam_ads
WHERE
agr_sam_ads.pid = agr_sam_places.id
AND
agr_sam_ads.trash IS FALSE
) AS ad_count,
(
SELECT
COUNT(*)
FROM
agr_sam_ads
WHERE
agr_sam_ads.pid = agr_sam_places.id
AND
agr_sam_ads.trash IS FALSE
AND
(
IF(agr_sam_ads.ad_users = 0, TRUE, IF(agr_sam_ads.ad_users_reg = 1, IF(agr_sam_ads.x_ad_users = 1, NOT FIND_IN_SET("admin", agr_sam_ads.x_view_users), TRUE
)
AND
IF(agr_sam_ads.ad_users_adv = 1, (agr_sam_ads.adv_nick <> "admin"), TRUE), FALSE)))
AND
(
(
agr_sam_ads.view_type = 1
)
OR
(
agr_sam_ads.view_type = 0
AND
(
agr_sam_ads.view_pages+0 & 256
)
)
)
AND
(
agr_sam_ads.ad_cats = 0
)
AND
(
agr_sam_ads.ad_authors = 0
)
AND
IF(agr_sam_ads.ad_schedule, CURDATE() BETWEEN agr_sam_ads.ad_start_date AND agr_sam_ads.ad_end_date, TRUE)
AND
IF(agr_sam_ads.limit_hits, agr_sam_ads.hits_limit > agr_sam_ads.ad_hits, TRUE)
AND
IF(agr_sam_ads.limit_clicks, agr_sam_ads.clicks_limit > agr_sam_ads.ad_clicks, TRUE)
AND
(
agr_sam_ads.ad_weight > 0
)
) AS ad_logic_count,
(
SELECT
COUNT(*)
FROM
agr_sam_ads
WHERE
agr_sam_ads.pid = agr_sam_places.id
AND
agr_sam_ads.trash IS FALSE
AND
(
IF(agr_sam_ads.ad_users = 0, TRUE, IF(agr_sam_ads.ad_users_reg = 1, IF(agr_sam_ads.x_ad_users = 1, NOT FIND_IN_SET("admin", agr_sam_ads.x_view_users), TRUE)
AND
IF(agr_sam_ads.ad_users_adv = 1, (agr_sam_ads.adv_nick <> "admin"), TRUE), FALSE)))
AND
(
(
agr_sam_ads.view_type = 1
)
OR
(
agr_sam_ads.view_type = 0
AND
(
agr_sam_ads.view_pages+0 & 256
)
)
)
AND
(
agr_sam_ads.ad_cats = 0
)
AND
(
agr_sam_ads.ad_authors = 0
)
AND
IF(agr_sam_ads.ad_schedule, CURDATE() BETWEEN agr_sam_ads.ad_start_date AND agr_sam_ads.ad_end_date, TRUE)
AND
IF(agr_sam_ads.limit_hits, agr_sam_ads.hits_limit > agr_sam_ads.ad_hits, TRUE)
AND
IF(agr_sam_ads.limit_clicks, agr_sam_ads.clicks_limit > agr_sam_ads.ad_clicks, TRUE)
AND
IF(agr_sam_ads.ad_weight > 0, (agr_sam_ads.ad_weight_hits*10/(agr_sam_ads.ad_weight*1000)) < 1, FALSE)
) AS ad_full_count
FROM
agr_sam_places
WHERE
agr_sam_places.id = 10
AND
agr_sam_places.trash IS FALSE;
It should be read as:
IF (
expression,
valueIfExpressionIsTrue,
valueIfExpressionIsValue
)
So to take part of your code:
IF(
agr_sam_ads.ad_users = 0,
TRUE,
IF(
agr_sam_ads.ad_users_reg = 1,
2,
3
)
)
I see that you're familiar with PHP, so essentially if this was PHP, the code would be
if ($arg_sam_ads_ad_users == 0) {
return true;
} else {
if ($arg_sam_ads_ad_users_reg == 1) {
return 2;
} else {
return 3;
}
}
This is basically creating an IF-ELSE tree without using the IF _ THEN _ ELSE _ ENDIF syntax.
Take the following example:
IF(1=0, 1, IF(1=1, 1, 0))
This is the equivalent of the following with C syntax:
IF (1=0) {
1
}
ELSE IF (1=1) {
1
}
ELSE {
0
}
The more common SQL syntax is the following:
IF 1=0 THEN
1;
ELSEIF 1=1 THEN
1;
ELSE
0;
ENDIF;

EF 4.1 - code first - query no longer works

I moved from using an edmx file to code first. The following query is no longer working (it's basically a group by with limit 1) :
(from sc in dbContext.student_courses
where
sc.student_id == student.id
&& sc.course_id == course.id
&& sc.complete_flag == 1
group sc by new
{
sc.complete_flag,
sc.direct_to_test_flag,
sc.dept_purchase_flag,
sc.issue_ce_flag,
sc.unlimited_purchase_flag,
sc.survey_taken
}
into scgroup
select new StudentCourseDetails
{
completeVideo = scgroup.Max(x => x.complete_flag),
directToTest = scgroup.Max(x => x.direct_to_test_flag),
isDepartmentPurchase = scgroup.Max(x => x.dept_purchase_flag),
issueCE = scgroup.Max(x => x.issue_ce_flag),
isUnlimitedPurchase = scgroup.Max(x => x.unlimited_purchase_flag),
surveyTaken = scgroup.Max(x => x.survey_taken)
}).FirstOrDefault();
The resulting sql :
SELECT `Limit1`.`complete_flag`,
`Limit1`.`C1`,
`Limit1`.`C2`,
`Limit1`.`C3`,
`Limit1`.`C4`,
`Limit1`.`C5`,
`Limit1`.`C6`
FROM (SELECT `GroupBy1`.`A1` AS `C1`,
`GroupBy1`.`A2` AS `C2`,
`GroupBy1`.`A3` AS `C3`,
`GroupBy1`.`A4` AS `C4`,
`GroupBy1`.`A5` AS `C5`,
`GroupBy1`.`A6` AS `C6`,
`GroupBy1`.`K1` AS `complete_flag`
FROM (SELECT Max(`complete_flag`) AS `A1`,
Max(`direct_to_test_flag`) AS `A2`,
Max(`dept_purchase_flag`) AS `A3`,
Max(`issue_ce_flag`) AS `A4`,
Max(`unlimited_purchase_flag`) AS `A5`,
Max(`survey_taken`) AS `A6`
FROM `student_courses` AS `Extent1`
WHERE ((`Extent1`.`student_id` = 3885 /* #p__linq__0 */)
AND (`Extent1`.`course_id` = 606 /* #p__linq__1 */))
AND (1 = `Extent1`.`complete_flag`)
GROUP BY `Extent1`.`complete_flag`,
`Extent1`.`dept_purchase_flag`,
`Extent1`.`direct_to_test_flag`,
`Extent1`.`issue_ce_flag`,
`Extent1`.`survey_taken`,
`Extent1`.`unlimited_purchase_flag`) AS `GroupBy1`
LIMIT 1) AS `Limit1`
The problem is that when it adds the limit 1, it also adds an extra column :
GroupBy1.K1 AS complete_flag - which doesn't exist.
Any thoughts ?

What the heck is happening with this Linq-to-SQL query?

[Resolved - see update]
I have this L2S query (looks scary, but the bit that's going wrong is simple):
var historicDataPointValues = from dpv in this._dataContext.DataPointValues
where (dpv.categoryId == historicCategoryId)
&& (dpv.retailerId == historicRetailerId)
&& (dpv.questionId == question.PreviousQuestionId)
&& historicResponseIds.Contains(dpv.responseIndex)
&& ((dpv.dataPointIndex == firstDataPointIndex) || (dpv.dataPointIndex == secondDataPointIndex))
select new KeyValuePair<string, double>(MakeDataPointValueKey(dpv), dpv.value);
...which was producing no results, so I looked at the SQL emitted by L2S.
When I execute it, the value of firstDataPointIndex is 1 and secondDataPointIndex is null. Yet the query that L2S produces (as shown in the debugger) is:
SELECT [t0].[categoryId], [t0].[retailerId], [t0].[questionId], [t0].[responseIndex], [t0].[dataPointIndex], [t0].[value]
FROM [dbo].[DataPointValue] AS [t0]
WHERE ([t0].[categoryId] = 1)
AND ([t0].[retailerId] = 1)
AND (([t0].[questionId]) = 8)
AND ([t0].[responseIndex] IN (1, 3, 4, 5, 6, 7, 8, 10, 11, 12))
AND (([t0].[dataPointIndex] = 13) OR ((CONVERT(Int,[t0].[dataPointIndex])) = 14))
Note the values 13 & 14 where firstDataPointIndex and secondDataPointIndex should be (they should be 1 and NULL; if I put in the correct values, then I get the results I expect).
WTF?
UPDATE: Turns out that the L2S debugger was at fault. It wasn't showing the same SQL as what's actually sent to the server, which is:
exec sp_executesql N'SELECT [t0].[categoryId], [t0].[retailerId], [t0].[questionId], [t0].[responseIndex], [t0].[dataPointIndex], [t0].[value]
FROM [dbo].[DataPointValue] AS [t0]
WHERE ([t0].[categoryId] = #p0) AND ([t0].[retailerId] = #p1) AND (([t0].[questionId]) = #p2) AND ([t0].[responseIndex] IN (#p3, #p4, #p5, #p6, #p7, #p8, #p9, #p10, #p11, #p12)) AND (([t0].[dataPointIndex] = #p13) OR ((CONVERT(Int,[t0].[dataPointIndex])) = #p14))',N'#p0 int,#p1 int,#p2 int,#p3 int,#p4 int,#p5 int,#p6 int,#p7 int,#p8 int,#p9 int,#p10 int,#p11 int,#p12 int,#p13 int,#p14 int',#p0=1,#p1=1,#p2=8,#p3=1,#p4=3,#p5=4,#p6=5,#p7=6,#p8=7,#p9=8,#p10=9,#p11=10,#p12=11,#p13=1,#p14=NULL
Note that the query now contains #p13 and #p14 where previously it had scalar values.

Does mysql have function for returning a number with ordinal suffix?

Basically I'm looking for something like
SELECT ordinal(my_number) FROM my_table
which would return
1st
11th
1071st
...
etc
but preferrably without the use of a stored procedure
I don't know of a built-in function but it's pretty easy to write:
SELECT
CONCAT(my_number, CASE
WHEN my_number%100 BETWEEN 11 AND 13 THEN "th"
WHEN my_number%10 = 1 THEN "st"
WHEN my_number%10 = 2 THEN "nd"
WHEN my_number%10 = 3 THEN "rd"
ELSE "th"
END)
FROM my_table;
mysql doesn't have support for this. You'll have to handle the strings in whichever language you are getting the mysql data from.
Based on Ken's code, a custom MySQL function would be as follows:
DELIMITER $$
CREATE FUNCTION ordinal(number BIGINT)
RETURNS VARCHAR(64)
DETERMINISTIC
BEGIN
DECLARE ord VARCHAR(64);
SET ord = (SELECT CONCAT(number, CASE
WHEN number%100 BETWEEN 11 AND 13 THEN "th"
WHEN number%10 = 1 THEN "st"
WHEN number%10 = 2 THEN "nd"
WHEN number%10 = 3 THEN "rd"
ELSE "th"
END));
RETURN ord;
END$$
DELIMITER ;
Then it can be used as:
SELECT ordinal(1) -- 1st
SELECT ordinal(11) -- 11th
SELECT ordinal(21) -- 21st
SELECT ordinal(my_number) FROM my_table
It is possible in MySQL using the string functions but it gets messy real fast. You'd better just do the suffix in the language you're using. For example, in PHP you could do something like this:
function ordSuffix($num) {
if(empty($num) || !is_numeric($num) || $num == 0) return $num;
$lastNum = substr($num, -1);
$suffix = 'th';
if($lastNum == 1 && $num != 11) { $suffix = 'st'; }
elseif($lastNum == 2 && $num != 12) { $suffix = 'nd'; }
elseif($lastNum == 3 && $num != 13) { $suffix = 'rd'; }
return $num.$suffix;
}
echo ordSuffix(4); // 4th
echo ordSuffix(1); // 1st
echo ordSuffix(12); // 12th
echo ordSuffix(1052); // 1052nd
I found a way that works for me but its a bit of a hack
DATE_FORMAT(CONCAT('2010-01-', my_number), '%D')
That works because currently the number I'm looking at never gets above 25. But it doesn't generalize well so someone might be entertained by this:
CONCAT(
IF(my_number % 100 BETWEEN 11 AND 13,
FLOOR(my_number / 100),
FLOOR(my_number / 10)),
DATE_FORMAT(
CONCAT('2010-01-',
IF(my_number % 100 BETWEEN 11 AND 13
my_number % 100,
my_number % 10)),
'%D'))
But that's a lot of work just to get at the DATE_FORMAT functionality when Ken's code is simpler.