I have list of predefined values Cash, Bank, Card, Cheque called payment modes (no db table for them).
Each payments will have it's mode.
+-----------+
+ Payments +
+-----------+
+ id +
+ amount +
+ date +
+ mode +
+-----------+
The below query will not show mode that are not in the payments table. If for example no payment is made through cheque, then it will not be in the result.
select p.mode, SUM(p.amount) 'total'
from payments p
I also found out about Table Value Constructor but I'm not sure if its supported in MySql as I'm having syntax error.
select p.mode, SUM(p.amount)
from (
VALUES
('Cash'),
('Card'),
('Cheque'),
('Bank')
) as m(name)
left join payments p on m.name = p.mode
group by p.mode
Is there a way to have a query that gets all the mode regardless if they are not present in the payments table? I need a result like:
++++++++++++++++++++
+ mode | total +
++++++++++++++++++++
+ cash | 100 +
+ cheque | 0 +
+ bank | 0 +
+ card | 300 +
++++++++++++++++++++
In MySQL, you can construct the table using union all:
select m.mode, SUM(p.amount)
from (select 'Cash' as mode union all
select 'Card' union all
select 'Cheque' union all
select 'Bank'
) m left join
payments p
on m.mode = p.mode
group by m.mode;
Notes:
I changed name to mode so the column with the same information has the same name.
The group by key needs to be from the first name, not the second (that is m.mode instead of p.mode).
If you want 0 instead of NULL, then use coalesce(sum(p.amount), 0).
You might want to consider a reference table that contains the mode values.
select
m.mode,
SUM(p.amount)
FROM
(
SELECT
m.Cash as mode
FROM
(
VALUES
('cash'),
('card'),
('cheque'),
('bank')
) as m
) as m
left join payments p on m.mode = p.mode
group by
m.mode
You need two aliases and you must use the first value of your array (Cash) in nested select as selected column.
It's worth checking your version of MySQL if the VALUES approach isn't working. It was only introduced from MySQL 8.0.19 , so anyone on an earlier issue will need to use the UNION ALL approach.
Related
I hope that I am able to explain the situation as much as possible :)
We need to take sum from Master and child records of MySQL tables. The current query is as follows:
select sum(
abs(ifnull(dt.N_RETAIL_PRICE,0 ) * ifnull(dt.N_QTY_NO ,0) )
+ ifnull(st.shipping_total,0 ) + ifnull(st.TaxAmount,0 ) - abs(ifnull(st.discount ,0))
) Total
FROM inv_store_transaction st
inner join inv_store_transaction_det dt
on st.OID = dt.INV_STORE_TRANSACTION
where st.INV_TRANSACTION_TYPE = 35
and st.INV_STORES = 1
The issue what we suspect is that if the detail column has more than 1 row, the columns of master will be summed that many times.
e.g if detail has say 3 rows, then the sum of its relevant master data will also be taken 3 times.
To summarize, we need to take a grand total of all Invoices that fall under the given condition.
Any help appreciated.
The solution to this problem is to pre-aggregate the detail data:
select (sum(dt.amt) + sum((st.shipping_total) + sum(st.TaxAmount) -
sum(abs(st.discount))
) Total
FROM inv_store_transaction st inner join
(select dt.INV_STORE_TRANSACTION,
abs(coalesce(dt.N_RETAIL_PRICE, 0) * coalesce(dt.N_QTY_NO, 0)) as dtamt
from inv_store_transaction_det dt
group by dt.INV_STORE_TRANSACTION
) dt
on st.OID = dt.INV_STORE_TRANSACTION
where st.INV_TRANSACTION_TYPE = 35 and st.INV_STORES = 1
You don't need to test for NULL unless all the rows have a NULL value for one of the columns.
Alright, I have a relation which stores two keys, a product Id and an attribute Id. I want to figure out which product is most similar to a given product. (Attributes are actually numbers but it makes the example more confusing so they have been changed to letters to simplify the visual representation.)
Prod_att
Product | Attributes
1 | A
1 | B
1 | C
2 | A
2 | B
2 | D
3 | A
3 | E
4 | A
Initially this seems fairly simple, just select the attributes that a product has and then count the number of attributes per product that are shared. The result of this is then compared to the number of attributes a product has and I can see how similar two products are. This works for products with a large number of attributes relative to their compared products, but issues arise when products have very few attributes. For example product 3 will have a tie for almost every other product (as A is very common).
SELECT Product, count(Attributes)
FROM Prod_att
WHERE Attributes IN
(SELECT Attributes
FROM prod_att
WHERE Product = 1)
GROUP BY Product
;
Any suggestions on how to fix this or improvements to my current query?
Thanks!
*edit: Product 4 will return count() =1 for all Products. I would like to show Product 3 is more similar as it has fewer differing attributes.
Try this
SELECT
a_product_id,
COALESCE( b_product_id, 'no_matchs_found' ) AS closest_product_match
FROM (
SELECT
*,
#row_num := IF(#prev_value=A_product_id,#row_num+1,1) AS row_num,
#prev_value := a_product_id
FROM
(SELECT #prev_value := 0) r
JOIN (
SELECT
a.product_id as a_product_id,
b.product_id as b_product_id,
count( distinct b.Attributes ),
count( distinct b2.Attributes ) as total_products
FROM
products a
LEFT JOIN products b ON ( a.Attributes = b.Attributes AND a.product_id <> b.product_id )
LEFT JOIN products b2 ON ( b2.product_id = b.product_id )
/*WHERE */
/* a.product_id = 3 */
GROUP BY
a.product_id,
b.product_id
ORDER BY
1, 3 desc, 4
) t
) t2
WHERE
row_num = 1
The above query gets the closest matches for all the products, you can include the product_id in the innermost query, to get the results for a particular product_id, I have used LEFT JOIN so that even if a product has no matches, its displayed
SQLFIDDLE
Hope this helps
Try the "Lower bound of Wilson score confidence interval for a Bernoulli parameter". This explicitly deals with the problem of statistical confidence when you have small n. It looks like a lot of math, but actually this is about the minimum amount of math you need to do this sort of thing right. And the website explains it pretty well.
This assumes it is possible to make the step from positive / negative scoring to your problem of matching / not matching attributes.
Here's an example for positive and negative scoring and 95% CL:
SELECT widget_id, ((positive + 1.9208) / (positive + negative) -
1.96 * SQRT((positive * negative) / (positive + negative) + 0.9604) /
(positive + negative)) / (1 + 3.8416 / (positive + negative))
AS ci_lower_bound FROM widgets WHERE positive + negative > 0
ORDER BY ci_lower_bound DESC;
You could write a little view that will give you the total shared attributes between two products.
create view vw_shared_attributes as
select a.product,
b.product 'product_match',
count(*) 'shared_attributes'
from your_table a
inner join test b on b.attribute = a.attribute and b.product <> a.product
group by a.product, b.product
and then use that view to select the top match.
select product,
(select top 1 s.product_match from vw_shared_attributes s where t.product = s.product order by s.shared_attributes desc)
from your_table t
group by product
See http://www.sqlfiddle.com/#!6/53039/1 for an example
I have a section of a report that has an outer group with an inner group. Some of my users will print this report while some will not. I'd like to be able to to keep the toggle capabilities while providing a summary on the outer group of the top 3 rows of the inner group. Basically, I'd like the notes column to see the top three defects and their total counts.
I could accomplish this by doing the work in SQL, but in this case I'd prefer, if possible, for the report server to handle the summary and grouping.
Outer Group Sum(Defects) First(DefectId)
Inner Group Sum(Defects)
Ended up using CTE
with MyCte AS (SELECT
MachineCode + ' | ' + Tool + ' | ' + comments + ' | ' + Operator + ' | ' + convert(nvarchar(50), MinutesDown) + ' ||| ' 'Things',
Reason,
MinutesDown,
PartitionedRowNum = ROW_NUMBER() OVER (PARTITION BY Reason ORDER BY MinutesDown DESC)
from #Comments)
insert into #t
select *
from MyCte
where PartitionedRowNum <= 3
I have a many-to-many relationship between invoices and credit card transactions, which I'm trying to map sums of together. The best way to think of the problem is to imagine TransactionInvoiceMap as a bipartite graph. For each connected subgraph, find the total of all invoices and the total of all transactions within that subgraph. In my query, I want to return the values computed for each of these subgraphs along with the transaction ids they're associated with. Totals for related transactions should be identical.
More explicitly, given the following transactions/invoices
Table: TransactionInvoiceMap
TransactionID InvoiceID
1 1
2 2
3 2
3 3
Table: Transactions
TransactionID Amount
1 $100
2 $75
3 $75
Table: Invoices
InvoiceID Amount
1 $100
2 $100
3 $50
my desired output is
TransactionID TotalAsscTransactions TotalAsscInvoiced
1 $100 $100
2 $150 $150
3 $150 $150
Note that invoices 2 and 3 and transactions 2 and 3 are part of a logical group.
Here's a solution (simplified, names changed) that apparently works, but is very slow. I'm having a hard time figuring out how to optimize this, but I think it would involve eliminating the subqueries into TransactionInvoiceGrouping. Feel free to suggest something radically different.
with TransactionInvoiceGrouping as (
select
-- Need an identifier for each logical group of transactions/invoices, use
-- one of the transaction ids for this.
m.TransactionID,
m.InvoiceID,
min(m.TransactionID) over (partition by m.InvoiceID) as GroupingID
from TransactionInvoiceMap m
)
select distinct
g.TransactionID,
istat.InvoiceSum as TotalAsscInvoiced,
tstat.TransactionSum as TotalAsscTransactions
from TransactionInvoiceGrouping g
cross apply (
select sum(ii.Amount) as InvoiceSum
from (select distinct InvoiceID, GroupingID from TransactionInvoiceGrouping) ig
inner join Invoices ii on ig.InvoiceID = ii.InvoiceID
where ig.GroupingID = g.GroupingID
) as istat
cross apply (
select sum(it.Amount) as TransactionSum
from (select distinct TransactionID, GroupingID from TransactionInvoiceGrouping) ig
left join Transactions it on ig.TransactionID = it.TransactionID
where ig.GroupingID = g.GroupingID
having sum(it.Amount) > 0
) as tstat
I've implemented the solution in a recursive CTE:
;with TranGroup as (
select TransactionID
, InvoiceID as NextInvoice
, TransactionID as RelatedTransaction
, cast(TransactionID as varchar(8000)) as TransactionChain
from TransactionInvoiceMap
union all
select g.TransactionID
, m1.InvoiceID
, m.TransactionID
, g.TransactionChain + ',' + cast(m.TransactionID as varchar(11))
from TranGroup g
join TransactionInvoiceMap m on g.NextInvoice = m.InvoiceID
join TransactionInvoiceMap m1 on m.TransactionID = m1.TransactionID
where ',' + g.TransactionChain + ',' not like '%,' + cast(m.TransactionID as varchar(11)) + ',%'
)
, RelatedTrans as (
select distinct TransactionID, RelatedTransaction
from TranGroup
)
, RelatedInv as (
select distinct TransactionID, NextInvoice as RelatedInvoice
from TranGroup
)
select TransactionID
, (
select sum(Amount)
from Transactions
where TransactionID in (
select RelatedTransaction
from RelatedTrans
where TransactionID = t.TransactionID
)
) as TotalAsscTransactions
, (
select sum(Amount)
from Invoices
where InvoiceID in (
select RelatedInvoice
from RelatedInv
where TransactionID = t.TransactionID
)
) as TotalAsscInvoiced
from Transactions t
There is probably some room for optimization (including object naming on my part!) but I believe I have at least a correct solution which will gather all possible Transaction-Invoice relations to include in the calculations.
I was unable to get the existing solutions on this page to give the OP's desired output, and they got uglier as I added more test data. I'm not sure if the OP's posted "slow" solution is correct as stated. It's very possible that I'm misinterpreting the question.
Additional info:
I've often seen that recursive queries can be slow when working with large sets of data. Perhaps that can be the subject of another SO question. If that's the case, things to try on the SQL side might be to limit the range (add where clauses), index base tables, select the CTE into a temp table first, index that temp table, think of a better stop condition for the CTE...but profile first, of course.
If I have understood the question right, I think you are trying to find the minimum of transaction id for each invoice and I have used ranking function to do the same.
WITH TransactionInvoiceGrouping AS (
SELECT
-- Need an identifier for each logical group of transactions/invoices, use
-- one of the transaction ids for this.
m.TransactionID,
m.InvoiceID,
ROW_NUMBER() OVER (PARTITION BY m.InvoiceID ORDER BY m.TransactionID ) AS recno
FROM TransactionInvoiceMap m
)
SELECT
g.TransactionID,
istat.InvoiceSum AS TotalAsscInvoiced,
tstat.TransactionSum AS TotalAsscTransactions
FROM TransactionInvoiceGrouping g
CROSS APPLY(
SELECT SUM(ii.Amount) AS InvoiceSum
FROM TransactionInvoiceGrouping ig
inner JOIN Invoices ii ON ig.InvoiceID = ii.InvoiceID
WHERE ig.TransactionID = g.TransactionID
AND ig.recno = 1
) AS istat
CROSS APPLY(
SELECT sum(it.Amount) AS TransactionSum
FROM TransactionInvoiceGrouping ig
LEFT JOIN transactions it ON ig.TransactionID = it.TransactionID
WHERE ig.TransactionID = g.TransactionID
AND ig.recno = 1
HAVING SUM(it.Amount) > 0
) AS tstat
WHERE g.recno = 1
I have a table that looks like this:
studentID | subjectID | attendanceStatus | classDate | classTime | lecturerID |
12345678 1234 1 2012-06-05 15:30:00
87654321
12345678 1234 0 2012-06-08 02:30:00
I want a query that reports if a student has been absent for 3 or more consecutive classes. based on studentID and a specific subject between 2 specific dates as well. Each class can have a different time. The schema for that table is:
PK(`studentID`, `classDate`, `classTime`, `subjectID, `lecturerID`)
Attendance Status: 1 = Present, 0 = Absent
Edit: Worded question so that it is more accurate and really describes what was my intention.
I wasn't able to create an SQL query for this. So instead, I tried a PHP solution:
Select all rows from table, ordered by student, subject and date
Create a running counter for absents, initialized to 0
Iterate over each record:
If student and/or subject is different from previous row
Reset the counter to 0 (present) or 1 (absent)
Else, that is when student and subject are same
Set the counter to 0 (present) or plus 1 (absent)
I then realized that this logic can easily be implemented using MySQL variables, so:
SET #studentID = 0;
SET #subjectID = 0;
SET #absentRun = 0;
SELECT *,
CASE
WHEN (#studentID = studentID) AND (#subjectID = subjectID) THEN #absentRun := IF(attendanceStatus = 1, 0, #absentRun + 1)
WHEN (#studentID := studentID) AND (#subjectID := subjectID) THEN #absentRun := IF(attendanceStatus = 1, 0, 1)
END AS absentRun
FROM table4
ORDER BY studentID, subjectID, classDate
You can probably nest this query inside another query that selects records where absentRun >= 3.
SQL Fiddle
This query works for intended result:
SELECT DISTINCT first_day.studentID
FROM student_visits first_day
LEFT JOIN student_visits second_day
ON first_day.studentID = second_day.studentID
AND DATE(second_day.classDate) - INTERVAL 1 DAY = date(first_day.classDate)
LEFT JOIN student_visits third_day
ON first_day.studentID = third_day.studentID
AND DATE(third_day.classDate) - INTERVAL 2 DAY = date(first_day.classDate)
WHERE first_day.attendanceStatus = 0 AND second_day.attendanceStatus = 0 AND third_day.attendanceStatus = 0
It's joining table 'student_visits' (let's name your original table so) to itself step by step on consecutive 3 dates for each student and finally checks the absence on these days. Distinct makes sure that result willn't contain duplicate results for more than 3 consecutive days of absence.
This query doesn't consider absence on specific subject - just consectuive absence for each student for 3 or more days. To consider subject simply add .subjectID in each ON clause:
ON first_day.subjectID = second_day.subjectID
P.S.: not sure that it's the fastest way (at least it's not the only).
Unfortunately, mysql does not support windows functions. This would be much easier with row_number() or better yet cumulative sums (as supported in Oracle).
I will describe the solution. Imagine that you have two additional columns in your table:
ClassSeqNum -- a sequence starting at 1 and incrementing by 1 for each class date.
AbsentSeqNum -- a sequence starting a 1 each time a student misses a class and then increments by 1 on each subsequent absence.
The key observation is that the difference between these two values is constant for consecutive absences. Because you are using mysql, you might consider adding these columns to the table. They are big challenging to add in the query, which is why this answer is so long.
Given the key observation, the answer to your question is provided by the following query:
select studentid, subjectid, absenceid, count(*) as cnt
from (select a.*, (ClassSeqNum - AbsentSeqNum) as absenceid
from Attendance a
) a
group by studentid, subjectid, absenceid
having count(*) > 2
(Okay, this gives every sequence of absences for a student for each subject, but I think you can figure out how to whittle this down just to a list of students.)
How do you assign the sequence numbers? In mysql, you need to do a self join. So, the following adds the ClassSeqNum:
select a.StudentId, a.SubjectId, count(*) as ClassSeqNum
from Attendance a join
Attendance a1
on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
a.ClassDate >= s1.classDate
group by a.StudentId, a.SubjectId
And the following adds the absence sequence number:
select a.StudentId, a.SubjectId, count(*) as AbsenceSeqNum
from Attendance a join
Attendance a1
on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
a.ClassDate >= a1.classDate
where AttendanceStatus = 0
group by a.StudentId, a.SubjectId
So the final query looks like:
with cs as (
select a.StudentId, a.SubjectId, count(*) as ClassSeqNum
from Attendance a join
Attendance a1
on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
a.ClassDate >= s1.classDate
group by a.StudentId, a.SubjectId
),
a as (
select a.StudentId, a.SubjectId, count(*) as AbsenceSeqNum
from Attendance a join
Attendance a1
on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
a.ClassDate >= s1.classDate
where AttendanceStatus = 0
group by a.StudentId, a.SubjectId
)
select studentid, subjectid, absenceid, count(*) as cnt
from (select cs.studentid, cs.subjectid,
(cs.ClassSeqNum - a.AbsentSeqNum) as absenceid
from cs join
a
on cs.studentid = a.studentid and cs.subjectid = as.subjectid
) a
group by studentid, subjectid, absenceid
having count(*) > 2