Getting rowcount for a column based on values from another column - sql

I have two tables, T1 AND T2.
T1 with following columns: Id, TypeofValue, Year, value
Typeofvalue can have 2 values
1 - indicates Actual
2 - indicates Target
T2 With following column: NoOfRecordsToDisplay
I need to fetch the number of records (if existing) for Target corresponding to an Id.
However, the catches are:
Sometimes Target value might not be present for a year
I need to get only last records for targets on the basis of NoOfRecordsToDisplay (The number of records to display comes from T2) for actual
Example1:
NoOfRecordsToDisplay =3, ID =123
The data below should return 3 as we have 3 non null values for target for last 3 years -2015, 2014,2013 in this case
Id TypeofValue Year Value
123 1 2015 55
123 1 2014 56
123 1 2013 57
123 1 2012 58
123 2 2015 50
123 2 2014 50
123 2 2013 50
123 2 2012 50
124 1 2015 55
124 1 2014 56
124 1 2013 57
124 1 2012 58
124 2 2015 50
124 2 2014 50
124 2 2013 50
124 2 2012 50
Another dataset -
NoOfRecordsToDisplay =3, ID =123
The data below should return 0, as we have no values for target for last 3 years -2015, 2014,2013
Id TypeofValue Year Value
123 1 2015 55
123 1 2014 56
123 1 2013 57
123 1 2012 58
123 2 2012 50
124 1 2015 55
124 1 2014 56
124 1 2013 57
124 1 2012 58
124 2 2012 50

OK so if I understand correctly, you want a count of rows where the TypeOfValue = 2, and the Year is in the top n values where TypeOfValue = 1, for a given Id.
This should be:
DECLARE #Id int, #NoOfRecordsToDisplay int
SET #Id = 123
SET #NoOfRecordsToDisplay = 3
SELECT COUNT(*) FROM myTable
WHERE
TypeofValue = 2
AND Id = #Id
AND [Year] IN ( SELECT TOP(#NoOfRecordsToDisplay) [Year] FROM myTable
WHERE TypeofValue = 1 AND Id = #Id
ORDER BY [Year] DESC)
In practice, you would probably want to create this as a stored proc with #Id as an input parameter. #NoOfRecordsToDisplay could either be a parameter too, or selected from some other table - I'm still not 100% clear on this from your question.
Updated SQL Fiddle here: http://sqlfiddle.com/#!3/87b0c/2
Edit: Forgot the ORDER BY on the subquery!
Edit 2: Updated query and SQL fiddle based on updated question.

With SQL and such these queries, understanding the problem and imagination of an algorithm or method for solving the problem is too much important, on base of what you want:
I need to get only last 3 records for targets on the basis of latest 3
values for actual
you need to have tow steps:
1.determine the last 3 years of actual values:
SELECT TOP 3 [Year]
FROM Your_Table
WHERE Typeofvalue = 1
ORDER BY [Year] DESC
2.count the records of target values which their years are in above query:
SELECT COUNT(*) FROM Your_Table
WHERE
Typeofvalue = 2
AND
[Year] IN (
SELECT TOP 3 [Year]
FROM Your_Table
WHERE Typeofvalue = 1
ORDER BY [Year] DESC)
You can do it with join too instead of subquery:
SELECT COUNT(*) FROM Your_Table t
JOIN
(
SELECT TOP 3 [Year]
FROM Your_Table
WHERE Typeofvalue = 1
ORDER BY [Year] DESC
)q
ON T.[Year]=q.[Year]
WHERE t.Typeofvalue=2

You can select the last three actual items and join in the corresponding target items, then count the non-null values:
select
count(Id)
from (
select top 3
t2.Id
from
TheTable t1
left join TheTable t2 on t2.Year = t1.Year and t2.TypeofValue = 2
where
t1.TypeofValue = 1
order by
t1.Year desc
) x

Related

Some Case statement issue

I have two tables that has data like
table1
Id id_nm
1 per
2 per
3 org
table2
Id Lst_id l_nm up_dt
1 22 abc 9/10/2015
1 21 abs 10/12/2016
2 21 xzc 10/12/2013
2 23 xyz 10/21/2013
2 23 xnh 01/12/2013
Need to pick the l_nm where lst_id is 22. If that is not present then we need to pick the l_nm with the most recent updated date.
Id lst_id lnm up_dt
1 22 abc 9/10/2015
2 23 xyz 10/21/2013
can any one please help me in implementing it.
Simple way is to use row_number with a window clause to generate a custom sort order:
select id, lst_id, l_nm as lnm, up_dt
from (
select id
,lst_id
,l_nm
,up_dt
,row_number()
over (partition by id
order by case when lst_id = 22 then 1 else 2 end
,up_dt desc) as rn
from table2
) where rn = 1;

Query to return all results except for the first record

I have a archive table that has records of transactions per locationID.
A location will have 0, 1 or many rows in this table.
I need a SELECT query that will return rows for any location that has more than 1 row, and to skip the first entry.
e.g.
Transactions table
transactionId locationId amount
1 11 2343
2 11 23434
3 25 342
4 32 234
5 77 234
6 11 38938
7 43 234
8 43 1235
So given the above, since the locationID has multiple rows, I will get back all rows except for the first one (lowest transacton_id):
2 11 23434
6 11 38938
8 43 1235
You can use row_number to do this. This assumes there would be no duplicate transactionid's.
select transactionid,locationid,amount
from
(select t.*, row_number() over(partition by locationid order by transactionid) as rn
from transactions t) t
where rn > 1
The other answer is fine. You could also write it this way, it might give you a little insight into grouping practices:
SELECT Transactions.TransactionID, Transactions.locationID, Transactions.amount
FROM Transactions INNER JOIN
(SELECT locationID, MIN(TransactionID) AS MinTransaction,
COUNT(TransactionID) AS CountTransaction
FROM Transactions
GROUP BY locationID) TableSum ON Transactions.locationID = TableSum.locationID
WHERE (Transactions.TransactionID <> TableSum.MinTransaction) AND
(TableSum.CountTransaction > 1)

Simple data, Complex query on SQL Server

I need to make a query over an SQL Server table but I don't know exactly how.
Consider this table (the real table is much more complex, Ord1 and Ord2 are dates that could be null, but i simplified it to this case):
Data of MyTable
ID MaqID Ord1 Ord2
------------------------
1 144 4 3
2 144 2 1
3 12 2 3
4 144 3 5
5 12 3 1
6 144 4 2
7 12 2 4
8 144 2 3
9 12 1 5
10 12 3 2
I need records for specific MaqID in Specific Order. I get that with this Query:
SELECT * FROM myTable WHERE MaqID=144 ORDER BY MaqID, Order1 DESC, Order2
Wich give me:
ID MaqID Ord1 Ord2
------------------------
6 144 4 2
1 144 4 3
4 144 3 5
2 144 2 1
8 144 2 3
Now, I need a single query that, for each MaqID, return the first ID for each subquery following above order. The result should be:
Expected result
MaqID ID
-----------
144 6
12 5
I have already try distinct conbination of TOP a MAX, but TOP result only one result and i need one for each MaqID, and for Max I have not field to maximize.
To sumarize: I need the first ID for each MaqID from a subquery in a specific order
Any ideas? Thanks!
You can do this using row_number():
select t.*
from (select t.*,
row_number() over (partition by macid Order1 DESC, Order2) as seqnum
from mytable t
) t
where seqnum = 1;

Select Previous Record in SQL Server 2008

Here's the case: I have one table myTable which contains 3 columns:
ID int, identity
Group varchar(2), not null
value decimal(18,0), not null
Table looks like this:
ID GROUP VALUE Prev_Value Result
------------------------------------------
1 A 20 0 20
2 A 30 20 10
3 A 35 30 5
4 B 100 0 100
5 B 150 100 50
6 B 300 200 100
7 C 40 0 40
8 C 60 40 20
9 A 50 35 15
10 A 70 50 20
Prev_Value and Result columns should be custom columns. I need to make it on view. Anyone can help? please... Thank you so much.
The gist of what you need to do here is to join the table to itself, where part of the join condition is that the value column of the joined copy of the table is less than value column of the original. Then you can group by the columns from the original table and select the max value from the joined table to get your results:
SELECT t1.id, t1.[Group], t1.Value
, coalesce(MAX(t2.Value),0) As Prev_Value
, t1.Value - coalesce(MAX(t2.Value),0) As Result
FROM MyTable t1
LEFT JOIN MyTable t2 ON t2.[Group] = t1.[Group] and t2.Value < t1.Value
GROUP BY t1.id, t1.[Group], t1.Value
Once you can update to Sql Server 2012 you'll also be able to take advantage of the new LAG keyword.

How to track how many times a column changed its value?

I have a table called crewWork as follows :
CREATE TABLE crewWork(
FloorNumber int, AptNumber int, WorkType int, simTime int )
After the table was populated, I need to know how many times a change in apt occurred and how many times a change in floor occurred. Usually I expect to find 10 rows on each apt and 40-50 on each floor.
I could just write a scalar function for that, but I was wondering if there's any way to do that in t-SQL without having to write scalar functions.
Thanks
The data will look like this:
FloorNumber AptNumber WorkType simTime
1 1 12 10
1 1 12 25
1 1 13 35
1 1 13 47
1 2 12 52
1 2 12 59
1 2 13 68
1 1 14 75
1 4 12 79
1 4 12 89
1 4 13 92
1 4 14 105
1 3 12 115
1 3 13 129
1 3 14 138
2 1 12 142
2 1 12 150
2 1 14 168
2 1 14 171
2 3 12 180
2 3 13 190
2 3 13 200
2 3 14 205
3 3 14 216
3 4 12 228
3 4 12 231
3 4 14 249
3 4 13 260
3 1 12 280
3 1 13 295
2 1 14 315
2 2 12 328
2 2 14 346
I need the information for a report, I don't need to store it anywhere.
If you use the accepted answer as written now (1/6/2023), you get correct results with the OP dataset, but I think you can get wrong results with other data.
CONFIRMED: ACCEPTED ANSWER HAS A MISTAKE (as of 1/6/2023)
I explain the potential for wrong results in my comments on the accepted answer.
In this db<>fiddle, I demonstrate the wrong results. I use a slightly modified form of accepted answer (my syntax works in SQL Server and PostgreSQL). I use a slightly modified form of the OP's data (I change two rows). I demonstrate how the accepted answer can be changed slightly, to produce correct results.
The accepted answer is clever but needs a small change to produce correct results (as demonstrated in the above db<>fiddle and described here:
Instead of doing this as seen in the accepted answer COUNT(DISTINCT AptGroup)...
You should do thisCOUNT(DISTINCT CONCAT(AptGroup, '_', AptNumber))...
DDL:
SELECT * INTO crewWork FROM (VALUES
-- data from question, with a couple changes to demonstrate problems with the accepted answer
-- https://stackoverflow.com/q/8666295/1175496
--FloorNumber AptNumber WorkType simTime
(1, 1, 12, 10 ),
-- (1, 1, 12, 25 ), -- original
(2, 1, 12, 25 ), -- new, changing FloorNumber 1->2->1
(1, 1, 13, 35 ),
(1, 1, 13, 47 ),
(1, 2, 12, 52 ),
(1, 2, 12, 59 ),
(1, 2, 13, 68 ),
(1, 1, 14, 75 ),
(1, 4, 12, 79 ),
-- (1, 4, 12, 89 ), -- original
(1, 1, 12, 89 ), -- new , changing AptNumber 4->1->4 ges)
(1, 4, 13, 92 ),
(1, 4, 14, 105 ),
(1, 3, 12, 115 ),
...
DML:
;
WITH groupedWithConcats as (SELECT
*,
CONCAT(AptGroup,'_', AptNumber) as AptCombo,
CONCAT(FloorGroup,'_',FloorNumber) as FloorCombo
-- SQL SERVER doesnt have TEMPORARY keyword; Postgres doesn't understand # for temp tables
-- INTO TEMPORARY groupedWithConcats
FROM
(
SELECT
-- the columns shown in Andriy's answer:
-- https://stackoverflow.com/a/8667477/1175496
ROW_NUMBER() OVER ( ORDER BY simTime) as RN,
-- AptNumber
AptNumber,
ROW_NUMBER() OVER (PARTITION BY AptNumber ORDER BY simTime) as RN_Apt,
ROW_NUMBER() OVER ( ORDER BY simTime)
- ROW_NUMBER() OVER (PARTITION BY AptNumber ORDER BY simTime) as AptGroup,
-- FloorNumber
FloorNumber,
ROW_NUMBER() OVER (PARTITION BY FloorNumber ORDER BY simTime) as RN_Floor,
ROW_NUMBER() OVER ( ORDER BY simTime)
- ROW_NUMBER() OVER (PARTITION BY FloorNumber ORDER BY simTime) as FloorGroup
FROM crewWork
) grouped
)
-- if you want to see how the groupings work:
-- SELECT * FROM groupedWithConcats
-- otherwise just run this query to see the counts of "changes":
SELECT
COUNT(DISTINCT AptCombo)-1 as CountAptChangesWithConcat_Correct,
COUNT(DISTINCT AptGroup)-1 as CountAptChangesWithoutConcat_Wrong,
COUNT(DISTINCT FloorCombo)-1 as CountFloorChangesWithConcat_Correct,
COUNT(DISTINCT FloorGroup)-1 as CountFloorChangesWithoutConcat_Wrong
FROM groupedWithConcats;
ALTERNATIVE ANSWER
The accepted-answer may eventually get updated to remove the mistake. If that happens I can remove my warning but I still want leave you with this alternative way to produce the answer.
My approach goes like this: "check the previous row, if the value is different in previous row vs current row, then there is a change". SQL doesn't have idea or row order functions per se (at least not like in Excel for example; )
Instead, SQL has window functions. With SQL's window functions, you can use the window function RANK plus a self-JOIN technique as seen here to combine current row values and previous row values so you can compare them. Here is a db<>fiddle showing my approach, which I pasted below.
The intermediate table, showing the columns which has a value 1 if there is a change, 0 otherwise (i.e. FloorChange, AptChange), is shown at the bottom of the post...
DDL:
...same as above...
DML:
;
WITH rowNumbered AS (
SELECT
*,
ROW_NUMBER() OVER ( ORDER BY simTime) as RN
FROM crewWork
)
,joinedOnItself AS (
SELECT
rowNumbered.*,
rowNumberedRowShift.FloorNumber as FloorShift,
rowNumberedRowShift.AptNumber as AptShift,
CASE WHEN rowNumbered.FloorNumber <> rowNumberedRowShift.FloorNumber THEN 1 ELSE 0 END as FloorChange,
CASE WHEN rowNumbered.AptNumber <> rowNumberedRowShift.AptNumber THEN 1 ELSE 0 END as AptChange
FROM rowNumbered
LEFT OUTER JOIN rowNumbered as rowNumberedRowShift
ON rowNumbered.RN = (rowNumberedRowShift.RN+1)
)
-- if you want to see:
-- SELECT * FROM joinedOnItself;
SELECT
SUM(FloorChange) as FloorChanges,
SUM(AptChange) as AptChanges
FROM joinedOnItself;
Below see the first few rows of the intermediate table (joinedOnItself). This shows how my approach works. Note the last two columns, which have a value of 1 when there is a change in FloorNumber compared to FloorShift (noted in FloorChange), or a change in AptNumber compared to AptShift (noted in AptChange).
floornumber
aptnumber
worktype
simtime
rn
floorshift
aptshift
floorchange
aptchange
1
1
12
10
1
0
0
2
1
12
25
2
1
1
1
0
1
1
13
35
3
2
1
1
0
1
1
13
47
4
1
1
0
0
1
2
12
52
5
1
1
0
1
1
2
12
59
6
1
2
0
0
1
2
13
68
7
1
2
0
0
Note instead of using the window function RANK and JOIN, you could use the window function LAG to compare values in the current row to the previous row directly (no need to JOIN). I don't have that solution here, but it is described in the Wikipedia article example:
Window functions allow access to data in the records right before and after the current record.
If I am not missing anything, you could use the following method to find the number of changes:
determine groups of sequential rows with identical values;
count those groups;
subtract 1.
Apply the method individually for AptNumber and for FloorNumber.
The groups could be determined like in this answer, only there's isn't a Seq column in your case. Instead, another ROW_NUMBER() expression could be used. Here's an approximate solution:
;
WITH marked AS (
SELECT
FloorGroup = ROW_NUMBER() OVER ( ORDER BY simTime)
- ROW_NUMBER() OVER (PARTITION BY FloorNumber ORDER BY simTime),
AptGroup = ROW_NUMBER() OVER ( ORDER BY simTime)
- ROW_NUMBER() OVER (PARTITION BY AptNumber ORDER BY simTime)
FROM crewWork
)
SELECT
FloorChanges = COUNT(DISTINCT FloorGroup) - 1,
AptChanges = COUNT(DISTINCT AptGroup) - 1
FROM marked
(I'm assuming here that the simTime column defines the timeline of changes.)
UPDATE
Below is a table that shows how the distinct groups are obtained for AptNumber.
AptNumber RN RN_Apt AptGroup (= RN - RN_Apt)
--------- -- ------ ---------
1 1 1 0
1 2 2 0
1 3 3 0
1 4 4 0
2 5 1 4
2 6 2 4
2 7 3 4
1 8 5 => 3
4 9 1 8
4 10 2 8
4 11 3 8
4 12 4 8
3 13 1 12
3 14 2 12
3 15 3 12
1 16 6 10
… … … …
Here RN is a pseudo-column that stands for ROW_NUMBER() OVER (ORDER BY simTime). You can see that this is just a sequence of rankings starting from 1.
Another pseudo-column, RN_Apt contains values produces by the other ROW_NUMBER, namely ROW_NUMBER() OVER (PARTITION BY AptNumber ORDER BY simTime). It contains rankings within individual groups of identical AptNumber values. You can see that, for a newly encountered value, the sequence starts over, and for a recurring one, it continues where it stopped last time.
You can also see from the table that if we subtract RN from RN_Apt (could be the other way round, doesn't matter in this situation), we get the value that uniquely identifies every distinct group of same AptNumber values. You might as well call that value a group ID.
So, now that we've got these IDs, it only remains for us to count them (count distinct values, of course). That will be the number of groups, and the number of changes is one less (assuming the first group is not counted as a change).
add an extra column changecount
CREATE TABLE crewWork(
FloorNumber int, AptNumber int, WorkType int, simTime int ,changecount int)
increment changecount value for each updation
if want to know count for each field then add columns corresponding to it for changecount
Assuming that each record represents a different change, you can find changes per floor by:
select FloorNumber, count(*)
from crewWork
group by FloorNumber
And changes per apartment (assuming AptNumber uniquely identifies apartment) by:
select AptNumber, count(*)
from crewWork
group by AptNumber
Or (assuming AptNumber and FloorNumber together uniquely identifies apartment) by:
select FloorNumber, AptNumber, count(*)
from crewWork
group by FloorNumber, AptNumber