sql - dynamically populate columns in a result set based on another column - sql

We want to determine the amount of assets that customers have insured or uninsured.
Basically the insured amount is £85K, and we aportion that accross several account types held by the custimer in order of the accounts liquidity, so for example, a current account is more liquid than a 95 day notice account therefore the current account would receive the money first...
We have the below code that generates the priorities based on the account type and of course we could now loop through the table and determine the insured amount for each account type for each customer, and anything non-insured would become the uninsured amount for that customer.
But is there a way to do this in the main select statement?
declare #Priorities TABLE
(
[refID] [int] IDENTITY(1,1) NOT NULL,
acno nvarchar(10),
Suffix nvarchar(6) null,
Unit nvarchar(5) null,
CT nvarchar(2) null,
LOB nvarchar(2) null,
Participants int null,
VAL float null,
xPriority int null,
insuredAmt float null,
uninsuredAmt float null
)
declare #Count int=0
declare #I int=0
insert into #Priorities
select acno, suffix, unit, CT, LOB, Participants, Val, xPriority =
case
when LOB BETWEEN 'GA' AND 'GN' then 1 -- Current accounts
when LOB IN ( 'HY' , 'H1') then 2 -- Call
when CHARINDEX(LOB, 'HA; HC; HM; HQ; IA; IB; IC; IE;IG; IU;')>0 then 3 -- Instant Access
when LOB='HO' then 4 -- 14 Day notice
when CHARINDEX(LOB, 'DN; HG; HK; HL; HP; HS; HU; IV; IW;')>0 then 5 -- 35-Day notice
when LOB IN ('DN', 'HV') then 6 -- 95-Day notice
when (LOB BETWEEN 'IS' AND 'IT') or (LOB between 'JA' and 'JZ' ) then 7 -- Residual Deals
end,
insuredAmt=null,
uninsuredAmt=null
from actdata
where datadate = '20131212' /*#processdate*/
and CHARINDEX(CT, 'AA;AC;AE;AG;BA;BC;BE;BG;CA;DE;DG;ZA;')=0
and LOB BETWEEN 'GA' AND 'KZ' And val>0
and participants is not null
order by acno
what I'd like to do is calculate the insured amount for each record based on the value of the column xPriority.
Also, we multiply the total insured amount by the number of participants in the account, so if there are 5 participants in the account, then the total insured amount for them all would be 5*85000 = £425000.
the alternative would be a loop or a cursor.
this would be run on a daily basis when the data is brought into the reporting database from the iSeries system by another process.
thanks
Philip

I believe the answer is 'No', but that's based on a particular interpretation of:
"Also, we multiply the total insured amount by the number of participants in the account, so if there are 5 participants in the account, then the total insured amount for them all would be 5*85000 = £425000."
Without multiple participants in an account, I think it would be possible to order the SELECT statement by the customer ID and the CASE statement for xPriority, keep a decreasing running total (down form 85K and resetting for each customer) and calculate insuredAmt and uninsuredAmt accordingly. This would tell you how each individual's insured amount was apportioned across their accounts.
However, with multiple participants you have to resolve the following problem:
Mary has a current account of £40k and a 30 day notice account with £20k.
Bob has a current account of £50k and a 30 day notice account with £20k.
Mary and Bob also have a joint, 14 day notice account of £60k.
The two current accounts are fully insured, as is the 14 day notice account (Mary has £45k and Bob £35k allowance left). The problem is that the coverage of the two 30 day accounts depends on which individual's insurance is apportioned to the joint account (all of Mary's plus a bit of Bob's; all of Bob's plus a bit of Mary's; £30k of each; 9:7 ratio from Mary:Bob?). I don't think this problem is even resolvable in logical terms without reference to the full terms and conditions of the accounts! When it comes to programming it, I'd advise extreme caution. Start by writing inefficient code you can test thoroughly on such cases, then use that as a benchmark for checking code with enhanced efficiency.
Good luck!

Related

Snowflake SQL: Selecting the top 1% of IDs by Spend Amount partitioned by industry

I have a data table like the following (thousands of records) with spend associated to an ID that is bucketed in an industry TYPE.
Essentially, I want to return the only the top 1% of ID's by Spend (3rd column) and partitioned by Type
In the desired output below (2nd table), as you can see, I have queried the original data table and only returned the top 1% of IDs within each Type and showed the spend amount accordingly.
Summary: Goal is to slim down the original data table of thousands of records, to the top 1% for each each type (e.g. media, null, professional services etc.) by Spend amount.
Original Data Table:
ID Type Spend
4412740 Media 145545
3300640 Real Estate 141449
6781206 Professional Services 125378
4458933 Consumer Discretionary 122604
4684890 NULL 113363
6781222 Professional Services 123
4458931 Consumer Discretionary 211
4684222 NULL 4242 [...]
Desired output:
ID Type Spend
4412740 Media 10000000
3300640 Media 9999998
6781206 Media 9999997
4458933 Media 9999996
4684890 Professional Services 99999
213124 Professional Services 99998
4343411 Professional Services 99997
468422 NULL 88888
4343334 NULL 88887
4684221 NULL 88886 [...]
I attempted to do this with some SQL CTE's and functions but have trouble with getting the 1% based on Spend and partitioned by Type
Use qualify. Here is a simple method using ntile():
select t.*
from t
qualify ntile(100) over (partition by type order by spend desc) = 1;

How to write a single query using group by to attain the following output

I have my own app which calculates the total amount spent at different modes of payment. The database to store the data is as follows
the table create query:
CREATE TABLE details
(ID int,
Fname VARCHAR(255),
Lname VARCHAR(255),
AmountSpentViaPaytm DECIMAL(10,2),
AmountSpentViaGpay DECIMAL(10,2),
AmountSpentViaNetB DECIMAL(10,2),
AmountSpentViaMobileB DECIMAL(10,2),
PaymentType VARCHAR(255),
MobileType VARCHAR(255),
PRIMARY KEY(ID));
the sample Insert query:
INSERT INTO TABLE details VALUES(1, A, A, 350.00, null, null, null, PAYTM, android);
INSERT INTO TABLE details VALUES(1, B, B, null, 3000.00, null, null, GPAY, android);
I want the output something like this:
TotalAmountSpentViaPaytmInAndroid 350.00
CountOfRowsInPaytmInAndroid 1
TotalAmountSpentViaPaytmInIphone 450.00
CountOfRowsInPaytmInIphone 1
TotalAmountSpentViaGpayInAndroid 4500.00
CountOfRowsInGpayInAndroid 2
TotalAmountSpentViaGpayInIphone 0.00
CountOfRowsInGpayInIphone 0
TotalAmountSpentViaNetBInAndroid 4000.00
CountOfRowsInNetBInAndroid 1
TotalAmountSpentViaNetBInIphone 7400.00
CountOfRowsInNetBInIphone 2
TotalAmountSpentViaMobileBInAndroid 4600.00
CountOfRowsInMobileBInAndroid 1
TotalAmountSpentViaMobileBInIphone 0.00
CountOfRowsInPMobileBInIphone 0
basically, I want the output as:
1. total number of people who payed using paymenttype paytm and mobile type android.
2. total number of people who payed using paymenttype paytm and mobile type iphone.
3. total number of people who payed using paymenttype gpay and mobile type android.
4. total number of people who payed using paymenttype gpay and mobile type iphone.
goes on ...
5. total amount paid via the paymenttype paytm and mobile type android.
6. total amount paid via the paymenttype paytm and mobile type iphone.
7. total amount paid via the paymenttype gpay and mobile type android.
8. total amount paid via the paymenttype gpay and mobile type iphone.
goes on...
I need to use a single query to get all these data since multiple queries become
a load to the backend because of the size of the database.
I have tried using the group by statement but I am not able to find a solution
to the above mentioned problem with a single query.
The command I tried is:
SELECT COUNT(*), PaymentType, MobileType
from details
group by PaymentType, MobileType;
Output is something like this:
count(*),paymentType,MobileType
1,PAYTM, android
1,PAYTM, iphone
2, GPAY, android
goes on...
The table structure seems a bit strange. Since you already have the payment type and from what I can see only one payment type per line can exist I would expect there to be one column for spend if that were the case it could be simply grouped by the PaymentType and MobileType.
SELECT PaymentType, MobileType, COUNT(*) CountOfRows, Sum(spend) Spent
from details
group by PaymentType, MobileType;
But since you have multiple columns we can just add them together before doing the sum.
SELECT PaymentType, MobileType, COUNT(*) CountOfRows,
Sum(coalesce(AmountSpentViaPaytm, 0) +
coalesce(AmountSpentViaGpay, 0) +
coalesce(AmountSpentViaNetB, 0) +
coalesce(AmountSpentViaMobileB, 0)) Spent
FROM details
GROUP BY PaymentType, MobileType;
This won't give you the text output that you initially asked for but it will present the information in a way that is easy to read/interpret. You could then use a presentation layer to present it (excel, SSRS, etc.)

Given account contributions: How to sum contributions per individual? In relation to a threshold?

Given contribution amounts per account, how do I 1)SUM the contributions made by each individual, 2)Find the number of people who have contributed <, =, or > $5,000?
Right now I have a database table "[dbo].[FakeRRSPs]" which looks like:
Account_ID
Personal_ID
Contributions
My current code gives the # of unique individuals successfully:
select distinct(personal_id), sum(contributions), count(account_id),
(select count(distinct(personal_id))
from [dbo].[FakeRRSPs]
)
from [dbo].[FakeRRSPs]
where personal_id is not null
group by personal_id
For example, there are 2M people holding 2.5M accounts.
Issues I face:
How do I count the number of individuals who contribute below, at, or
above the $5K threshold (after SUM(contribution) per person)
There are people who contribute $10K total for example, $5K in 2
accounts. Both accounts are picked up when I'm hoping to only capture
the SUM(Contribution) for this person.
I hope this is clear enough - it certainly isn't to me! Thanks everyone.
SQL Fiddle
MS SQL Server 2017 Schema Setup:
create table Contribution (PID int,AID int,C int)
insert into Contribution(PID,AID,C)VALUES(235,1245,1200)
insert into Contribution(PID,AID,C)VALUES(256,1246,0)
insert into Contribution(PID,AID,C)VALUES(256,1247,3500)
insert into Contribution(PID,AID,C)VALUES(256,1248,10000)
insert into Contribution(PID,AID,C)VALUES(421,1249,0)
Query 1:
select * from (select PID,sum(C) AS SC from Contribution
group by PID) as test
where test.SC<=5000
Results:
| PID | SC |
|-----|------|
| 235 | 1200 |
| 421 | 0 |

Trying double entry system for accounting, but getting transaction list is a problem

It's been asked a few times, but more about design. I'm trying to make a little home finance application to learn some new technologies. I've gone with a single double row double entry system design.
So, a Journal is the root transaction, which has 2 or more Transactions. An Account can be either a Third Party (I paid my mate some cash, I got paid by my company, I paid a restaurant for dinner) or a bank account (I paid money from my credit card)
So, I can Transfer between two accounts.
(I transferred $200 from my Current Account, to my Credit Card account)
I can recieve money.
(I got paid by My Company, into my Current Account.
I can pay someone.
(I paid the restaurant $20 for dinner)
So, lets use the last example. I use my Current Account to pay KFC.
I have two accounts to deal with. KFC, an my current account.
I create a Journal for this transaction.
And then for that Journal, I create two transactions.
First one, is an amount of -20, and the Account is my bank account.
Second one is an amount of +20, and the account is KFC.
I can quickly get the balance of my bank account.
SELECT SUM(Amount) FROM Journal WHERE AccountID = MyBankAccountId
Perfect.
But I have an issue.
How do I show a transaction list for my bank account?
So I want to get a 'Statement' for AccountId 1
But from this, I can't really tell to whom I paid, or was paid. It only shows the lines related to Account 1.
The data above shows the transactions against the account I care about, but .. I need to show the OTHER account details too. So I need to somehow get 'SourceAccountId' and 'DestinationAccountId'.
Is my design wrong, and I need to go to one line transactions, with a Source and Destination account id in the same row?
The issue I have with that is - I want to be able to assign budgets to my transactions. For example, I was going to add a BudgetId to the Transaction table, allowing me apportion the amounts to different budgets or categories.
(I went to the hardware store, and spend $25. Of that $25, $10 was for my "garden" budget, and $15 was for "internal home decoration" budget.
So my transaction line might have 3 rows.
1 for the credit to the hardware store account for $25.
1 for a debit to my bank account for $10, with a budget Id of my 'Garden Budget'.
1 for a debit to my bank account for $15 with a budget Id of my 'Internal home decoration' budget.
This may be an issue too, as I will then get two 'lines' on my statement for my bank account... One for $15 and one for $10. Maybe I can go one line transaction, but then ... an extra table for the budget apportioning?
That would make getting a balance for an account more tricky. I’d need to check SoutceAccountId and DestinationAccountId.
Any ideas would be great.
I've worked and built some A/R systems before, it's... interesting to say the least. Typically the way it's done is to record the amount as a positive number (regardless of payment transaction... $100 moved somewhere at the transaction level, the direction is irrelevant), so as it is other than recording +/- in it, I think your transaction table is fine. Might want to add a memo field though so you can record what it was for (ie, let's say you pay the electric bill, you can record that this transaction was for "June '19 bill").
The journal table is where the magic happens.
There's two models I've seen, both work:
ID - PKey - Integer - IDENTITY
TransactionID - FKey - Integer - Indicates the parent Transaction
Amount - Decimal - The amount of the journal entry
AccountID - FKey - integer - Indicates the Account the journal entry is for
NetEffect - Bit - Flag which indicates if the item is a Credit (True) or Debit (False)
OR...
ID - PKey - Integer - IDENTITY
TransactionID - FKey - Integer - Indicates the parent Transaction
DebitAmount - Decimal - Amount of Debit
CreditAmount - Decimal - Amount of Credit
DebitAccountID - FKey - Integer - Indicates the Debit account for the item
CreditAccountID - FKey - Integer - Indicates the Credit account for the item
Both would then use the Sum(Debits) - Sum(Debits) to get the balance.
The first one should be explanitory. The second one is closer to a double-entry form. You would use either the Credit OR the Debit fields for an entry, but not both. But it also makes your budget calculations easier.
The Transaction would record $25. The Transactions then record like this:
1 -- 1 -- 00 -- 25 -- null -- {account number for garden store}
2 -- 1 -- 10 -- 00 -- {Account for Garden Budget} -- null
3 -- 1 -- 15 -- 00 -- {Internal home Budget} -- null
To allocate something to the budget:
4 -- 2 -- 25 -- 00 -- null -- {Account for Garden Budget}
5 -- 2 -- 25 -- 00 -- null -- {Internal hom Budget}
6 -- 2 -- 00 -- 50 -- {Your checking account} -- null
That moves $50 into your two budgets, 25 for one, 25 for the other, and deducts if from your account.
Then you get paid:
7 -- 3 -- 1000 -- 00 -- null -- {Checking account number}
8 -- 3 -- 00 -- 1000 -- {Account for employer} -- null
Hopefully that all made sense.

db design for ad delivery

i am building an ad delivery system similar to the one on facebook.. Basically my clients can create adverts and target them to particular members, based on location and bio data that we will constantly collect from them.
at the moment it is designed as so..
member
-----
id
fname
lname
country_id
state_id
region_id
postcode_id
etc..
I know i only need the postcodeid and the look it up in the postcode table.. but i have around 50 postcode tables one for each country.. just easier for me..
member_profile
-----
id
mem_id
bio_q (id linked to varchar .. eg. 'profession')
bio_a
advert
-------
id
client_id
ad_title
ad_line
start_date
end_date
status
etc..
advert_target
-----
id
advert_id
fk_id
type
eg data for above..
1, 1, 1, 'state'
2, 1, 2, 'state'
3, 1, 5, 'profession'
one way i though of doing it is by doing a whole heap of union statements... not sure if this the most effecient way.. any help with direction would be greatly appreciated..
thanks
a
Typically, this kind of application introduces the concept of "segment", assigns users to segments, and targets ads at segments.
So, you might assign a given user to 5 segments "lives in Europe", "lives in Italy", "lives in Rome", "lives within 5 kilometers of postcode x", "is at least 18 years old",
You then might have ads targeting segments "lives in Italy", "is at least 18 years old", "has visited the site at least 3 times"; our sample user score 2 out of 3 criteria, so might only be shown ads if noboy more qualified turns up.
You want to pre-populate your segments in regular database jobs; doing this on the fly will cripple your system with even moderate levels of traffic.