SQL query to show users that have null values - sql

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.

Related

Show data from table even if there is no data

I have 3 tables:
Data in Compania table:
Data in Periodo table:
Data in VAC_PERIODOCIA table:
I want to show all the companies (COMPANIA) and the value in (vac_aplica column) searching by Periodo, whether or not they are registered.
I tried this:
SELECT
COMPANIA.CIA_CLAVE, COMPANIA.CIA_NOM,
CASE
WHEN VAC_PERIODOCIA.VAC_APLICA IS NULL
THEN 'N'
ELSE 'Y'
END VAC_APLICA
FROM
COMPANIA
LEFT JOIN
VAC_PERIODOCIA ON COMPANIA.CIA_CLAVE = VAC_PERIODOCIA.CIA_CLAVE
WHERE
VAC_PERIODOCIA.PERIODO = '2018 - 2019'
Result:
What I want is this:
First of all, the question is a mess: tables and columns from the question and examples you've provided us with are different. Please fix that.
I don't speak Spanish, so I can only assume the VAC_PERIODICA is Periodo. In that case you need to move what you have in where condition to the join clause. Like this
SELECT COMPANIA.CIA_CLAVE,COMPANIA.CIA_NOM,
CASE
WHEN Periodo.valor IS NULL THEN 'N'
ELSE 'Y'
END VAC_APLICA
FROM Compania
LEFT JOIN Periodo
ON COMPANIA.CIA_CLAVE = Periodo.valor
AND Periodo.PERIODO = '2018 - 2019'
order by 1
dbfiddle

Retrieving specific column data from SQL sub-query

My goal is to pull out the values shown in red in the picture.
I used this query to pull the data together. The rows are associated via ActionID and filtered with the 'AND' conditions shown.
SELECT [lclTime],[Descript],[Event],[PValue],[Phase],[ActionID]
FROM TableA
WHERE uniqueid = 5742
AND ActionID <> 0
AND LEN(Phase) = 0
AND [ActionID] in
(
SELECT [ActionID] FROM TableA
WHERE uniqueid = 5742
AND ActionID <> 0
AND LEN(Phase) = 0
GROUP BY [ActionID]
HAVING COUNT(*) > 2
)
I thought I could treat the output from above, as the input to another query, to allow me to choose the values I need to extract. But was unsuccesful.
I also thought that maybe PIVOT could be used to pull out the values in the columns... but I need values from multiple columns and couldn't get that syntax to even work.
Can this be done without temp tables? I don't think the 3rd party app, that will execute this code, plays nicely with temp tables.
Thanks in advance.
UPDATE: I should have clarified what I'm trying to achieve for a result set. For each set described by the subquery, I'm trying to extract a single record per unique ActionID. And the values with the ActionID would include the value of lclTime, Descript and Pvalue when Event=Successful Signoff, and the value of PValue when Event=State Command.
I suspect that you want conditional aggregation, to bring the values of events "State Command" and "Successful Signoff" for each actionid that satisfies the filter conditions and that have at least 3 occurences.
If so, you can use a window count for filtering, and then conditional aggregation to pivot:
select actionid,
max(case when event = 'State Command' then pvalue end) command_value,
max(case when event = 'State Command' then lcltime end) command_lcltime,
max(case when event = 'Successful Signoff' then pvalue end) signoff_value,
max(case when event = 'Successful Signoff' then lcltime end) signoff_lcltime,
from (
select a.*, count(*) over(partition by actionid) cnt
from tablea
where uniqueid = 5742 and actionid <> 0 and len(phase) = 0
) t
where cnt > 2
group by actionid

Deriving values based on result of the query and grouping the data

I am writing a sql where I am trying to pull out information of the status of the courses the user has enrolled. I am trying to return single record for each user. Two fields in the select list would derive the value in the following manner
CourseResultStatusId -
If the status of all the courses is passed then return the status as passed otherwise.
If any status is fail, the overall status is fail.
If any of the status is expired, then overall status is expired.
If any of the status is in-progress then overall status is in-progress
ExpiryDateTime - Training expiring (nearest date)
I need to apply the following logic on courses he has been assigned.
cr.ExpiryDateTime > GetDate() and cr.ExpiryDateTime <= dateadd(dd,30,getdate()) )
If you see below , the query I have written so far pulls the courses that each user has been enrolled but it is not a cumulative result. Do I need to group, if yes would need help.
DECLARE #Rep1 INT;
SET #Rep1 = 13119;
SELECT
cr.[CourseID]
,cr.[UserID]
,u.[Code]
,u.[DisplayName]
,t.[Name]
,cr.[CourseResultStatusID] AS [CourseResultStatusID]
,crs.[Description] AS [CourseResultStatusDescription]
,c.[PointsRequired]
,cr.[ExpiryDateTime]
FROM [training].[CourseResult] cr
INNER JOIN [training].[Course] c
ON cr.[CourseID] = c.[ID] and c.[IsOptional] = 0 -- and cr.ExpiryDateTime > GetDate() and cr.ExpiryDateTime <= dateadd(dd,30,getdate())
INNER JOIN [training].[CourseResultStatus] crs
ON cr.[CourseResultStatusID] = crs.[ID]
INNER JOIN org.RepresentativeTierHistory rth on rth.RepresentativeID = cr.[UserID] and GetDate() between rth.StartDate and rth.EndDate
INNER JOIN org.tier t on t.ID = rth.TierID
LEFT JOIN [org].[User] u
ON u.[ID] = cr.[UserID]
WHERE cr.[UserID] IN (
SELECT hd.DescendantId FROM org.HierarchyDescendant hd WHERE hd.RepresentativeId = #Rep1 UNION ALL SELECT #Rep1 -- for management exchange info
)
order by UserID
The result of the query is as follows. I have circled to show you records that belong to a particular user and the columns that I am interested in . I need help in getting single record for each user based on the of logic that I mentioned above.
If I followed you correctly, you can implement the priorization rules on the overall result of each user using conditional aggregation.
Starting from your existing query, the logic would be:
select
cr.[UserID],
case
when min(case when crs.[Description] = 'Complete' then 1 else 0 end) = 1
then 'Complete'
when max(case when crs.[Description] = 'Fail' then 1 else 0 end) = 1
then 'Fail'
when max(case when crs.[Description] = 'Expired' then 1 else 0 end) = 1
then 'Expired'
when max(case when crs.[Description] = 'In Progress' then 1 else 0 end) = 1
then 'In Progress'
end as ResultStatus
from ...
where ...
group by cr.[UserID]
As for the date filtering logic, you should be able to implement it directly in the where clause.
It is possible that other parts of your query can be optimized - you might want to ask a new question for this, providing proper sample data and desired results.

How to re-write the following mysql query

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.

Group by when joining the same table twice

I'm writing a query to summarize some data. I have a flag in the table that is basically boolean, so I need some sums and counts based on one value of it, and then the same thing for the other value, like so:
select
location
,count(*)
,sum(duration)
from my.table
where type = 'X'
and location = #location
and date(some_tstamp) = #date
group by location
And then the same for another value of the type column. If I join this table twice, how do I still group so I can only get aggregation for each table, i.e. count(a.*) instead of count(*)...
Would it be better to write two separate queries?
EDIT
Thanks everybody, but that's not what I meant. I need to get a summary where type = 'X' and a summary where type = 'Y' separately...let me post a better example. What I meant was a query like this:
select
a.location
,count(a.*)
,sum(a.duration)
,count(b.*)
,sum(b.duration)
from my.table a, my.table b
where a.type = 'X'
and a.location = #location
and date(a.some_tstamp) = #date
and b.location = #location
and date(b.some_tstamp) = #date
and b.type = 'Y'
group by a.location
What do I need to group by? Also, DB2 doesn't like count(a.*), it's a syntax error.
select
location
,Sum(case when type = 'X' then 1 else 0 end) as xCount
,Sum(case when type = 'Y' then 1 else 0 end) as YCount
,Sum(case when type = 'X' then duration else 0 end) as xCountDuration
,Sum(case when type = 'Y' then duration else 0 end) as YCountDuration
from my.table
where
location = #location
and date(some_tstamp) = #date
group by location
This should work in SQL Server. I guess db2 should have something similar.
Edit: Add a where condition to limit the records to select type = X or type = Y, if "type" can have value other than X and Y.
Your example with the join doesn't make a lot of sense. You're doing a Cartesian product between A and B. Is this really what you want?
The following will find count(*) and sum(duration) for each pair that satisfies the WHERE clause. Based on your description, this sounds like what you're looking for:
select
type
,location
,count(*)
,sum(duration)
from my.table
where type IN ('X', 'Y')
and location = #location
and date(some_tstamp) = #date
group by type, location
To make the counts work, instead of count(a.*) just do count(a.location), or any other not-null column (the PK would be ideal).
As to the main question, either of the answers given by shahkalpesh or George Eadon above would work. There is no reason in this example to join the table twice.