Eliminating matching values in a SQL result set - tsql

I have a table with a list of transactions (invoices and credits) and I need to get a list of all the rows where the invoices and credits don't match up.
eg
user product value
bill ThingA 200
jim ThingA -200
sue ThingB 100
liz ThingC 50
I only want to see the third and fourth rows, as the values of the others match off.
I can do this if I select product, sum(value)
...
group by product
having sum(value) <> 0
which works well, but I want to return the user name as well.
As soon as I add the user to the select, I need to group by it as well, which messes it up as the amounts don't match up by user AND product.
Any ideas ? I am using MS SQL 2000...
Cheers

You can do like this:
SELECT tab2.user, product, sum_val
FROM
(SELECT product, SUM(value) sum_val
FROM your_table
GROUP BY product HAVING SUM(value) <> 0) tab1
INNER JOIN your_table tab2
ON tab1.product = tab2.product

#LolCoder solution is good, but given a context where you have "Thing B" with a "100" value by both "sue" and "liz", you could be able to retrieve the following resultset with my query :
| product | value | users |
+----------------------------+
| Thing B | 200 | sue, liz |
Here is the query :
select product
,sum(value) as value
,Stuff(( select ',' + convert(varchar(40), SQ.user)
from YourTable SQ
where Q.product = SQ.product
for xml path('')
), 1, 1, '') as users
from YourTable Q
group by Q.product

Related

How to find the latest date and price>0 per id?

In POSTGRESQL 13, I have a table of ids,dates, prices.
I simply want to have the latest date where the price is greater than 0 per id.
One row per id.
So the optimal output is :
id | the_date | price
1 2013-08-09 0.45
2 2013-08-11 0.34
I have an SQL fiddle at this link :
https://dbfiddle.uk/?rdbms=postgres_13&fiddle=a89bbbc922601be5465ad764fd035161
I have tried an INNER JOIN with the MAX date unsuccessfully.
SELECT DISTINCT ON (id)
id, the_date, price
FROM inventory
WHERE price>0
ORDER BY id ASC, the_date DESC
You can do something like this:
select i.id, i.the_date, i.price
from inventory as i, (
select id, max(the_date) as max_date
from inventory
where price > 0
group by id
) as c where c.id = i.id and i.the_date = c.max_date
Demo in dbfiddle.uk
This might work for you.
SELECT inventory.id, the_date, price
FROM inventory
join (select id,max(the_date) md from inventory where price>0 group by id ) d
on inventory.id=d.id and the_date=d.md
If you want a row for id's with not price you'd use left join.

Count With Conditional on PostgreSQL

I have a table with people and another with visits. I want to count all visits but if the person signed up with 'emp' or 'oth' on ref_signup then remove the first visit. Example:
This are my tables:
PEOPLE:
id | ref_signup
---------------------
20 | emp
30 | oth
23 | fri
VISITS
id | date
-------------------------
20 | 10-01-2019
20 | 10-05-2019
23 | 10-09-2019
23 | 10-10-2019
30 | 09-10-2019
30 | 10-07-2019
On this example the visit count should be 4 because persons with id's 20 and 30 have their ref_signup as emp or oth, so it should exclude their first visit, but count from the second and forward.
This is what I have as a query:
SELECT COUNT(*) as visit_count FROM visits
LEFT JOIN people ON people.id = visits.people_id
WHERE visits.group_id = 1
Would using a case on the count help on this case as I just want to remove one visit not all of the visits from the person.
Subtract from COUNT(*) the distinct number of person.ids with person.ref_signup IN ('emp', 'oth'):
SELECT
COUNT(*) -
COUNT(DISTINCT CASE WHEN p.ref_signup IN ('emp', 'oth') THEN p.id END) as visit_count
FROM visits v LEFT JOIN people p
ON p.id = v.id
See the demo.
Result:
| visit_count |
| ----------- |
| 4 |
Note: this code and demo fiddle use the column names of your sample data.
Premise, select the count of visits from each person, along with a synthetic column that contains a 1 if the referral was from emp or oth, a 0 otherwise. Select the sum of the count minus the sum of that column.
SELECT SUM(count) - SUM(ignore_first) FROM (SELECT COUNT(*) as count, CASE WHEN ref_signup in ('emp', 'oth') THEN 1 ELSE 0 END as ignore_first as visit_count FROM visits
LEFT JOIN people ON people.id = visits.people_id
WHERE visits.group_id = 1 GROUP BY id) a
where's "people_id" in your example ?
SELECT COUNT(*) as visit_count
FROM visits v
JOIN people p ON p.id = v.people_id
WHERE p.ref_signup IN ('emp','oth');
then remove the first visit.
You cannot select count and delete the first visit at same time.
DELETE FROM visits
WHERE id IN (
SELECT id
FROM visits v
JOIN people p ON p.id = v.people_id
WHERE p.ref_signup IN ('emp','oth')
ORDER BY v.id
LIMIT 1
);
edit: typos
First, I create the tables
create table people (id int primary key, ref_signup varchar(3));
insert into people (id, ref_signup) values (20, 'emp'), (30, 'oth'), (23, 'fri');
create table visits (people_id int not null, visit_date date not null);
insert into visits (people_id, visit_date) values (20, '10-01-2019'), (20, '10-05-2019'), (23, '10-09-2019'), (23, '10-10-2019'), (30, '09-10-2019'), (30, '10-07-2019');
You can use the row_number() window function to mark which visit is "visit number one":
select
*,
row_number() over (partition by people_id order by visit_date) as visit_num
from people
join visits
on people.id = visits.people_id
Once you have that, you can do another query on those results, and use the filter clause to count up the correct rows that match the condition where visit_num > 1 or ref_signup = 'fri':
-- wrap the first query in a WITH clause
with joined_visits as (
select
*,
row_number() over (partition by people_id order by visit_date) as visit_num
from people
join visits
on people.id = visits.people_id
)
select count(1) filter (where visit_num > 1 or ref_signup = 'fri')
from joined_visits;
-- First get the corrected counts for all users
WITH grouped_visits AS (
SELECT
COUNT(visits.*) -
CASE WHEN people.ref_signup IN ('emp', 'oth') THEN 1 ELSE 0 END
AS visit_count
FROM visits
INNER JOIN people ON (people.id = visits.id)
GROUP BY people.id, people.ref_signup
)
-- Then sum them
SELECT SUM(visit_count)
FROM grouped_visits;
This should give you the result you're looking for.
On a side note, I can't help but think clever use of a window function could do this in a single shot without the CTE.
EDIT: No, it can't since window functions run after needed WHERE and GROUP BY and HAVING clauses.

Combine similar rows using case statement

I have a query currently populating a report which has a few rows of "duplicate" information. Similar IDs are being passed through which should be combined but are unique enough that we do not want to Concat/Insert them within our model. In order for the report to be processed correctly, I need to sum their $ values (The only information I actually need to keep preserved is the name, the final Summed amount, and the ID.
Is there a simple way to achieve this by creating a case statement the solely will sum the Amount field? I tried using a SUM(CASE WHEN statement but I do not want a new column since my report is only using that field to populate $$ information. Here is a sample of my issue below:
ID Name Amount Person
+-------+--------------+------------+-----------------------+
21011 Place A -210.30 John Doe
210115 Place A-a 6500.70 John Doe
21060 Place B 255.00 Wayne C
2106015 Place Bb 212.30 Wayne C
2106015 Place Bb 1212.30 Wayne C
2106015 Place Bb 212.30 Wayne C
21080 Place J 57212.30 Billy J
My desired result for this would be:
ID Name Amount Person
+-------+--------------+------------+-----------------------+
21011 Place A 6290.40 John Doe
21060 Place B 1889.90 Wayne C
21080 Place J 57212.30 Billy J
Is there a simplified way to combine these rows in TSQL without modifying the db?
You can try this (provided your ID column is a number and not a character field):
;WITH cte_getsum AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY Person ORDER BY ID) AS RowNum,
ID,
NAME,
(SELECT SUM(Amount) FROM TableName WHERE TableName.Person = t1.Person) AS SumAmount,
Person
FROM
TableName t1
)
SELECT * FROM cte_getsum
WHERE rownum = 1
You can try with below script, I created a temp table just for sample Data.. but in your case you can directly refer to table you have.
SELECT * INTO #tmpInput
FROM (VALUES('21011','Place A', -210.30,'John Doe'),
('210115','Place A-a',6500.70,'John Doe'),
('21060', 'Place B' ,255.00,'Wayne C'),
('2106015', 'Place Bb' ,212.30,'Wayne C'),
('2106015' , 'Place Bb' ,1212.30,'Wayne C'),
('2106015' , 'Place Bb' ,212.30 ,'Wayne C')
,('21080' , 'Place J' ,57212.30,'Billy J')
)Input (ID,Name,Amount,Person)
SELECT SUBSTRING(t1.ID,0,6) ID
,t2.Name
,SUM(t1.Amount) AMOUNT
,t2.Person
FROM #tmpInput t1
INNER JOIN #tmpInput t2 ON t2.ID=SUBSTRING(t1.ID,0,6)
GROUP BY SUBSTRING(t1.ID,0,6),t2.Name,t2.Person

Select rows based on grouping in same table

Sorry about the lame Title... If I could summarize this in a few words I might have had better luck finding an existing solution here!
I have a table that simplified looks like this:
ID PRODUCT
___ _________
100 Savings
200 Mortgage
200 Visa
300 Mortgage
300 Savings
I need to select rows based on the product of each ID. For example, I can do this:
SELECT DISTINCT ID
FROM table1
WHERE Product NOT IN ('Savings', 'Chequing')
This would return:
ID
___
200
300
However, in the case of ID 300 they do have Savings so I actually do not want this returned. In plain English I want to
Select * from table1 where 'Savings' and 'Chequing' are not the product for any row with that ID.
Desired result in this case would be one row with ID 200 since they do not have Savings or Chequing.
How can I do this?
Select the rows that match the item you do not want to match then compare therr ids
e.g.
select distinct id from table1 where id not in (
SELECT ID
FROM table1
WHERE Product IN ('Savings', 'Chequing')
)
You can use NOT EXISTS:
SELECT DISTINCT t1.ID
FROM dbo.Table1 t1
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.Table1 t2
WHERE t2.Poduct IN ('Savings', 'Chequing')
AND t2.ID = t1.ID
)
Demo
Worth reading: Should I use NOT IN, OUTER APPLY, LEFT OUTER JOIN, EXCEPT, or NOT EXISTS?

How can I feed back a subquery as a boolean column in PostgreSQL?

We store our accounts information in a PostgreSQL database.
Accounts are in the "accounts" table, groups in the "grp" table, and they're tied together by the "account_grp" table, which maps account_id to grp_id.
I'm trying to craft a query which will give me a view which lets me search for whether members of one group are members of another group, i.e. I want an "is_in_foobar_group" column in the view, so I can SELECT * FROM my_view WHERE grp_id = 1234; and get back
username | is_in_foobar_group | grp_id
---------+--------------------+-------
bob | true | 1234
alice | false | 1234
The foobar bit is hardcoded, and will not need to change.
Any suggestions?
Simpler, faster, more convenient:
WITH x AS (SELECT 1234 AS foobar) -- optional, to enter value only once
SELECT a.username
,EXISTS (
SELECT 1 FROM account_grp g
WHERE g.account_id = a.account_id
AND g.grp_id = x.foobar
) AS is_in_foobar_group
,x.foobar AS grp_id
FROM accounts a, x
Maybe using the EXISTS operator would help:
http://www.postgresql.org/docs/9.2/static/functions-subquery.html#FUNCTIONS-SUBQUERY-EXISTS
I'm not sure you can use it in a SELECT statement, and I don't have a PostgreSQL instance to check it.
Worst case you'll have to do 2 queries, something like:
SELECT username, true, grp_id
FROM accounts a INNER JOIN account_grp g1 on a.account_id = g.account_id
WHERE EXIST (SELECT 1 FROM account_grp g2
WHERE g2.account_id = a.account_id and g2.grp_id = [foobar])
UNION
SELECT username, false, grp_id
FROM accounts a INNER JOIN account_grp g1 on a.account_id = g.account_id
WHERE NOT EXIST (SELECT 1 FROM account_grp g2
WHERE g2.account_id = a.account_id and g2.grp_id = [foobar])