hope to pick on the wisdom of the folks here. (current approach at the bottom)
Please see the SQL Fiddle Link below.
http://sqlfiddle.com/#!17/4a818/1
create table paymentmethod (
methodtype varchar(6) not null,
methodname varchar(6) not null
);
insert into paymentmethod values
('cc', 'mstr'),
('cc', 'visa'),
('cc', 'amex')
;
create table chargepolicy (
accountname varchar(6) not null,
methodtype varchar(6) not null,
methodname varchar(6) not null,
chargerate float not null
);
insert into chargepolicy values
('acct1','cc', 'mstr', 0.02),
('acct1','cc', 'visa', 0.02),
('acct1','cc', 'amex', 0.025),
('acct2','cc', 'all', 0.015),
('acct3','cc', 'amex', 0.03),
('acct3','cc', 'all', 0.02),
('acct4','cc', 'visa', 0.0),
('acct4','cc', 'all', 0.025)
;
select * from chargepolicy, paymentmethod
where chargepolicy.methodtype = paymentmethod.methodtype
and (chargepolicy.methodname = paymentmethod.methodname
or chargepolicy.methodname = 'all')
order by 1,2,3;
So, this a hypothetical case (obviously). I have payment methods like Visa,MasterCard, Amex and then charge rates for each of these methods. The charge rates will vary base on the account. These accounts to method & rate mapping is defined in another table.
Now then, when I have charge rates defined for each method, things work fine (e.g. acct1 and acct2). Things get insteresting - and this is where I need help - when we come to acct3 and acct4. In acct3, I am saying that for Amex the charge should be 3% and for everything else (all - Amex) it should be 2%. Similarly, in acct4, I want charge for Visa to be 0% and for everything else to be 2.5%.
The way current SQL is written, I am getting two values for acct3.amex and acct4.visa.
How can I simplify that. It must be a straight forward thing but I don't believe I am looking at it right.
This is what I have come up with:
http://sqlfiddle.com/#!17/36a1c/6
select *
from
chargepolicy,
paymentmethod
where
chargepolicy.methodtype = paymentmethod.methodtype
and (chargepolicy.methodname = paymentmethod.methodname
or (chargepolicy.methodname = 'all'
and paymentmethod.methodname not in (
select methodname from chargepolicy cp2 where cp2.accountname = chargepolicy.accountname
and cp2.methodname !='all')
))
order by 1,2,3;
But since my actual use case has a lot of additional options to be mapped (e.g. Country, Product, Currency etc), I am not sure if my current approach is scalable - too many nested queries.
My approach would be like this, its much simpler to follow i think.
select
ac.accountname,
pm.methodtype,
pm.methodname,
coalesce(cp1.chargerate,cp2.chargerate) as chargerate
from
paymentmethod as pm
join (select distinct accountname from chargepolicy) as ac on 1=1
left join chargepolicy as cp1 on pm.methodname=cp1.methodname and ac.accountname=cp1.accountname
left join chargepolicy as cp2 on cp2.methodname='all' and ac.accountname=cp2.accountname
order by 1,2,3;
http://sqlfiddle.com/#!17/36a1c/11
Related
I have a database with a 'General Ledger', 'Account Types' and 'Chart of Accounts' tables. The layout is as follows:
'Account Types':
AcctTypeID Autonumber Long Integer Not Null Primary Key
TypeName Text(50) Not Null
Description Memo
Credit Yes/No Not Null Default Yes
'Chart of Accounts':
AcctID Long Integer Not Null Primary Key
AcctName Text(30) Not Null
Description Memo
AcctType Long Integer Not Null
'General Ledger':
EntryID Autonumber Long Integer Not Null Primary Key
EntryDate Date/Time Not Null Default #1/1/1900#
EntryType Text(12) Not Null Default "DC"
Description Memo
FromAcct Long Integer Not Null
ToAcct Long Integer Not Null
Taxable Yes/No Not Null Default No
Relationships Between Tables:
One-to-Many from 'Chart of Accounts'->AcctID to 'General Ledger'->FromAcct
One-to-Many from 'Chart of Accounts'->AcctID to 'General Ledger'->ToAcct
One-to-Many from 'Account Types'->TypeID to 'Chart of Accounts'->AcctType
I'm needing to start by fulfilling the following requirements in two different SQL queries:
If ToAcct.AcctType == (Income OR Asset) then add 'General Ledger'.Amount to a TotalRevenue
If ToAcct.AcctType == (Expense OR Liability) then add 'General Ledger'.Amount to TotalExpenses
I've tried these two SQL queries, before I modified my 'Account Types' to include whether they are income/assets or expenses/liabilities:
SELECT SUM(IIF((Credit = TRUE), Amount, 0)) AS RevenueTotal FROM GeneralLedger;
SELECT SUM(IIF((Credit = FALSE), Amount, 0)) AS ExpenseTotal FROM GeneralLedger
These queries worked, but I had the extra checkbox on the General Ledger form for "Credit". In normalizing, I figured it would be better for the account to determine whether it is a debit or a credit account.
After multiple searches on Google and reading I-don't-know-how-many questions and answers, I'm not seeing anything that is similar to what I'm trying to do here.
The output should give me the information needed for creating the Profit & Loss, Income Statement, Business and Tax reports. I believe I can get the SQL queries going for pulling the amounts based on their account, but I'm not sure how to get at the account types' 'Debit' field to determine whether to add or subtract the amount.
If necessary, I may do this inside the C# application that this database will underpin. If I can do it at the SQL Query level, I'd be able to use it in the project as a view, instead of having to code it. Any help at all is greatly appreciated.
After a couple more searches, I found my answer as being that I need to do 4 different queries to accomplish what I was attempting. The appropriate queries turned out to be:
For Total Expenses:
SELECT SUM(Amount) AS TotalExpense
FROM GeneralLedger INNER JOIN ChartOfAccounts ON ChartOfAccounts.AcctID=GeneralLedger.ToAcct
WHERE ChartOfAccounts.AcctType = 4;
For Total Revenue:
SELECT SUM(Amount) AS TotalRevenue
FROM GeneralLedger INNER JOIN ChartOfAccounts ON ChartOfAccounts.AcctID=GeneralLedger.ToAcct
WHERE ChartOfAccounts.AcctType = 3;
For Total Liabilities:
SELECT SUM(Amount) AS TotalLiabilities
FROM GeneralLedger INNER JOIN ChartOfAccounts ON ChartOfAccounts.AcctID = GeneralLedger.ToAcct
WHERE ChartOfAccounts.AcctType = 2;
For Total Assets:
SELECT SUM(Amount) AS TotalAssets
FROM GeneralLedger INNER JOIN ChartOfAccounts ON ChartOfAccounts.AcctID = GeneralLedger.ToAcct
WHERE ChartOfAccounts.AcctType = 1;
It was just a matter of getting my INNER JOIN working with my WHERE clause properly. It's the simple things that kill...
The link that proved most helpful was: StackOverflow: sql - INNER JOIN WHERE clause.
Not sure how helpful title is, so let me get straight to it.
Below is a query (and proceeding result set) to give you an idea of what I'm working with:
select PRACT_ID, ID_Number, DocumentName
from Practitioner_ID_Numbers
where PRACT_ID = 1193
PRACT_ID ID_Number DocumentName
1193 H9704 State License
1193 BR1918804 DEA Number
1193 10080428 Controlled Substance
1193 E51693 Medicare UPIN
1193 00419V Medicare Provider
1193 None Medicaid Provider
Pract_ID = unique identifier of person
ID_Number = identifying number associated to document
DocumentName = identifies type of document (for example, id_number could be (555)555-1234 and documentname would be 'phone number')
So what I need to do is write a query that identifies all pract_id's who have no entry for documentname type 'NPI number'.
You can use aggregation:
select PRACT_ID
from Practitioner_ID_Numbers
group by PRACT_ID
having sum(case when DocumentName = 'NPI number' then 1 else 0 end) = 0;
Exclusion is easily solved with NOT EXISTS:
select distinct PRACT_ID
from Practitioner_ID_Numbers p
where not exists (
select 1 from Practitioner_ID_Numbers
where PRACT_ID = p.PRACT_ID and DocumentName = 'NPI number'
)
You can join a record-set of practitioners to Practitioner_ID_Numbers to figure out which practitioners don't have an NPI.
Here, I'm using a CTE based on Practitioner_ID_Numbers, but if you have a separate table that stores practitioners, you can use that instead:
WITH
Practitioners(PRACT_ID) AS
(
SELECT DISTINCT PRACT_ID FROM Practitioner_ID_Numbers
)
SELECT Practitioners.PRACT_ID
FROM Practitioners
LEFT JOIN Practitioner_ID_Numbers AS ProviderNPIs ON
ProviderNPIs.PRACT_ID = Practitioners.PRACT_ID
AND ProviderNPIs.DocumentName = 'NPI number'
WHERE ProviderNPIs.PRACT_ID IS NULL
Note that in the JOIN, we indicate that we're only concerned with records where the DocumentName is "NPI number". We then indicate in the WHERE clause that we want records with a null PRACT_ID. This is how we determine which practitioners are missing an NPI.
A couple of additional notes:
Your naming conventions are a bit wonky: Inconsistent capitalization use of underscores can make queries tricky (did that field have an underscore or not?)
Why would you need to store "None" as an ID number? If the provider was missing an ID, wouldn't the value be NULL (or simply not exist in the table)?
Hopefully, your ID types (State License, DEA Number, etc.) are stored in a separate lookup table.
You might want to reconsider the attribute name DocumentName. Something like ID_Type might be more appropriate.
OK, I'm quite new to SQL and didn't get to much training!
I'm using SSMS to create stored procedures and open them in Excel.
The code below work just fine, but I need to add a drill down to get more info on some lines.
We need to follow what was invoice and paid on a batch of contracts for our project. Each contract have multiple lines with a description and a couple of other fields(reference, line #, G/L # etc). Plus we have the value of the line, the amount that was invoice for this line and the amount that was paid.
Main table 'CSCOMVTL' have the basic infos including the base value and the invoice amount, but not the paid amount.
'JRAPRVTL' is the list of all invoices with; invoice no., invoice date, invoiced amount and paid amount that we may need to see.
So for each base line, we need a +/- button to show/hide details of the invoice.
Invoice amount and paid amount could be from a rollup, but the number and date won't be on the parent line. If they could be in the same column as other field not needed it would be great, but I could live with 2 extra columns.
Thanks!
ALTER PROCEDURE [dbo].[marpt_qmd_AccPmt_DetailsST]
#contrat varchar(30), #projet varchar(30)
AS
BEGIN
CREATE TABLE #RPT
(
Ligne INT,
Lien INT,
Act VARCHAR (10),
Descr VARCHAR (90),
MntBase DECIMAL (20,2),
MntFact DECIMAL (20,2),
Modif VARCHAR (40),
Descr3 VARCHAR (90),
Lien2 INT,
MntPy DECIMAL (20,2) default '0',
)
INSERT INTO #RPT (Ligne, Lien, Act, Descr, MntBase, MntFact)
SELECT ROW, DETAILCHANGEORDERCOU, ACTIVITY, DESCRIPTION, AMOUNT, INVOICE
FROM cscomvtl
WHERE PROJECTNUMBER = #projet
and LTRIM(RTRIM(PONUMBER)) = #contrat
UPDATE #RPT
SET Modif=m.CHANGEORDERNUMBER, Descr3=m.DESCRIPTION, Lien2=m.CHANGEORDERCOUNTER
FROM cscomac m, #RPT r
where m.COUNTER=r.Lien
UPDATE #RPT
SET MntPy=payment
FROM #RPT r, (select POLINE, sum(payment) payment from jraprvtl where PROJECTNO=#projet
and LTRIM(RTRIM(PURCHASEORDER))=#contrat group by POLINE) d
where r.Ligne=d. POLINE
SELECT
Ligne as 'Ligne',
Act as 'Act.',
Descr as 'Description 1',
MntBase as '$ Base',
MntFact as '$ Invoiced',
Modif as 'Num. Modif.',
Descr3 as 'Description 2',
MntPy as '$ Paid'
FROM #RPT
Order by Ligne
Drop table #RPT
First off, take the time & learn SQL. It's an invaluable tool in your toolkit!
Okay, enough of the lecture. In looking through your code, you don't seem to really need the temp table #rpt, you just need to understand JOINs. Hopefully this SQL will get you what you are looking for:
SELECT vtl.ROW AS Ligne, vtl.DETAILCHANGEORDERCOU AS Lein, vtl.ACTIVITY AS Act,
vtl.DESCRIPTION AS Descr, vtl.AMOUNT AS MntBase, vtl.INVOICE AS MntFact,
mac.CHANGEORDERNUMBER AS Modif, mac.DESCRIPTION AS Descr3, mac.CHANGEORDERCOUNTER AS Lien2,
sum(jrap.payment) AS MntPy
FROM cscomvtl AS vtl
LEFT OUTER JOIN cscomac AS mac
ON vtl.detailchangeordercou = mac.counter
LEFT OUTER JOIN jraprvtl AS jrap
ON vtl.row = jrap.poline
WHERE projectnumber = #projet AND LTRIM(RTRIM(ponumber)) = #contrat
GROUP BY vtl.row, vtl.detailchangeordercou, vtl.activity, vtl.description, vtl.amount,
vtl.invoice, mac.changeordernumber, mac.description, mac.changeordercounter
You will likely have to tweak it to fit what you're trying to do in Excel, since you really didn't give much to go on there.
I have project on CRM which maintains product sales order for every organization.
I want to count everyday sold stock which I have managed to do by looping over by date but obviously it is a ridiculous method and taking more time and memory.
Please help me to find out it in single query. Is it possible?
Here is my database structure for your reference.
product : id (PK), name
organization : id (PK), name
sales_order : id (PK), product_id (FK), organization_id (FK), sold_stock, sold_date(epoch time)
Expected Output for selected month :
organization | product | day1_sold_stock | day2_sold_stock | ..... | day30_sold_stock
http://sqlfiddle.com/#!15/e1dc3/3
Create tablfunc :
CREATE EXTENSION IF NOT EXISTS tablefunc;
Query :
select "proId" as ProductId ,product_name as ProductName,organizationName as OrganizationName,
coalesce( "1-day",0) as "1-day" ,coalesce( "2-day",0) as "2-day" ,coalesce( "3-day",0) as "3-day" ,
coalesce( "4-day",0) as "4-day" ,coalesce( "5-day",0) as "5-day" ,coalesce( "6-day",0) as "6-day" ,
coalesce( "7-day",0) as "7-day" ,coalesce( "8-day",0) as "8-day" ,coalesce( "9-day",0) as "9-day" ,
coalesce("10-day",0) as "10-day" ,coalesce("11-day",0) as "11-day" ,coalesce("12-day",0) as "12-day" ,
coalesce("13-day",0) as "13-day" ,coalesce("14-day",0) as "14-day" ,coalesce("15-day",0) as"15-day" ,
coalesce("16-day",0) as "16-day" ,coalesce("17-day",0) as "17-day" ,coalesce("18-day",0) as "18-day" ,
coalesce("19-day",0) as "19-day" ,coalesce("20-day",0) as "20-day" ,coalesce("21-day",0) as"21-day" ,
coalesce("22-day",0) as "22-day" ,coalesce("23-day",0) as "23-day" ,coalesce("24-day",0) as "24-day" ,
coalesce("25-day",0) as "25-day" ,coalesce("26-day",0) as "26-day" ,coalesce("27-day",0) as"27-day" ,
coalesce("28-day",0) as "28-day" ,coalesce("29-day",0) as "29-day" ,coalesce("30-day",0) as "30-day" ,
coalesce("31-day",0) as"31-day"
from crosstab(
'select hist.product_id,pr.name,o.name,EXTRACT(day FROM TO_TIMESTAMP(hist.sold_date/1000)),sum(sold_stock)
from sales_order hist
left join product pr on pr.id = hist.product_id
left join organization o on o.id = hist.organization_id
where EXTRACT(MONTH FROM TO_TIMESTAMP(hist.sold_date/1000)) =5
and EXTRACT(YEAR FROM TO_TIMESTAMP(hist.sold_date/1000)) = 2017
group by hist.product_id,pr.name,EXTRACT(day FROM TO_TIMESTAMP(hist.sold_date/1000)),o.name
order by o.name,pr.name',
'select d from generate_series(1,31) d')
as ("proId" int ,product_name text,organizationName text,
"1-day" float,"2-day" float,"3-day" float,"4-day" float,"5-day" float,"6-day" float
,"7-day" float,"8-day" float,"9-day" float,"10-day" float,"11-day" float,"12-day" float,"13-day" float,"14-day" float,"15-day" float,"16-day" float,"17-day" float
,"18-day" float,"19-day" float,"20-day" float,"21-day" float,"22-day" float,"23-day" float,"24-day" float,"25-day" float,"26-day" float,"27-day" float,"28-day" float,
"29-day" float,"30-day" float,"31-day" float);
Please note, use PostgreSQL Crosstab Query. I have used coalesce for handling null values(Crosstab Query to show "0" when there is null data to return).
Following query will help to find the same:
select o.name,
p.name,
sum(case when extract (day from to_timestamp(sold_date))=1 then sold_stock else 0 end)day1_sold_stock,
sum(case when extract (day from to_timestamp(sold_date))=2 then sold_stock else 0 end)day2_sold_stock,
sum(case when extract (day from to_timestamp(sold_date))=3 then sold_stock else 0 end)day3_sold_stock,
from sales_order so,
organization o,
product p
where so.organization_id=o.id
and so.product_id=p.id
group by o.name,
p.name;
I just provided logic to find for 3 days, you can implement the same for rest of the days.
basically first do basic joins on id, and then check if each date(after converting epoch to timestamp and then extract day).
You have a few options here but it is important to understand the limitations first.
The big limitation is that the planner needs to know the record size before the planning stage, so this has to be explicitly defined, not dynamically defined. There are various ways of getting around this. At the end of the day, you are probably going to have somethign like Bavesh's answer, but there are some tools that may help.
Secondly, you may want to aggregate by date in a simple query joining the three tables and then pivot.
For the second approach, you could:
You could do a simple query and then pull the data into Excel or similar and create a pivot table there. This is probably the easiest solution.
You could use the tablefunc extension to create the crosstab for you.
Then we get to the first problem which is that if you are always doing 30 days, then it is easy if tedious. But if you want to do every day for a month, you run into the row length problem. Here what you can do is create a dynamic query in a function (pl/pgsql) and return a refcursor. In this case the actual planning takes place in the function and the planner doesn't need to worry about it on the outer level. Then you call FETCH on the output.
I have 2 tables UserSession and Sale.
Table User has 4 columns, namely UserID, UserSessionID, SessionOpenDate and SessionCloseDate.
Table Sale has 2 columns, which are price, cost, userID and CompletedDate.
When a user logs in, a new row is created in the User table, where the user's login timestamp will be saved in the SessionOpenDate and a new UserSessionID will be assigned to the session. When the user logs off, the log off timestamp will be be saved in SessionCloseDate.
When the user is still logged in, the user can make some sale and the sale information is saved in the Sale table. The timestamp when the sale is finalized in saved in CompletedDate column.
For some reason, I need to get the all sales done in a certain UserSessionID where the CompletedDate must be within the SessionOpenDate and SessionCloseDate. However, if the user has not logged off yet, which means that the value in SessionCloseDate is null, the CompletedDate should be between SessionOpenDate and now.
Here's my query:
SELECT SUM(s.cost) AS Cost, SUM(s.price) AS Price
FROM Sale AS s
INNER JOIN UserSession AS u
ON s.userID = u.userID
WHERE
(s.CompletedDate >=
( SELECT SessionOpenDate
FROM UserSession
WHERE (UserSessionID = u.UserSessionID)
)
)
AND
(s.CompletedDate <
(
IF EXISTS
(
SELECT SessionCloseDate AS closeTime
FROM UserSession AS UserSessionTemp
WHERE (UserSessionID = u.UserSessionID)
)
BEGIN
SET closeTime = SELECT CURRENT_TIMESTAMP
END
)
)
AND u.UserSessionID IN (1)
However, Sql Server says Incorrect syntax near the keyword 'IF'. and Incorrect syntax near ')'.
Can anyone tell me what went wrong with my IF block?
You can't use an IF block inside a SELECT statement. Also, I don't know what you're really trying to accomplish with SET, since closeTime is not a variable/parameter.
You can use IIF in SQL Server 2012 (syntactical sugar for CASE WHEN <condition> THEN <true_value> ELSE <false_value> END - use this syntax for earlier versions):
IIF(EXISTS
(
SELECT SessionCloseDate AS closeTime
FROM UserSession AS UserSessionTemp
WHERE (UserSessionID = u.UserSessionID)
), (
SELECT SessionCloseDate AS closeTime
FROM UserSession AS UserSessionTemp
WHERE (UserSessionID = u.UserSessionID)
),
CURRENT_TIMESTAMP
)
Honestly, without getting too complicated, here's what I would do instead:
SELECT SUM(s.cost) AS Cost, SUM(s.price) AS Price
FROM userSession AS u
INNER JOIN Sale AS s ON u.userID = s.userID
WHERE u.UserSessionID = #UserSessionId
AND s.CompletedDate >= u.SessionOpenDate
AND (u.SessionCloseDate IS NULL OR s.CompletedDate < u.SessionCloseDate)
Or,
SELECT SUM(s.cost) AS Cost, SUM(s.price) AS Price
FROM userSession AS u
INNER JOIN Sale AS s ON u.userID = s.userID
WHERE u.UserSessionID = #UserSessionId
AND s.CompletedDate BETWEEN u.SessionOpenDate
AND COALESCE(u.SessionCloseDate, '12/31/9999 23:59:59.9999')
I'd go with something like this, depending on what you're looking for. I couldn't tell if you wanted the info for a particular user or session, but that's simple enough to add.
SELECT UserSessionID, SUM(cost) AS TotalCost, SUM(price) AS TotalPrice
FROM UserSession LEFT OUTER JOIN sale
ON UserSession.userid = sale.userid AND
((UserSession.SessionCloseDate IS NULL AND sale.CompletedDate BETWEEN UserSession.SessionOpenDate AND GetDate())
OR (sale.SessionCloseDate IS NOT NULL AND sale.CompletedDate BETWEEN UserSession.SessionOpenDate AND UserSession.SessionCloseDate))
WHERE SUM(cost) > 0
GROUP BY UserSessionID
(you can ADD AND UserSessionID = 'mysessionid' or and UserID = 'myuserid' above the group by if you don't want the full list)
Others have explained the specific problem and "given you a fish". I would like to "teach you how to fish", though.
Please see mixed-up statement types for a full discussion (disclosure: my own blog post). Here are some snippets.
An expression consists of one or more literal values or functions tied together with operators, which when evaluated in the correct order result in a value or collection of values.
Snip...
Procedural statements are called that because there is some procedure that must be followed. It isn't a simple case of order-of-operations resulting in a single value. There is in fact no value expressed at all.
Snip...
Now that you know the three main kinds of statements (and I won't rule out the possibility of there being more or of there being subclassifications of these) the key concept you must know to get along well with SQL Server is that when a certain kind of statement is expected, you can't use a different one in its place.