How to re-write the following mysql query - sql

I have a file upload site, and I want to run a maintenance script that will run every day and delete items that haven't been accessed in a week. I log views for each day, and each item into a table:
hit_itemid
hit_date
hit_views
The main table actually has the files that were uploaded, for the purposes of this example, its just vid_id, vid_title thats in this table, and vid_id will equal to hit_itemid.
I have a query as follows:
SELECT vid_id,
vid_title,
SUM(case when hit_date >= '2009-09-17' then hit_hits else 0 end) as total_hits
FROM videos
LEFT JOIN daily_hits ON vid_id = hit_itemid
WHERE vid_posttime <= '$last_week_timestamp' AND vid_status != 3
GROUP BY hit_itemid
HAVING total_hits < 1
But this always returns a single record.
How can I rewrite this query?

An idea:
SELECT DISTINCT
vid_id, vid_title
FROM
videos v
LEFT JOIN daily_hits dh ON (
v.vid_id = dh.hit_itemid AND dh.hit_date >= '2009-09-17'
)
WHERE
v.vid_posttime <= '$last_week_timestamp' AND v.vid_status != 3
AND dh.hit_itemid IS NULL;
Alternatively (benchmark to see which is faster):
SELECT
vid_id, vid_title
FROM
videos v
WHERE
v.vid_posttime <= '$last_week_timestamp' AND v.vid_status != 3
AND NOT EXISTS (
SELECT 1 FROM daily_hits dh
WHERE v.vid_id = dh.hit_itemid AND dh.hit_date >= '2009-09-17'
)
I'm guessing the first form will be faster, but can't check (I don't
have access to your data). Haven't tested these queries either, for the
same reason.

first guess, may be you have to do a
GROUP BY vid_id
instead of
GROUP BY hit_itemid

SELECT
vd.vid_id,
vd.vid_title,
sum(case when dh.hit_date >= '2009-09-17' then dh.hit_views else 0 end) as total_hits
FROM videos vd
LEFT JOIN daily_hits dh ON dh.hit_itemid = vd.vid_id
WHERE vd.vid_posttime <= '$last_week_timestamp' AND vd.vid_status != 3
GROUP BY vd.vid_id
HAVING total_hits < 1
This is how I would have the query... Assuming vid_posttime & vid_status are fields of table videos

Do you definitely have data which satisfy this criteria? You're only considering rows for videos created before a certain timestamp and with a certain status -- perhaps this is limiting your result set to where only one video matches.

Related

sql group by satisfying multiple conditions within the group

I have a table like below:
I want to select the group which has RELB_CD =9093 and INFO_SRC_CD with 7784. Both conditions should be present in the group. In the table below my output should be the group with id=139993690.
You can use aggregation with having:
select id
from t
group by id
having sum(case when relb_cd = 9093 then 1 else 0 end) > 0 and
sum(case when info_src_cde = 7784 then 1 else 0 end) > 0
hey use this code hope this will help you.
you have to ignore the date column because that one is not allowing to group
select id,fisc_ind, sum(sls_amt),relb_cd,info_scop,info_src_cd from yourtable group by id,fisc_ind,relb_cd,info_scop,info_src_cd
Another working answer. If your data are large, you could compare both GL's and this working answer and see which runs faster for you. I honestly don't know which is faster. This was slightly faster with a very short set of data.
select id
from table1
where relb_cd = 9093
intersect
select id
from table1
where info_src_cd = 7784

Calculate number of rows with having clause

I have 3 tables what I'm trying to achieve is to calculate exact number of rows for two kinds of queries.
The first one must count number of accounts which has exactly only one row in accounts_extra for specific service_id.
The second one must count number of accounts which has exactly only one row in accounts_extra and also trial has not ended for specific id
http://sqlfiddle.com/#!15/313db/3
Basically I get in the first query 0 which is correct but in second query I get 1 which is not correct.
I assume that subscription is optional so that's why I get 1 in the second query what should I do to achieve 0 in the second query but still taken into consideration trial_ends_at
Your question is rather hard to follow, but I think this does what you are describing:
SELECT SUM( (cnt = 1)::int ) as count1,
SUM( (cnt = 1 AND cnt2 > 0)::int ) as count2
FROM (SELECT a.id, COUNT(DISTINCT ae.id) AS cnt,
COUNT(ans.id) as cnt2
FROM accounts a JOIN
accounts_extra ae
ON a.id = ae.account_id LEFT JOIN
account_number_subscriptions ans
ON ans.account_id = a.id AND
ans.trial_ends_at > now()
WHERE a.service_id = '101' AND
a.closed = false AND
ae.created_at < '2019-07-01'
GROUP BY a.id
) a;

SQL query to show users that have null values

I am quite new to SQL and I have a slight issue with my query that I have created, What I have done is created a query that will create a time summary of all of our staffs time recording on a particular day, currently the query works but what I need it to do is bring through a list of all of our users, not just filter by the users that did time recording on that day.
SELECT
SUM(CASE WHEN TimeTransactions.ChargeBasis = 'C' THEN TimeTransactions.QuantityOfTime/60/6 ELSE 0 END) AS ChargableUnits,
SUM(CASE WHEN TimeTransactions.ChargeBasis = 'N' THEN TimeTransactions.QuantityOfTime/60/6 ELSE 0 END) AS NonChargableUnits,
SUM(CASE WHEN TimeTransactions.ChargeBasis = 'C' THEN TimeTransactions.ValueOfTime ELSE 0 END) AS ChargableValue,
SUM(CASE WHEN TimeTransactions.ChargeBasis = 'N' THEN TimeTransactions.ValueOfTime ELSE 0 END) AS NonChargableValue,
SUM(TimeTransactions.QuantityOfTime)/60/6 AS TotalUnits,
SUM(TimeTransactions.ValueOfTime) AS TotalValue,
Users.Code AS FeeEarner
FROM
Users INNER JOIN TimeTransactions ON TimeTransactions.FeeEarnerRef = Users.Code
WHERE
Users.FeeEarner = 1 AND Users.UserStatus = 0 AND
TimeTransactions.TransactionDate >= dateadd(day,datediff(day,1,GETDATE()),0)
AND TimeTransactions.TransactionDate < dateadd(day,datediff(day,0,GETDATE()),0)
GROUP BY
Users.Code
I expect it to show users that did not do time recording on that day as showing 'NULL' in each row instead of removing them from the table.
Any help or guidance will be greatly appreciated :)
Something like this will do it.
Use a CTE to build your user query and place your user related filter conditions in there, then left join this on to your time table and apply your time filter conditions there. This avoids left joining the 2 tables raw but then applying a filter on to the right table which forces an inner join under the hood.
EDIT:
This is a working example of what you are trying to achieve. I have greatly SIMPLIFIED the table structure as the columns referenced in the query supplied don't match the columns provided in the image samples. Also it looks like the query is trying to use a column alias in a where clause (which isn't possible in SQL server).
In the below example I've applied filtering on the timetransaction table in the common table expression and then left joined the user table on to that. This produces the following output.
You can see a zero is returned for users whose time transactions do not match the filtering condition, where using an inner join those users would not be returned at all.
With more comprehensive data examples (that represent the column structure and the expected output or something similar) we could work out a solution which would be far closer to cut and paste ready, whereas this example is simply that, an example of how I would construct a left join where filtering needs to happen in the table on the right hand side of the join.
Good luck, if you have any questions let me know.
declare #users table (
userid int identity(1,1),
username nvarchar(50)
);
declare #timetransaction table (
timetransactionid int identity(1,1),
userid int,
quantityoftime int
);
insert #users
values
('SomeBody'),
('AnyBody'),
('SomeoneElse');
insert #timetransaction
values
(1, 7),
(1, 12),
(2, 5),
(3, 71),
(3, 4);
declare #userid int = 1;
with timetransaction as (select userid, quantityoftime from #timetransaction where userid=1)
select u.userid, coalesce(SUM(quantityoftime), 0) as total from #users u
left join timetransaction t on u.userid=t.userid
group by u.userid;
Example of users table
enter image description here
Example of time transactions table
enter image description here
You should use a LEFT JOIN. But it is very important to get the filtering conditions right:
SELECT SUM(CASE WHEN tt.ChargeBasis = 'C' THEN tt.QuantityOfTime/60/6 ELSE 0 END) AS ChargableUnits,
SUM(CASE WHEN tt.ChargeBasis = 'N' THEN tt.QuantityOfTime/60/6 ELSE 0 END) AS NonChargableUnits,
SUM(CASE WHEN tt.ChargeBasis = 'C' THEN tt.ValueOfTime ELSE 0 END) AS ChargableValue,
SUM(CASE WHEN tt.ChargeBasis = 'N' THEN tt.ValueOfTime ELSE 0 END) AS NonChargableValue,
SUM(tt.QuantityOfTime)/60/6 AS TotalUnits,
SUM(tt.ValueOfTime) AS TotalValue,
u.Code AS FeeEarner
FROM Users u LEFT JOIN
TimeTransactions tt
ON tt.FeeEarnerRef = u.Code AND
tt.TransactionDate >= dateadd(day, -1, CONVERT(date, GETDATE())) AND
tt.TransactionDate < CONVERT(date, GETDATE())
WHERE u.FeeEarner = 1 AND u.UserStatus = 0
GROUP BY u.Code;
Notes:
The conditions on TimeTransactions need to go in the ON clause rather than the WHERE.
SQL Server supports the DATE data type. There is no need to do arcane calculations using date differences to remove the time component from a value.
Table aliases make the query easier to write and to read.

sql count query with case statement

I have to execute a query from three tables avg_salary, person and emails. This simple sql query works fine.
SELECT avg_salary.id, COUNT(emails.message_from) AS email_PGA
FROM avg_salary, person, emails
WHERE person.works_in = avg_salary.id
AND person.email_address = emails.message_from
AND person.salary::numeric > avg_salary.avg
GROUP BY avg_salary.id
But I want to add another column email_PLA with the condition when
person.salary::numeric < avg_salary.avg. I can do that by joining the whole query again. But I want to use CASE in this situation. And even after trying so many times I can't get the syntax right.
I assume you need another count?
You would need something like:
SUM(CASE WHEN (person.salary::numeric < avg_salary.avg) THEN 1 ELSE 0 END) AS email_PGA
You can do conditional aggregation by using case expression and always use explicit JOIN syntax
SELECT avg_salary.id,
SUM(CASE WHEN p.salary::numeric > asal.avg THEN 1 ELSE 0 END) AS email_PGA,
SUM(CASE WHEN p.salary::numeric < asal.avg THEN 1 ELSE 0 END) AS email_PLA
FROM avg_salary asal,
INNER JOIN person p on p.works_in = asal.id
INNER JOIN emails e on e.message_from = p.email_address
--WHERE p.salary::numeric > asal.avg
GROUP BY avg_salary.id;
If you need different columns on specific conditions you have to do different SQL queries.

How do I use the value from row above when a given column value is zero?

I have a table of items by date (each row is a new date). I am drawing out a value from another column D. I need it to replace 0s though. I need the following logic: when D=0 for that date, use the value in column D from the date prior.
Actually, truth be told, I need it to say, when D is 0, use the value from the latest date where D was not a 0, but the first will get me most of the way there.
Is there a way to build this logic? Maybe a CTE?
Thank you very much.
PS I'm using SSMS 2008.
EDIT: I wasn't very clear at first. The value I want to change is not the date. I want change the value in D with the latest non-zero value from D, based on date.
May be the following query might help you. It uses the OUTER APPLY to fetch the results. Screenshot #1 shows the sample data and query output against the sample data. This query can be written better but this is what I could come up with right now.
Hope that helps.
SELECT ITM.Id
, COALESCE(DAT.New_D, ITM.D) AS D
, ITM.DateValue
FROM dbo.Items ITM
OUTER APPLY (
SELECT
TOP 1 D AS New_D
FROM dbo.Items DAT
WHERE DAT.DateValue < ITM.DateValue
AND DAT.D <> 0
AND ITM.D = 0
ORDER BY DAT.DateValue DESC
) DAT
Screenshot #1:
UPDATE t
Set value = SELECT value
FROM table
WHERE date = (SELECT MAX(t1.date)
FROM table t1
WHERE t1.value != 0
AND t1.date < t.date)
FROM table t
WHERE t.value = 0
You could maybe something like this as part of an update script...
SET myTable.D = (
SELECT TOP 1 myTable2.D
FROM myTable2
WHERE myTable2.myDateField < myTable.myDateField
AND myTable2.D != 0
ORDER BY myTable2.myDateField DESC)
That's assuming that you want to actually update the data though rather than just replace the values for the purpose of a select query.
How about:
SELECT
i.ID,
i.DateValue,
D = CASE WHEN I.D <> 0 THEN I.D ELSE X.D END
FROM
Items I
OUTER APPLY (
SELECT TOP 1 S.D
FROM Items S
WHERE S.DATEVALUE < I.DATEVALUE AND S.D <> 0
ORDER BY S.DATEVALUE DESC
) X
SELECT t.id,
CASE WHEN t.D = 0 THEN t0.D
ELSE t.D END
FROM table AS t
LEFT JOIN table AS t0
ON t0.time =
(
SELECT MAX(time) FROM t0
WHERE t0.time < t.time
AND t0.D != 0
)
or if you want to avoid aggregates entirely,
SELECT t.id,
CASE WHEN t.D = 0 THEN t0.D
ELSE t.D END
FROM table AS t
LEFT JOIN table AS t0
ON t0.time < t.time
LEFT JOIN table AS tx
ON tx.time > t0.time
WHERE t0.D != 0
AND tx.D != 0
AND tx.id IS NULL -- i.e. there isn't any