I hope i word this out in a way that makes sense;
I'm trying to do a basic select statement for a list of companyIDs. The arguments in questions are options regarding invoices. We have multiple invoice types and they can either be set to be sent by E-mail or be printed. They can be mixed as in some e-mail, some printed, some have nothing selected. There's a total of 9 unique invoice types that i need to check for each company ID.
I'm trying to select distinct company IDs(cmp_id) where the print AND e-mail options are all N or NULL for every single invoicetype. Both options have to be off for both print and e-mail for all invoice types ( In the table i'm selecting from, there's 9 records for the same companyID for each invoicetype)
Some examples ( I'm only using 3 invoice types for the example):
Cmp_id invoiceType print e-mail
company1 credit Y N
company1 supplemental Y N
company1 misc Y Y
company2 supplemental N N
company2 misc N N
The results should be a single column for cmp_id; In the example it would only be company2.
Just perfrom a coditional COUNT to check how many 'Y' you have.
Im assuming print and e-mail only have 3 states, Y, N or NULL
SELECT Cmp_id
FROM YourTable
GROUP BY Cmp_id
HAVING COUNT(CASE WHEN print = 'Y' or e-mail = 'Y' THEN 1 END) = 0
So if count is 0 mean all values are NULL or N
Working DEMO
SELECT DISTINCT Cmp_ID
FROM dbo.table
WHERE
COALESCE([Print], 'N') = 'N'
AND
COALESCE([E-Mail], 'N') = 'N'
Use coalesce to catch the NULLs and Ns.
Would this work:
SELECT DISTINCT Cmp_ID from dbo.table where (Print = 'N') AND (E-Mail = 'N') OR
(Print IS NULL) AND (E-Mail IS NULL)
SELECT T1.Cmp_id, COUNT(*)
FROM dbo.table T1
WHERE
COALESCE([Print], 'N') = 'N'
AND
COALESCE([E-Mail], 'N') = 'N'
GROUP BY T1.Cmp_id
HAVING COUNT(T1.Cmp_id) = (SELECT COUNT(1) FROM dbo.table WHERE Cmp_id = T1.Comp_Id)
Count where both options are either NULL or N and that count = the count of Cmp_Ids in the table.
Assumption:
each [Cmp_ID] has n rows in the table where n is the number of [invoiceTypes]
Sample Data:
CREATE TABLE #temp ([Cmp_id] VARCHAR(20), [invoiceType] VARCHAR(20), [print] VARCHAR(20), [e-mail] VARCHAR(20), )
INSERT INTO #temp
VALUES
('company1','credit','Y','N'),
('company1','supplemental','Y','N'),
('company1','misc','Y','Y'),
('company2','credit','N','N'),
('company2','supplemental','N','N'),
('company2','misc','N','N')
Query using Common table Expression, GROUP BY and HAVING:
We assign the value 1 for every row that has [Print], and [e-mail] as NULL or 'N'. Then we only retrieve the [Cmp_id] of the company that has all its invoiceType with [Print], and [e-mail] as NULL or 'N'.
;WITH CTE AS ( SELECT [Cmp_id],
[NoneSelected] = CASE WHEN ISNULL([print],'N') = 'N' AND ISNULL([e-mail],'N') ='N' THEN 1 ELSE 0 END
FROM #temp )
SELECT Cmp_id
FROM CTE
GROUP BY Cmp_id
HAVING SUM(NoneSelected) = 3 --<-- change this to 9 for 9 invoice types
Results:
Try using Temp tables in response to the comment "We're on compatibility 80, i've tried using CTEs before but they didn't work :S Thanks forthe input though.":
IF OBJECT_ID('tempdb..#temp2') IS NOT NULL
DROP TABLE #temp2;
SELECT [Cmp_id],
[NoneSelected] = CASE WHEN ISNULL([print],'N') = 'N' AND ISNULL([e-mail],'N') ='N' THEN 1 ELSE 0 END INTO #temp2
FROM #temp
SELECT Cmp_id
FROM #temp2
GROUP BY Cmp_id
HAVING SUM(NoneSelected) = 3 --<-- change this to 9 for 9 invoice types
Related
I have a query structured like below, it checks if any of the entry in a given column is not null, if it finds a match (a value that is not null) it immediately returns Y, if all values are null it returns N. It works with a column called first_name but I need to make it work for other columns as well i.e last_name, middle_name, preferably all in a single query to save execution time. Y or N must be returned for each of the columns specified.
SELECT CASE
WHEN EXISTS(SELECT *
FROM (patient AS p JOIN patient_score AS s ON p.patient_id=s.patient_id)
WHERE first_name IS NOT NULL)
THEN 'Y'
ELSE 'N'
END AS your_result
I have another query which is an alternative and does the same job (1/0 instead of Y/N). But I don't know how to make it work with multiple columns either. A procedure that could work by supplying multiple column names would work as well.
SELECT COUNT(*) AS fn FROM
(SELECT 1
FROM (patient AS p JOIN patient_score AS s ON p.patient_id=s.patient_id)
WHERE first_name IS NOT NULL
LIMIT 1) AS T;
I think it is the query that you want, you have to add a SELECT element (CASE WHEN SUM(CASE WHEN column_name IS NULL THEN 0 ELSE 1 END) > 0 THEN 'Y' ELSE 'N' END AS column_name) for each column you want to check.
SELECT
CASE WHEN SUM(CASE WHEN first_name IS NULL THEN 0 ELSE 1 END) > 0 THEN 'Y' ELSE 'N' END AS first_name
FROM patient AS p
INNER JOIN patient_score AS s ON p.patient_id = s.patient_id
I want combine mutiple rows columns as single record in MySQL
For eg: Actual Data
--------------------------------------------------------------
id name Loantype Amount
--------------------------------------------------------------
1 ABC 1 500000
2 ABC 2 3500000
3 XYZ 1 250000
4 XYZ 2 2500000
I tried with the following query
SELECT
id,
(
CASE Loantype
WHEN 1 THEN Amount
ELSE NULL
END
) AS PersonalLoan,
(
CASE Loantype
WHEN 2 THEN Amount
ELSE NULL
END
) AS HomeLoan
FROM
customer
WHERE
name = 'ABC'
but result comes as below
--------------------------------------------------------------
id name PersonalLoan HomeLoan
--------------------------------------------------------------
1 ABC 500000 NULL
1 ABC NULL 2500000
Expected Result set
--------------------------------------------------------------
id name PersonalLoan HomeLoan
--------------------------------------------------------------
1 ABC 500000 3500000
You can self-join the table so that you will be able to combine the 2 kinds of loans into single row:
SELECT t1.id, t1.name, t1.amount AS personal, t2.amount AS home
FROM customer AS t1
LEFT JOIN customer AS t2 ON t1.name = t2.name AND t2.loantype = 2
WHERE t1.loantype = 1
If you are looking for just 1 user - you can speedup the query by limiting the size of the JOIN:
SELECT t1.id, t1.name, t1.amount AS personal, t2.amount AS home
FROM (SELECT * FROM customer WHERE name = "ABC" AND loantype = 1) AS t1
LEFT JOIN customer AS t2 ON t1.name = t2.name AND t2.loantype = 2
Note that generally speaking you shouldn't denormalize data in SQL (e.g. converting rows to columns: SQL is row-oriented, not column-oriented) - I assume this is to simplify display logic - just be careful of using queries like this when you want to pass meaningful data to other parts of the database rather than directly to the user.
You need to GROUP BY first.
You can also simplify your CASE expressions:
ELSE NULL is always implicit and if the CASE WHEN expression is an equality comparison then you can use the simpler switch-style syntax.
So CASE WHEN a = b THEN c ELSE NULL END can be simplified to CASE a WHEN b THEN c END.
I've added COALESCE so the query will return 0 for the SUM aggregates if there are no matching rows instead of NULL. Note that COUNT (unlike SUM) generally doesn't need to be wrapped in a COALESCE (though I forget precisely how MySQL handles this - it also depends on what version of MySQL you're using and what strict-mode and ANSI/ISO-compliance options are enabled).
Note that the database design you posted seems to allow the same customer.name to have multiple loans of the same loantype.
You can avoid this by adding a UNIQUE CONSTRAINT or UNIQUE INDEX or use a Composite Primary Key:
CREATE UNIQUE INDEX UX_name_loantype ON customer ( name, loantype )
To prevent those rows from causing issues in this query, this uses a SUM and a COUNT to make it clear to readers that the data is an aggregate over multiple rows:
SELECT
name,
COUNT( CASE Loantype WHEN 1 THEN 1 END ) AS CountPersonalLoans,
COALESCE( SUM( CASE Loantype WHEN 1 THEN Amount END ), 0 ) AS SumPersonalLoans,
COUNT( CASE Loantype WHEN 2 THEN 1 END ) AS CountHomeLoans,
COALESCE( SUM( CASE Loantype WHEN 2 THEN Amount END ), 0 ) AS SumHomeLoans
FROM
customer
GROUP BY
name
To maximise query code reuse if you want to filter by name then convert this to a VIEW - or if it's a one-off then make it a CTE query, like so:
WITH aggs AS (
SELECT
name,
COUNT( CASE Loantype WHEN 1 THEN 1 END ) AS CountPersonalLoans,
COALESCE( SUM( CASE Loantype WHEN 1 THEN Amount END ), 0 ) AS SumPersonalLoans,
COUNT( CASE Loantype WHEN 2 THEN 1 END ) AS CountHomeLoans,
COALESCE( SUM( CASE Loantype WHEN 2 THEN Amount END ), 0 ) AS SumHomeLoans
FROM
customer
GROUP BY
name
)
SELECT
name,
CountPersonalLoans,
SumPersonalLoans,
CountHomeLoans,
SumHomeLoans
FROM
aggs
WHERE
name = 'ABC'
ORDER BY
name
I have the below table. I would like to count all unique Ids and then divide by a count where Role is Teacher.
Table A
ID Userid Role
1 A Teacher
2 b Teacher
3 c Student
Something like below.
count(distinct id) / count(distinct id) When Role = Teacher
You can use a simple COUNT DISTINCT with an aggregated CASE expression. I've converted to float so that the result returns decimals but you will want to check out your data types before applying to your system.
Temp Table for sample data
CREATE TABLE #TestData (ID float, Userid nvarchar(1), Role nvarchar(7))
INSERT INTO #TestData
VALUES
(1,'A','Teacher')
,(2,'B','Teacher')
,(3,'C','Student')
Query
SELECT
COUNT(distinct td.ID) DistinctID
,SUM(CASE WHEN td.Role = 'Teacher' THEN 1 ELSE 0 END) Teachers
,CONVERT(float,COUNT(distinct td.ID)) / CONVERT(float,SUM(CASE WHEN td.Role = 'Teacher' THEN 1 ELSE 0 END)) FinalField
FROM #TestData td
Result
DistinctID Teachers FinalField
3 2 1.5
Here is my testing table data:
Testing
ID Name Payment_Date Fee Amt
1 BankA 2016-04-01 100 20000
2 BankB 2016-04-02 200 10000
3 BankA 2016-04-03 100 20000
4 BankB 2016-04-04 300 20000
I am trying to compare fields Name, Fee and Amt of each data records to see whether there are the same values or not. If they got the same value, I'd like to mark something like 'Y' to those record. Here is the expected result
ID Name Payment_Date Fee Amt SameDataExistYN
1 BankA 2016-04-01 100 20000 Y
2 BankB 2016-04-02 200 10000 N
3 BankA 2016-04-03 100 20000 Y
4 BankB 2016-04-04 300 20000 N
I have tried these two methods below. but I am looking for any other solutions so I can pick out the best one for my work.
Method 1.
select t.*, iif((select count(*) from testing where name=t.name and fee=t.fee and amt=t.amt)=1,'N','Y') as SameDataExistYN from testing t
Method 2.
select t.*, case when ((b.Name = t.Name)
and (b.Fee = t.Fee) and (b.Amt = t.Amt)) then 'Y' else 'N' end as SameDataExistYN
from testing t
left join ( select Name, Fee, Amt
from testing
Group By Name, Fee, Amt
Having count(*)>1 ) as b on b.Name = t.Name
and b.Fee = t.Fee
and b.Amt = t.Amt
There are several approaches, with differences in performance characteristics.
One option is to run a correlated subquery. This approach is best suited if you have a suitable index, and you are pulling a relatively small number of rows.
SELECT t.id
, t.name
, t.payment_date
, t.fee
, t.amt
, ( SELECT 'Y'
FROM testing s
WHERE s.name = t.name
AND s.fee = t.fee
AND s.amt = t.amt
AND s.id <> t.id
LIMIT 1
) AS SameDataExist
FROM testing t
WHERE ...
LIMIT ...
The correlated subquery in the SELECT list will return a Y when there is at least one "matching" row found. If no "matching" row is found, SameDataExist column will have a value of NULL. To convert the NULL to an 'N', you could wrap the subquery in an IFULL() function.
Your method 2 is a workable approach. The expression in the SELECT list doesn't need to do all those comparisons, those have already been done in the join predicates. All you need to know is whether a matching row was found... just testing one of the columns for NULL/NOT NULL is sufficient.
SELECT t.id
, t.name
, t.payment_date
, t.fee
, t.amt
, IF(s.name IS NOT NULL,'Y','N') AS SameDataExists
FROM testing t
LEFT
JOIN ( -- tuples that occur in more than one row
SELECT r.name, r.fee, r.amt
FROM testing r
GROUP BY r.name, r.fee, r.amt
HAVING COUNT(1) > 1
) s
ON s.name = t.name
AND s.fee = t.fee
AND s.amt = t.amt
WHERE ...
You could also make use of an EXISTS (correlated subquery)
Check this out
Select statement to find duplicates on certain fields
Not sure how to mark this as a dupe...
Here is another method, but I think you have to run tests on your data to find out which is best:
SELECT
t.*,
CASE WHEN EXISTS(
SELECT * FROM testing WHERE id <> t.id AND Name = t.Name AND Fee = t.Fee AND Amt = t.Amt
) THEN 'Y' ELSE 'N' END SameDataExistYN
FROM
testing t
;
Select t.name ,t.fee,t.amt,if(count(*)>1),'Y','N') from testing t group by t.name,t.fee,t.amt
I have a table that is sorted by id and value in descending order. I want to return all id's that match a group of keys in a specific order. So given (a5, a3) I want to return a and b but not d.
id value key
a 3 a5
a 2 a3
a 1 a4
b 4 a5
b 2 a3
c 6 a1
c 2 a2
d 4 a3
d 2 a5
The expected output would be
id
a
b
So far I've managed to match (a5, a3) but in any order. Here I'm returning all rows and fields that match in any order; not just the id.
SELECT tablename.*
FROM tablename, (SELECT * FROM tablename a
WHERE key IN ('a5', 'a3')
GROUP BY id
HAVING COUNT(*) >= 1) AS result
WHERE tablename.id = result.id
This is an example of a set-within-sets query, although it is a bit more complicated then most.
select id
from tablename t
group by id
having (max(case when "key" = 'a5' then value end) >
max(case when "key" = 'a3' then value end)
);
What this is doing is finding the value for "a5" and "a3" and directly comparing them. If neither is present, then the max(case . . .) will return NULL and the comparison will fail. If there is more than one value for either (or both), then it returns the largest value.
This should be pretty easy to generalize to additional keys in a particular order, by adding more similar clauses. This is why I like the aggregation with having approach to this sort of query -- it works in a lot of cases.
For the "nothing-in-between" case that you mention, I think this will work:
select id
from (select t.*, #rn := #rn + 1 as seqnum
from tablename t cross join (select #rn := 0) const
order by key, value
) t
group by id
having (max(case when "key" = 'a5' then seqnum end) = max(case when "key" = 'a3' then seqnum end) + 1
);
The appends a sequence number and then checks that they are consecutive for your two values.
For this you can use following query -
select distinct t_1.ID from tablname t_1, tablename t_2
where t_1.id = t_2.id
and t_1.key = 'a5' and t_2.key = 'a3'
and t_1.value > t_2.value