how to use group by on this table - sql-server-2008

here is a screen shot of my table
I am trying to remove all those rows whose sum of PostAmt comes to be 0 when grouped by the sales_contract_nbr and the name.
for example :
the sales_contract_nbr 51101008103 will be removed when grouped by name and sales_contract_nbr as -96.83 and 96.83 when summed up amounts to 0.
Quite simple right?
but what I want apart from this is that I want to remove the contracts in group. I mean if the contract 51101008195 is grouped it amounts to be 533.87 which won't be removed (highlighted)
But I want to remove it in groups
for example
two rows of contract number 51101008195 should be summed first (see the image below) I mean the amount -533.87 and 533.87 should be summed to get the total of 0. Only one record for the contract should be left.
Update
More Description :
what i want to do is first group the row number 1 and 2 (matching amounts one positive and the other negative) and then group the others. If there were 4 rows of the same contract number then the row 1 and row 2 should have been grouped then the row 3 and row 4 should be grouped if there absolute amounts are same if not the row number 3 and 4 doesn't get deleted.
I want to use group by to eliminate the rows whose total ends up to be 0 and which have the same name or the contract number.
I hope I have made the question clear. If not please ask.
how can it be done?
what i am doing till now is :
SELECT sales_contract_nbr
,name
,SUM(PostAmt) PostAmt
FROM tblMasData
GROUP BY sales_contract_nbr, name
thanks.

Here is what I come up with for now :
SELECT location
,sales_contract_nbr
,name
,SUM(absPostAmt * nbPostAmt) / ABS(SUM(nbPostAmt)) PostAmt
,ABS(SUM(nbPostAmt)) nbPostAmt
,SUM(absPostAmt * nbPostAmt) PostAmtTotal
FROM (
SELECT location
,sales_contract_nbr
,name
,PostAmt
,ABS(PostAmt) absPostAmt
,SUM(CASE WHEN PostAmt >= 0 THEN 1 ELSE -1 END) nbPostAmt
FROM tblMasData
GROUP BY location
,sales_contract_nbr
,name
,PostAmt
,ABS(PostAmt)
) t
GROUP BY location
,sales_contract_nbr
,name
,absPostAmt
HAVING SUM(absPostAmt * nbPostAmt) != 0
See SQLFiddle.
This doesn't totally answer your question, as if you have 100 + 100 - 200 for instance, it won't hide all three rows. But it can be pretty messy to find combinations which equal to 0 among a bunch of rows.
More, if some rows have the same amount, they will be grouped. That's why I added a column counting those rows being equal, and a column summing them up at the end.
This should at least allow you to deal with the data programmatically.
Let me know if this fills your needs, or if you need some improvement (which could involve some not so pretty SQL).

Related

mysql: how do I ignore all rows that contain (value column A), if one of these rows has a specific value in column B?

I am looking for a way to filter not only the duplicate rows, but also the "initial" row. The goal is to have a clean list of all positions. The list is used by sales / accounting to see open positions, thats why the initial "Invoice" position has to be removed as well if a "Cancellcation" exists for that invoice.
I've tried solutions with group by, subqueries and EXISTS, but can't get the expected result. Ideally, I get this to work as an additional filter inside the where clause.
Default
ID
Nr
Type
Amount
1
NR-100
Invoice
100
2
NR-101
Invoice
200
3
NR-102
Invoice
300
4
NR-100
Cancellation
100
5
NR-102
Cancellation
300
6
NR-103
Invoice
150
Expected results
ID
Nr
Type
Amount
2
NR-101
Invoice
200
6
NR-103
Invoice
150
EXISTence test would seem to be the way to go so I wonder what problem you had with it..
select *
from t
where type = 'invoice' and
not exists (select 1 from t t1 where t1.nr = t.nr and t1.type = 'cancellation')

MYSQL Alternative to UNION for same table reusing same columns selected as new name

I'm trying to generate a result set from a table with effectively a unique/primary key as billyear, billmonth and type along with cost and consumption. So there could be 3 bill year and bill month identical entries but the type could be one of three values: E, W or NG.
I need to create a result set that has just one row per billyear and billmonth entry.
(
select month as billmonth, year as billyear, cost_estimate as eleccost, consumption_estimate as eleccons from tblbillforecast where buildingid=19 and type='E'
)
UNION (
select month as billmonth, year as billyear, cost_estimate as gascost, consumption_estimate as gascons from tblbillforecast where buildingid=19 and type='NG'
)
UNION (
select month as billmonth, year as billyear, cost_estimate as watercost, consumption_estimate as watercons from tblbillforecast where buildingid=19 and type='W'
)
This generates a result set with only billmonth, billyear, eleccost and eleccons columns. I've tried all kinds of solutions but the above example is the simplest to show where it's going wrong.
Additionally it still has 3 rows per billmonth/billyear unique combination instead of merging to one.
UPDATE:
Sample data
SELECT month AS billmonth,
year AS billyear,
SUM(CASE type WHEN 'E' THEN cost_estimate END) AS eleccost,
SUM(CASE type WHEN 'NG' THEN cost_estimate END) AS gascost,
SUM(CASE type WHEN 'W' THEN cost_estimate END) AS watercost
FROM tblbillforecast
WHERE buildingid=19
GROUP BY billmonth, billyear;
Result:
Expected result, eg:
year | month | eleccost | gascost | watercost
2018 | 1 | 32800 | 4460 | 4750
This is behaving correctly. An SQL query result set has one name per column, and this name applies to all the rows. So if you try to rename the column in the second or subsequent queries of the UNION, those new names are ignored. The name of the column is determined only by the first query of the UNION.
Additionally it still has 3 rows per billmonth/billyear unique combination instead of merging to one.
That's also correct behavior, according to the query you tried. UNION does not merge multiple rows into one, it only appends sets of rows.
As Akina hinted in the comments above, you may use multiple columns:
SELECT month AS billmonth,
year AS billyear,
SUM(CASE type WHEN 'E' THEN cost_estimate END) AS eleccost,
SUM(CASE type WHEN 'NG' THEN cost_estimate END) AS gascost,
SUM(CASE type WHEN 'W' THEN cost_estimate END) AS watercost
FROM tblbillforecast
WHERE buildingid=19
GROUP BY billmonth, billyear;
This uses GROUP BY to "merge" rows together, so you get one row in the result per month/year.
A quick bit of guidance on various data shaping operations in SQL:
JOIN - makes resultsets wider (more columns) by bringing together tables/resultsets in a side-by-side fashion generating output rows that have all the columns of the two input column sets
SELECT - typically makes resultsets narrower by allowing you to specify which columns you're interested in and which you are not; by not mentioning an available column it disappears meaning you output fewer columns
UNION - makes resultsets taller (more rows) by bringing together resultsets and outputting one on top of the other. Because columns always have a fixed data type and one name, you must have the same number of and type of, and order of columns
WHERE - makes resultsets shorter (fewer rows) by allowing you to specify truth based filters that exclude rows
It's not hard and fast; you can use select to create more columns too, but just in a very rudimentary sense these concepts hold true - JOIN to widen, UNION for taller, SELECT for narrower and WHERE for shorter. All the work you do with SQL is a data shaping exercise; you're either paring a rectangular block of data down or extending it, and in either a vertical or horizontal direction (or a mix).
I'm not going to get into grouping because that mixes rows up, and isn't something you tried in the question.. The reason for me writing this out was purely because you'd attempted to use a UNION (height-increasing) operation when you actually wanted a widen which, regardless of how it is done (JOIN or as per Bill's answer a SELECT+GROUP, which is valid, but relies on the "mixes rows up" aspect of grouping), specifically isn't done with a UNION. Union only makes stuff taller.
To give an example of how it might be done in an alternative way to Bill's approach, this task of yours has one huge table that is "too tall" - it uses 3 rows where 1 would do, if only it were a bit wider. That is to say if only there were 3 columns for electric/gas/water then we wouldn't need 3 rows with 1 utility in each.
Of course, we have this "one utility per row" because it is very flexible. Database tables don't have varying numbers of columns but they DO have varying numbers of rows. If a new bill type came along tomorrow - internet - no table changes are needed to accommodate it; add a new type I, and away you go, adding another row. We now store 4 rows of 1 utility where 1 row with 4 columns would do, but crucially we didn't have to change the table structure. We could have infinite different kinds of bills, and not need infinite columns because we can already have infinite rows
So you want to reshape your data from 4-rows-by-1-column to 1-row-by-4-columns. It could be solved as :
narrow the table to just year,month,building,type,cost AND shorten it to just electricity
separately narrow the table to just year,month,building,type,cost AND shorten it to just gas
separately narrow the table to just year,month,building,type,cost AND shorten it to just water
join (widening) all these newly created result sets , then narrow to remove the repeated year,month,building,type columns
That would look like:
SELECT e.year, e.month, e.building, e.cost, g.cost, w.cost
FROM
(SELECT year,month,building,cost FROM t WHERE type = 'E') e
JOIN
(SELECT year,month,building,cost FROM t WHERE type = 'NG') g
ON
e.year = g.year AND e.month = g.month AND e.building = g.building
JOIN
(SELECT year,month,building,cost FROM t WHERE type = 'W') w
ON
e.year = w.year AND e.month = w.month AND e.building = w.building
WHERE
e.building = 19
You can see clearly the 3 narrowing-and-shortening operations that pick out "just the gas", "just the electric", and "just the water" - they're the (SELECT year,month,building,cost FROM t WHERE type = 'NG') and that's what reduces the height of the original table, making it three times shorter than it was in each case. If we had 999 rows X 5 cols in the big table it goes to 3 sets of 333 x 5 rows each
You can see that we then JOIN these together to widen the results - our e.g 3 sets of 333 x 5 rows each widens to 333 x 15 when JOINed..
Then went from 333x15 down to 333 X 7 when SELECTed to ditch the repeated columns
It's likely not perfect (I'd perhaps left join all 3 onto a 4th set of numbers that are just the common columns in case some utilities aren't present for a particular month), and perhaps some people will come along complaining that it's less performant because it hits the table 3 times.. All that is accessory to the point I'm making about SQL being an exercise in reshaping data - tables are the starting blocks of data and you cut them up narrower and shorter, then stick them together side by side, or on top of each other and that becomes your new data block that's maybe wider, higher, both.. In any case it's definitely a different shape to what you started with. And then you can cut and shape again, and again..
Go with Bill's conditional agg (though this way would be fine if there is one row per building/year/month) but take away a stronger notion about in what direction these common operations (SELECT/JOIN/WHERE/UNION) reshape your data
Footnote about Bill's conditional aggregation (I know I said I wouldn't talk about it but it might make more sense to now). If you have:
Type, Cost
E, 123
NG, 456
W, 789
And you do a
SELECT
CASE WHEN Type = 'E' THEN Cost END as CostE,
CASE WHEN Type = 'NG' THEN Cost END as CostG,
CASE WHEN Type = 'W' THEN Cost END as CostW
...
It spreads the data out over more columns - the data has "gone from vertical to diagonal"
CostE, CostNG, CostW
123, NULL, NULL
NULL, 456, NULL
NULL, NULL, 789
But it's still too tall. If you then run a GROUP BY, which mixes rows up and ask for e.g. just the MAX from each column, then all the NULLs will disappear (because there is a non null somewhere in the column, and NULL is lost if there is a non null, no matter what you're doing) and the rows collapse, mixing together, into one:
CostE, CostNG, CostW
123, 456, 789
The data has pivoted round from being vertical, to being horizontal - another data shaping. It was pulled wider, and squashed flatter

how can I make a field have specific numbers in the order

hello I have an as3 app. this app divides the users into groups, every group consisting of 3 users.
in my mysql database there is a field in "users_into" table that identifies the number of user in his group.
this field is called "num_in_group" and its value must be a number between 1 and 3 for every user.
For clarification
The first user who registered in the application will have number 1 and the second one will have number 2 and the third one will have number 3 ---and the forth one will have 1 (not 4) ----- and fifth one will have 2 and sixth one will have 3 and seventh one will have 1 again and so on ......
so my question is how can I make the field have numbers 1 , 2 , 3 , 1 , 2 , 3 in the order constantly
One option which you might find acceptable would be to use ROW_NUMBER() and generate this value at the time you query:
SELECT
id,
1 + (ROW_NUMBER() OVER (ORDER BY id) - 1) % 3 AS seq
FROM yourTable
ORDER BY id;
The above assumes that id is an auto increment column, with a unique idea for each user. The only potential problem with this approach is that the id might not always be sequential or increasing. Assuming you can tolerate the vast majority of users have an even spread of 1, 2, and 3 values, then the above might be acceptable to you.

How to select two MySQL rows and then compare a column and return an output

I've a table with a structure something like this,
Device | paid | time
abc 1 2 days ago
abc 0 1 day ago
abc 0 5 mins ago
Is it possible to write a query that checks the paid column on all the rows where Device = abc and then outputs the most recent two rows that different. Basically, something like an if statement saying if row 1 = 1 and row 2 = 0 output that but only if it's the most recent two columns that are different. For example, in this case, the first and second row. The table is being updated whenever a user changes from a free to paid account etc. It is also updated in different columns for different reasons hence the duplicate 0s for example.
I know this would probably be done better by having another table altogether and updating that every time the user switches account type, but is there any way to make this work?
Thanks
Example:
http://rextester.com/MABU7860 need further testing on edge cases but this seems to work.
SELECT A.*, B.*
FROM SQLfoo A
INNER JOIN SQLFoo B
on A.Device = B.Device
and A.mTime < B.mTime
WHERE A.Paid <> B.Paid
and A.device = 'abc'
ORDER BY B.mTime Desc, A.MTime Desc
LIMIT 1
By performing a self join we on the devices where the time from one table is less than the time from the next table (thus the two records will never matach and we only get the reuslts one way) and we order by those times descending, the highest times appear first in the result since we limit by a single device we don't need to concern ourselves with the devices. We then just need compare the paid from one source to the paid in the 2nd source and return the first result encountered thus limit 1.
Or using user variables
http://rextester.com/TWVEVX7830
in other engines one might accomplish this task by performing the join as in above, assigning a row number partitioned by the device and then simply return all those row_numbers with a value of 1; which would be the earliest date discrepency.
Use LIMIT to limit the number of record on mysql:
http://www.mysqltutorial.org/mysql-limit.aspx
In your case, use LIMIT 2
and then put the 2 record that you just select into an array, then compare the array if the value is different. If they are different then print

SQL select a column value that is not zero and if there are multiple values to select the last one

I have a table with two columns called NAME and NUMB.
NAME NUMB
John 0
Sam 0
Tom 1
Bob 0
Tom 0
Ryu 2
Ken 0
I would like to use some SQL to select that last number in that table that isn't a zero. So my results should show up as "2". If the 2 was a zero then my results would be "1". What would be the best way. Thanks!
Filter the 0 values, order by number descending and take the first row.
SELECT numb
FROM myTable
WHERE num != 0
ORDER BY numb DESC
LIMIT 1
Of course I supposed you wanted the biggest value in the columns since there's no "last" row in mysql.
There is no "last" dataset in SQL. Unordered sets are one of the core principles...
A workaround might be to add another column that auto increments with each inserted row. This way you could just query the Max(auto_increment_row) and get the dataset that was last inserted.
Of course you'll need an additional check if that value is 0 and take according action if it is. There are multiple approaches to that, so just come back to us if you have any concrete problems with the SQL query you tried.