How to calculate asserts with 2 same parameters in CLIPS? - artificial-intelligence

I am new of CLIPS. I would like to calculate the average of asserts with two same parameters. For example, if i have this template:
(deftemplate quiz
(slot id (type INTEGER))
(slot course(type STRING))
(slot quizname (type STRING))
(slot userid (type INTEGER))
(slot firstname (type STRING))
(slot average (type FLOAT))
)
with these asserts:
(deffacts users
(quiz (id 1) (course "Math") (quizname "Equations") (userid 1) (firstname "Mike") (average 70.00))
(quiz (id 2) (course "Math") (quizname "Exercise") (userid 1) (firstname "Mike") (average 20.00))
(quiz (id 3) (course "Math") (quizname "Sum") (userid 2) (firstname "Ronald") (average 90.00))
(quiz (id 4) (course "Math") (quizname "Equations") (userid 2) (firstname "Ronald") (average 60.00))
(quiz (id 9) (course "English") (quizname "Listening") (userid 5) (firstname "Lionel") (average 60.00))
)
I'd like to build a rule that calculate averages of assert with same 'userid' and 'course' and write the results on a file. The answer that i'd like to have is:
Mike in math has 45.00; Ronald in math has 75.00; Lionel in english has 60.00
I tried this rule. I know is absolutely wrong:
(defrule averageStudent
=>
(bind ?sum 0)
(do-for-all-facts ((?f quiz)) TRUE
(bind ?sum (+ ?sum ?f:average)))
(bind ?sum (/ ?sum (length$ (find-all-facts ((?nFacts quiz)) (eq ?nFacts:userid) (eq ?nFacts:namequiz math)))))
(printout t ?firstname " in math has " ?sum crlf))

You need to construct something to hold all of the firstname/course combinations that you want to generate averages for. In the actions of the rule you'd have to iterate over the quiz facts and generate one multifield variable containing ("Mike" "Ronald" "Lionel") and another containing ("Math" "Math" "English"). Then you'd have to iterate these lists using the indices from 1 to the length of the lists and then use the nth$ function to pull out the name and the course from each list. Then you could use the fact query functions to pull out the averages for each name/course and compute the total average.
Rather than do all that, you could write a rule which determines all of the name/course for which an average is needed and assert that as a fact:
(deftemplate compute
(slot course (type STRING))
(slot firstname (type STRING)))
(defrule determine-course-name
(logical (compute-averages))
(quiz (course ?course) (firstname ?name))
(not (compute (course ?course) (firstname ?name)))
=>
(assert (compute (course ?course) (firstname ?name))))
Then you can write a rule which just computes the average for one name/course:
(defrule compute-average
(compute (course ?course) (firstname ?name))
=>
(bind ?sum 0)
(bind ?count 0)
(do-for-all-facts ((?f quiz))
(and (eq ?f:course ?course)
(eq ?f:firstname ?name))
(bind ?sum (+ ?sum ?f:average))
(bind ?count (+ ?count 1)))
(format t "%s in %s has %0.2f%n" ?name ?course (/ ?sum ?count)))
The determine-course-name rule won't initially be activated:
CLIPS (6.4 2/9/21)
CLIPS>
(deftemplate quiz
(slot id (type INTEGER))
(slot course (type STRING))
(slot quizname (type STRING))
(slot userid (type INTEGER))
(slot firstname (type STRING))
(slot average (type FLOAT)))
CLIPS>
(deffacts users
(quiz (id 1) (course "Math") (quizname "Equations") (userid 1) (firstname "Mike") (average 70.00))
(quiz (id 2) (course "Math") (quizname "Exercise") (userid 1) (firstname "Mike") (average 20.00))
(quiz (id 3) (course "Math") (quizname "Sum") (userid 2) (firstname "Ronald") (average 90.00))
(quiz (id 4) (course "Math") (quizname "Equations") (userid 2) (firstname "Ronald") (average 60.00))
(quiz (id 9) (course "English") (quizname "Listening") (userid 5) (firstname "Lionel") (average 60.00)))
CLIPS>
(deftemplate compute
(slot course (type STRING))
(slot firstname (type STRING)))
CLIPS>
(defrule determine-course-name
(logical (compute-averages))
(quiz (course ?course) (firstname ?name))
(not (compute (course ?course) (firstname ?name)))
=>
(assert (compute (course ?course) (firstname ?name))))
CLIPS>
(defrule compute-average
(compute (course ?course) (firstname ?name))
=>
(bind ?sum 0)
(bind ?count 0)
(do-for-all-facts ((?f quiz))
(and (eq ?f:course ?course)
(eq ?f:firstname ?name))
(bind ?sum (+ ?sum ?f:average))
(bind ?count (+ ?count 1)))
(format t "%s in %s has %0.2f%n" ?name ?course (/ ?sum ?count)))
CLIPS> (reset)
CLIPS> (facts)
f-1 (quiz (id 1) (course "Math") (quizname "Equations") (userid 1) (firstname "Mike") (average 70.0))
f-2 (quiz (id 2) (course "Math") (quizname "Exercise") (userid 1) (firstname "Mike") (average 20.0))
f-3 (quiz (id 3) (course "Math") (quizname "Sum") (userid 2) (firstname "Ronald") (average 90.0))
f-4 (quiz (id 4) (course "Math") (quizname "Equations") (userid 2) (firstname "Ronald") (average 60.0))
f-5 (quiz (id 9) (course "English") (quizname "Listening") (userid 5) (firstname "Lionel") (average 60.0))
For a total of 5 facts.
CLIPS> (agenda)
CLIPS>
Once you assert (compute-averages), the rules can then calculate the averages:
CLIPS> (assert (compute-averages))
<Fact-6>
CLIPS> (agenda)
0 determine-course-name: f-6,f-5,*
0 determine-course-name: f-6,f-4,*
0 determine-course-name: f-6,f-3,*
0 determine-course-name: f-6,f-2,*
0 determine-course-name: f-6,f-1,*
For a total of 5 activations.
CLIPS> (run)
Lionel in English has 60.00
Ronald in Math has 75.00
Mike in Math has 45.00
CLIPS>
The intermediate compute facts are still present, but since these are logically dependent on the (compute-averages) fact, you can remove them by retracting that fact if you need them cleaned up:
CLIPS> (facts)
f-1 (quiz (id 1) (course "Math") (quizname "Equations") (userid 1) (firstname "Mike") (average 70.0))
f-2 (quiz (id 2) (course "Math") (quizname "Exercise") (userid 1) (firstname "Mike") (average 20.0))
f-3 (quiz (id 3) (course "Math") (quizname "Sum") (userid 2) (firstname "Ronald") (average 90.0))
f-4 (quiz (id 4) (course "Math") (quizname "Equations") (userid 2) (firstname "Ronald") (average 60.0))
f-5 (quiz (id 9) (course "English") (quizname "Listening") (userid 5) (firstname "Lionel") (average 60.0))
f-6 (compute-averages)
f-7 (compute (course "English") (firstname "Lionel"))
f-8 (compute (course "Math") (firstname "Ronald"))
f-9 (compute (course "Math") (firstname "Mike"))
For a total of 9 facts.
CLIPS> (retract 6)
CLIPS> (facts)
f-1 (quiz (id 1) (course "Math") (quizname "Equations") (userid 1) (firstname "Mike") (average 70.0))
f-2 (quiz (id 2) (course "Math") (quizname "Exercise") (userid 1) (firstname "Mike") (average 20.0))
f-3 (quiz (id 3) (course "Math") (quizname "Sum") (userid 2) (firstname "Ronald") (average 90.0))
f-4 (quiz (id 4) (course "Math") (quizname "Equations") (userid 2) (firstname "Ronald") (average 60.0))
f-5 (quiz (id 9) (course "English") (quizname "Listening") (userid 5) (firstname "Lionel") (average 60.0))
For a total of 5 facts.
CLIPS>

Related

Bitwise operation in SQL Server

I have a column CategoryId that will store more than one value at the time, some of them have 1 (BA), 2 (SA) or 3 (both). I'm not sure if this is the right way.
For example the query down brings all records because 3 includes 1 and 2. If I want rows that have both categories then bitwise does not work. I believe I'm confusing terms.
Sample data and query:
CREATE TABLE #Payment (Id INT, Name NVARCHAR(50), CategoryId INT)
INSERT #Payment (Id, Name, CategoryId) VALUES(1, N'A', 1) --BA
INSERT #Payment (Id, Name, CategoryId) VALUES(1, N'B', 2) --SA
INSERT #Payment (Id, Name, CategoryId) VALUES(1, N'C', 3) --BA and SA
INSERT #Payment (Id, Name, CategoryId) VALUES(1, N'D', 2) --SA
DECLARE #Value INT = 3
SELECT *
FROM #Payment
WHERE (CategoryId & #Value) = CategoryId
There is a subtle correction needed in the WHERE clause. It should be:
WHERE (CategoryID & 3) = 3 -- bits 1 and 2 are set (matches 3, 7, 11, 15, ...)
For completeness here are the other variations:
WHERE (CategoryID & 3) <> 0 -- bits 1 or 2 are set (matches 1, 2, 3, 5, 6, 7, 9, 10, 11, ...)
WHERE (CategoryID & 3) = 0 -- bits 1 and 2 are not set (matches 0, 4, 8, 12, ...)
WHERE (CategoryID & 3) = CategoryID -- bits other than 1 and 2 are not set (matches 0, 1, 2, 3)

SQL ROUND decimal and display as varchar

I have some decimal fields in my table. I am trying to ROUND them and display in the desired format. Here is an example:
CREATE TABLE #SummaryData(
[ColA] [decimal](18, 1) NULL,
[ColB] [decimal](18, 2) NULL,
[ColC] [decimal](18, 4) NULL,
[ColD] [decimal](18, 4) NULL,
[ColE] [decimal](18, 4) NULL
)
INSERT INTO #SummaryData ([ColA],[ColB],[ColC],[ColD],[ColE])
VALUES (36754.0 ,9090.07, 2.4507 ,33536.0000 ,0.0073)
INSERT INTO #SummaryData ([ColA],[ColB],[ColC],[ColD],[ColE])
VALUES (54978.0 , 12535.32 , 9.9419,47041.0000, 0.0088)
INSERT INTO #SummaryData ([ColA],[ColB],[ColC],[ColD],[ColE])
VALUES (53501.0, 13346.62, 2.8152, 32371.0000, 0.0042)
SELECT [ColA],[ColB],[ColC],[ColD],[ColE]
FROM #SummaryData
-- We need data to be displayed like this for the purpose of reporting
ColA ColB ColC ColD ColE
----------------------------------------
36,754 9090 2.46 33.5% 0.7%
I tried to use ROUND and convert to varchar but did not work. Not sure how to get rid of zeros after decimal and apply "," (commas). Any suggestions?
SELECT SUBSTRING(CONVERT(VARCHAR, CAST(cola as money), 1), 1, LEN(CONVERT(VARCHAR, CAST(cola as money), 1)) - 3) AS ColA,
CONVERT(INT, ColB) AS ColB,
CONVERT(NUMERIC(18,2), ColC) AS ColC,
FORMAT(ColD / 100000., 'p1') AS ColD,
FORMAT(ColE, 'p1') AS ColE
FROM #SummaryData
Here's the output:
36,754 9090 2.45 33.5 % 0.7 %
54,978 12535 9.94 47.0 % 0.9 %
53,501 13346 2.82 32.4 % 0.4 %
SELECT FORMAT(ColA, '#,###') AS ColA,
CAST(ColB AS INT) AS ColB,
CAST(ColC AS INT) AS ColC,
FORMAT(ColD / 100000., 'p1') AS ColD,
FORMAT(ColE, 'p1') AS ColE
FROM #SummaryData
The output is:
ColA ColB ColC ColD ColE
36,754 9090 2 33.5 % 0.7 %
54,978 12535 9 47.0 % 0.9 %
53,501 13346 2 32.4 % 0.4 %

How to count an overall total of a buyer and the cost

I would like to make a table that will look like this, but I am not sure on how to fix my problems nor do a count on the buyer and overall total. If you can please help
Buyer |department |procure |overall total
Steve (3) |IT |AWD |$70.00
John (2) |Maint |RWD |$10.00
Mark (5) |Executive |Awd |$700.00
but the only thing I know how to do is get a chart that says
Steve| IT | AWD |$25
Steve| IT | AWD |$25
Steve| IT | AWD |$25
John| Maint| RWD |$7
John| maint |RWD| $3
Mark| Executive|AWD|$100
Mark| Executive|AWD|$100
Mark| Executive|AWD|$100
Mark| Executive|AWD|$300
Mark| Executive|AWD|$100
MY CODE LOOKS LIKES THIS :
SELECT
SSU.P_HEADER.BUYER,
SSU.P_HEADER.DEPT_ID,
SSU.P_HEADER.PROCURE_TYPE,
enter code here
(SSU.P_LINE.QUANTITY* SSU.P_LINE.COST) AS TOTAL
FROM
SSU.P_HEADER INNER JOIN SSU.P_LINE ON SSU.P_HEADER.TRX = SSU.P_LINE.TRX
WHERE
SSU.P_HEADER.PROCURE_TYPE= 'AWD'
OR SSU.P_HEADER.PROCURE_TYPE= 'REQ'
sum( buyer= total)
ORDER BY BUYER DESC
enter code here
Here's the metadata and data I used to (I believe) give you what you are looking for (NOTE: if not it will give you a start):
create table buyers (buyer varchar2(100), department varchar2(100), procure varchar2(100), cost integer);
insert into buyers values ('Steve', 'IT', 'AWD', 25);
insert into buyers values ('Steve', 'IT', 'AWD', 25);
insert into buyers values ('Steve', 'IT', 'AWD', 25);
insert into buyers values ('John', 'Maint', 'RWD', 7);
insert into buyers values ('John', 'Maint', 'RWD', 3);
insert into buyers values ('Mark', 'Executive', 'AWD', 100);
insert into buyers values ('Mark', 'Executive', 'AWD', 100);
insert into buyers values ('Mark', 'Executive', 'AWD', 100);
insert into buyers values ('Mark', 'Executive', 'AWD', 300);
insert into buyers values ('Mark', 'Executive', 'AWD', 100);
And here's the SELECT to get your results:
select buyer, department, procure, sum(cost) as overall_total
from buyers
group by buyer, department, procure;
And here is your results:
If you want those buyer count numbers in () then the SELECT you could use would be:
select b.buyer || ' (' || a.buyer_cnt || ')' as buyer, department, procure, sum(cost) as overall_total
from buyers b,
(select count(*) buyer_cnt, buyer
from buyers
group by buyer
) a
where b.buyer = a.buyer
group by b.buyer || ' (' || a.buyer_cnt || ')', department, procure;
And then your result set will look like this:

SSAS Measure average related to range values

I have Sales data provided weekly and Lookup data provided quarterly.
In the SSAS data cube I have pre-calculated average of sales data for each period of time and what I need to do is to get related record from LookupTable for next calculations, where: LookupTable.Min < Sales Average < LookupTable.Max
Example:
Sales = 297 + 33 + 311 = 641
SalesAverage = 213.66
LookupRecordShrinkageIndicator = Min < SalesAverage < Max = 0 < 213.66 < 9000 = 0.007
CREATE TABLE dbo.SalesData
(
Id int,
Sales decimal(18, 2) )
CREATE TABLE dbo.LookupTable
(
Id int,
Min int,
Max int,
Shrinkage decimal(10, 5),
Wages decimal(10, 5),
Waste decimal(10, 5)
)
INSERT [dbo].[SalesData] ([Id], [Sales]) VALUES (1, 297)
INSERT [dbo].[SalesData] ([Id], [Sales]) VALUES (2, 33)
INSERT [dbo].[SalesData] ([Id], [Sales]) VALUES (3, 311)
INSERT [dbo].[LookupTable] ([Id], [Min], [Max], [Shrinkage], [Wages], [Waste]) VALUES (1, 0, 9000, 0.00700, 0.12700, 0.00300)
INSERT [dbo].[LookupTable] ([Id], [Min], [Max], [Shrinkage], [Wages], [Waste]) VALUES (2, 9000, 9250, 0.00700, 0.12700, 0.00300)
INSERT [dbo].[LookupTable] ([Id], [Min], [Max], [Shrinkage], [Wages], [Waste]) VALUES (3, 9250, 9500, 0.00700, 0.12300, 0.00300)
I need to create calculated member based on sales average which contains indicators from lookup table for next calculations.
To solve this issue I had to use my LookupTable as dimension and as measures, let's see how I did this.
Create dimension based on LookupTable:
Add Lookup measures do the cube and add Lookup dimension to the cube as well.
Create Fact relationship between Lookup dimension and Lookup measures group
That's all:
Let's see mdx example:
SELECT
{
FILTER([Lookup Table].[Id].AllMembers , [Measures].[Min] <= 213 AND [Measures].[Max] > 213 )
}
ON COLUMNS,
{
[Measures].[Shrinkage - Lookup Table], [Measures].[Wages - Lookup Table], [Measures].[Waste - Lookup Table]
} ON ROWS
FROM
[MyCube]
And result:
I hope this example will be useful

TSQL Insert missing dates and increments

In SQL Server 2008 R2, I have a table (StatDays) that contains a count of days (DaysInStatus) that an order was in a particular status. There is an entry for each day the order remained in that status, except for Sundays when there was no process to write to this table. In that case, the order would appear again on Monday, with DaysInStatus advanced two days.
My problem is how to populate the missing Sunday values. (I am assuming that if an order has a Saturday entry but no Monday entry, that Saturday is the last desired data point.)
Example:
OrderID StatDate DaysInStatus
A11111 6/1/2012 20
A11111 6/2/2012 21
A11111 6/4/2012 23
A11111 6/5/2012 24
A11111 6/6/2012 25
A11111 6/7/2012 26
A11111 6/8/2012 27
A11111 6/9/2012 28
A11111 6/11/2012 30
Here I would want to insert (or select) into another table all of these values, plus the missing dates 6/3 and 6/10 - where DaysInStatus is advanced to 22 and 29, respectively - and the OrderID.
Also, an OrderID will NOT always appear in this table at DaysInStatus = 1. In can appear at almost any value, and drop out at almost any value.
I have tried generating a dates table, then doing left outer join to StatDays on statDate and StatDate+1, StatDate-1. This works for small recordsets but the actual table has over 100 million records, so this would not work for me.
Thanks in advance for any suggestions!
This is your statement
INSERT Status
SELECT s1.OrderID
,DATEADD(day,1,s1.StatDate)
,s1.DaysInStatus+1
FROM Status s1
INNER JOIN
Status s2 ON DATEADD(day,2,s1.StatDate)=s2.StatDate
AND
s1.DaysInStatus+2=s2.DaysInStatus
LEFT JOIN
Status s3 ON DATEADD(day,1,s1.StatDate)=s3.StatDate
AND
s1.DaysInStatus+1=s3.DaysInStatus
WHERE DATENAME(dw,s1.StatDate)='Saturday'
AND
s3.OrderID IS NULL
You can see an SQLFiddle here
Try this. It will help with any missing days.
create table StatDays(OrderID varchar(20),StatDate datetime,
DaysInStatus int)
insert into statdays values('A11111', '6/1/2012', 20)
insert into statdays values('A11111', '6/2/2012', 21)
insert into statdays values('A11111', '6/4/2012', 23)
insert into statdays values('A11111', '6/5/2012', 24)
insert into statdays values('A11111', '6/6/2012', 25)
insert into statdays values('A11111', '6/7/2012', 26)
insert into statdays values('A11111', '6/8/2012', 27)
insert into statdays values('A11111', '6/9/2012', 28)
insert into statdays values('A11111', '6/11/2012', 30)
insert into statdays values('A11112', '6/1/2012', 20)
insert into statdays values('A11112', '6/2/2012', 21)
insert into statdays values('A11112', '6/4/2012', 23)
insert into statdays values('A11113', '6/5/2012', 24)
insert into statdays values('A11113', '6/6/2012', 25)
insert into statdays values('A11113', '6/7/2012', 26)
insert into statdays values('A11113', '6/8/2012', 27)
insert into statdays values('A11113', '6/9/2012', 28)
insert into statdays values('A11113', '6/13/2012', 32)
with
demo_cte as
(select orderid, min(statdate) Min_Date,max(statdate) Max_Date,
min(DaysInStatus) Min_Day from StatDays group by orderid
union all
select orderid,DateAdd(day,1,Min_Date), Max_date,Min_day+1 from demo_cte
where DateAdd(day,1,Min_Date)<=Max_date
)
select OrderId,Min_Date Stat_days,Min_Day from demo_cte
order by orderid
SQL Fiddle

Resources