Divide Sequential Records - sql

I have a table in MS Access like:
table
+-----+-----+-----+
| 1st | 2nd | 3rd |
+-----+-----+-----+
| A | 1 | 100 |
| A | 2 | 200 |
| A | 3 | 300 |
| B | 1 | 100 |
| B | 2 | 200 |
| B | 3 | 300 |
| C | 1 | 100 |
| C | 2 | 200 |
| C | 3 | 300 |
+-----+-----+-----+
Now I want to read the values from the 3rd column, do some sort of manipulation to it and store them in to another table like:
summary
+-----+---------+---------+
| 1st | 2nd | 3rd |
+-----+---------+---------+
| A | 100/200 | 200/300 |
| B | 100/200 | 200/300 |
| C | 100/200 | 200/300 |
+-----+---------+---------+
In another words, for summary.2nd this means:
select table.3rd FROM table where table.1st = A AND table.2nd = 1
divided by
select table.3rd FROM table where table.1st = A AND table.2nd = 3
Can someone give me a hint how this could be done?
Maybe VBA / ADO Recordset etc?

One method is conditional aggregation:
select [1st],
max(iif([2nd] = 1, [3rd], null)) / max(iif([2nd] = 2, [3rd], null)) as [2nd],
max(iif([2nd] = 2, [3rd], null)) / max(iif([2nd] = 3, [3rd], null)) as [3rd]
from t
group by [1st];

Try this SQL
INSERT INTO Summary
SELECT DISTINCT a.[1st],
a.[3rd] / b.[3rd] AS [2nd],
a.[3rd] / c.[3rd] AS [3rd]
FROM ((tbl AS a
INNER JOIN tbl AS b
ON a.[1st] = b.[1st])
INNER JOIN tbl AS c
ON a.[1st] = c.[1st] )
WHERE a.[2nd] = 1
AND b.[2nd] = 2
AND c.[2nd] = 3

Here's another alternative, using calculated join criteria:
select
t1.[1st],
t1.[3rd]/t2.[3rd] as [2nd],
t2.[3rd]/t3.[3rd] as [3rd]
from
(
[table] t1 inner join [table] t2
on t1.[1st] = t2.[1st] and t1.[2nd] = t2.[2nd]-1
)
inner join [table] t3
on t1.[1st] = t3.[1st] and t1.[2nd] = t3.[2nd]-2
Since the 2nd column values 1, 2 & 3 are not hard-coded, this is applicable to any three integers in the 2nd column whose values differ sequentially by one.
Change [table] to the name of your table.

Related

Implementing where condition in DAX

I have a scenario wherein I want to implement a BELOW SQL Query in Dax:
select count(distinct a.ID)
from Table1 a
join Table2 b
on a.ID=b.ID
where a.[In_Time] = "No" OR b.[In Time] = "No"
how do I implement using Dax?
You did not provide much of details as to what is is the relationship between t1 and t2 and whether you expect this through a DAX query or DAX measure.
Assuming you want this to be returned through DAX query with no relationship between t1 and t2 an equivalent DAX query would be following. It joins a filtered t1 and filtered t2 on desired criteria.
t1
| id | inTime |
|----|--------|
| 1 | yes |
| 1 | no |
| 2 | no |
| 3 | no |
| 4 | no |
| 5 | no |
| 6 | no |
t2
| id | inTime |
|----|--------|
| 1 | yes |
| 1 | no |
| 2 | yes |
| 3 | no |
| 5 | no |
Query
Table =
VAR _1 =
DISTINCT (
NATURALINNERJOIN (
SELECTCOLUMNS (
FILTER ( t1, t1[inTime] = "no" ),
"id", t1[id] + 0,
"inTIme", t1[inTime] & ""
),
SELECTCOLUMNS (
FILTER ( t2, t2[inTim] = "no" ),
"id", t2[id] + 0,
"inTIme", t2[inTim] & ""
)
)
)
VAR _2 =
ROW ( "count", COUNTX ( _1, [id] ) )
RETURN
_2
returns the following for VAR _1
| id | inTime |
|----|--------|
| 1 | no |
| 3 | no |
| 5 | no |

Joining table on two columns only joins it on a single

How do I correctly join a table on two columns. My issue is that the result is not correct as it only joins on a single column.
This question started of in this other question: SQL query returns product of results instead of sum . I am creating a new question as there is an other issue I am trying to solve.
I join a table of materials on a table which contains multiple supply and disposal movements. Each movement references a material id. I would like to join the material on each movement.
My query:
SELECT supply_material_refer, disposal_material_refer, material_id, material_name
FROM "construction_sites"
JOIN projects ON construction_sites.project_refer = projects.project_id
JOIN addresses ON construction_sites.address_refer = addresses.address_id
cross join lateral ( select *
from (select row_number() over () as rn, *
from supplies
where supplies.supply_project_refer = projects.project_id) as supplies
full join (select row_number() over () as rn, *
from disposals
where disposals.disposal_project_refer = projects.project_id
) as disposals
on (supplies.rn = disposals.rn)
) as combined
LEFT JOIN materials material ON combined.disposal_material_refer = material.material_id
OR combined.supply_material_refer = material.material_id
WHERE (projects.project_name = 'Project 15')
ORDER BY construction_site_id asc;
The result of the query:
+-----------------------+-------------------------+-------------+---------------+
| supply_material_refer | disposal_material_refer | material_id | material_name |
+-----------------------+-------------------------+-------------+---------------+
| 1 | 1 | 1 | Materialtest |
| 2 | 1 | 1 | Materialtest |
| 2 | 1 | 2 | Dirt |
| 1 | 1 | 1 | Materialtest |
| 2 | 1 | 1 | Materialtest |
| 2 | 1 | 2 | Dirt |
| 1 | (null) | 1 | Materialtest |
| 4 | (null) | 4 | Stones |
+-----------------------+-------------------------+-------------+---------------+
An example line I have issues with:
+------------------------+-------------------------+-------------+---------------+
| supply_material_refer | disposal_material_refer | material_id | material_name |
+------------------------+-------------------------+-------------+---------------+
| 2 | 1 | 1 | Materialtest |
+------------------------+-------------------------+-------------+---------------+
A prefered output would be like:
+------------------------+----------------------+-------------------------+------------------------+
| supply_material_refer | supply_material_name | disposal_material_refer | disposal_material_name |
+------------------------+----------------------+-------------------------+------------------------+
| 2 | Dirt | 1 | Materialtest |
+------------------------+----------------------+-------------------------+------------------------+
I have created a sqlfiddle with dummy data: http://www.sqlfiddle.com/#!17/863d78/2
To my understanding the solution would be to have a disposal_material column and and supply_material column for the material names. I do not know how I can achieve this goal though...
Thanks for any help!

Join three tables based on one key, putting data into same column

I have three tables that I am trying to join together to check that the proper data matches. I have table A which is a list of all accounts that a commission was paid on and what that commission amount was. I have Table B and Table C which are two tables that have commission calculations in it. The goal is to compare Table A to Table and to Table C and pulling back the amounts from both tables to ensure a match. The part I am struggling with is, Table A has all the accounts that are the base population. Table B has some and Table C as some. An account will be in either Table B or C, but never in both. I want to pull the payment from Table A, and then verify to the payment in Table B or C(whichever it occurs) and the same with commission. I then am doing a case when that compares the two fields and tells me if it matches are not.
+---------+---------+-----+------+
| Table A | | | |
+---------+---------+-----+------+
| Account | Uniq_ID | Pay | Comm |
| 12345 | ABCD | 100 | 10 |
| 23456 | OLPOL | 25 | 2 |
| 45678 | LKJHG | 200 | 15 |
| 96385 | LKJ67 | 250 | 26 |
+---------+---------+-----+------+
+---------+---------+-----+------+
| Table B | | | |
+---------+---------+-----+------+
| Account | Uniq_ID | Pay | Comm |
| 12345 | ABCD | 100 | 8 |
| 45678 | LKJHG | 200 | 15 |
+---------+---------+-----+------+
+---------+---------+-----+------+
| Table C | | | |
+---------+---------+-----+------+
| Account | Uniq_ID | Pay | Comm |
| 23456 | OLPOL | 25 | 2 |
| 96385 | LKJ67 | 250 | 32 |
+---------+---------+-----+------+
I am trying to get my results to show up in a columns called pay_ver and comm_verf, and it would populate with the data from either Table B or C based on which it matched with. I am hoping to have to output look like so....
+---------+---------+-----+----------+------+-----------+---------+
| Output | | | | | | |
+---------+---------+-----+----------+------+-----------+---------+
| Account | Uniq_ID | Pay | Pay_verf | comm | comm_Verf | Matched |
| 12345 | ABCD | 100 | 100 | 10 | 8 | No |
| 23456 | OLPOL | 25 | 25 | 2 | 2 | Yes |
| 45678 | LKJHG | 200 | 200 | 15 | 15 | Yes |
| 96385 | LKJ67 | 250 | 250 | 26 | 32 | No |
+---------+---------+-----+----------+------+-----------+---------+
This is the code I have used to join Table A to B, and Table A to C but I have done this in two separate queries giving me two outputs. I would like to be able to do this in one, so I only have one output.
select a.account, a.uniq_id, a.pay, b.pay as pay_verf, a.comm, b.comm as comm_verf,
CASE WHEN a.comm = b.comm THEN 'MATCHED'
ELSE 'UNMATCHED'
END as Matched
from tblA a
left join tblB b
on a.account = b.account
and a.uniq_id = b.uniq_id;
I can not just figure out how to also get it to join to Table C without adding an extra column.
You can do:
select
account, uniq_id, pay,
pay_total as pay_verf,
comm,
comm - comm_total as comm_verf,
case when comm = comm_total then 'Yes' else 'No' end as matched
from (
select
a.account, a.uniq_id, a.pay, a.comm,
coalesce(b.pay, 0) + coalesce(c.pay, 0) as pay_total,
coalesce(b.comm, 0) + coalesce(c.comm, 0) as comm_total
from table_a a
left join table_b b on a.account = b.account
left join table_c c on a.account = c.account
) x
You are very close. Just need to add one more join and an addition WHEN to your case statement. This should act like an if elseif else logic. So it checks if a.comm = b.comm and then checks a.comm = c.comm. If neither match if will set to unmatched. This works well because you stated the ID can't be in both B and C.
select a.account, a.uniq_id, a.pay, b.pay as pay_verf, a.comm, b.comm as comm_verf,
CASE WHEN a.comm = b.comm THEN 'MATCHED'
WHEN a.comm = c.comm THEN 'MATCHED'
ELSE 'UNMATCHED'
END as Matched
from tblA a
left join tblB b
on a.account = b.account
and a.uniq_id = b.uniq_id;
left join tblB c
on a.account = c.account
and a.uniq_id = c.uniq_id;
Yet another option could be something like
SELECT a.account, a.uniq_id, a.pay, bc.pay as pay_verf, a.comm, bc.comm as comm_verf
FROM a left join (
SELECT * from b
UNION ALL
SELECT * from c
) bc on (a.account = bc.account and a.uniq_id = bc.uniq_id)

SUM values in SQL starting from a specific point in another table

I have a table that lists the index/order, the name, and the value. For example, it looks like this:
TABLE1:
ID | NAME | VALUE
1 | A | 2
2 | B | 5
3 | C | 2
4 | D | 7
5 | E | 0
Now, I have another table that has a random list of NAMEs. It'll just show either A, B, C, D, or E. Depending on what the NAME is, I wanted to calculate the SUM of all the values that it will take to get to E. Does that make sense?
So if for example, my table looks like this:
TABLE2:
NAME
D
B
A
I'd want another column next to NAME that'll show the sum. So D would have 7 because the next event is E. B would have to be the sum of 5, 2, and 7 because B is 5, and C is 2, and D is 7. And A would have the sum of 2, 5, 3, and 7 and so on.
Hopefully this is easy to understand.
I actually don't have much at all aside from joining the two tables and getting the current value of the NAME. But I wasn't sure how to increment and so on and keep adding?
SELECT T2.NAME, T1.VALUE
FROM Table1 T1
LEFT JOIN Table2 T2 ON T1.NAME = T2.NAME
Is doing this even possible? Or am I wasting my time? Should I be referring to actual code to do this? Or should I make a function?
I wasn't sure where to start and I was hoping someone could help me out.
Thank you in advance!
The query is in two parts; this is hard to see at first, so I'll walk through each step.
Step 1: Obtain the rolling sum
Join table1 to itself for any letters greater than itself:
select *
from table1 t1
inner join table1 t2 on t2.name >= t1.name
order by t1.name
This produces the following table
+ -- + ---- + ----- + -- + ---- + ----- +
| id | name | value | id | name | value |
+ -- + ---- + ----- + -- + ---- + ----- +
| 1 | A | 2 | 1 | A | 2 |
| 1 | A | 2 | 2 | B | 5 |
| 1 | A | 2 | 3 | C | 2 |
| 1 | A | 2 | 4 | D | 7 |
| 1 | A | 2 | 5 | E | 0 |
| 2 | B | 5 | 2 | B | 5 |
| 2 | B | 5 | 3 | C | 2 |
| 2 | B | 5 | 4 | D | 7 |
| 2 | B | 5 | 5 | E | 0 |
| 3 | C | 2 | 3 | C | 2 |
| 3 | C | 2 | 4 | D | 7 |
| 3 | C | 2 | 5 | E | 0 |
| 4 | D | 7 | 4 | D | 7 |
| 4 | D | 7 | 5 | E | 0 |
| 5 | E | 0 | 5 | E | 0 |
+ -- + ---- + ----- + -- + ---- + ----- +
Notice that if we group by the name from t1, we can get the rolling sum by summing the values from t2. This query
select t1.name,
SUM(t2.value) as SumToE
from table1 t1
inner join table1 t2
on t2.name >= t1.name
group by t1.name
gives us the rolling sums we want
+ ---- + ------ +
| name | sumToE |
+ ---- + ------ +
| A | 16 |
| B | 14 |
| C | 9 |
| D | 7 |
| E | 0 |
+ ---- + ------ +
Note: This is equivalent to using a windowed function that sums over a set, but it is much easier to visually see what you're doing via this joining technique.
Step 2: Join the rolling sum
Now that you have this rolling sum for each letter, you simply join it to table2 for the letters you want
select t1.*
from table2 t2
inner join (
select t1.name,
SUM(t2.value) as SumToE
from table1 t1
inner join table1 t2
on t2.name >= t1.name
group by t1.name
) t1 on t1.name = t2.name
Result:
+ ---- + ------ +
| name | sumToE |
+ ---- + ------ +
| A | 16 |
| B | 14 |
| D | 7 |
+ ---- + ------ +
As gregory suggests, you can do this with a simple windowed function, which (in this case) will sum up all the rows after and including the current one based on the ID value. Obviously there are a number of different ways in which you can slice your data, though I'll leave that up to you to explore :)
declare #t table(ID int,Name nvarchar(50),Val int);
insert into #t values(1,'A',2),(2,'B',5),(3,'C',2),(4,'D',7),(5,'E',0);
select ID -- The desc makes the preceding work the right way. This is
,Name -- essentially shorthand for "sum(Val) over (order by ID rows between current row and unbounded following)"
,Val -- which is functionally the same, but a lot more typing...
,sum(Val) over (order by ID desc rows unbounded preceding) as s
from #t
order by ID;
Which will output:
+----+------+-----+----+
| ID | Name | Val | s |
+----+------+-----+----+
| 1 | A | 2 | 16 |
| 2 | B | 5 | 14 |
| 3 | C | 2 | 9 |
| 4 | D | 7 | 7 |
| 5 | E | 0 | 0 |
+----+------+-----+----+
CREATE TABLE #tempTable2(name VARCHAR(1))
INSERT INTO #tempTable2(name)
VALUES('D')
INSERT INTO #tempTable2(name)
VALUES('B')
INSERT INTO #tempTable2(name)
VALUES('A')
CREATE TABLE #tempTable(id INT, name VARCHAR(1), value INT)
INSERT INTO #temptable(id,name,value)
VALUES(1,'A',2)
INSERT INTO #temptable(id,name,value)
VALUES(2,'B',5)
INSERT INTO #temptable(id,name,value)
VALUES(3,'C',2)
INSERT INTO #temptable(id,name,value)
VALUES(4,'D',7)
INSERT INTO #temptable(id,name,value)
VALUES(5,'E',0)
;WITH x AS
(
SELECT id, value, name, RunningTotal = value
FROM dbo.#temptable
WHERE id = (SELECT MAX(id) FROM #temptable)
UNION ALL
SELECT y.id, y.value, y.name, x.RunningTotal + y.value
FROM x
INNER JOIN dbo.#temptable AS y ON
y.id = x.id - 1
)
SELECT x.id, x.value, x.name, x.RunningTotal
FROM x
JOIN #tempTable2 t2 ON
x.name = t2.name
ORDER BY x.id
DROP TABLE #tempTable
DROP TABLE #tempTable2

How to write a Sql query to find distinct values that have never met the following "Where Not(a=x and b=x)"

I have the following table called Attributes
* AttId * CustomerId * Class * Code *
| 1 | 1 | 1 | AA |
| 2 | 1 | 1 | AB |
| 3 | 1 | 1 | AC |
| 4 | 1 | 2 | AA |
| 5 | 1 | 2 | AB |
| 6 | 1 | 3 | AB |
| 7 | 2 | 1 | AA |
| 8 | 2 | 1 | AC |
| 9 | 2 | 2 | AA |
| 10 | 3 | 1 | AB |
| 11 | 3 | 3 | AB |
| 12 | 4 | 1 | AA |
| 13 | 4 | 2 | AA |
| 14 | 4 | 2 | AB |
| 15 | 4 | 3 | AB |
Where each Class, Code pairing represents a specific Attribute.
I'm trying to write a query that returns all customers that are NOT linked to the Attribute pairing Class = 1, Code = AB.
This would return Customer Id values 2 and 4.
I started to write Select Distinct A.CustomerId From Attributes A Where (A.Class = 1 and A.Code = 'AB') but stopped when I realised I was writing a SQL query and there is not an operator available to place before the parentheses to indicate the clause within must Not be met.
What am I missing? Or which operator should I be looking at?
Edit:
I'm trying to write a query that only returns those Customers (ie distinct Customer Id's) that have NO link to the Attribute pairing Class = 1, Code = AB.
This could only be Customer Id values 2 and 4 as the table does Not contain the rows:
* AttId * CustomerId * Class * Code *
| x | 2 | 1 | AB |
| x | 4 | 1 | AB |
Changed Title from:
How to write "Where Not(a=x and b=x)"in Sql Query
To:
How to write a Sql query to find distinct values that have never met the following "Where Not(a=x and b=x)"
As the previous title was a question in it's own right however the detail of the question added an extra dimension which led to confusion.
One way would be
SELECT DISTINCT CustomerId FROM Attributes a
WHERE NOT EXISTS (
SELECT * FROM Attributes forbidden
WHERE forbidden.CustomerId = a.CustomerId AND forbidden.Class = _forbiddenClassValue_ AND forbidden.Code = _forbiddenCodeValue_
)
or with join
SELECT DISTINCT a.CustomerId FROM Attributes a
LEFT JOIN (
SELECT CustomerId FROM Attributes
WHERE Class = _forbiddenClassValue_ AND Code = _forbiddenCodeValue_
) havingForbiddenPair ON a.CustomerId = havingForbiddenPair.CustomerId
WHERE havingForbiddenPair.CustomerId IS NULL
Yet another way is to use EXCEPT, as per ypercube's answer
SELECT CustomerId
FROM Attributes
EXCEPT
SELECT CustomerId
FROM Attributes
WHERE Class = 1
AND Code = AB ;
Since no one has posted the simple logical statement, here it is:
select . . .
where A.Class <> 1 OR A.Code <> 'AB'
The negative of (X and Y) is (not X or not Y).
I see, this is a grouping thing. For this, you use aggregation and having:
select customerId
from Attributes a
group by CustomerId
having sum(case when A.Class = 1 and A.Code = 'AB' then 1 else 0 end) = 0
I always prefer to solve "is it in a set" type questions using this technique.
Select Distinct A.CustomerId From Attributes A Where not (A.Class = 1 and A.Code = 'AB')
Try this:
SELECT DISTINCT A.CustomerId From Attributes A Where
0 = CASE
WHEN A.Class = 1 and A.Code = 'AB' THEN 1
ELSE 0
END
Edit: of course this still gives you cust 1 (doh!), you should probably use pjotrs NOT EXISTS query ideally, serves me right for not looking at the data closely enough :)