Stored Procedure Union All issue - tsql

I have a stored procedure where I am trying to combine two different SELECT statements using UNION ALL, but after the query is executed only the items from the first SELECT are returned.
I wanted to get all items from both the first and second select statements.
Here is what I have:
CREATE PROCEDURE [dbo].[Report_AllActivitiesOfficesAll]
#BeginDate datetime,
#EndDate datetime
AS
SET #BeginDate = .dbo.DateOnly(#BeginDate)
SET #EndDate = .dbo.DateOnly(#EndDate)
BEGIN
SET NOCOUNT ON;
SELECT
O.OfficeId,
O.OfficeName AS Name,
AT.Description AS Activity,
SUM(A.Duration) AS [Minutes],
CAST(SUM(A.Duration) AS FLOAT) / 60 AS [Hours],
COUNT(A.ActivityId) AS Activities,
COUNT(DISTINCT A.CaseId) AS Cases,
MIN(CAST(A.Duration AS FLOAT) / 60) AS [Min Time],
MAX(CAST(A.Duration AS FLOAT) / 60) AS [Max Time],
SUM(CAST(A.Duration AS FLOAT) / 60) / COUNT(A.ActivityId) AS [Avg Time],
SUM(CAST(A.Duration AS FLOAT) / 60) AS [TotalHours]
FROM Activity A
INNER JOIN ActivityType AT ON A.ActivityTypeId = AT.ActivityTypeId
INNER JOIN ActivityEntry AE ON A.ActivityEntryId = AE.ActivityEntryId
INNER JOIN [Case] C ON A.CaseId = C.CaseId
INNER JOIN [Office] O ON AE.OfficeId = O.OfficeId
INNER JOIN [User] U ON C.CreatedByUserId = U.UserId
WHERE .dbo.DateOnly(AE.ActivityDate) BETWEEN #BeginDate AND #EndDate
GROUP BY
O.OfficeId,
O.OfficeName,
AT.Description
UNION ALL
SELECT
O.OfficeId,
O.OfficeId AS NonCaseOfficeId,
O.OfficeName AS OfficeName,
NCAT.Description AS NonCaseActivityType,
SUM(NCA.Duration) AS [Minutes],
CAST(SUM(NCA.Duration) AS FLOAT) / 60 AS [Hours],
COUNT(NCA.NonCaseActivityId) AS Activities,
MIN(CAST(NCA.Duration AS FLOAT) / 60) AS [Min Time],
MAX(CAST(NCA.Duration AS FLOAT) / 60) AS [Max Time],
SUM(CAST(NCA.Duration AS FLOAT) / 60) / COUNT(NCA.NonCaseActivityId) AS [Avg Time],
SUM(CAST(NCA.Duration AS FLOAT) / 60) AS [TotalHours]
FROM NonCaseActivity NCA
INNER JOIN NonCaseActivityType NCAT ON NCA.NonCaseActivityTypeId = NCAT.NonCaseActivityTypeId
INNER JOIN [Office] O ON NCA.OfficeId = O.OfficeId
INNER JOIN [User] U ON NCA.UserId = U.UserId
WHERE .dbo.DateOnly(NCA.ActivityDate) BETWEEN #BeginDate AND #EndDate
GROUP BY
O.OfficeId,
O.OfficeName,
NCAT.Description
END

Columns must match in data type and content to correctly and effectively use a union. You cannot change column names in the middle. If you need to know which part of the union a record came from add another column to do so. Yours do not. Officename is in the 2nd column in the first one and the third column in the second query. What is in the second column of the second query is highly likely not to be the same datatype as the second column of the first one. This is not the only mismatch but an example.
If you need a column in the second query that you don't need in the first, you must still put it in the first query. And if you need a column in the first that you don't need in the second you must put a null value in that place in the query. For example (not a complete rewrite of what you have but enough to get an idea of what I am talking about):
SELECT
O.OfficeId,
CAST(NULL as int) as NonCaseOfficeId
O.OfficeName AS Name,
AT.Description AS Activity,
COUNT(DISTINCT A.CaseId) AS Cases,
cast('Case' as varchar (10)) as recordType
FROM Activity A
INNER JOIN ActivityType AT ON A.ActivityTypeId = AT.ActivityTypeId
INNER JOIN ActivityEntry AE ON A.ActivityEntryId = AE.ActivityEntryId
INNER JOIN [Case] C ON A.CaseId = C.CaseId
INNER JOIN [Office] O ON AE.OfficeId = O.OfficeId
INNER JOIN [User] U ON C.CreatedByUserId = U.UserId
WHERE .dbo.DateOnly(AE.ActivityDate) BETWEEN #BeginDate AND #EndDate
GROUP BY
O.OfficeId,
O.OfficeName,
AT.Description
UNION ALL
SELECT
O.OfficeId,
O.OfficeId,
O.OfficeName,
NCAT.Description,
cast(NULL as int),
'NonCase'
FROM NonCaseActivity NCA
INNER JOIN NonCaseActivityType NCAT
ON NCA.NonCaseActivityTypeId = NCAT.NonCaseActivityTypeId
INNER JOIN [Office] O ON NCA.OfficeId = O.OfficeId
INNER JOIN [User] U ON NCA.UserId = U.UserId
WHERE .dbo.DateOnly(NCA.ActivityDate) BETWEEN #BeginDate AND #EndDate
Now since the query also takes the datatype from the first query, you will see that I specifically cast it to the datatype I wanted. You may need to do that on the null in the second part of the union as well just be sure that the datatypes are matching. I believe null is assumed by SQL server to be an int if you don't specify, so this is most important when you want some other data type.

Seems fine to me. Keep in mind, the column names used will come from the first query.
So if I used a query like:
SELECT 1 AS Something
UNION ALL
SELECT 2 AS SomethingElse
I'd see just results (1 and 2) under a field called Something and wouldn't see anything for a field called SomethingElse.
Perhaps that's the issue?
Try adding an extra an extra field to both queries that identifies which resultset it's pulling from.
Maybe add 'Activity' AS Source for the first query and 'Non-Case Activity' AS Source for the second query, just to make sure you're getting a clear view of what's being shown?

Related

how to get last added record for a battery with left join PSQL

I have query such as
select * from batteries as b ORDER BY inserted_at desc
which gives me data such as
and I have an query such as
select voltage, datetime, battery_id from battery_readings ORDER BY inserted_at desc limit 1
which returns data as
I want to combine both 2 above queries, so in one go, I can have each battery details as well as its last added voltage and datetime from battery_readings.
Postgres has a very useful syntax for this, called DISTINCT ON. This is different from plain DISTINCT in that it keeps only the first row of each set, defined by the sort order. In your case, it would be something like this:
SELECT DISTINCT ON (b.id)
b.id,
b.name,
b.source_url,
b.active,
b.user_id,
b.inserted_at,
b.updated_at,
v.voltage,
v.datetime
FROM battery b
JOIN battery_voltage v ON (b.id = v.battery_id)
ORDER BY b.id, v.datetime desc;
I think that widowing will make what you expected.
Assuming two tables
create table battery (id int, name text);
create table bat_volt(measure_time int, battery_id int, val int);
One of the possible queries is like this:
with latest as (select battery_id, max(measure_time) over (partition by battery_id) from bat_volt)
select * from battery b join bat_volt bv on bv.battery_id=b.id where (b.id,bv.measure_time) in (select * from latest);
If you have Postgres version which supports lateral, it might also make sense to try it out (in case there are way more values than batteries, it could have better performance).
select * from battery b
join bat_volt bv on bv.battery_id=b.id
join lateral
(select battery_id, max(measure_time) over (partition by battery_id) from bat_volt bbv
where bbv.battery_id = b.id limit 1) lbb on (lbb.max = bv.measure_time AND lbb.battery_id = b.id);

Select Date and Count, Group By Date -- How to show Dates with NULL Counts?

SELECT
CAST(c.DT AS DATE) AS 'Date'
, COUNT(p.PatternID) AS 'Count'
FROM CalendarMain c
LEFT OUTER JOIN Pattern p
ON c.DT = p.PatternDate
INNER JOIN Result r
ON p.PatternID = r.PatternID
INNER JOIN Detail d
ON p.PatternID = d.PatternID
WHERE r.Type = 7
AND d.Panel = 501
AND CAST(c.DT AS DATE)
BETWEEN '20190101' AND '20190201'
GROUP BY CAST(c.DT AS DATE)
ORDER BY CAST(c.DT AS DATE)
The query above isn't working for me. It still skips days where the COUNT is NULL for it's c.DT.
c.DT and p.PatternDate are both time DateTime, although c.DT can't be NULL. It is actually the PK for the table. It is populated as DateTimes for every single day from 2015 to 2049, so the records for those days exist.
Another weird thing I noticed is that nothing returns at all when I join C.DT = p.PatternDate without a CAST or CONVERT to a Date style. Not sure why when they are both DateTimes.
There are a few things to talk about here. At this stage it's not clear what you're actually trying to count. If it's the number of "patterns" per day for the month of Jan 2019, then:
Your BETWEEN will also count any activity occurring on 1 Feb,
It looks like one pattern could have multiple results, potentially causing a miscount
It looks like one pattern could have multiple details, potentially causing a miscount
If one pattern has say 3 eligible results, and also 4 details, you'll get the cross product of them. Your count will be 12.
I'm going to assume:
you only want the distinct number of patterns, regardless of the number of details and results.
You only want January's activity
--Set up some dummy data
DROP TABLE IF EXISTS #CalendarMain
SELECT cast('20190101' as datetime) as DT
INTO #CalendarMain
UNION ALL SELECT '20190102' as DT
UNION ALL SELECT '20190103' as DT
UNION ALL SELECT '20190104' as DT
UNION ALL SELECT '20190105' as DT --etc etc
;
DROP TABLE IF EXISTS #Pattern
SELECT cast('1'as int) as PatternID
,cast('20190101 13:00' as datetime) as PatternDate
INTO #Pattern
UNION ALL SELECT 2,'20190101 14:00'
UNION ALL SELECT 3,'20190101 15:00'
UNION ALL SELECT 4,'20190104 11:00'
UNION ALL SELECT 5,'20190104 14:00'
;
DROP TABLE IF EXISTS #Result
SELECT cast(100 as int) as ResultID
,cast(1 as int) as PatternID
,cast(7 as int) as [Type]
INTO #Result
UNION ALL SELECT 101,1,7
UNION ALL SELECT 102,1,8
UNION ALL SELECT 103,1,9
UNION ALL SELECT 104,2,8
UNION ALL SELECT 105,2,7
UNION ALL SELECT 106,3,7
UNION ALL SELECT 107,3,8
UNION ALL SELECT 108,4,7
UNION ALL SELECT 109,5,7
UNION ALL SELECT 110,5,8
;
DROP TABLE IF EXISTS #Detail
SELECT cast(201 as int) as DetailID
,cast(1 as int) as PatternID
,cast(501 as int) as Panel
INTO #Detail
UNION ALL SELECT 202,1,502
UNION ALL SELECT 203,1,503
UNION ALL SELECT 204,1,502
UNION ALL SELECT 205,1,502
UNION ALL SELECT 206,1,502
UNION ALL SELECT 207,2,502
UNION ALL SELECT 208,2,503
UNION ALL SELECT 209,2,502
UNION ALL SELECT 210,4,502
UNION ALL SELECT 211,4,501
;
-- create some variables
DECLARE #start_date as date = '20190101';
DECLARE #end_date as date = '20190201'; --I assume this is an exclusive end date
SELECT cal.DT
,isnull(patterns.[count],0) as [Count]
FROM #CalendarMain cal
LEFT JOIN ( SELECT cast(p.PatternDate as date) as PatternDate
,COUNT(DISTINCT p.PatternID) as [Count]
FROM #Pattern p
JOIN #Result r ON p.PatternID = r.PatternID
JOIN #Detail d ON p.PatternID = d.PatternID
WHERE r.[Type] = 7
and d.Panel = 501
GROUP BY cast(p.PatternDate as date)
) patterns ON cal.DT = patterns.patternDate
WHERE cal.DT >= #start_date
and cal.DT < #end_date --Your code would have included 1 Feb, which I assume was unintentional.
ORDER BY cal.DT
;

MariaDB - order by with more selects

I have this SQL:
select * from `posts`
where `posts`.`deleted_at` is null
and `expire_at` >= '2017-03-26 21:23:42.000000'
and (
select count(distinct tags.id) from `tags`
inner join `post_tag` on `tags`.`id` = `post_tag`.`tag_id`
where `post_tag`.`post_id` = `posts`.`id`
and (`tags`.`tag` like 'PHP' or `tags`.`tag` like 'pop' or `tags`.`tag` like 'UI')
) >= 1
Is it possible order the results by number of tags in posts?
Maybe add there alias?
Any information can help me.
Convert your correlated subquery into a join:
select p.*
from posts p
join (
select pt.post_id,
count(distinct t.id) as tag_count
from tags t
inner join post_tag pt on t.id = pt.tag_id
where t.tag in ('PHP', 'pop', 'UI')
group by pt.post_id
) pt on p.id = pt.post_id
where p.deleted_at is null
and p.expire_at >= '2017-03-26 21:23:42.000000'
order by pt.tag_count desc;
Also, note that I changed the bunch of like and or to single IN because you are not matching any pattern i.e. there is no % in the string. So, better using single IN instead.
Also, if you have defined your table names, column names etc keeping keywords etc in mind, you shouldn't have the need to use the backticks. They make reading a query difficult.

Removing duplicate date periods

I am working on script to get data from a database with millions of rows and have a problem with gaps in periods. We have decided that gaps less than 10 days should not be considered gaps at all. Thus, these gaps should be deleted (See example below. The bold dates form the “real” periods of interest)
ID InDate OutDate
1 2008-10-10 2009-02-05
1 2009-02-08 2009-05-13
1 2011-01-01 2011-05-20
2 2007-03-17 2008-10-19
2 2009-05-30 2010-10-12
2 2010-10-14 2010-12-31
Thus, several problems arises. The first problem is to identify which Outdates and Indates are that close to each other for the period to be transformed into a single one. The next problem is to move the Outdate from the higher row number to the lower row number (that is up the table). The last problem is to identify and get rid of the rows which are now duplicates.
I have tried to solve the question down below. The first two problems are solved in table #t4a. The strategy in table #t4aa is to get rid of the duplicates by marking the duplicate rows in question in a new (dummy) variable and get rid of all such values (1:s) in a later stage. However, it does not work! All rows are marked with a 0, even those which should be marked with an 1. Any suggestions?
--This temp table measures gaps and creates a new variable OutDate2 which in the cases of a to small gap (less than 11 days) write the next Outdate on the row instead of the original value.
WITH C AS (SELECT Id, InDate, OutDate, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY InDate) Rownum FROM #t4 t4)
SELECT cur.Rownum, cur.Id, cur.InDate CurInDate, cur.OutDate, nxt.InDate NxtInDate, DATEDIFF(day, cur.OutDate, nxt.InDate) Number_of_days,
CASE WHEN DATEDIFF(day, cur.OutDate, nxt.InDate)<11 AND DATEDIFF(day, cur.OutDate, nxt.InDate)>0 THEN nxt.OutDate ELSE cur.OutDate END AS OutDate2
INTO #t4a
FROM C cur
LEFT OUTER JOIN C nxt ON (nxt.rownum=cur.rownum+1 AND nxt.Id=cur.Id)
--This temp table creates a dummy which identifies the OVERLAP of rows in order for these to be eliminated in a later temporary table. It is this table that does not work.
WITH C AS (SELECT Id, InDate, OutDate, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY InDate) rownum FROM #t4a)
SELECT cur.Id, cur.InDate, nxt.OutDate2,
CASE WHEN cur.OutDate2 < nxt.InDate THEN 1.0 ELSE 0.0
END AS Overlap
INTO #t4aa
FROM C cur
LEFT OUTER JOIN C nxt on (cur.rownum=nxt.rownum+1 AND cur.Id=nxt.Id)
This is kind of conceptual but might give you some ideas
WITH C AS
(SELECT Id, InDate, OutDate, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY InDate) Rownum FROM #t4 t4)
select Cgood.*
from c
join C as Cgood
on Cgood.ID = C1.ID
and Cgood.Rownum = C.Rownum + 1
and DATEDIFF(day, C.OutDate, nxt.InDate)>=11
group by Cgood.*
union
select Cgood.*
from c
join C as Cgood
on Cgood.ID = C1.ID
and Cgood.Rownum = 1
and C.Rownum = 2
and DATEDIFF(day, C.OutDate, nxt.InDate)>=11
group by Cgood.*
union
select cMerge.ID, c.Indate, cMerge.OutDate
from c
join C as cMerge
on cMerge.ID = C1.ID
and cMerge.Rownum = C.Rownum + 1
and DATEDIFF(day, C.OutDate, cMerge.InDate) < 11
group by cMerge.ID, c.Indate, cMerge.OutDate
union
select cMerge.ID, c.Indate, cMerge.OutDate
from c
join C as cMerge
on cMerge.ID = C1.ID
and cMerge.Rownum = 1
and C.Rownum = 2
and DATEDIFF(day, C.OutDate, cMerge.InDate) < 11
group b
I solved my own question yesterday. I got rid of the last temp table and incorporated creating the dummy variable in the first temp table. The core of the solution was to join backwards as well as forward.
WITH C AS (SELECT Id, InDate, OutDate, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY InDate) Rownum FROM #t4 t4)
SELECT cur.Rownum, cur.Id, cur.InDate CurInDate, cur.OutDate, nxt.InDate NxtInDate, DATEDIFF(day, cur.OutDate, nxt.InDate) Number_of_days,
CASE
WHEN DATEDIFF(day, prv.OutDate, cur.InDate)<11
AND DATEDIFF(day, prv.OutDate, cur.InDate)>0
THEN 1.0
ELSE 0.0
END AS Overlap,
CASE
WHEN DATEDIFF(day, cur.OutDate, nxt.InDate)<11
AND DATEDIFF(day, cur.OutDate, nxt.InDate)>0
THEN nxt.OutDate
ELSE cur.OutDate
END AS OutDate2
INTO #t4a
FROM C cur
LEFT OUTER JOIN C prv ON (prv.rownum=cur.rownum-1 AND prv.Id=cur.Id)
LEFT OUTER JOIN C nxt ON (nxt.rownum=cur.rownum+1 AND nxt.Id=cur.Id)

T-SQL Stored procedure with multiple selects

I'm trying to produce a Winforms report in my .NET application. Our users want to know for a specific welder what type of welds they have done between two dates. The users also need proof that this weld has been completed. The front end is straightforward but I'm struggling on how to get the data.
I have the following SQL query in a stored procedure:
INPUT parameters: #WelderNo INT, #StartDate DATETIME, #EndDate DATETIME
SELECT DISTINCT wi.wi_wpsnumbers
FROM wi_weld_instance wi
INNER JOIN wlds_weld_section ws ON wi.weldinstanceid = ws.weldinstanceid
INNER JOIN erd_employee_resourcedetails e ON e.employeeid = ws.employeeid
WHERE (wi.wi_completiondate Between #StartDate AND #EndDate)
AND e.erd_welderno in (#WelderNo)
Now I want to get some other information on each distinct wi_wpsnumbers that was returned. This is the proof that the weld has been completed. The query would look something like this:
Input parameters: #WelderNo INT, #StartDate DATETIME, #EndDate DATETIME, #wi_wpsnumber NVARCHAR(MAX)
SELECT TOP(1) e.fabemployeename,
erd_welderno,
(pm.PM_Number),
(mm.mm_assemblymark),
(wd.wd_number),
(wd.wd_length),
(wi.wi_completiondate)
FROM wi_weld_instance wi
INNER JOIN wlds_weld_section ws ON wi.weldinstanceid = ws.weldinstanceid
INNER JOIN erd_employee_resourcedetails e ON e.employeeid = ws.employeeid
INNER JOIN wd_weld_definition wd ON wd.welddefinitionid = wi.welddefinitionid
INNER JOIN pm_project_map pm ON pm.projectmapid = wd.projectid
INNER JOIN mm_mark_map mm ON wd.assemblyid = mm.markmapid
WHERE (wi.wi_completiondate Between #StartDate AND #EndDate)
AND e.erd_welderno in (#WelderNo) AND wi.wi_wpsnumbers = #wi_wpsnumber
How can I create a stored procedure that returns a table with the combination of both these queries? I can't combine the queries as the distinct will not work and return multiple wi_wpsnumbers the same. I was looking at temp tables but then I don't understand how to insert both the results of these queries into the same row. Thanks for your help
Link to database diagram: http://i1215.photobucket.com/albums/cc510/gazamatazzer/DatabaseDiagram.jpg
You can return two resultsets from an SP.
Just call two SELECTs and use NextResult in the client code to retrieve the second resultset after you have read the first one.
select x.*, y.* from
(select * from xtable) x, (select * from ytable) y
Where x.* gives you all from xtable and y.* gives all from ytable