Update first table based on values from second table - sql-server

I have two tables:
- #CAMERC
- #CAMERC_LOG
I have to update column #CAMERC.MERC_LPR with values from column #CAMERC_LOG.MERC_LPR.
Records must match on MERC_KEY, but only one record must be taken from #CAMERC_LOG - with highest MERC_KEY_LOG, and #CAMERC_LOG.MERC_LPR must not be null or 0.
My problem is updating one table based on results from second table. I don't know how to properly make such an update?
Table #CAMERC:
+----------+----------+
| MERC_KEY | MERC_LPR |
+----------+----------+
| 1 | 0.0000 |
| 2 | NULL |
| 3 | 0.0000 |
| 4 | 0.0000 |
+----------+----------+
Table #CAMERC_LOG:
+----------+--------------+----------+
| MERC_KEY | MERC_KEY_LOG | MERC_LPR |
+----------+--------------+----------+
| 1 | 1 | 1.1000 |
| 1 | 2 | 2.3000 |
| 2 | 3 | 3.4000 |
| 2 | 4 | 4.4000 |
| 1 | 5 | 7.8000 |
| 1 | 6 | NULL |
| 2 | 7 | 0.0000 |
| 2 | 8 | 12.4000 |
| 3 | 1 | 12.1000 |
| 3 | 2 | 42.3000 |
| 3 | 3 | 43.4000 |
| 3 | 4 | 884.4000 |
| 4 | 5 | 57.8000 |
| 4 | 6 | NULL |
| 4 | 7 | 0.0000 |
| 4 | 8 | 412.4000 |
+----------+--------------+----------+
Code for table creation:
DECLARE #CAMERC TABLE
(
MERC_KEY INT,
MERC_LPR DECIMAL(10,4)
)
DECLARE #CAMERC_LOG TABLE
(
MERC_KEY INT,
MERC_KEY_LOG INT,
MERC_LPR DECIMAL(10,4)
)
INSERT INTO #CAMERC(MERC_LPR, MERC_KEY) VALUES(0, 1),(NULL,2),(0,3),(0,4)
INSERT INTO #CAMERC_LOG(MERC_LPR, MERC_KEY, MERC_KEY_LOG) VALUES(1.1, 1,1),(2.3,1,2),(3.4,2,3),(4.4,2,4),(7.8, 1,5),(NULL,1,6),(0,2,7),(12.4,2,8),
(12.1, 3,1),(42.3,3,2),(43.4,3,3),(884.4,3,4),(57.8, 4,5),(NULL,4,6),(0,4,7),(412.4,4,8)

Try this:
WITH DataSource AS
(
SELECT MERC_KEY
,ROW_NUMBER() OVER (PARTITION BY MERC_KEY ORDER BY MERC_KEY_LOG DESC) AS [RowID]
,MERC_LPR
FROM #CAMERC_LOG
WHERE MERC_LPR IS NOT NULL
AND MERC_LPR <> 0
)
UPDATE #CAMERC
SET MERC_LPR = B.[MERC_LPR]
FROM #CAMERC A
INNER JOIN DataSource B
ON A.[MERC_KEY] = B.[MERC_KEY]
AND B.[RowID] = 1
SELECT *
FROM #CAMERC
The idea is to eliminated the invalid records from the #CAMER_LOG and then using ROW_NUMBER to order the rows by MERC_KEY_LOG. After that, we are performing UPDATE by only where RowID = 1.

Related

SQL-Server Closure table query

I need a hierarchy for my database and decided to use the closure table model. The hierarchy tables have the usual structure, like this:
locations table
+----+---------+
| id | name |
+----+---------+
| 1 | Europe |
| 2 | France |
| 3 | Germany |
| 4 | Spain |
| 5 | Paris |
| 6 | Nizza |
| 7 | Berlin |
| 8 | Munich |
| 9 | Madrid |
+----+---------+
CREATE TABLE locations (
id int IDENTITY(1,1) PRIMARY KEY,
name varchar(30)
)
lacations_relation table
+----+--------+--------+-------+
| id | src_id | dst_id | depth |
+----+--------+--------+-------+
| 1 | 1 | 1 | 0 |
| 2 | 2 | 2 | 0 |
| 3 | 1 | 2 | 1 |
| 4 | 3 | 3 | 0 |
| 5 | 1 | 3 | 1 |
| 6 | 4 | 4 | 0 |
| 7 | 1 | 4 | 1 |
| 8 | 5 | 5 | 0 |
| 9 | 2 | 5 | 1 |
| 10 | 1 | 5 | 2 |
| 11 | 6 | 6 | 0 |
| 12 | 2 | 6 | 1 |
| 13 | 1 | 6 | 2 |
| 14 | 7 | 7 | 0 |
| 15 | 3 | 7 | 1 |
| 16 | 1 | 7 | 2 |
| 17 | 8 | 8 | 0 |
| 18 | 3 | 8 | 1 |
| 19 | 1 | 8 | 2 |
| 20 | 9 | 9 | 0 |
| 21 | 4 | 9 | 1 |
| 22 | 1 | 9 | 2 |
+----+--------+--------+-------+
CREATE TABLE locations_relation (
id int IDENTITY(1,1) PRIMARY KEY,
src_id int,
dst_id int,
depth int,
CONSTRAINT FK_src FOREIGN KEY (src_id)
REFERENCES locations (id),
CONSTRAINT FK_dst FOREIGN KEY (dst_id)
REFERENCES locations (id)
)
Now there is a third table, which holds information about documents and is referencing the locations table, which looks like this:
closure_junction
+----+------------+-------------+
| id | country_id | document_id |
+----+------------+-------------+
| 1 | 2 | 1 |
| 2 | 2 | 2 |
| 3 | 6 | 2 |
| 4 | 6 | 3 |
| 5 | 5 | 2 |
| 6 | 5 | 4 |
+----+------------+-------------+
CREATE TABLE closure_junction (
id int IDENTITY(1,1) PRIMARY KEY,
country_id int NOT NULL,
document_id int,
CONSTRAINT FK_countries FOREIGN KEY (id)
REFERENCES countries(id)
)
What I'd like to have is single SQL-Query which counts the document per location and if there are documents in a child it should be counted up in the parent. For example if paris holds 2 documents than france should automatically also hold 2 documents. The query should also output the path of each node to the root aswell as the depth of the node. I know there is way to do this recursively, but I'd like to avoid that.
I have a query which gives me the correct result, but I'm not satisfied with how it works. Is there a way to circumentvent storing the children in a column?
This is my query with the correct output:
;WITH cte (name, path, depth, children) AS
(
SELECT
node.name,
STRING_AGG(locations.name, ' / ' ) WITHIN GROUP (ORDER BY relation.depth DESC) as path,
MAX(relation.depth) as depth,
STRING_AGG(locations.id, ' ') as children
FROM locations node
INNER JOIN locations_relation relation
ON node.id = relation.dst_id
INNER JOIN locations
ON relation.src_id = locations.id
GROUP BY node.name
)
SELECT
name,
path,
depth,
COUNT(DISTINCT document_id) as count_docs
FROM cte
CROSS APPLY string_split(children, ' ')
LEFT JOIN closure_junction ON
closure_junction.country_id = value
GROUP BY name, path, depth
ORDER BY depth ASC
+---------+---------------------------+-------+------------+
| name | path | depth | count_docs |
+---------+---------------------------+-------+------------+
| Europe | Europe | 0 | 0 |
| France | Europe / France | 1 | 2 |
| Germany | Europe / Germany | 1 | 0 |
| Spain | Europe / Spain | 1 | 0 |
| Berlin | Europe / Germany / Berlin | 2 | 0 |
| Madrid | Europe / Spain / Madrid | 2 | 0 |
| Munich | Europe / Germany / Munich | 2 | 0 |
| Nizza | Europe / France / Nizza | 2 | 3 |
| Paris | Europe / France / Paris | 2 | 3 |
+---------+---------------------------+-------+------------+
Would be great if someone could give me a clue on how to accomplish this.
The count you can easily replace with a simple LEFT JOIN, but for this path you will still need to concatenate it somehow.
Something like this:
WITH CTE_path
AS
( SELECT node.id,
STRING_AGG(locations.name, ' / ' ) WITHIN GROUP (ORDER BY relation.depth DESC) as path
FROM locations node
INNER JOIN locations_relation relation
ON node.id = relation.dst_id
INNER JOIN locations
ON relation.src_id = locations.id
GROUP BY node.id)
SELECT l.name,count(DISTINCT cj.document_id),pa.path
FROM locations l
JOIN CTE_path pa
ON pa.id = l.id
LEFT JOIN locations_relation lr
ON l.id = lr.dst_id
LEFT JOIN closure_junction cj
ON cj.country_id = lr.src_id
GROUP BY l.name,pa.path

How to sort one column by two ways

I have a table called product and its data sample data structer as follows,
+-----------+-------------+-----------+
| ProductID | ProductName | SortValue |
+-----------+-------------+-----------+
| 1 | AA12 | 0 |
+-----------+-------------+-----------+
| 2 | AA10 | 0 |
+-----------+-------------+-----------+
| 3 | AA11 | 0 |
+-----------+-------------+-----------+
| 4 | AA13 | 0 |
+-----------+-------------+-----------+
| 5 | AA1 | 2 |
+-----------+-------------+-----------+
| 6 | AA2 | 1 |
+-----------+-------------+-----------+
| 7 | AA3 | 4 |
+-----------+-------------+-----------+
| 8 | AA4 | 3 |
+-----------+-------------+-----------+
| 9 | AA5 | 5 |
+-----------+-------------+-----------+
| 10 | AA6 | 6 |
+-----------+-------------+-----------+
I need to insert this table data into another temporary table with sorting using its SortValue. Here you can see multiple sortvalue as 0. Those 0 consists SortValue Should be inserted into end of the table by its ProductName. My expected output should be,
+-----------+-------------+-----------+
| ProductID | ProductName | SortValue |
+-----------+-------------+-----------+
| 6 | AA2 | 1 |
+-----------+-------------+-----------+
| 5 | AA1 | 2 |
+-----------+-------------+-----------+
| 8 | AA4 | 3 |
+-----------+-------------+-----------+
| 7 | AA3 | 4 |
+-----------+-------------+-----------+
| 9 | AA5 | 5 |
+-----------+-------------+-----------+
| 10 | AA6 | 6 |
+-----------+-------------+-----------+
| 2 | AA10 | 0 |
+-----------+-------------+-----------+
| 3 | AA11 | 0 |
+-----------+-------------+-----------+
| 1 | AA12 | 0 |
+-----------+-------------+-----------+
| 4 | AA13 | 0 |
+-----------+-------------+-----------+
How can I do this? I just tried something like this.
DECLARE #tmp TABLE (
[ProductID] INT,
[ProductName] VARCHAR(50),
[SortValue] INT )
INSERT INTO #tmp
p.ProductID,p.ProductName,p.SortValue
SELECT FROm Product p ORDER by p.SortValue
As a starter: database tables represent unordered set of rows. There is no inherent ordering of the rows: when you select from a table, you control the order in which rows are returned with the order by clause.
So I understand your question as how to select rows in the relevant order. For this, you can use a conditional sort:
select t.*
from mytbable
order by
case when sortValue = 0 then 1 else 0 end,
sortValue,
productName
The case expression in the first level of sorting puts rows where sortValue is not 0 first. Then, groups are sorted by sortValue and productName.

SQL Server Lag by partitioned group

I have a table of data as follows:
+----+-------+----------+
| id | value | group_id |
+----+-------+----------+
| 1 | -200 | 0 |
| 2 | -620 | 0 |
| 3 | -310 | 0 |
| 4 | 400 | 1 |
| 5 | 300 | 1 |
| 6 | 100 | 1 |
| 7 | -200 | 2 |
| 8 | -400 | 2 |
| 9 | -500 | 2 |
+----+-------+----------+
What I would like to do is produce a 4th column that, for each record, shows the last value of the preceding group_id.
So the result I want is as follows:
+----+-------+----------+----------------+
| id | value | group_id | LastValByGroup |
+----+-------+----------+----------------+
| 1 | -200 | 0 | 0 |
| 2 | -620 | 0 | 0 |
| 3 | -310 | 0 | 0 |
| 4 | 400 | 1 | -310 |
| 5 | 300 | 1 | -310 |
| 6 | 100 | 1 | -310 |
| 7 | -200 | 2 | 100 |
| 8 | -400 | 2 | 100 |
| 9 | -500 | 2 | 100 |
+----+-------+----------+----------------+
What I have done so far is in 2 parts. First I use the LAST_VALUE function to get the last Value in each group. Then I have tried to use the LAG function to get the last value from the previous group. Unfortunately the second part of my code isn't working as desired.
Here is my code:
CREATE TABLE #temp
(
id int identity(1,1),
value int,
group_id int
)
INSERT #temp VALUES(-200,0)
INSERT #temp VALUES(-620,0)
INSERT #temp VALUES(-310,0)
INSERT #temp VALUES(400,1)
INSERT #temp VALUES(300,1)
INSERT #temp VALUES(100,1)
INSERT #temp VALUES(-200,3)
INSERT #temp VALUES(-400,3)
INSERT #temp VALUES(-500,3)
;WITH cte AS
(
SELECT
*,
LastValByGroup = LAST_VALUE(Value) OVER(Partition By group_id ORDER BY id
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
FROM
#temp
), lagged AS
(
SELECT
*,
LaggedLastValByGroup = LAG(LastValByGroup,1,0) OVER(Partition By group_id ORDER BY id)
FROM
cte
)
SELECT * FROM lagged ORDER BY id
DROP TABLE #temp
And this is the result I get:
+----+-------+----------+----------------+----------------------+
| id | value | group_id | LastValByGroup | LaggedLastValByGroup |
+----+-------+----------+----------------+----------------------+
| 1 | -200 | 0 | -310 | 0 |
| 2 | -620 | 0 | -310 | -310 |
| 3 | -310 | 0 | -310 | -310 |
| 4 | 400 | 1 | 100 | 0 |
| 5 | 300 | 1 | 100 | 100 |
| 6 | 100 | 1 | 100 | 100 |
| 7 | -200 | 3 | -500 | 0 |
| 8 | -400 | 3 | -500 | -500 |
| 9 | -500 | 3 | -500 | -500 |
+----+-------+----------+----------------+----------------------+
Any help is much appreciated.
Thanks
You can use first_value like following to get the desired result.
select distinct t2.*, ISNULL(FIRST_VALUE(t1.[value]) over(partition by t1.group_id order by t1.id desc), 0) LastValByGroup
from #data t1
right join #data t2 on t1.group_id + 1 = t2.group_id
Please find the db<>fiddle here.

Creating a conditonal ROW_NUMBER() Partition clause based on previous row value

I have a table that looks like this:
+----------------+--------+
| EvidenceNumber | ID |
+----------------+--------+
| 001 | 8 |
| 001.A | 8 |
| 001.A.01 | 8 |
| 001.A.02 | 8 |
| 001.B | 8 |
| 001.C | 8 |
| 001.D | 8 |
| 001.E | 8 |
| 001.F | 8 |
| 001.G | 8 |
| 001.G.01 | 8 |
+----------------+--------+
If 001 were a bag, inside of it was 001.A, 001.B, and so on through to 001.G
In the output above, 001.A was another bag, and that bag contained 001.A.01 and 001.A.02. The same thing can be seen with 001.G.01.
Every entry in this table is either a bag or an item. I am only interested in counting the amount of items per ID.
Since 001.A.01 and 001.A.02 is the last we see of the "001.A's" we know A.01 and A.02 were items.
Since we see 001.B only once, that was an item as well.
001.G was a bag, but 001.G.01 was an item.
The above output is showing 8 items and 3 bags.
I feel like Row_number and the Partition clause is the perfect tool for the job, but I can't find a way to partition based on a clause that uses a previous row's value.
Maybe something like that isn't even necessary here, but I pictured it like:
{001} -- variable
{001}.A -- variable seen again, obviously 001 was a bag. Create new variable {001.A} and move on.
{001.A}.01 -- same thing.
{001.A.01} -- Unique variable. This is a final step. This is a bag and should be Row number 1.
Obviously, the below code is just making "ItemNum" 1 for each item since there are not duplicates.
SELECT
ROW_NUMBER() OVER(Partition BY EvidenceNumber ORDER BY EvidenceNumber) AS ItemNum,
EvidenceNumber,
ID
FROM EVIDENCE
WHERE ID = '18'
ORDER BY EvidenceNumber
+---------+----------------+--------+
| ItemNum | EvidenceNumber | ID |
+---------+----------------+--------+
| 1 | 001 | 8 |
| 1 | 001.A | 8 |
| 1 | 001.A.01 | 8 |
| 1 | 001.A.02 | 8 |
| 1 | 001.B | 8 |
| 1 | 001.C | 8 |
| 1 | 001.D | 8 |
| 1 | 001.E | 8 |
| 1 | 001.F | 8 |
| 1 | 001.G | 8 |
| 1 | 001.G.01 | 8 |
+---------+----------------+--------+
Ideally, it would partition on the items only, so in this case:
+---------+----------------+----+
| ItemNum | EvidenceNumber | ID |
+---------+----------------+----+
| 0 | 001 | 8 |
| 0 | 001.A | 8 |
| 1 | 001.A.01 | 8 |
| 2 | 001.A.02 | 8 |
| 3 | 001.B | 8 |
| 4 | 001.C | 8 |
| 5 | 001.D | 8 |
| 6 | 001.E | 8 |
| 7 | 001.F | 8 |
| 0 | 001.G | 8 |
| 8 | 001.G.01 | 8 |
+---------+----------------+----+
I don't think window functions alone are the best approach. Instead:
select t.*,
(case when exists (select 1
from evidence t2
where t2.caseid = t.caseid and
t2.EvidenceNumber like t.EvidenceNumber + '.%'
)
then 0 else 1
end) as is_item
from evidence t ;
Then sum these up using another subquery:
select t.*,
sum(is_item) over (partition by caseid order by EvidenceNumber) as item_counter
from (select t.*,
(case when exists (select 1
from evidence t2
where t2.caseid = t.caseid and
t2.EvidenceNumber like t.EvidenceNumber + '.%'
)
then 0 else 1
end) as is_item
from evidence t
) t;
trick with Lead and Row_Number:
DECLARE #Table TABLE (
EvidenceNumber varchar(64),
Id int
)
INSERT INTO #Table VALUES
('001',8),
('001.A',8),
('001.A.01',8),
('001.A.02',8),
('001.B',8),
('001.C',8),
('001.D',8),
('001.E',8),
('001.F',8),
('001.G',8),
('001.G.01',8);
WITH CTE AS (
SELECT
[IsBag] = PATINDEX(EvidenceNumber+'%',
IsNull(LEAD(EvidenceNumber) OVER (ORDER BY EvidenceNumber),0)
),
[EvidenceNumber],
[Id]
FROM
#Table
)
SELECT
[NumItem] = IIF(IsBag = 0,ROW_NUMBER() OVER (PARTITION BY [ISBag] order by [IsBag]),0),
[EvidenceNumber],
[Id]
FROM
CTE
ORDER BY EvidenceNumber

I made stored procedure, but I don't know what to put on my WHERE clause to filter the null column

I made a INNER JOIN in stored procedure, but I don't know what to put to my WHERE clause to filter those column with null values and only shows those rows who has not null on a particular column.
CREATE PROCEDURE [dbo].[25]
#param1 int
AS
SELECT c.Name, c.Age, c2.Name, c2.Country
FROM Cus C
INNER JOIN Cus2 C2 ON c.id = c2.id
WHERE c2.country is not null and c2.id = #param1
Order by c2.Country
RETURN 0
ID 1
+-----+----+---------+---------+
| QID | ID | Name | Country |
+-----+----+---------+---------+
| 1 | 1 | Null | PH |
| 2 | 1 | Null | CN |
| 3 | 1 | Japhet | USA |
| 4 | 1 | Abegail | UK |
| 5 | 1 | Norlee | Ger |
+-----+----+---------+---------+
ID 2
+-----+----+----------+---------+
| QID | ID | Name | Country |
+-----+----+----------+---------+
| 1 | 2 | Null | PH |
| 2 | 2 | Null | CN |
| 3 | 2 | Reynaldo | USA |
| 4 | 2 | Abegail | UK |
| 5 | 2 | Norlee | Ger |
+-----+----+----------+---------+
ID 3
+-----+----+----------+---------+
| QID | ID | Name | Country |
+-----+----+----------+---------+
| 1 | 3 | Gab | PH |
| 2 | 3 | Null | CN |
| 3 | 3 | Reynaldo | USA |
| 4 | 3 | Abegail | UK |
| 5 | 3 | Norlee | Ger |
+-----+----+----------+---------+
I want when I choose any of the user in the C Table it will display the C child table data and remove the null name rows and remain the rows with not null name column.
Desired Result:
C Table (Parent)
+----+---------+-----+
| ID | Name | Age |
+----+---------+-----+
| 3 | Abegail | 31 |
+----+---------+-----+
C2 Table (Child)
+-----+----+----------+---------+
| QID | ID | Name | Country |
+-----+----+----------+---------+
| 1 | 3 | Gab | PH |
| 3 | 3 | Reynaldo | USA |
| 4 | 3 | Abegail | UK |
| 5 | 3 | Norlee | Ger |
+-----+----+----------+---------+
WHERE column IS NOT NULL is the syntax to filter out NULL values.
Solution 1: test not null value
Example:
WHERE yourcolumn IS NOT NULL
Solution 2: test comparaison value in your where clause (comparaison substract null values)
Examples:
WHERE yourcolumn = value
WHERE yourcolumn <> value
WHERE yourcolumn in ( value)
WHERE yourcolumn not in ( value)
WHERE yourcolumn between value1 and value2
WHERE yourcolumn not between value1 and value2

Resources