Compress rows with nulls and duplicates into single rows - sql

I'm not sure exactly how to describe what I want to do, so I'll use a contrived example
On SQL Server 2005, Say I have a view with rows like this, call it vwGrades:
ID AssnDate AssnTxt Sally Ted Bob
----------- ----------------------- ------------- ----------- ----------- -----------
2999 2007-09-22 00:00:00 Homework #1 20 NULL NULL
2999 2007-09-22 00:00:00 Homework #1 NULL 0 NULL
2999 2007-09-22 00:00:00 Homework #1 NULL NULL 24
2999 2007-09-22 00:00:00 Final Exam 57 NULL NULL
2999 2007-09-22 00:00:00 Final Exam NULL 0 NULL
2999 2007-09-22 00:00:00 Final Exam NULL NULL 35
How can I query it, such that I get this, ridding myself of all the annoying nulls and duplicate rows?
ID AssnDate AssnTxt Sally Ted Bob
----------- ----------------------- ------------- ----------- ----------- -----------
2999 2007-09-22 00:00:00 Homework #1 20 0 24
2999 2007-09-22 00:00:00 Final Exam 57 0 35

Select
ID,
AssnDate,
AssnTxt,
Max(IsNull(Sally,0)) AS Sally,
Max(IsNull(Ted, 0)) As Ted,
Max(IsNull(Bob, 0)) As Bob
From vwGrades
Group By
ID,
AssnDate,
AssnTxt

Looks like a denormalized schema, you should have a FirstName column instead of Sally Ted and Bob. That would make the query much simpler. Can you refactor?

Related

SQL - Some result when no row

I have a table (DATA), from which I would like to perform some simple math queries
Security Date Price
-------- ---- ------
1 2017-08-31 130
2 2017-08-31 150
1 2017-07-31 115
2 2017-07-31 140
1 2017-06-30 100
2 2017-06-30 130
1 2017-05-31 90
1 2017-04-30 85
1 2017-03-31 80
SELECT x.Security, x.Price/y.Price-1 'MONTHLY RETURN', x.Price/z.Price-1 'QUARTERLY RETURN'
FROM DATA AS x
JOIN DATA AS y
ON x.Security = y.Security
JOIN DATA AS z
ON x.Security = z.Security
WHERE x.Security IN (1,2)
AND x.Date = '2017-08-31'
AND y.Date = '2017-07-31'
AND z.Date = '2017-05-31'
I would like to have full table, even when there is no result (150/NO ROW)-1
Security MONTHLY RETURN QUARTERLY RETURN
-------- -------------- ----------------
1 (130/115)-1 (130/90)-1
2 (150/140)-1 NULL or any other data
Instead SQL is returning results just for Security 1 as there is no data (NO ROW) for Security 2 for '2017-05-31'. However it is not a good solution as I have data for '2017-07-31' so I would like to see them for Security 2.
Result of my query:
Security MONTHLY RETURN QUARTERLY RETURN
-------- -------------- ----------------
1 (130/150)-1 (130/90)-1
Is there any way to somehow prepare a table in which I will have all the data and let's say NULL when there is no result. Please note that normally I will have 20 or 30 securities.
I will be grateful for your assist on this one.
This is my second month with SQL and it is a way better than at the beginning but I still have some issues with logic. Do you know any good literature to start with?

Oracle - Converting a column to rows and getting a sum

Im trying to convert a column to rows and get a sum of the items ordered. Below is how the data is currently in the database. Notice how the DollarDays store gives the indication that not all stores have ordered all items.
Store ItemNumber Description Ordered
---- ---------- ---------- ---------
WallyMart 10021 Corn 10
J-Mart 10021 Corn 4
Big-H Foods 10021 Corn 32
WallyMart 20055 Beans 11
J-Mart 20055 Beans 3
Big-H Foods 20055 Beans 21
DollarDays 50277 Onions 48
This is my goal below. It looks to be pretty much grouping by ItemNumber
ItemNumber Description WallyMart J-Mart Big-H Foods DollarDays TotalOrdered
---------- ----------- --------- ------ ----------- ---------- ------------
10021 Corn 10 4 32 0 46
20055 Beans 11 3 21 0 35
50277 Onions 0 0 0 48 48
When I try PIVOT this is shortened example of what I get. Im totally lost.
ItemNumber Description WallyMart J-Mart Big-H Foods DollarDays
---------- ----------- --------- ------ ----------- ----------
10021 Corn 10 Null Null Null
10021 Corn Null 4 Null Null
10021 Corn Null Null 32 Null
10021 Corn Null Null Null 0
FYI I am of course a beginner and a student so please forgive me I haven't posted exactly right.
Any help is appreciated.
Try this:
select
itemnumber, description,
coalesce("WallyMart",0) as "WallyMart",
coalesce("J-Mart",0) as "J-Mart",
coalesce("Big-H Foods",0) as "Big-H Foods",
coalesce("DollarDays",0) as "DollarDays",
coalesce("WallyMart",0) + coalesce("J-Mart",0) + coalesce("Big-H Foods",0) + coalesce("DollarDays",0) as "Total"
from
(select * from stores) s
pivot
(max(ordered)
for store in
('WallyMart' as "WallyMart",
'J-Mart' as "J-Mart",
'Big-H Foods' as "Big-H Foods",
'DollarDays' as "DollarDays")) p
To explain a bit, the PIVOT clause consists of 2 parts - the aggregation and the list of values used for this aggregation. For your case, the aggregation used is MAX, since it looks like one store can have only one record for a particular product. If that is not so, SUM would be the right function to use. Likewise, since we want store-wise details, and not product-wise, we specify the distinct values of the store column in the list.
COALESCE is used to default null values in the ordered column to 0. Finally, we add the 4 derived columns (after coalescing to 0) to get the total value.
SQLFiddle

Complete datediff calculation based on MAX and MIN values

Sorry I did post a question similar earlier, but I was not that clear. I have a table with the fields, Customer, ID_Date, Pstng_Date, SUMOfAmount, Days_BetweenMax and days_between Min.
What I want is a query that shows me the date difference between the pstng_date and the ID_Date where the pstng_date is the max value for that customer and another column that shows the same calculation where the pstng_date is the minimum value for that customer. Those customers with only one Pstng_date should display as zero
So the Query should display the results like this:
Customer ID_Date Pstng_Date SumOfAmount Days_BetweenMAX days_betweenMIN
-------- ---------- ---------- ----------- ------------
Holmes 31/01/2014 10/01/2014 $21,545.59 0 0
James 31/01/2014 10/01/2014 -$21,197.89 0 21
James 31/01/2014 5/01/2014 -$7,823.14 0 0
James 31/01/2014 24/01/2014 $308.00 7 0
Rod 31/01/2014 17/01/2014 -$2,603.95 0 0
Lisa 31/01/2014 17/01/2014 $22,019.49 0 0
Assuming that your existing table is called [Postings], you could create a query to calculate the MIN() and MAX() values of [Pstng_Date]
SELECT
Customer,
MIN(Pstng_Date) AS MinOfPstng_Date,
MAX(Pstng_Date) AS MaxOfPstng_Date
FROM Postings
GROUP BY Customer
returning
Customer MinOfPstng_Date MaxOfPstng_Date
-------- --------------- ---------------
Holmes 2014-01-10 2014-01-10
James 2014-01-05 2014-01-24
Lisa 2014-01-17 2014-01-17
Rod 2014-01-17 2014-01-17
Then you could use that as a subquery in the query to calculate the date differences
SELECT
p.Customer,
p.ID_Date,
p.Pstng_Date,
p.SumOfAmount,
IIf(q.MaxOfPstng_Date=q.MinOfPstng_Date,0,IIf(p.Pstng_Date=q.MaxOfPstng_Date,DateDiff("d",p.Pstng_Date,p.ID_Date),0)) AS Days_BetweenMAX,
IIf(q.MaxOfPstng_Date=q.MinOfPstng_Date,0,IIf(p.Pstng_Date=q.MinOfPstng_Date,DateDiff("d",p.Pstng_Date,p.ID_Date),0)) AS Days_BetweenMIN
FROM
Postings AS p
INNER JOIN
(
SELECT
Customer,
MIN(Pstng_Date) AS MinOfPstng_Date,
MAX(Pstng_Date) AS MaxOfPstng_Date
FROM Postings
GROUP BY Customer
) AS q
ON p.Customer = q.Customer
returning
Customer ID_Date Pstng_Date SumOfAmount Days_BetweenMAX Days_BetweenMIN
-------- ---------- ---------- ----------- --------------- ---------------
Holmes 2014-01-31 2014-01-10 21545.59 0 0
James 2014-01-31 2014-01-10 -21197.89 0 0
James 2014-01-31 2014-01-05 -7823.14 0 26
James 2014-01-31 2014-01-24 308.00 7 0
Rod 2014-01-31 2014-01-17 -2603.95 0 0
Lisa 2014-01-31 2014-01-17 22019.49 0 0

How to list data when a count = 0 for last 3 years?

I am trying to produce a list of branches that haven't made any sales in the last 3 years. I have been able to produce a list of sales that are older than 3 years but not with the added condition of 0 sales in the 3 years prior.
My task is as follows: List all of the branches that have not rented out any tools for more than 3 years.
I think that I have to do a nested subquery but I cannot work out what should go where. Here are the two relevant tables with their descriptions and data values. The only value that should be returned is that for branch 70.
SQL> desc toolorder
Name Null? Type
--------------------------------------------------------- -------- ---
ORDERID NOT NULL VARCHAR2(6)
CUST NOT NULL VARCHAR2(6)
SNAME NOT NULL VARCHAR2(20)
BRANCHID NOT NULL VARCHAR2(6)
TYPE NOT NULL VARCHAR2(15)
TOOLID NOT NULL VARCHAR2(6)
DATEOUT NOT NULL DATE
DUEDATE NOT NULL DATE
SQL> desc branch
Name Null? Type
--------------------------------------------------------------
BRANCHID NOT NULL VARCHAR2(6)
BNAME NOT NULL VARCHAR2(15)
ADDRESS NOT NULL VARCHAR2(25)
TELEPHONE VARCHAR2(11)
MANAGERID VARCHAR2(6)
SQL> select * from toolorder;
ORDERI CUSTOM SNAME BRANCH TYPE TOOLID DATEOUT DUEDATE
------ ------ -------------------- ------ --------------- ------ --------- ---------
000001 000100 smith 10 Adhesive 00042 20-OCT-13 27-NOV-12
000002 000101 jones 10 Guage 00050 13-OCT-12 30-OCT-12
000003 000103 may 10 Generic 00023 21-NOV-12 28-NOV-12
000004 000100 smith 10 Generic 00023 19-NOV-13 28-NOV-13
000005 000104 circus 10 Generic 00023 05-JAN-09 28-JAN-09
000006 000106 hanks 10 Wood 00062 11-APR-10 01-MAY-10
000007 000102 bond 20 Cutting 00073 13-DEC-11 27-DEC-11
000008 000102 bond 20 Guage 00053 13-DEC-11 27-DEC-11
000009 000104 circus 30 Generic 00025 13-DEC-06 28-DEC-06
000010 000104 circus 30 Brickwork 00035 13-DEC-06 28-DEC-06
000011 000105 harris 30 Cutting 00075 13-OCT-13 25-OCT-13
000012 000105 harris 40 Brickwork 00036 13-DEC-11 27-DEC-11
000013 000105 harris 40 Generic 00027 13-DEC-11 27-DEC-11
000014 000105 harris 40 Electric 00006 13-DEC-11 27-DEC-11
000015 000106 hanks 40 Adhesive 00046 13-MAY-11 27-MAY-11
000016 000107 head 50 Adhesive 00047 13-MAR-13 27-MAR-13
000017 000107 head 50 Wood 00018 13-MAR-13 27-MAR-13
000018 000101 jones 50 Guage 00055 06-JAN-13 20-JAN-13
000019 000103 may 60 Brickwork 00039 06-APR-13 20-APR-13
000020 000101 jones 60 Cutting 00080 24-DEC-12 07-JAN-13
000021 000101 circus 70 Cutting 00081 13-AUG-08 27-AUG-08
21 rows selected.
SQL> select * from branch;
BRANCH BNAME ADDRESS TELEPHONE MANAGE
------ --------------- ------------------------- ----------- ------
10 Oxford 18 Oxford Estate 08456325312
20 Greenwood 21 Greenwood Lane 02380282185
30 Weston 36 Weston Road 02380282635
40 Highstreet 12-15 Stafford Highstreet 02380865963
50 Meadow 16 The Meadow Yard 07974296353
60 Port Down 168 Port Down Av 08953164826
70 Red Rd 12-15 Red Road 07948247384
7 rows selected.
Now, running the following query returns the orders that are 3 years old. I need to adjust it (I think) using a nested subqueries, so that it checks there are no sales in the 3 years, but cannot work out how.
SQL> select count(toolorder.orderid) as rentalcount, branch.branchid, branch.bname,
branch.address from toolorder left outer join branch on toolorder.branchid =
branch.branchid where MONTHS_BETWEEN(sysdate, dateout) > 36 group by branch.branchid,
branch.bname, branch.address order by 1 desc;
RENTALCOUNT BRANCH BNAME ADDRESS
----------- ------ --------------- -------------------------
2 30 Weston 36 Weston Road
2 10 Oxford 18 Oxford Estate
1 70 Red Rd 12-15 Red Road
The easiest way to do this is to get the maximum dateout for each branchid and check that it is more than 36 months in the bast:
select b.*
from branch b join
(select branchid, max(dateout) as maxd
from toolorder
group by branchid
) tob
on b.branchid = tob.branchid
where MONTHS_BETWEEN(sysdate, tob.maxd) > 36;
You can use NOT EXISTS to check if something does not match in a correlated sub-query:
SELECT *
FROM branch b
WHERE NOT EXISTS ( SELECT 1
FROM toolorder r
WHERE r.branchid = b.branchid
AND MONTHS_BETWEEN(sysdate, dateout) <= 36 );
From your comment on #Gordon Linoff's answer, it looks like you want to delete matching rows; in which case you can do:
DELETE FROM branch b
WHERE NOT EXISTS ( SELECT 1
FROM toolorder r
WHERE r.branchid = b.branchid
AND MONTHS_BETWEEN(sysdate, dateout) <= 36 );

Running Totals Results Incorrect

I have a cursor that collects information from a different table and then updates the summary,here is the code snippet that is giving me a headache.
I am trying to update the summary table by input the correct Males and Females information that will add up to the total numbers of patients.
BEGIN
Set #MaleId =154
set #Femaleid =655
if #Category =#MaleId
begin
set #Males = #Value
end
if #Category = #Femaleid
begin
set #CummFemalesOnHaart =#Value
end
Update Stats
set Males =#Malest,Females =#Females
where
Category =#Category and
periodType =#PeriodType AND StartDate =#Startdate and EndDate =#enddate
Problem:
The results are inaccurate
organisation PeriodType StartDate EndDate Deaths Patients Males Females
------------ ---------- ---------- ---------- ------ -------- ----- -------
34 Monthly 2010-04-01 2010-04-30 NULL 6843 896 463
34 Monthly 2010-04-01 2010-04-30 NULL 10041 896 463
34 Monthly 2010-05-01 2010-05-31 NULL 10255 898 463
34 Monthly 2010-05-01 2010-05-31 NULL 7086 898 461
34 Monthly 2010-06-01 2010-06-30 NULL 10344 922 461
36 Monthly 2010-03-01 2010-03-31 NULL 4317 1054 470
36 Monthly 2010-03-01 2010-03-31 NULL 5756 896 470
36 Monthly 2010-04-01 2010-04-30 NULL 4308 896 463
36 Monthly 2010-04-01 2010-04-30 NULL 5783 896 463
The Males and Female should only update single rows that add up to the total number of patiens
e,g
Patients Males Females
-------- ----- -------
41 21 20
Any ideas?
Just to clarify a bit more
/Query Table/
organisation PeriodType StartDate EndDate Males Females
------------ ---------- ---------- ---------- ----- -------
34 Monthly 01-02-2012 01-02-2012 220 205
34 Monthly 01-02-2012 01-02-2012 30 105
/*Update statetement */
Update Stats
set Males =#Malest,Females =#Females
where
--Category =#Category and
periodType =#PeriodType AND StartDate =#Startdate and EndDate =#enddate
Stats Table /Before Input/
organisation PeriodType StartDate EndDate Deaths Patients Males Females
------------ ---------- ---------- ---------- ------ -------- ----- -------
34 Monthly 01-02-2012 01-02-2012 0 425 null null
34 Monthly 01-02-2012 01-02-2012 25 null null null
34 Monthly 01-02-2012 01-02-2012 5 null null null
34 Monthly 01-02-2012 01-02-2012 5 135 null null
/if you look at closely at the rows the period type,startdate,enddate are the same so I don't want the update values to affect any other row besides the ones with the Patients Totals/
Stats Table /* Output */
organisation PeriodType StartDate EndDate Deaths Patients Males Females
------------ ---------- ---------- ---------- ------ -------- ----- -------
34 Monthly 01-02-2012 01-02-2012 0 425 220 205
34 Monthly 01-02-2012 01-02-2012 25 null null null
34 Monthly 01-02-2012 01-02-2012 5 null null null
34 Monthly 01-02-2012 01-02-2012 5 135 30 105
I hope this gives more information
What you are proposing, from what I can see, will always run the risk of having incorrect data assigned. What happens when you have two records, one with patients 500 (males 250 and females 250) and another with patients 500 (but males 300 and females 200)?
There is no clear distinction which record totals to then use, and you'll end up with possible multiple updates with incorrect data.
That being said, I think what you should do is the following.
create a CTE which selects the patients from stats table, and males / females from query table, where patients = (males + females)
Run the update statement on the stats table, using values from the CTE joined on patients total.
This should, as far as I can understand, give a solution to what you're after.
You can read more about CTE's here: MSDN Common Table Expressions
I'm not entirely sure I understand your question... but think this is what you are after?
UPDATE Stats
SET Males = (SELECT COUNT(*) From <queryTable> WHERE Category = #MaleId and periodType =#PeriodType AND StartDate =#Startdate and EndDate =#enddate)
,SET Females = (SELECT COUNT(*) From <queryTable> WHERE Category = #FemaleId and periodType =#PeriodType AND StartDate =#Startdate and EndDate =#enddate)
Hope it helps!
I am not sure I understand the question either. But it sounds like you are saying there can be multiple records having the same periodType, startDate and endDate, but a different number of Patients. You wish to ensure the query only updates records where the total number of Patients matches the total you calculated ie total = #Males + #Females.
If that is correct, add a filter on the Patients value:
UPDATE Stats
SET Males = #Males,
Females = #Females
WHERE Category = #Category
AND periodType = #PeriodType
AND StartDate = #Startdate
AND EndDate = #enddate
AND Patients = (#Males + #Females)
Though as an aside, cursors generally tend to be less efficient than set based alternatives ..